1
/*
2
 * Hurl (https://hurl.dev)
3
 * Copyright (C) 2024 Orange
4
 *
5
 * Licensed under the Apache License, Version 2.0 (the "License");
6
 * you may not use this file except in compliance with the License.
7
 * You may obtain a copy of the License at
8
 *
9
 *          http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 *
17
 */
18
use crate::ast::{SourceInfo, Template, TemplateElement};
19
use crate::parser::primitives::try_literal;
20
use crate::parser::{string, ParseError, ParseErrorKind, ParseResult};
21
use crate::reader::Reader;
22

            
23
use super::placeholder;
24

            
25
/// Parse a filename.
26
///
27
/// A few characters need to be escaped such as space
28
/// for example: file\ with\ space.txt
29
/// This is very similar to the behaviour in a standard shell.
30
///
31
915
pub fn parse(reader: &mut Reader) -> ParseResult<Template> {
32
915
    let start = reader.cursor();
33
915

            
34
915
    let mut elements = vec![];
35
    loop {
36
1830
        let start = reader.cursor();
37
1830
        match placeholder::parse(reader) {
38
70
            Ok(placeholder) => {
39
70
                let element = TemplateElement::Placeholder(placeholder);
40
70
                elements.push(element);
41
            }
42
1760
            Err(e) => {
43
1760
                if e.recoverable {
44
1760
                    let value = filename_content(reader)?;
45
1760
                    if value.is_empty() {
46
915
                        break;
47
                    }
48
845
                    let encoded = reader.read_from(start.index);
49
845
                    let element = TemplateElement::String { value, encoded };
50
845
                    elements.push(element);
51
                } else {
52
                    return Err(e);
53
                }
54
            }
55
        }
56
    }
57
915
    if elements.is_empty() {
58
10
        let kind = ParseErrorKind::Filename;
59
10
        return Err(ParseError::new(start.pos, false, kind));
60
    }
61
905
    if let Some(TemplateElement::String { encoded, .. }) = elements.first() {
62
840
        if encoded.starts_with('[') {
63
            let kind = ParseErrorKind::Expecting {
64
                value: "filename".to_string(),
65
            };
66
            return Err(ParseError::new(start.pos, false, kind));
67
        }
68
    }
69

            
70
905
    let end = reader.cursor();
71
905
    Ok(Template {
72
905
        delimiter: None,
73
905
        elements,
74
905
        source_info: SourceInfo {
75
905
            start: start.pos,
76
905
            end: end.pos,
77
905
        },
78
905
    })
79
}
80

            
81
1760
fn filename_content(reader: &mut Reader) -> ParseResult<String> {
82
1760
    let mut s = String::new();
83
    loop {
84
2645
        match filename_escaped_char(reader) {
85
20
            Ok(c) => {
86
20
                s.push(c);
87
            }
88
2625
            Err(e) => {
89
2625
                if e.recoverable {
90
2625
                    let s2 = filename_text(reader);
91
2625
                    if s2.is_empty() {
92
1760
                        break;
93
865
                    } else {
94
865
                        s.push_str(&s2);
95
                    }
96
                } else {
97
                    return Err(e);
98
                }
99
            }
100
        }
101
    }
102
1760
    Ok(s)
103
}
104

            
105
2625
fn filename_text(reader: &mut Reader) -> String {
106
2625
    let mut s = String::new();
107
    loop {
108
11945
        let save = reader.cursor();
109
11945
        match reader.read() {
110
5
            None => break,
111
11940
            Some(c) => {
112
11940
                if ['#', ';', '{', '}', ' ', '\n', '\\'].contains(&c) {
113
2620
                    reader.seek(save);
114
2620
                    break;
115
9320
                } else {
116
9320
                    s.push(c);
117
                }
118
            }
119
        }
120
    }
121
2625
    s
122
}
123

            
124
2645
fn filename_escaped_char(reader: &mut Reader) -> ParseResult<char> {
125
2645
    try_literal("\\", reader)?;
126
20
    let start = reader.cursor();
127
20
    match reader.read() {
128
        Some('\\') => Ok('\\'),
129
        Some('b') => Ok('\x08'),
130
        Some('f') => Ok('\x0c'),
131
        Some('n') => Ok('\n'),
132
        Some('r') => Ok('\r'),
133
        Some('t') => Ok('\t'),
134
        Some('#') => Ok('#'),
135
        Some(';') => Ok(';'),
136
20
        Some(' ') => Ok(' '),
137
        Some('{') => Ok('{'),
138
        Some('}') => Ok('}'),
139
        Some('u') => string::unicode(reader),
140
        _ => Err(ParseError::new(
141
            start.pos,
142
            false,
143
            ParseErrorKind::EscapeChar,
144
        )),
145
    }
146
}
147

            
148
#[cfg(test)]
149
mod tests {
150
    use super::*;
151
    use crate::ast::{Expr, ExprKind, Placeholder, Variable, Whitespace};
152
    use crate::reader::Pos;
153

            
154
    #[test]
155
    fn test_filename() {
156
        let mut reader = Reader::new("data/data.bin");
157
        assert_eq!(
158
            parse(&mut reader).unwrap(),
159
            Template {
160
                delimiter: None,
161
                elements: vec![TemplateElement::String {
162
                    value: "data/data.bin".to_string(),
163
                    encoded: "data/data.bin".to_string()
164
                }],
165
                source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 14)),
166
            }
167
        );
