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