use regex::Regex;
use std::str::FromStr;
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct CodeFlags {
lang: String,
filename: Option<String>,
run: Option<String>,
}
impl CodeFlags {
pub fn filename(&self) -> Option<String> {
self.filename.clone()
}
pub fn run(&self) -> Option<String> {
self.run.clone()
}
}
#[derive(Fail, Debug, Clone, Copy, Hash, PartialEq, Eq)]
enum Error {
#[fail(display = "Code block has no flags")]
NoFlags,
#[fail(display = "File name attribute exists but is empty")]
EmptyFilename,
#[fail(display = "File name flag found twice")]
DuplicateFilename,
#[fail(display = "Run flag found twice")]
DuplicateRun,
}
impl FromStr for CodeFlags {
type Err = ::failure::Error;
fn from_str(flags: &str) -> Result<Self, Self::Err> {
lazy_static! {
static ref SPLIT: Regex = Regex::new(r"[\s,]").unwrap();
}
let mut flags = SPLIT.split(flags);
let lang = flags.next().map(str::to_string).ok_or(Error::NoFlags)?;
let mut filename = None;
let mut run = None;
for flag in flags {
if let Some(f) = flag.splitn(2, "file=").nth(1) {
ensure!(filename.is_none(), Error::DuplicateFilename);
ensure!(!f.is_empty(), Error::EmptyFilename);
filename = Some(f.to_string());
}
let run_prefix = "run=";
if flag.starts_with(run_prefix) {
ensure!(run.is_none(), Error::DuplicateRun);
let r = &flag[run_prefix.len()..];
run = Some(r.to_string());
}
}
Ok(CodeFlags {
lang,
filename,
run,
})
}
}
#[cfg(test)]
mod test {
use super::CodeFlags;
macro_rules! flag_check {
($flags:expr => $field:ident None) => {
assert_eq!($flags.parse::<CodeFlags>().unwrap().$field(), None);
};
($flags:expr => $field:ident $value:expr) => {
assert_eq!(
$flags.parse::<CodeFlags>().unwrap().$field(),
Some($value.to_string())
);
};
}
#[test]
fn simple_flags_comma() {
flag_check!("rust,file=Cargo.toml" => filename "Cargo.toml");
flag_check!("rust,file=src/lib.rs" => filename "src/lib.rs");
flag_check!("rust,file=../foo/__bar.rs" => filename "../foo/__bar.rs");
}
#[test]
fn simple_flags_space() {
flag_check!("rust file=Cargo.toml" => filename "Cargo.toml");
flag_check!("rust file=src/lib.rs" => filename "src/lib.rs");
flag_check!("rust file=../foo/__bar.rs" => filename "../foo/__bar.rs");
}
#[test]
fn no_filename_in_flags() {
flag_check!("rust,ignore" => filename None);
flag_check!("rust ignore" => filename None);
flag_check!("rust,foo=bar" => filename None);
flag_check!("rust foo=bar" => filename None);
}
#[test]
fn all_the_flags() {
flag_check!("rust,ignore,file=Cargo.toml" => filename "Cargo.toml");
flag_check!("rust ignore file=Cargo.toml" => filename "Cargo.toml");
flag_check!("rust,norun,file=src/lib.rs" => filename "src/lib.rs");
flag_check!("rust norun file=src/lib.rs" => filename "src/lib.rs");
}
#[test]
fn no_lang() {
flag_check!("file=src/lib.rs" => filename None);
}
#[test]
fn run_flag() {
flag_check!("sh,file=src/lib.rs,run=sh" => run "sh");
flag_check!("file=src/lib.rs,run=sh" => run "sh");
flag_check!("run=sh" => run None);
}
}