168
        assert_eq!(reader.cursor().index, 13);
169

            
170
        let mut reader = Reader::new("data.bin");
171
        assert_eq!(
172
            parse(&mut reader).unwrap(),
173
            Template {
174
                //value: String::from("data.bin"),
175
                delimiter: None,
176
                elements: vec![TemplateElement::String {
177
                    value: "data.bin".to_string(),
178
                    encoded: "data.bin".to_string()
179
                }],
180
                source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 9)),
181
            }
182
        );
183
        assert_eq!(reader.cursor().index, 8);
184
    }
185

            
186
    #[test]
187
    fn test_include_space() {
188
        let mut reader = Reader::new("file\\ with\\ spaces");
189
        assert_eq!(
190
            parse(&mut reader).unwrap(),
191
            Template {
192
                delimiter: None,
193
                elements: vec![TemplateElement::String {
194
                    value: "file with spaces".to_string(),
195
                    encoded: "file\\ with\\ spaces".to_string()
196
                }],
197
                source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 19)),
198
            }
199
        );
200
        assert_eq!(reader.cursor().index, 18);
201
    }
202

            
203
    #[test]
204
    fn test_escaped_chars() {
205
        let mut reader = Reader::new("filename\\{"); // to the possible escaped chars
206
        assert_eq!(
207
            parse(&mut reader).unwrap(),
208
            Template {
209
                delimiter: None,
210
                elements: vec![TemplateElement::String {
211
                    value: "filename{".to_string(),
212
                    encoded: "filename\\{".to_string()
213
                }],
214
                source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 11)),
215
            }
216
        );
217
        assert_eq!(reader.cursor().index, 10);
218
    }
219

            
220
    #[test]
221
    fn test_filename_error() {
222
        let mut reader = Reader::new("{");
223
        let error = parse(&mut reader).err().unwrap();
224
        assert_eq!(error.kind, ParseErrorKind::Filename);
225
        assert_eq!(error.pos, Pos { line: 1, column: 1 });
226

            
227
        let mut reader = Reader::new("\\:");
228
        let error = parse(&mut reader).err().unwrap();
229
        assert_eq!(error.kind, ParseErrorKind::EscapeChar);
230
        assert_eq!(error.pos, Pos { line: 1, column: 2 });
231
    }
232

            
233
    #[test]
234
    fn test_filename_with_variables() {
235
        let mut reader = Reader::new("foo_{{bar}}");
236
        assert_eq!(
237
            parse(&mut reader).unwrap(),
238
            Template {
239
                delimiter: None,
240
                elements: vec![
241
                    TemplateElement::String {
242
                        value: "foo_".to_string(),
243
                        encoded: "foo_".to_string(),
244
                    },
245
                    TemplateElement::Placeholder(Placeholder {
246
                        space0: Whitespace {
247
                            value: String::new(),
248
                            source_info: SourceInfo::new(Pos::new(1, 7), Pos::new(1, 7)),
249
                        },
250
                        expr: Expr {
251
                            kind: ExprKind::Variable(Variable {
252
                                name: "bar".to_string(),
253
                                source_info: SourceInfo::new(Pos::new(1, 7), Pos::new(1, 10)),
254
                            }),
255
                            source_info: SourceInfo::new(Pos::new(1, 7), Pos::new(1, 10)),
256
                        },
257
                        space1: Whitespace {
258
                            value: String::new(),
259
                            source_info: SourceInfo::new(Pos::new(1, 10), Pos::new(1, 10)),
260
                        },
261
                    })
262
                ],
263
                source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 12)),
264
            }
265
        );
266

            
267
        let mut reader = Reader::new("foo_{{bar}}_baz");
268
        assert_eq!(
269
            parse(&mut reader).unwrap(),
270
            Template {
271
                delimiter: None,
272
                elements: vec![
273
                    TemplateElement::String {
274
                        value: "foo_".to_string(),
275
                        encoded: "foo_".to_string(),
276
                    },
277
                    TemplateElement::Placeholder(Placeholder {
278
                        space0: Whitespace {
279
                            value: String::new(),
280
                            source_info: SourceInfo::new(Pos::new(1, 7), Pos::new(1, 7)),
281
                        },
282
                        expr: Expr {
283
                            kind: ExprKind::Variable(Variable {
284
                                name: "bar".to_string(),
285
                                source_info: SourceInfo::new(Pos::new(1, 7), Pos::new(1, 10)),
286
                            }),
287
                            source_info: SourceInfo::new(Pos::new(1, 7), Pos::new(1, 10)),
288
                        },
289
                        space1: Whitespace {
290
                            value: String::new(),
291
                            source_info: SourceInfo::new(Pos::new(1, 10), Pos::new(1, 10)),
292
                        },
293
                    }),
294
                    TemplateElement::String {
295
                        value: "_baz".to_string(),
296
                        encoded: "_baz".to_string(),
297
                    },
298
                ],
299
                source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 16)),
300
            }
301
        );
302
    }
303
}