pub mod functions;
use arrayvec::ArrayVec;
use std::fmt;
use std::io;
use std::path::Path;
use std::borrow::Cow;
pub use self::functions::*;
#[derive(Debug)]
pub enum TokenErr {
File(io::Error),
OutOfBounds,
}
impl fmt::Display for TokenErr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
TokenErr::File(ref io) => write!(f, "parallel: unable to obtain the Nth input: {}", io),
TokenErr::OutOfBounds => write!(f, "parallel: input token out of bounds")
}
}
}
#[derive(Clone, PartialEq, Debug)]
pub enum Token {
Argument(Cow<'static, str>),
BaseAndExt,
BaseAndSuffix(&'static str),
Basename,
Dirname,
Job,
Placeholder,
RemoveExtension,
RemoveSuffix(&'static str),
Slot
}
struct Number {
id: usize,
token: Token,
}
impl Number {
fn new(id: usize, token: Token) -> Number {
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::BaseAndSuffix(pat) => basename(remove_pattern(input, pat)),
Token::Dirname => dirname(input),
Token::Job => unreachable!(),
Token::Placeholder => input,
Token::RemoveExtension => remove_extension(input),
Token::RemoveSuffix(pat) => remove_pattern(input, pat),
Token::Slot => unreachable!()
};
Ok(String::from(argument))
}
}
pub fn tokenize(tokens: &mut ArrayVec<[Token; 128]>, template: &'static 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.bytes().enumerate() {
match (character, pattern_matching) {
(b'{', 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));
}
},
(b'}', 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); },
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(pattern: &'static str, path: &Path, nargs: usize) -> Result<Option<Token>, 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())))),
_ => {
if pattern.starts_with('^') && pattern.len() > 1 {
Ok(Some(Token::RemoveSuffix(&pattern[1..])))
} else if pattern.starts_with("/^") && pattern.len() > 2 {
Ok(Some(Token::BaseAndSuffix(&pattern[2..])))
} else {
let ndigits = pattern.bytes().take_while(|&x| (x as char).is_numeric()).count();
let nchars = ndigits + pattern.bytes().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)
}
}
}
}
}