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::*;
19
use crate::parser::error::*;
20
use crate::parser::primitives::try_literal;
21
use crate::parser::template::template;
22
use crate::parser::{string, ParseResult};
23
use crate::reader::Reader;
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
910
pub fn parse(reader: &mut Reader) -> ParseResult<Template> {
32
910
    let start = reader.cursor();
33
910

            
34
910
    let mut elements = vec![];
35
    loop {
36
1820
        let start = reader.cursor();
37
1820
        match template(reader) {
38
70
            Ok(expr) => {
39
70
                let element = TemplateElement::Expression(expr);
40
70
                elements.push(element);
41
            }
42
1750
            Err(e) => {
43
1750
                if e.recoverable {
44
1750
                    let value = filename_content(reader)?;
45
1750
                    if value.is_empty() {
46
910
                        break;
47
                    }
48
840
                    let encoded = reader.read_from(start.index);
49
840
                    let element = TemplateElement::String { value, encoded };
50
840
                    elements.push(element);
51
                } else {
52
                    return Err(e);
53
                }
54
            }
55
        }
56
    }
57
910
    if elements.is_empty() {
58
10
        let kind = ParseErrorKind::Filename;
59
10
        return Err(ParseError::new(start.pos, false, kind));
60
    }
61
900
    if let Some(TemplateElement::String { encoded, .. }) = elements.first() {
62
835
        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
900
    let end = reader.cursor();
71
900
    Ok(Template {
72
900
        delimiter: None,
73
900
        elements,
74
900
        source_info: SourceInfo {
75
900
            start: start.pos,
76
900
            end: end.pos,
77
900
        },
78
900
    })
79
}
80

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

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

            
124
2630
fn filename_escaped_char(reader: &mut Reader) -> ParseResult<char> {
125
2630
    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::reader::Pos;
152

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

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

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

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

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

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

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

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