use crate::combinator::not_whitespace;
use crate::{Module, ModuleDependency, ModuleReplacement, ModuleRetract, Replacement};
use std::collections::HashMap;
use winnow::ascii::{multispace0, multispace1, space0, space1};
use winnow::combinator::{fail, not, opt, peek, preceded, repeat, terminated};
use winnow::stream::AsChar;
use winnow::token::{any, take_till, take_while};
use winnow::{dispatch, Parser, Result};
const WHITESPACES: [char; 4] = [' ', '\t', '\r', '\n'];
const CRLF: [char; 2] = ['\r', '\n'];
#[derive(Debug, PartialEq, Eq)]
pub(crate) enum Directive<'a> {
Comment(&'a str),
Module(&'a str),
Go(&'a str),
GoDebug(HashMap<String, String>),
Tool(Vec<String>),
Toolchain(&'a str),
Require(Vec<ModuleDependency>),
Exclude(Vec<ModuleDependency>),
Replace(Vec<ModuleReplacement>),
Retract(Vec<ModuleRetract>),
Ignore(Vec<String>),
}
pub(crate) fn gomod<'a>(input: &mut &'a str) -> Result<Vec<Directive<'a>>> {
repeat(0.., |i: &mut &'a str| {
comment.parse_next(i).or_else(|_| directive.parse_next(i))
})
.parse_next(input)
}
fn directive<'a>(input: &mut &'a str) -> Result<Directive<'a>> {
let _ = take_while(0.., CRLF).parse_next(input)?;
dispatch!(peek(not_whitespace);
"module" => module,
"go" => go,
"godebug" => godebug,
"tool" => tool,
"toolchain" => toolchain,
"require" => require,
"exclude" => exclude,
"replace" => replace,
"retract" => retract,
"ignore" => ignore,
_ => fail,
)
.parse_next(input)
}
fn comment<'a>(input: &mut &'a str) -> Result<Directive<'a>> {
let res = preceded((opt(space0), "//", opt(space0)), take_till(0.., CRLF)).parse_next(input)?;
let _ = take_while(0.., CRLF).parse_next(input)?;
Ok(Directive::Comment(res))
}
fn module<'a>(input: &mut &'a str) -> Result<Directive<'a>> {
let res = preceded(("module", space1), take_till(1.., CRLF)).parse_next(input)?;
let _ = take_while(0.., CRLF).parse_next(input)?;
Ok(Directive::Module(res))
}
fn go<'a>(input: &mut &'a str) -> Result<Directive<'a>> {
let res = preceded(("go", space1), take_till(1.., CRLF)).parse_next(input)?;
let _ = take_while(0.., CRLF).parse_next(input)?;
Ok(Directive::Go(res))
}
fn godebug<'a>(input: &mut &'a str) -> Result<Directive<'a>> {
let res = preceded(
("godebug", space1),
dispatch! {peek(any);
'(' => godebug_multi,
_ => godebug_single,
},
)
.parse_next(input)?;
let _ = take_while(0.., CRLF).parse_next(input)?;
Ok(Directive::GoDebug(HashMap::from_iter(res)))
}
fn godebug_single(input: &mut &str) -> Result<Vec<(String, String)>> {
peek(not(')')).parse_next(input)?;
let (key, _, value) =
(take_till(1.., '='), '=', take_till(1.., WHITESPACES)).parse_next(input)?;
Ok(vec![(key.into(), value.into())])
}
fn godebug_multi(input: &mut &str) -> Result<Vec<(String, String)>> {
let _ = ("(", multispace1).parse_next(input)?;
let res: Vec<Vec<(String, String)>> =
repeat(1.., terminated(godebug_single, multispace0)).parse_next(input)?;
let _ = (")", multispace0).parse_next(input)?;
Ok(res.into_iter().flatten().collect::<Vec<(String, String)>>())
}
fn tool<'a>(input: &mut &'a str) -> Result<Directive<'a>> {
let res = preceded(
("tool", space1),
dispatch! {peek(any);
'(' => tool_multi,
_ => tool_single,
},
)
.parse_next(input)?;
let _ = take_while(0.., CRLF).parse_next(input)?;
Ok(Directive::Tool(res))
}
fn tool_single(input: &mut &str) -> Result<Vec<String>> {
peek(not(')')).parse_next(input)?;
let value = take_till(1.., WHITESPACES).parse_next(input)?;
let _ = opt(comment).parse_next(input)?;
Ok(vec![value.into()])
}
fn tool_multi(input: &mut &str) -> Result<Vec<String>> {
let _ = ("(", multispace1).parse_next(input)?;
let res: Vec<Vec<String>> =
repeat(1.., terminated(tool_single, multispace0)).parse_next(input)?;
let _ = (")", multispace0).parse_next(input)?;
Ok(res.into_iter().flatten().collect::<Vec<String>>())
}
fn toolchain<'a>(input: &mut &'a str) -> Result<Directive<'a>> {
let res = preceded(("toolchain", space1), take_till(1.., CRLF)).parse_next(input)?;
let _ = take_while(0.., CRLF).parse_next(input)?;
Ok(Directive::Toolchain(res))
}
fn require<'a>(input: &mut &'a str) -> Result<Directive<'a>> {
let res = preceded(
("require", space1),
dispatch! {peek(any);
'(' => require_multi,
_ => require_single,
},
)
.parse_next(input)?;
let _ = take_while(0.., CRLF).parse_next(input)?;
Ok(Directive::Require(res))
}
fn require_single(input: &mut &str) -> Result<Vec<ModuleDependency>> {
peek(not(')')).parse_next(input)?;
let (module_path, _, version) = (
take_till(1.., AsChar::is_space),
space1,
take_till(1.., WHITESPACES),
)
.parse_next(input)?;
let indirect = opt(comment).parse_next(input)? == Some(Directive::Comment("indirect"));
Ok(vec![ModuleDependency {
module: Module {
module_path: module_path.to_string(),
version: version.to_string(),
},
indirect,
}])
}
fn require_multi(input: &mut &str) -> Result<Vec<ModuleDependency>> {
let _ = ("(", multispace1).parse_next(input)?;
let res: Vec<Vec<ModuleDependency>> =
repeat(1.., terminated(require_single, multispace0)).parse_next(input)?;
let _ = (")", multispace0).parse_next(input)?;
Ok(res.into_iter().flatten().collect::<Vec<ModuleDependency>>())
}
fn exclude<'a>(input: &mut &'a str) -> Result<Directive<'a>> {
let res = preceded(
("exclude", space1),
dispatch! {peek(any);
'(' => require_multi,
_ => require_single,
},
)
.parse_next(input)?;
let _ = take_while(0.., CRLF).parse_next(input)?;
Ok(Directive::Exclude(res))
}
fn replace<'a>(input: &mut &'a str) -> Result<Directive<'a>> {
let res = preceded(
("replace", space1),
dispatch! {peek(any);
'(' => replace_multi,
_ => replace_single,
},
)
.parse_next(input)?;
let _ = take_while(0.., CRLF).parse_next(input)?;
Ok(Directive::Replace(res))
}
fn replace_single(input: &mut &str) -> Result<Vec<ModuleReplacement>> {
peek(not(')')).parse_next(input)?;
let (src_path, src_version) = (
terminated(take_till(1.., AsChar::is_space), space1),
opt(terminated(
preceded(peek(not("=>")), take_till(1.., AsChar::is_space)),
space1,
)),
)
.parse_next(input)?;
let _ = ("=>", space1).parse_next(input)?;
let (dest_path, dest_version) = (
terminated(take_till(1.., WHITESPACES), space0),
opt(terminated(take_till(1.., WHITESPACES), multispace1)),
)
.parse_next(input)?;
let replacement = dest_version.map_or_else(
|| Replacement::FilePath(dest_path.to_string()),
|version| {
Replacement::Module(Module {
module_path: dest_path.to_string(),
version: version.to_string(),
})
},
);
Ok(vec![ModuleReplacement {
module_path: src_path.to_string(),
version: src_version.map(ToString::to_string),
replacement,
}])
}
fn replace_multi(input: &mut &str) -> Result<Vec<ModuleReplacement>> {
let _ = ("(", multispace1).parse_next(input)?;
let res: Vec<Vec<ModuleReplacement>> =
repeat(1.., terminated(replace_single, multispace0)).parse_next(input)?;
let _ = (")", multispace0).parse_next(input)?;
Ok(res
.into_iter()
.flatten()
.collect::<Vec<ModuleReplacement>>())
}
fn retract<'a>(input: &mut &'a str) -> Result<Directive<'a>> {
let res = preceded(
("retract", space1),
dispatch! {peek(any);
'(' => retract_multi,
_ => retract_single,
},
)
.parse_next(input)?;
let _ = take_while(0.., CRLF).parse_next(input)?;
Ok(Directive::Retract(res))
}
fn retract_single(input: &mut &str) -> Result<Vec<ModuleRetract>> {
peek(not(')')).parse_next(input)?;
let res = dispatch! {peek(any);
'[' => version_range,
_ => version_single,
}
.parse_next(input)?;
let _ = opt(comment).parse_next(input)?;
Ok(vec![res])
}
fn version_range(input: &mut &str) -> Result<ModuleRetract> {
let lower_bound = preceded('[', take_till(1.., |c| c == ',' || c == ' ')).parse_next(input)?;
let _ = (',', space0).parse_next(input)?;
let upper_bound =
terminated(take_till(1.., |c| c == ']' || c == ' '), ']').parse_next(input)?;
Ok(ModuleRetract::Range(
lower_bound.to_string(),
upper_bound.to_string(),
))
}
fn version_single(input: &mut &str) -> Result<ModuleRetract> {
let version = terminated(take_till(1.., WHITESPACES), multispace1).parse_next(input)?;
Ok(ModuleRetract::Single(version.to_string()))
}
fn retract_multi(input: &mut &str) -> Result<Vec<ModuleRetract>> {
let _ = ("(", multispace1).parse_next(input)?;
let res: Vec<Vec<ModuleRetract>> =
repeat(1.., terminated(retract_single, multispace0)).parse_next(input)?;
let _ = (")", multispace0).parse_next(input)?;
Ok(res.into_iter().flatten().collect::<Vec<ModuleRetract>>())
}
fn ignore<'a>(input: &mut &'a str) -> Result<Directive<'a>> {
let res = preceded(
("ignore", space1),
dispatch! {peek(any);
'(' => ignore_multi,
_ => ignore_single,
},
)
.parse_next(input)?;
let _ = take_while(0.., CRLF).parse_next(input)?;
Ok(Directive::Ignore(res))
}
fn ignore_single(input: &mut &str) -> Result<Vec<String>> {
peek(not(')')).parse_next(input)?;
let path = take_till(1.., WHITESPACES).parse_next(input)?;
let _ = opt(comment).parse_next(input)?;
Ok(vec![path.to_string()])
}
fn ignore_multi(input: &mut &str) -> Result<Vec<String>> {
let _ = ("(", multispace1).parse_next(input)?;
let res: Vec<Vec<String>> =
repeat(1.., terminated(ignore_single, multispace0)).parse_next(input)?;
let _ = (")", multispace0).parse_next(input)?;
Ok(res.into_iter().flatten().collect::<Vec<String>>())
}