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