use itertools::Itertools;
use mun_paths::RelativePathBuf;
const DEFAULT_FILE_NAME: &str = "mod.mun";
const META_LINE: &str = "//-";
#[derive(Debug, Eq, PartialEq)]
pub struct Fixture {
pub relative_path: RelativePathBuf,
pub text: String,
}
impl Fixture {
pub fn parse(text: impl AsRef<str>) -> Vec<Fixture> {
let text = trim_raw_string_literal(text);
let mut result: Vec<Fixture> = Vec::new();
let default_start = if text.contains(META_LINE) {
None
} else {
Some(format!("{} /{}", META_LINE, DEFAULT_FILE_NAME))
};
for (idx, line) in default_start
.as_deref()
.into_iter()
.chain(text.lines())
.enumerate()
{
if line.contains(META_LINE) {
assert!(
line.starts_with(META_LINE),
"Metadata line {} has invalid indentation. \
All metadata lines need to have the same indentation \n\
The offending line: {:?}",
idx,
line
);
}
if line.starts_with(META_LINE) {
let meta = Fixture::parse_meta_line(line);
result.push(meta);
} else if let Some(entry) = result.last_mut() {
entry.text.push_str(line);
entry.text.push('\n');
}
}
result
}
fn parse_meta_line(line: impl AsRef<str>) -> Fixture {
let line = line.as_ref();
assert!(line.starts_with(META_LINE));
let line = line[META_LINE.len()..].trim();
let components = line.split_ascii_whitespace().collect::<Vec<_>>();
let path = components[0].to_string();
assert!(path.starts_with('/'));
let relative_path = RelativePathBuf::from(&path[1..]);
Fixture {
relative_path,
text: String::new(),
}
}
}
pub fn trim_raw_string_literal(text: impl AsRef<str>) -> String {
let mut text = text.as_ref();
if text.starts_with('\n') {
text = &text[1..];
}
let minimum_indentation = text
.lines()
.filter(|it| !it.trim().is_empty())
.map(|it| it.len() - it.trim_start().len())
.min()
.unwrap_or(0);
text.lines()
.map(|line| {
if line.len() <= minimum_indentation {
line.trim_start_matches(' ')
} else {
&line[minimum_indentation..]
}
})
.join("\n")
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn trim_raw_string_literal() {
assert_eq!(
&super::trim_raw_string_literal(
r#"
fn hello_world() {
// code
}
"#
),
"fn hello_world() {\n // code\n}\n"
);
}
#[test]
fn empty_fixture() {
assert_eq!(
Fixture::parse(""),
vec![Fixture {
relative_path: RelativePathBuf::from(DEFAULT_FILE_NAME),
text: "".to_owned()
}]
);
}
#[test]
fn single_fixture() {
assert_eq!(
Fixture::parse(format!("{} /foo.mun\nfn hello_world() {{}}", META_LINE)),
vec![Fixture {
relative_path: RelativePathBuf::from("foo.mun"),
text: "fn hello_world() {}\n".to_owned()
}]
);
}
#[test]
fn multiple_fixtures() {
assert_eq!(
Fixture::parse(
r#"
//- /foo.mun
fn hello_world() {
}
//- /bar.mun
fn baz() {
}
"#
),
vec![
Fixture {
relative_path: RelativePathBuf::from("foo.mun"),
text: "fn hello_world() {\n}\n\n".to_owned()
},
Fixture {
relative_path: RelativePathBuf::from("bar.mun"),
text: "fn baz() {\n}\n".to_owned()
}
]
);
}
#[test]
#[should_panic]
fn incorrectly_indented_fixture() {
Fixture::parse(
r"
//- /foo.mun
fn foo() {}
//- /bar.mun
pub fn baz() {}
",
);
}
}