use nom::{ bytes::complete::{tag, take_while1}, combinator::{all_consuming, opt}, error::ParseError, Err, IResult, InputLength, Parser, }; pub struct Output<'a> { pub recipe: &'a str, pub task: &'a str, pub host: Option<&'a str>, pub target: Option<&'a str>, } fn is_name_char(c: char) -> bool { matches!(c, 'a'..='z' | 'A' ..='Z' | '0'..='9' | '_' | '-') } fn name(input: &str) -> IResult<&str, &str> { take_while1(is_name_char)(input) } fn task_id(input: &str) -> IResult<&str, (&str, &str)> { let (input, recipe) = name(input)?; let (input, _) = tag(":")(input)?; let (input, task) = name(input)?; Ok((input, (recipe, task))) } fn task_arg_target(input: &str) -> IResult<&str, &str> { let (input, _) = tag(":")(input)?; let (input, target) = name(input)?; Ok((input, target)) } fn task_args(input: &str) -> IResult<&str, (Option<&str>, Option<&str>)> { let (input, _) = tag("/")(input)?; let (input, host) = opt(name)(input)?; let (input, target) = opt(task_arg_target)(input)?; Ok((input, (host, target))) } fn task_ref(input: &str) -> IResult<&str, Output> { let (input, (recipe, task)) = task_id(input)?; let (input, args) = opt(task_args)(input)?; let (host, target) = args.unwrap_or_default(); Ok(( input, Output { recipe, task, host, target, }, )) } fn parse_all, F>(f: F, input: I) -> Result> where I: InputLength, F: Parser, { all_consuming(f)(input).map(|(_, result)| result) } pub fn parse_task(input: &str) -> Option { parse_all(task_ref, input).ok() }