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