#![forbid(unsafe_code)]
#![deny(
missing_docs,
missing_debug_implementations,
missing_copy_implementations
)]
#[cfg(test)]
#[macro_use]
extern crate pretty_assertions;
pub mod scanners;
pub mod validation;
pub use validation::{validate, BasicContext};
use codespan::{FileId, Span};
use http::uri::PathAndQuery;
use std::path::PathBuf;
use url::Url;
#[derive(Debug, Clone, PartialEq, Eq)]
enum Category {
FileSystem {
path: PathBuf,
fragment: Option<String>,
},
CurrentFile { fragment: String },
Url(Url),
MailTo(String),
}
impl Category {
fn categorise(src: &str) -> Option<Self> {
if src.is_empty() {
return None;
}
let mailto_prefix = "mailto:";
if src.starts_with(mailto_prefix) {
let address = &src[mailto_prefix.len()..];
return Some(Category::MailTo(address.to_string()));
}
if let Ok(url) = src.parse() {
return Some(Category::Url(url));
}
if src.starts_with("#") {
return Some(Category::CurrentFile {
fragment: String::from(&src[1..]),
});
}
let (path, fragment) = match src.find("#") {
Some(hash) => {
let (path, rest) = src.split_at(hash);
(path, Some(String::from(&rest[1..])))
},
None => (src, None),
};
if let Ok(path_and_query) = path.parse::<PathAndQuery>() {
return Some(Category::FileSystem {
path: PathBuf::from(path_and_query.path()),
fragment,
});
}
None
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde-1", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub struct Link {
pub href: String,
pub span: Span,
pub file: FileId,
}
impl Link {
pub fn new<S: Into<String>>(href: S, span: Span, file: FileId) -> Self {
Link {
href: href.into(),
span,
file,
}
}
fn category(&self) -> Option<Category> { Category::categorise(&self.href) }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_into_categories() {
let inputs = vec![
(
"https://example.com/",
Some(Category::Url(
Url::parse("https://example.com/").unwrap(),
)),
),
(
"README.md",
Some(Category::FileSystem {
path: PathBuf::from("README.md"),
fragment: None,
}),
),
(
"./README.md",
Some(Category::FileSystem {
path: PathBuf::from("./README.md"),
fragment: None,
}),
),
(
"./README.md#license",
Some(Category::FileSystem {
path: PathBuf::from("./README.md"),
fragment: Some(String::from("license")),
}),
),
(
"mailto:michael@example.com",
Some(Category::MailTo(String::from("michael@example.com"))),
),
];
for (src, should_be) in inputs {
let got = Category::categorise(src);
assert_eq!(got, should_be, "{}", src);
}
}
}