use colored::Colorize;
use nom::{branch::alt, bytes::complete::tag, character::complete::{char, multispace0}, combinator::{opt, recognize}, multi::separated_list1, sequence::{delimited, preceded}, IResult, Parser};
use crate::{model::{SELF_STR_KEYWORD, SUPER_STR_KEYWORD}, parser::{context::ParseContext, doc::StofParseError, ident::ident, parse_attributes, string::{double_string, single_string}, whitespace::whitespace}};
pub fn import<'a>(input: &'a str, context: &mut ParseContext) -> IResult<&'a str, (), StofParseError> {
let (input, (_attrs, do_import)) = parse_attributes(input, context)?;
let (input, (format, path, scope)) = parse_import(input)?;
if !do_import {
return Ok((input, ()));
}
let mut start = None;
if scope.starts_with(SELF_STR_KEYWORD.as_str()) || scope.starts_with(SUPER_STR_KEYWORD.as_str()) {
start = Some(context.self_ptr());
}
let node = context.graph.ensure_named_nodes(&scope, start, true, None);
match context.parse_from_file(&format, &path, node) {
Ok(_) => {
Ok((input, ()))
},
Err(error) => {
return Err(nom::Err::Failure(StofParseError::from(format!("{} {}", "import:".dimmed(), error.to_string()))))
}
}
}
pub(self) fn parse_import(input: &str) -> IResult<&str, (String, String, String), StofParseError> {
let (input, _) = whitespace(input)?;
let (input, _) = tag("import").parse(input)?;
let (input, format) = opt(preceded(multispace0, ident)).parse(input)?;
let (input, mut path) = preceded(multispace0, alt((single_string, double_string))).parse(input)?;
let (input, scope) = opt(preceded(delimited(multispace0, alt((tag("as"), tag("on"))), multispace0), recognize(separated_list1(char('.'), ident)))).parse(input)?;
let (input, _) = opt(preceded(multispace0, alt((char(';'), char(','))))).parse(input)?;
path = path.trim().to_string();
let mut res_format = "stof".to_string();
if let Some(fmt) = format {
res_format = fmt.to_string();
} else {
let path_list = path.trim_start_matches('.').split('.').collect::<Vec<_>>();
if path_list.len() > 1 {
res_format = path_list.last().unwrap().to_string();
}
}
let mut res_scope = "self".to_string();
if let Some(scp) = scope {
res_scope = scp.to_string();
}
Ok((input, (res_format, path, res_scope)))
}
#[cfg(test)]
mod tests {
use crate::parser::import::parse_import;
#[test]
fn basic_import() {
let (_input, (format, path, scope)) = parse_import("\n\nimport './hello'\n\n").unwrap();
assert_eq!(format, "stof");
assert_eq!(path, "./hello");
assert_eq!(scope, "self");
}
#[test]
fn ext_import() {
let (_input, (format, path, scope)) = parse_import("\n\nimport './hello.json'\n\n").unwrap();
assert_eq!(format, "json");
assert_eq!(path, "./hello.json");
assert_eq!(scope, "self");
}
#[test]
fn fmt_import() {
let (_input, (format, path, scope)) = parse_import("\n\nimport pkg './hello.json'\n\n").unwrap();
assert_eq!(format, "pkg");
assert_eq!(path, "./hello.json");
assert_eq!(scope, "self");
}
#[test]
fn scope_import() {
let (_input, (format, path, scope)) = parse_import("\n\nimport './hello.json' as self.Example;\n\n").unwrap();
assert_eq!(format, "json");
assert_eq!(path, "./hello.json");
assert_eq!(scope, "self.Example");
}
#[test]
fn together_import() {
let (_input, (format, path, scope)) = parse_import("\n\nimport yaml \"src/dude/hello\" on Another.Sub.myobj;\n\n").unwrap();
assert_eq!(format, "yaml");
assert_eq!(path, "src/dude/hello");
assert_eq!(scope, "Another.Sub.myobj");
}
}