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