mod macros;
mod url;
use chumsky::{
IterParser, Parser,
error::Error,
extra::Full,
prelude::Simple,
primitive::{any, choice, empty, end, group, just},
recursive::recursive,
};
use crate::macros::set;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Dependency<'a> {
pub name: &'a str,
pub extras: Vec<&'a str>,
pub spec: Option<Spec<'a>>,
pub marker: Option<Marker<'a>>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Spec<'a> {
Url(&'a str),
Version(Vec<VersionSpec<'a>>),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct VersionSpec<'a> {
pub comparator: Comparator,
pub version: &'a str,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Marker<'a> {
And(Box<Marker<'a>>, Box<Marker<'a>>),
Or(Box<Marker<'a>>, Box<Marker<'a>>),
Operator(Variable<'a>, Operator, Variable<'a>),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Variable<'a> {
PythonVersion,
PythonFullVersion,
OsName,
SysPlatform,
PlatformRelease,
PlatformSystem,
PlatformVersion,
PlatformMachine,
PlatformPythonImplementation,
ImplementationName,
ImplementationVersion,
Extra,
String(&'a str),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Operator {
Comparator(Comparator),
In,
NotIn,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Comparator {
Lt,
Le,
Ne,
Eq,
Ge,
Gt,
Cp,
Ae,
}
pub fn parse(dependency: &str) -> Result<Dependency<'_>, Vec<Simple<'_, char>>> {
parser().then_ignore(end()).parse(dependency).into_result()
}
pub fn parser<'a, E: Error<'a, &'a str> + 'a>()
-> impl Parser<'a, &'a str, Dependency<'a>, Full<E, (), ()>> {
let ws = set!(' ' | '\t').repeated().ignored();
let ident = any()
.filter(char::is_ascii_alphanumeric)
.then(
set!('-' | '_' | '.')
.or_not()
.then(any().filter(char::is_ascii_alphanumeric))
.repeated(),
)
.to_slice();
let cmp = choice((
just("===").to(Comparator::Ae),
just("<=").to(Comparator::Le),
just("!=").to(Comparator::Ne),
just("==").to(Comparator::Eq),
just(">=").to(Comparator::Ge),
just("~=").to(Comparator::Cp),
just('<').to(Comparator::Lt),
just('>').to(Comparator::Gt),
));
let version_spec = cmp
.then_ignore(ws)
.then(
set!(
'A' ..= 'Z' | 'a' ..= 'z' | '0' ..= '9' | '-' | '_' | '.' | '*' | '+' | '!'
)
.repeated()
.at_least(1)
.to_slice(),
)
.map(|(comparator, version)| VersionSpec {
comparator,
version,
})
.then_ignore(ws)
.separated_by(just(',').ignore_then(ws))
.at_least(1)
.collect();
group((
ws.ignore_then(ident).then_ignore(ws),
ident
.then_ignore(ws)
.separated_by(just(',').ignore_then(ws))
.at_least(1)
.collect()
.delimited_by(just('[').ignore_then(ws), just(']'))
.then_ignore(ws)
.or(empty().map(|_| Vec::new())),
just('@')
.ignore_then(ws)
.ignore_then(url::parser())
.map(Spec::Url)
.or(version_spec
.delimited_by(just('(').then_ignore(ws), just(')'))
.or(version_spec)
.map(Spec::Version))
.then_ignore(ws)
.or_not(),
just(';')
.ignore_then(ws)
.ignore_then(recursive(|marker_or| {
macro_rules! c {
() => {
' ' | '\t' | 'A' ..= 'Z' | 'a' ..= 'z' | '0' ..= '9' | '(' | ')' | '.' |
'{' | '}' | '-' | '_' | '*' | '#' | ':' | ';' | ',' | '/' | '?' | '[' |
']' | '!' | '~' | '`' | '@' | '$' | '%' | '^' | '&' | '=' | '+' | '|' |
'<' | '>'
};
}
let marker_var = choice((
just('\'')
.ignore_then(set!(c!() | '"').repeated().to_slice())
.then_ignore(just('\''))
.map(Variable::String),
just('"')
.ignore_then(set!(c!() | '\'').repeated().to_slice())
.then_ignore(just('"'))
.map(Variable::String),
just("python_version").to(Variable::PythonVersion),
just("python_full_version").to(Variable::PythonFullVersion),
just("os_name").to(Variable::OsName),
just("sys_platform").to(Variable::SysPlatform),
just("platform_release").to(Variable::PlatformRelease),
just("platform_system").to(Variable::PlatformSystem),
just("platform_version").to(Variable::PlatformVersion),
just("platform_machine").to(Variable::PlatformMachine),
just("platform_python_implementation")
.to(Variable::PlatformPythonImplementation),
just("implementation_name").to(Variable::ImplementationName),
just("implementation_version").to(Variable::ImplementationVersion),
just("extra").to(Variable::Extra),
));
let marker_expr = group((
marker_var.clone().then_ignore(ws),
cmp.map(Operator::Comparator)
.or(just("in").to(Operator::In).or(just("not")
.ignore_then(set!(' ' | '\t').repeated().at_least(1))
.ignore_then(just("in"))
.to(Operator::NotIn)))
.then_ignore(ws),
marker_var,
))
.map(|(lhs, op, rhs)| Marker::Operator(lhs, op, rhs))
.or(marker_or
.then_ignore(ws)
.delimited_by(just('(').then_ignore(ws), just(')')));
let marker_and = marker_expr
.clone()
.then(
ws.ignore_then(just("and"))
.ignore_then(ws)
.ignore_then(marker_expr)
.or_not(),
)
.map(|(lhs, rhs)| match rhs {
Some(rhs) => Marker::And(Box::new(lhs), Box::new(rhs)),
None => lhs,
});
marker_and
.clone()
.then(
ws.ignore_then(just("or"))
.ignore_then(ws)
.ignore_then(marker_and)
.or_not(),
)
.map(|(lhs, rhs)| match rhs {
Some(rhs) => Marker::Or(Box::new(lhs), Box::new(rhs)),
None => lhs,
})
}))
.or_not(),
))
.then_ignore(ws)
.map(|(name, extras, spec, marker)| Dependency {
name,
extras,
spec,
marker,
})
}