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 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 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 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 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 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}