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 Ignore(Vec<String>),
26}
27
28pub(crate) fn gomod<'a>(input: &mut &'a str) -> Result<Vec<Directive<'a>>> {
29 repeat(0.., |i: &mut &'a str| {
30 comment.parse_next(i).or_else(|_| directive.parse_next(i))
32 })
33 .parse_next(input)
34}
35
36fn directive<'a>(input: &mut &'a str) -> Result<Directive<'a>> {
37 let _ = take_while(0.., CRLF).parse_next(input)?;
38 dispatch!(peek(not_whitespace);
39 "module" => module,
40 "go" => go,
41 "godebug" => godebug,
42 "tool" => tool,
43 "toolchain" => toolchain,
44 "require" => require,
45 "exclude" => exclude,
46 "replace" => replace,
47 "retract" => retract,
48 "ignore" => ignore,
49 _ => fail,
50 )
51 .parse_next(input)
52}
53
54fn comment<'a>(input: &mut &'a str) -> Result<Directive<'a>> {
55 let res = preceded((opt(space0), "//", opt(space0)), take_till(0.., CRLF)).parse_next(input)?;
56 let _ = take_while(0.., CRLF).parse_next(input)?;
57
58 Ok(Directive::Comment(res))
59}
60
61fn module<'a>(input: &mut &'a str) -> Result<Directive<'a>> {
62 let res = preceded(("module", space1), take_till(1.., CRLF)).parse_next(input)?;
63 let _ = take_while(0.., CRLF).parse_next(input)?;
64
65 Ok(Directive::Module(res))
66}
67
68fn go<'a>(input: &mut &'a str) -> Result<Directive<'a>> {
69 let res = preceded(("go", space1), take_till(1.., CRLF)).parse_next(input)?;
70 let _ = take_while(0.., CRLF).parse_next(input)?;
71
72 Ok(Directive::Go(res))
73}
74
75fn godebug<'a>(input: &mut &'a str) -> Result<Directive<'a>> {
76 let res = preceded(
77 ("godebug", space1),
78 dispatch! {peek(any);
79 '(' => godebug_multi,
80 _ => godebug_single,
81 },
82 )
83 .parse_next(input)?;
84 let _ = take_while(0.., CRLF).parse_next(input)?;
85
86 Ok(Directive::GoDebug(HashMap::from_iter(res)))
87}
88
89fn godebug_single(input: &mut &str) -> Result<Vec<(String, String)>> {
90 peek(not(')')).parse_next(input)?;
92
93 let (key, _, value) =
94 (take_till(1.., '='), '=', take_till(1.., WHITESPACES)).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.., CRLF)).parse_next(input)?;
110 let _ = take_while(0.., CRLF).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.., CRLF)).parse_next(input)?;
117 let _ = take_while(0.., CRLF).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.., CRLF).parse_next(input)?;
132
133 Ok(Directive::Require(res))
134}
135
136fn require_single(input: &mut &str) -> Result<Vec<ModuleDependency>> {
137 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.., CRLF).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.., CRLF).parse_next(input)?;
191
192 Ok(Directive::Replace(res))
193}
194
195fn replace_single(input: &mut &str) -> Result<Vec<ModuleReplacement>> {
196 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.., CRLF).parse_next(input)?;
253
254 Ok(Directive::Retract(res))
255}
256
257fn retract_single(input: &mut &str) -> Result<Vec<ModuleRetract>> {
258 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 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}
299
300fn ignore<'a>(input: &mut &'a str) -> Result<Directive<'a>> {
301 let res = preceded(
302 ("ignore", space1),
303 dispatch! {peek(any);
304 '(' => ignore_multi,
305 _ => ignore_single,
306 },
307 )
308 .parse_next(input)?;
309 let _ = take_while(0.., CRLF).parse_next(input)?;
310
311 Ok(Directive::Ignore(res))
312}
313
314fn ignore_single(input: &mut &str) -> Result<Vec<String>> {
315 peek(not(')')).parse_next(input)?;
317
318 let path = take_till(1.., WHITESPACES).parse_next(input)?;
319
320 let _ = opt(comment).parse_next(input)?;
322
323 Ok(vec![path.to_string()])
324}
325
326fn ignore_multi(input: &mut &str) -> Result<Vec<String>> {
327 let _ = ("(", multispace1).parse_next(input)?;
328 let res: Vec<Vec<String>> =
329 repeat(1.., terminated(ignore_single, multispace0)).parse_next(input)?;
330 let _ = (")", multispace0).parse_next(input)?;
331
332 Ok(res.into_iter().flatten().collect::<Vec<String>>())
333}