Skip to main content

gomod_parser/
parser.rs

1use crate::combinator::not_whitespace;
2use crate::{Module, ModuleDependency, ModuleReplacement, ModuleRetract, Replacement};
3use std::collections::HashMap;
4use winnow::ascii::{multispace0, multispace1, space0, space1};
5use winnow::combinator::{fail, not, opt, peek, preceded, repeat, terminated};
6use winnow::stream::AsChar;
7use winnow::token::{any, take_till, take_while};
8use winnow::{dispatch, Parser, Result};
9
10const WHITESPACES: [char; 4] = [' ', '\t', '\r', '\n'];
11const CRLF: [char; 2] = ['\r', '\n'];
12
13#[derive(Debug, PartialEq, Eq)]
14pub(crate) enum Directive<'a> {
15    Comment(&'a str),
16    Module(&'a str),
17    Go(&'a str),
18    GoDebug(HashMap<String, String>),
19    Tool(Vec<String>),
20    Toolchain(&'a str),
21    Require(Vec<ModuleDependency>),
22    Exclude(Vec<ModuleDependency>),
23    Replace(Vec<ModuleReplacement>),
24    Retract(Vec<ModuleRetract>),
25    Ignore(Vec<String>),
26}
27
28pub(crate) fn gomod<'a>(input: &mut &'a str) -> Result<Vec<Directive<'a>>> {
29    repeat(0.., |i: &mut &'a str| {
30        // check for comments first
31        comment.parse_next(i).or_else(|_| directive.parse_next(i))
32    })
33    .parse_next(input)
34}
35
36fn directive<'a>(input: &mut &'a str) -> Result<Directive<'a>> {
37    let _ = take_while(0.., CRLF).parse_next(input)?;
38    dispatch!(peek(not_whitespace);
39        "module" => module,
40        "go" => go,
41        "godebug" => godebug,
42        "tool" => tool,
43        "toolchain" => toolchain,
44        "require" => require,
45        "exclude" => exclude,
46        "replace" => replace,
47        "retract" => retract,
48        "ignore" => ignore,
49        _ => fail,
50    )
51    .parse_next(input)
52}
53
54fn comment<'a>(input: &mut &'a str) -> Result<Directive<'a>> {
55    let res = preceded((opt(space0), "//", opt(space0)), take_till(0.., CRLF)).parse_next(input)?;
56    let _ = take_while(0.., CRLF).parse_next(input)?;
57
58    Ok(Directive::Comment(res))
59}
60
61fn module<'a>(input: &mut &'a str) -> Result<Directive<'a>> {
62    let res = preceded(("module", space1), take_till(1.., CRLF)).parse_next(input)?;
63    let _ = take_while(0.., CRLF).parse_next(input)?;
64
65    Ok(Directive::Module(res))
66}
67
68fn go<'a>(input: &mut &'a str) -> Result<Directive<'a>> {
69    let res = preceded(("go", space1), take_till(1.., CRLF)).parse_next(input)?;
70    let _ = take_while(0.., CRLF).parse_next(input)?;
71
72    Ok(Directive::Go(res))
73}
74
75fn godebug<'a>(input: &mut &'a str) -> Result<Directive<'a>> {
76    let res = preceded(
77        ("godebug", space1),
78        dispatch! {peek(any);
79            '(' => godebug_multi,
80            _ => godebug_single,
81        },
82    )
83    .parse_next(input)?;
84    let _ = take_while(0.., CRLF).parse_next(input)?;
85
86    Ok(Directive::GoDebug(HashMap::from_iter(res)))
87}
88
89fn godebug_single(input: &mut &str) -> Result<Vec<(String, String)>> {
90    // terminate, if `)` is found
91    peek(not(')')).parse_next(input)?;
92
93    let (key, _, value) =
94        (take_till(1.., '='), '=', take_till(1.., WHITESPACES)).parse_next(input)?;
95
96    Ok(vec![(key.into(), value.into())])
97}
98
99fn godebug_multi(input: &mut &str) -> Result<Vec<(String, String)>> {
100    let _ = ("(", multispace1).parse_next(input)?;
101    let res: Vec<Vec<(String, String)>> =
102        repeat(1.., terminated(godebug_single, multispace0)).parse_next(input)?;
103    let _ = (")", multispace0).parse_next(input)?;
104
105    Ok(res.into_iter().flatten().collect::<Vec<(String, String)>>())
106}
107
108fn tool<'a>(input: &mut &'a str) -> Result<Directive<'a>> {
109    let res = preceded(
110        ("tool", space1),
111        dispatch! {peek(any);
112            '(' => tool_multi,
113            _ => tool_single,
114        },
115    )
116    .parse_next(input)?;
117    let _ = take_while(0.., CRLF).parse_next(input)?;
118
119    Ok(Directive::Tool(res))
120}
121
122fn tool_single(input: &mut &str) -> Result<Vec<String>> {
123    // terminate, if `)` is found
124    peek(not(')')).parse_next(input)?;
125
126    let value = take_till(1.., WHITESPACES).parse_next(input)?;
127
128    // remove any comments added to the same line
129    let _ = opt(comment).parse_next(input)?;
130
131    Ok(vec![value.into()])
132}
133
134fn tool_multi(input: &mut &str) -> Result<Vec<String>> {
135    let _ = ("(", multispace1).parse_next(input)?;
136    let res: Vec<Vec<String>> =
137        repeat(1.., terminated(tool_single, multispace0)).parse_next(input)?;
138    let _ = (")", multispace0).parse_next(input)?;
139
140    Ok(res.into_iter().flatten().collect::<Vec<String>>())
141}
142
143fn toolchain<'a>(input: &mut &'a str) -> Result<Directive<'a>> {
144    let res = preceded(("toolchain", space1), take_till(1.., CRLF)).parse_next(input)?;
145    let _ = take_while(0.., CRLF).parse_next(input)?;
146
147    Ok(Directive::Toolchain(res))
148}
149
150fn require<'a>(input: &mut &'a str) -> Result<Directive<'a>> {
151    let res = preceded(
152        ("require", space1),
153        dispatch! {peek(any);
154            '(' => require_multi,
155            _ => require_single,
156        },
157    )
158    .parse_next(input)?;
159    let _ = take_while(0.., CRLF).parse_next(input)?;
160
161    Ok(Directive::Require(res))
162}
163
164fn require_single(input: &mut &str) -> Result<Vec<ModuleDependency>> {
165    // terminate, if `)` is found
166    peek(not(')')).parse_next(input)?;
167
168    let (module_path, _, version) = (
169        take_till(1.., AsChar::is_space),
170        space1,
171        take_till(1.., WHITESPACES),
172    )
173        .parse_next(input)?;
174
175    let indirect = opt(comment).parse_next(input)? == Some(Directive::Comment("indirect"));
176
177    Ok(vec![ModuleDependency {
178        module: Module {
179            module_path: module_path.to_string(),
180            version: version.to_string(),
181        },
182        indirect,
183    }])
184}
185
186fn require_multi(input: &mut &str) -> Result<Vec<ModuleDependency>> {
187    let _ = ("(", multispace1).parse_next(input)?;
188    let res: Vec<Vec<ModuleDependency>> =
189        repeat(1.., terminated(require_single, multispace0)).parse_next(input)?;
190    let _ = (")", multispace0).parse_next(input)?;
191
192    Ok(res.into_iter().flatten().collect::<Vec<ModuleDependency>>())
193}
194
195fn exclude<'a>(input: &mut &'a str) -> Result<Directive<'a>> {
196    let res = preceded(
197        ("exclude", space1),
198        dispatch! {peek(any);
199            '(' => require_multi,
200            _ => require_single,
201        },
202    )
203    .parse_next(input)?;
204    let _ = take_while(0.., CRLF).parse_next(input)?;
205
206    Ok(Directive::Exclude(res))
207}
208
209fn replace<'a>(input: &mut &'a str) -> Result<Directive<'a>> {
210    let res = preceded(
211        ("replace", space1),
212        dispatch! {peek(any);
213            '(' => replace_multi,
214            _ => replace_single,
215        },
216    )
217    .parse_next(input)?;
218    let _ = take_while(0.., CRLF).parse_next(input)?;
219
220    Ok(Directive::Replace(res))
221}
222
223fn replace_single(input: &mut &str) -> Result<Vec<ModuleReplacement>> {
224    // terminate, if `)` is found
225    peek(not(')')).parse_next(input)?;
226
227    let (src_path, src_version) = (
228        terminated(take_till(1.., AsChar::is_space), space1),
229        opt(terminated(
230            preceded(peek(not("=>")), take_till(1.., AsChar::is_space)),
231            space1,
232        )),
233    )
234        .parse_next(input)?;
235    let _ = ("=>", space1).parse_next(input)?;
236    let (dest_path, dest_version) = (
237        terminated(take_till(1.., WHITESPACES), space0),
238        opt(terminated(take_till(1.., WHITESPACES), multispace1)),
239    )
240        .parse_next(input)?;
241
242    let replacement = dest_version.map_or_else(
243        || Replacement::FilePath(dest_path.to_string()),
244        |version| {
245            Replacement::Module(Module {
246                module_path: dest_path.to_string(),
247                version: version.to_string(),
248            })
249        },
250    );
251
252    Ok(vec![ModuleReplacement {
253        module_path: src_path.to_string(),
254        version: src_version.map(ToString::to_string),
255        replacement,
256    }])
257}
258
259fn replace_multi(input: &mut &str) -> Result<Vec<ModuleReplacement>> {
260    let _ = ("(", multispace1).parse_next(input)?;
261    let res: Vec<Vec<ModuleReplacement>> =
262        repeat(1.., terminated(replace_single, multispace0)).parse_next(input)?;
263    let _ = (")", multispace0).parse_next(input)?;
264
265    Ok(res
266        .into_iter()
267        .flatten()
268        .collect::<Vec<ModuleReplacement>>())
269}
270
271fn retract<'a>(input: &mut &'a str) -> Result<Directive<'a>> {
272    let res = preceded(
273        ("retract", space1),
274        dispatch! {peek(any);
275            '(' => retract_multi,
276            _ => retract_single,
277        },
278    )
279    .parse_next(input)?;
280    let _ = take_while(0.., CRLF).parse_next(input)?;
281
282    Ok(Directive::Retract(res))
283}
284
285fn retract_single(input: &mut &str) -> Result<Vec<ModuleRetract>> {
286    // terminate, if `)` is found
287    peek(not(')')).parse_next(input)?;
288
289    let res = dispatch! {peek(any);
290        '[' => version_range,
291        _ => version_single,
292    }
293    .parse_next(input)?;
294
295    // remove any comments added to the same line
296    let _ = opt(comment).parse_next(input)?;
297
298    Ok(vec![res])
299}
300
301fn version_range(input: &mut &str) -> Result<ModuleRetract> {
302    let lower_bound = preceded('[', take_till(1.., |c| c == ',' || c == ' ')).parse_next(input)?;
303    let _ = (',', space0).parse_next(input)?;
304    let upper_bound =
305        terminated(take_till(1.., |c| c == ']' || c == ' '), ']').parse_next(input)?;
306
307    Ok(ModuleRetract::Range(
308        lower_bound.to_string(),
309        upper_bound.to_string(),
310    ))
311}
312
313fn version_single(input: &mut &str) -> Result<ModuleRetract> {
314    let version = terminated(take_till(1.., WHITESPACES), multispace1).parse_next(input)?;
315
316    Ok(ModuleRetract::Single(version.to_string()))
317}
318
319fn retract_multi(input: &mut &str) -> Result<Vec<ModuleRetract>> {
320    let _ = ("(", multispace1).parse_next(input)?;
321    let res: Vec<Vec<ModuleRetract>> =
322        repeat(1.., terminated(retract_single, multispace0)).parse_next(input)?;
323    let _ = (")", multispace0).parse_next(input)?;
324
325    Ok(res.into_iter().flatten().collect::<Vec<ModuleRetract>>())
326}
327
328fn ignore<'a>(input: &mut &'a str) -> Result<Directive<'a>> {
329    let res = preceded(
330        ("ignore", space1),
331        dispatch! {peek(any);
332            '(' => ignore_multi,
333            _ => ignore_single,
334        },
335    )
336    .parse_next(input)?;
337    let _ = take_while(0.., CRLF).parse_next(input)?;
338
339    Ok(Directive::Ignore(res))
340}
341
342fn ignore_single(input: &mut &str) -> Result<Vec<String>> {
343    // terminate, if `)` is found
344    peek(not(')')).parse_next(input)?;
345
346    let path = take_till(1.., WHITESPACES).parse_next(input)?;
347
348    // remove any comments added to the same line
349    let _ = opt(comment).parse_next(input)?;
350
351    Ok(vec![path.to_string()])
352}
353
354fn ignore_multi(input: &mut &str) -> Result<Vec<String>> {
355    let _ = ("(", multispace1).parse_next(input)?;
356    let res: Vec<Vec<String>> =
357        repeat(1.., terminated(ignore_single, multispace0)).parse_next(input)?;
358    let _ = (")", multispace0).parse_next(input)?;
359
360    Ok(res.into_iter().flatten().collect::<Vec<String>>())
361}