use indexmap::IndexMap;
use nom::{
branch::alt,
bytes::complete::{tag, take_till},
character::complete::{
alpha1, alphanumeric1, char, newline, space0, space1,
},
combinator::{opt, recognize},
multi::many0_count,
sequence::{pair, tuple},
IResult, Parser,
};
use std::str;
use crate::{template::Template, RestVariables};
type StrResult<'a> = IResult<&'a str, &'a str>;
const REQUEST_DELIMITER: &str = "###";
const NAME_ANNOTATION: &str = "@name";
const COMMAND_ANNOTATION: &str = "@";
#[derive(Debug, Clone, PartialEq)]
pub enum Line {
Seperator(Option<String>),
Name(String),
Command {
name: String,
params: Option<String>,
},
Request(String),
}
fn parse_seperator(input: &str) -> IResult<&str, Option<String>> {
let (input, _) = tag(REQUEST_DELIMITER)(input)?;
let (input, req_name) =
opt(pair(space1, take_till(|c| c == ' ' || c == '\n')))(input)?;
let potential_name = req_name.map(|(_, name)| name.to_string());
Ok((input, potential_name))
}
fn starting_comment(line: &str) -> StrResult {
alt((tag("//"), tag("#")))(line)
}
fn parse_request_name_annotation(input: &str) -> IResult<&str, &str> {
let (input, _) = pair(starting_comment, space0)(input)?;
let (input, _) = tag(NAME_ANNOTATION)(input)?;
let (input, _) = pair(alt((char('='), char(' '))), space0)(input)?;
let (input, req_name) = take_till(|c| c == ' ' || c == '\n')(input)?;
Ok((input, req_name))
}
fn parse_request_command(input: &str) -> IResult<&str, (&str, Option<&str>)> {
let (input, _) = pair(starting_comment, space0)(input)?;
let (input, _) = tag(COMMAND_ANNOTATION)(input)?;
let (input, cmd_name) = take_till(|c| c == ' ' || c == '\n')(input)?;
let (input, _) = space0(input)?;
let (input, params) = opt(take_till(|c| c == '\n'))(input)?;
let params = match params {
Some("") => None,
other => other,
};
Ok((input, (cmd_name, params)))
}
pub fn parse_variable_identifier(input: &str) -> IResult<&str, &str> {
recognize(pair(
alpha1,
many0_count(alt((alphanumeric1, tag("_"), tag("-"), tag(".")))),
))
.parse(input)
}
fn parse_variable_assignment(input: &str) -> IResult<&str, (&str, &str)> {
let (input, _) = char('@')(input)?;
let (input, id) = parse_variable_identifier(input)?;
let (input, _) = tuple((space0, char('='), space0))(input)?;
let (input, value) = take_till(|c| c == '\n')(input)?;
let (input, _) = newline(input)?;
Ok((input, (id, value)))
}
fn is_comment(line: &str) -> bool {
starting_comment(line).is_ok()
}
pub fn parse_lines(
input: &str,
) -> anyhow::Result<(Vec<Line>, RestVariables)> {
let mut lines: Vec<Line> = vec![];
let mut variables: IndexMap<String, Template> = IndexMap::new();
for line in input.trim().lines() {
let line = &format!("{line}\n");
if let Ok((_, seperator_name)) = parse_seperator(line) {
lines.push(Line::Seperator(seperator_name));
continue;
}
if let Ok((_, name)) = parse_request_name_annotation(line) {
lines.push(Line::Name(name.into()));
continue;
}
if let Ok((_, (name, params))) = parse_request_command(line) {
lines.push(Line::Command {
name: name.to_string(),
params: params.map(|x| x.to_string()),
});
continue;
}
if is_comment(line) {
continue
}
if let Ok((_, (key, val))) = parse_variable_assignment(line) {
variables.insert(key.into(), Template::new(val));
continue;
}
lines.push(Line::Request(line.trim().into()));
}
Ok((lines, variables))
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn parse_http_variable() {
let example_var = "@MY_VAR = 1231\n";
let (_, var) = parse_variable_assignment(example_var).unwrap();
assert_eq!(var, ("MY_VAR", "1231"));
let example_var = "@MY_NAME =hello\n";
let (rest, var) = parse_variable_assignment(example_var).unwrap();
assert_eq!(var, ("MY_NAME", "hello"));
assert_eq!(rest, "");
let example_var = "@Cool-Word = super_cool\n";
let (_, var) = parse_variable_assignment(example_var).unwrap();
assert_eq!(var, ("Cool-Word", "super_cool"));
}
#[test]
fn parse_seperator_line() {
let line = "### RequestName";
let (_, name_opt) = parse_seperator(line).unwrap();
assert_eq!(name_opt, Some("RequestName".into()));
let line = "#######";
let (_, name_opt) = parse_seperator(line).unwrap();
assert_eq!(name_opt, None);
let line = "###";
let (_, name_opt) = parse_seperator(line).unwrap();
assert_eq!(name_opt, None);
let line = "#";
let res = parse_seperator(line);
assert!(res.is_err());
}
#[test]
fn parse_request_name_test() {
let line = "# @name=hello";
let (_, name) = parse_request_name_annotation(line).unwrap();
assert_eq!(name, "hello".to_string());
let line = "# @name Cool";
let (_, name) = parse_request_name_annotation(line).unwrap();
assert_eq!(name, "Cool".to_string());
let line = "# a comment";
assert!(parse_request_name_annotation(line).is_err());
}
#[test]
fn parse_request_command_test() {
let line = "# @no-log";
let (_, out) = parse_request_command(line).unwrap();
assert_eq!(out, ("no-log", None));
let line = "# @timeout 100";
let (_, out) = parse_request_command(line).unwrap();
assert_eq!(out, ("timeout", Some("100")));
let line = "# @connection-timeout 2 m";
let (_, out) = parse_request_command(line).unwrap();
assert_eq!(out, ("connection-timeout", Some("2 m")));
}
}