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