pub mod functions;
use arrayvec::ArrayVec;
use std::io;
use std::path::Path;
use std::borrow::Cow;
pub use self::functions::*;
#[derive(Debug)]
pub enum TokenErr {
File(io::Error),
OutOfBounds,
}
#[derive(Clone, PartialEq, Debug)]
pub enum Token<'a> {
Argument(Cow<'a, str>),
BaseAndExt,
Basename,
Dirname,
Job,
Placeholder,
RemoveExtension,
Slot
}
struct Number<'a> {
id: usize,
token: Token<'a>,
}
impl<'a> Number<'a> {
fn new(id: usize, token: Token<'a>) -> Number<'a> {
Number{ id: id, token: token }
}
fn into_argument(self, path: &Path) -> Result<String, TokenErr> {
use std::fs::File;
use std::io::{BufRead, BufReader};
let file = File::open(path).map_err(TokenErr::File)?;
let input = &BufReader::new(file).lines().nth(self.id-1).unwrap().map_err(TokenErr::File)?;
let argument = match self.token {
Token::Argument(_) => unreachable!(),
Token::Basename => basename(input),
Token::BaseAndExt => basename(remove_extension(input)),
Token::Dirname => dirname(input),
Token::Job => unreachable!(),
Token::Placeholder => input,
Token::RemoveExtension => remove_extension(input),
Token::Slot => unreachable!()
};
Ok(String::from(argument))
}
}
pub fn tokenize<'a>(tokens: &mut ArrayVec<[Token<'a>; 128]>, template: &'a str, path: &Path, nargs: usize)
-> Result<(), TokenErr>
{
let mut pattern_matching = false;
let mut pattern_start = 0;
let mut argument_matching = false;
let mut argument_start = 0;
for (id, character) in template.chars().enumerate() {
match (character, pattern_matching) {
('{', false) => {
pattern_matching = true;
pattern_start = id;
if argument_matching {
argument_matching = false;
let argument = Cow::Borrowed(&template[argument_start..id]);
tokens.push(Token::Argument(argument));
}
},
('}', true) => {
pattern_matching = false;
if id == pattern_start+1 {
tokens.push(Token::Placeholder);
} else {
match match_token(&template[pattern_start+1..id], path, nargs)? {
Some(token) => { tokens.push(token).unwrap(); },
None => { tokens.push(Token::Argument(Cow::Borrowed(&template[pattern_start..id+1]))); }
}
}
},
(_, false) if !argument_matching => {
argument_matching = true;
argument_start = id;
},
(_, _) => ()
}
}
if pattern_matching {
tokens.push(Token::Argument(Cow::Borrowed(&template[pattern_start..])));
} else if argument_matching {
tokens.push(Token::Argument(Cow::Borrowed(&template[argument_start..])));
}
Ok(())
}
fn match_token<'a>(pattern: &'a str, path: &Path, nargs: usize) -> Result<Option<Token<'a>>, TokenErr> {
match pattern {
"." => Ok(Some(Token::RemoveExtension)),
"#" => Ok(Some(Token::Job)),
"%" => Ok(Some(Token::Slot)),
"/" => Ok(Some(Token::Basename)),
"//" => Ok(Some(Token::Dirname)),
"/." => Ok(Some(Token::BaseAndExt)),
"#^" => Ok(Some(Token::Argument(Cow::Owned(nargs.to_string())))),
_ => {
let ndigits = pattern.chars().take_while(|&x| x.is_numeric()).count();
let nchars = ndigits + pattern.chars().skip(ndigits).count();
if ndigits != 0 {
let number = pattern[0..ndigits].parse::<usize>().unwrap();
if ndigits == nchars {
if number == 0 || number > nargs { return Err(TokenErr::OutOfBounds); }
let argument = Number::new(number, Token::Placeholder).into_argument(path)?;
Ok(Some(Token::Argument(Cow::Owned(argument))))
} else {
match match_token(&pattern[ndigits..], path, nargs)? {
None | Some(Token::Job) | Some(Token::Slot) => Ok(None),
Some(token) => {
let argument = Number::new(number, token).into_argument(path)?;
Ok(Some(Token::Argument(Cow::Owned(argument))))
},
}
}
} else {
Ok(None)
}
}
}
}
#[test]
fn tokenizer_argument() {
let mut tokens = Vec::new();
let _ = tokenize(&mut tokens, "foo", &Path::new("."), 1);
assert_eq!(tokens, vec![Token::Argument("foo".to_owned())]);
}
#[test]
fn tokenizer_placeholder() {
let mut tokens = Vec::new();
let _ = tokenize(&mut tokens, "{}", &Path::new("."), 1);
assert_eq!(tokens, vec![Token::Placeholder]);
}
#[test]
fn tokenizer_remove_extension() {
let mut tokens = Vec::new();
let _ = tokenize(&mut tokens, "{.}", &Path::new("."), 1);
assert_eq!(tokens, vec![Token::RemoveExtension]);
}
#[test]
fn tokenizer_basename() {
let mut tokens = Vec::new();
let _ = tokenize(&mut tokens, "{/}", &Path::new("."), 1);
assert_eq!(tokens, vec![Token::Basename]);
}
#[test]
fn tokenizer_dirname() {
let mut tokens = Vec::new();
let _ = tokenize(&mut tokens, "{//}", &Path::new("."), 1);
assert_eq!(tokens, vec![Token::Dirname]);
}
#[test]
fn tokenizer_base_and_ext() {
let mut tokens = Vec::new();
let _ = tokenize(&mut tokens, "{/.}", &Path::new("."), 1);
assert_eq!(tokens, vec![Token::BaseAndExt]);
}
#[test]
fn tokenizer_slot() {
let mut tokens = Vec::new();
let _ = tokenize(&mut tokens, "{%}", &Path::new("."), 1);
assert_eq!(tokens, vec![Token::Slot]);
}
#[test]
fn tokenizer_job() {
let mut tokens = Vec::new();
let _ = tokenize(&mut tokens, "{#}", &Path::new("."), 1);
assert_eq!(tokens, vec![Token::Job]);
}
#[test]
fn tokenizer_multiple() {
let mut tokens = Vec::new();
let _ = tokenize(&mut tokens, "foo {} bar", &Path::new("."), 1);
assert_eq!(tokens, vec![Token::Argument("foo ".to_owned()), Token::Placeholder,
Token::Argument(" bar".to_owned())]);
}
#[test]
fn tokenizer_no_space() {
let mut tokens = Vec::new();
let _ = tokenize(&mut tokens, "foo{}bar", &Path::new("."), 1);
assert_eq!(tokens, vec![Token::Argument("foo".to_owned()), Token::Placeholder,
Token::Argument("bar".to_owned())]);
}