use semver::Version;
use std::fmt;
pub(crate) const CORE: &str = "@compmd/core";
pub(crate) const RESERVED_SCOPE: &str = "@compmd/";
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub(crate) enum Origin {
File(String),
Pack {
name: String,
version: Version,
path: String,
},
}
impl Origin {
pub(crate) fn core() -> Self {
Self::Pack {
name: CORE.to_string(),
version: Version::new(1, 0, 0),
path: "index.comp.md".to_string(),
}
}
pub(crate) fn is_core(&self) -> bool {
*self == Self::core()
}
pub(crate) fn join(&self, relative: &str) -> Option<Self> {
match self {
Self::File(path) => joined(path, relative).map(Self::File),
Self::Pack {
name,
version,
path,
} => joined(path, relative).map(|path| Self::Pack {
name: name.clone(),
version: version.clone(),
path,
}),
}
}
pub(crate) fn path(&self) -> &str {
match self {
Self::File(path) | Self::Pack { path, .. } => path,
}
}
}
impl fmt::Display for Origin {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::File(path) => f.write_str(path),
Self::Pack {
name,
version,
path,
} => write!(f, "{name}@{version}/{path}"),
}
}
}
fn joined(base: &str, relative: &str) -> Option<String> {
let mut parts: Vec<&str> = base.split('/').collect();
parts.pop();
for part in relative.split('/') {
match part {
"" | "." => {}
".." => {
parts.pop()?;
}
name => parts.push(name),
}
}
Some(parts.join("/"))
}
#[cfg(test)]
mod tests {
use super::*;
fn file(path: &str) -> Origin {
Origin::File(path.to_string())
}
#[test]
fn relative_paths_resolve_against_the_documents_folder() {
assert_eq!(
file("input.comp.md").join("./ui.comp.md"),
Some(file("ui.comp.md"))
);
assert_eq!(
file("docs/a.comp.md").join("../ui/b.jsx"),
Some(file("ui/b.jsx"))
);
assert_eq!(
file("docs/a.comp.md").join("./x/../y.comp.md"),
Some(file("docs/y.comp.md"))
);
assert_eq!(file("a.comp.md").join("../b.comp.md"), None);
assert_eq!(file("docs/a.csv").path(), "docs/a.csv");
}
#[test]
fn a_pack_keeps_its_paths_inside_the_package() {
let kit = Origin::Pack {
name: "@acme/kit".into(),
version: Version::new(1, 4, 2),
path: "kit.comp.md".into(),
};
assert_eq!(kit.to_string(), "@acme/kit@1.4.2/kit.comp.md");
assert_eq!(
kit.join("./parts/chip.comp.md")
.map(|origin| origin.to_string()),
Some("@acme/kit@1.4.2/parts/chip.comp.md".into())
);
assert_eq!(kit.join("../x.comp.md"), None);
assert_eq!(file("a.comp.md").to_string(), "a.comp.md");
}
#[test]
fn core_is_a_pack() {
let core = Origin::core();
assert!(core.is_core() && !kit_file().is_core());
assert_eq!(core.to_string(), "@compmd/core@1.0.0/index.comp.md");
assert_eq!(core.path(), "index.comp.md");
}
fn kit_file() -> Origin {
Origin::Pack {
name: "@acme/kit".into(),
version: Version::new(1, 0, 0),
path: "index.comp.md".into(),
}
}
}