diff --git a/src/arguments/errors.rs b/src/arguments/errors.rs index 29500f3..857ac73 100644 --- a/src/arguments/errors.rs +++ b/src/arguments/errors.rs @@ -1,3 +1,4 @@ +use std::fmt; use std::io::{self, Write, stderr, stdout}; use std::path::PathBuf; use std::process::exit; @@ -10,23 +11,30 @@ pub enum FileErr { Write(PathBuf, io::Error), } -/// The `InputIterator` may possibly encounter an error with reading from the unprocessed file. -#[derive(Debug)] -pub enum InputIteratorErr { - FileRead(PathBuf, io::Error), +impl fmt::Display for FileErr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + FileErr::Open(ref path, ref io) => write!(f, "unable to open {:?}: {}", path, io), + FileErr::Read(ref path, ref io) => write!(f, "unable to read {:?}: {}", path, io), + FileErr::Write(ref path, ref io) => write!(f, "unable to write {:?}: {}", path, io) + } + } } /// The error type for the argument module. #[derive(Debug)] pub enum ParseErr { + /// The value of the job delay parameter is not set to a number. DelayNaN(usize), + /// The job delay parameter was not set. DelayNoValue, /// An error occurred with accessing the unprocessed file. File(FileErr), + /// The joblog parameter was not set. JoblogNoValue, - /// The value of jobs was not set to a number. + /// The jobs number parameter was not set to a number. JobsNaN(String), - /// No value was provided for the jobs flag. + /// The jobs number parameter was not set. JobsNoValue, /// An invalid argument flag was provided. InvalidArgument(usize), @@ -34,13 +42,19 @@ pub enum ParseErr { MaxArgsNaN(usize), /// No value was provided for the `max_args` flag. MaxArgsNoValue, + /// The memfree parameter was invalid. MemInvalid(usize), + /// The memfree parameter was not set. MemNoValue, /// No arguments were given, so no action can be taken. NoArguments, + /// The standard input could not be redirected to the given file RedirFile(PathBuf), + /// The timeout parameter was not set to a number. TimeoutNaN(usize), + /// The timeout parameter was not set. TimeoutNoValue, + /// The workdir parameter was not set. WorkDirNoValue, } @@ -57,17 +71,9 @@ impl ParseErr { let stdout = &mut stdout.lock(); let _ = stderr.write(b"parallel: parsing error: "); match self { - ParseErr::File(file_err) => match file_err { - FileErr::Open(file, why) => { - let _ = write!(stderr, "unable to open file: {:?}: {}\n", file, why); - }, - FileErr::Read(file, why) => { - let _ = write!(stderr, "unable to read file: {:?}: {}\n", file, why); - }, - FileErr::Write(file, why) => { - let _ = write!(stderr, "unable to write to file: {:?}: {}\n", file, why); - }, - }, + ParseErr::File(file_err) => { + let _ = writeln!(stderr, "{}", file_err); + } ParseErr::DelayNaN(index) => { let _ = write!(stderr, "delay parameter, '{}', is not a number.\n", arguments[index]); }, diff --git a/src/arguments/mod.rs b/src/arguments/mod.rs index 205d87e..d71f511 100644 --- a/src/arguments/mod.rs +++ b/src/arguments/mod.rs @@ -19,7 +19,7 @@ use num_cpus; use self::errors::ParseErr; // Re-export key items from internal modules. -pub use self::errors::{FileErr, InputIteratorErr}; +pub use self::errors::FileErr; #[derive(PartialEq)] enum Mode { Arguments, Command, Inputs, InputsAppend, Files, FilesAppend } @@ -66,7 +66,8 @@ impl Args { /// Performs all the work related to parsing program arguments pub fn parse(&mut self, comm: &mut String, arguments: &[String], base_path: &mut PathBuf) - -> Result { + -> Result + { // Each list will consist of a series of input arguments let mut lists: Vec> = Vec::new(); // The `current_inputs` variable will contain all the inputs that have been collected for the first list. diff --git a/src/execute/argument_splitter.rs b/src/execute/argument_splitter.rs new file mode 100644 index 0000000..3f2145b --- /dev/null +++ b/src/execute/argument_splitter.rs @@ -0,0 +1,63 @@ +const DOUBLE: u8 = 1; +const SINGLE: u8 = 2; +const BACK: u8 = 4; + +/// An efficient `Iterator` structure for splitting arguments +pub struct ArgumentSplitter<'a> { + buffer: String, + data: &'a str, + read: usize, + flags: u8, +} + +impl<'a> ArgumentSplitter<'a> { + pub fn new(data: &'a str) -> ArgumentSplitter<'a> { + ArgumentSplitter { + buffer: String::with_capacity(32), + data: data, + read: 0, + flags: 0, + } + } +} + +impl<'a> Iterator for ArgumentSplitter<'a> { + type Item = String; + + fn next(&mut self) -> Option { + for character in self.data.chars().skip(self.read) { + self.read += 1; + match character { + _ if self.flags & BACK != 0 => { + self.buffer.push(character); + self.flags ^= BACK; + }, + '"' if self.flags & SINGLE == 0 => self.flags ^= DOUBLE, + '\'' if self.flags & DOUBLE == 0 => self.flags ^= SINGLE, + ' ' if !self.buffer.is_empty() & (self.flags & (SINGLE + DOUBLE) == 0) => break, + '\\' if (self.flags & (SINGLE + DOUBLE) == 0) => self.flags ^= BACK, + _ => self.buffer.push(character) + } + } + + if self.buffer.is_empty() { + None + } else { + let mut output = self.buffer.clone(); + output.shrink_to_fit(); + self.buffer.clear(); + Some(output) + } + } +} + +#[test] +fn test_split_args() { + let argument = ArgumentSplitter::new("ffmpeg -i \"file with spaces\" \"output with spaces\""); + let expected = vec!["ffmpeg", "-i", "file with spaces", "output with spaces"]; + assert_eq!(argument.collect::>(), expected); + + let argument = ArgumentSplitter::new("one\\ two\\\\ three"); + let expected = vec!["one two\\", "three"]; + assert_eq!(argument.collect::>(), expected); +} diff --git a/src/execute/child.rs b/src/execute/child.rs index 02c60e8..22e93b9 100644 --- a/src/execute/child.rs +++ b/src/execute/child.rs @@ -8,6 +8,8 @@ use super::signals; use super::pipe::disk::output as pipe_output; use super::pipe::disk::State; +/// Receives a `Child` and handles the child according. If a `timeout` is specified then the process will be killed +/// if it exceeds the `timeout` value. Job stats are also gathered in case the `--joblog` parameter was supplied. pub fn handle_child(mut child: Child, output: &Sender, flags: u16, job_id: usize, input: String, has_timeout: bool, timeout: Duration, base: &str, buffer: &mut [u8]) -> (Timespec, Timespec, i32, i32) { diff --git a/src/execute/command.rs b/src/execute/command.rs index 07b86d9..7351966 100644 --- a/src/execute/command.rs +++ b/src/execute/command.rs @@ -4,6 +4,7 @@ use std::io::{self, Write}; use std::process::{Child, Command, Stdio}; use arguments; use tokenizer::*; +use super::argument_splitter::ArgumentSplitter; pub enum CommandErr { IO(io::Error) @@ -150,67 +151,3 @@ fn shell_output>(args: S, flags: u16) -> io::Result { .spawn() } } - -const DOUBLE: u8 = 1; -const SINGLE: u8 = 2; -const BACK: u8 = 4; - -/// An efficient `Iterator` structure for splitting arguments -struct ArgumentSplitter<'a> { - buffer: String, - data: &'a str, - read: usize, - flags: u8, -} - -impl<'a> ArgumentSplitter<'a> { - fn new(data: &'a str) -> ArgumentSplitter<'a> { - ArgumentSplitter { - buffer: String::with_capacity(32), - data: data, - read: 0, - flags: 0, - } - } -} - -impl<'a> Iterator for ArgumentSplitter<'a> { - type Item = String; - - fn next(&mut self) -> Option { - for character in self.data.chars().skip(self.read) { - self.read += 1; - match character { - _ if self.flags & BACK != 0 => { - self.buffer.push(character); - self.flags ^= BACK; - }, - '"' if self.flags & SINGLE == 0 => self.flags ^= DOUBLE, - '\'' if self.flags & DOUBLE == 0 => self.flags ^= SINGLE, - ' ' if !self.buffer.is_empty() & (self.flags & (SINGLE + DOUBLE) == 0) => break, - '\\' if (self.flags & (SINGLE + DOUBLE) == 0) => self.flags ^= BACK, - _ => self.buffer.push(character) - } - } - - if self.buffer.is_empty() { - None - } else { - let mut output = self.buffer.clone(); - output.shrink_to_fit(); - self.buffer.clear(); - Some(output) - } - } -} - -#[test] -fn test_split_args() { - let argument = ArgumentSplitter::new("ffmpeg -i \"file with spaces\" \"output with spaces\""); - let expected = vec!["ffmpeg", "-i", "file with spaces", "output with spaces"]; - assert_eq!(argument.collect::>(), expected); - - let argument = ArgumentSplitter::new("one\\ two\\\\ three"); - let expected = vec!["one two\\", "three"]; - assert_eq!(argument.collect::>(), expected); -} diff --git a/src/execute/dry.rs b/src/execute/dry.rs index d7baddb..1eba349 100644 --- a/src/execute/dry.rs +++ b/src/execute/dry.rs @@ -1,6 +1,6 @@ -use input_iterator::InputIterator; +use input_iterator::{InputIterator, InputIteratorErr}; use tokenizer::Token; -use arguments::{self, InputIteratorErr}; +use arguments; use execute::command; use misc::NumToA; @@ -16,8 +16,8 @@ pub fn dry_run(flags: u16, inputs: InputIterator, arguments: &[Token]) { let mut command_buffer = String::new(); let slot = "{SLOT_ID}"; let pipe = flags & arguments::PIPE_IS_ENABLED != 0; - let mut id_buffer = [0u8; 64]; - let mut total_buffer = [0u8; 64]; + let mut id_buffer = [0u8; 20]; + let mut total_buffer = [0u8; 20]; let truncate = inputs.total_arguments.numtoa(10, &mut total_buffer); let job_total = &total_buffer[0..truncate]; diff --git a/src/execute/exec_commands.rs b/src/execute/exec_commands.rs index accf6fd..8f7dfc9 100644 --- a/src/execute/exec_commands.rs +++ b/src/execute/exec_commands.rs @@ -36,9 +36,9 @@ impl ExecCommands { let mut command_buffer = &mut String::with_capacity(64); let has_timeout = self.timeout != Duration::from_millis(0); let mut input = String::with_capacity(64); - let mut id_buffer = [0u8; 64]; - let mut job_buffer = [0u8; 64]; - let mut total_buffer = [0u8; 64]; + let mut id_buffer = [0u8; 20]; + let mut job_buffer = [0u8; 20]; + let mut total_buffer = [0u8; 20]; let truncate = self.num_inputs.numtoa(10, &mut total_buffer); let job_total = &total_buffer[0..truncate]; diff --git a/src/execute/exec_inputs.rs b/src/execute/exec_inputs.rs index 0ce47c9..b688d93 100644 --- a/src/execute/exec_inputs.rs +++ b/src/execute/exec_inputs.rs @@ -30,7 +30,7 @@ impl ExecInputs { let has_timeout = self.timeout != Duration::from_millis(0); let mut input = String::with_capacity(64); - let mut id_buffer = [0u8; 64]; + let mut id_buffer = [0u8; 20]; while let Some(job_id) = self.inputs.try_next(&mut input) { if flags & arguments::VERBOSE_MODE != 0 { diff --git a/src/execute/job_log.rs b/src/execute/job_log.rs index a07617a..b0d727e 100644 --- a/src/execute/job_log.rs +++ b/src/execute/job_log.rs @@ -3,16 +3,24 @@ use std::fs::File; use std::io::{Write, BufWriter}; use time::Timespec; +// Each `JobLog` consists of a single job's statistics ready to be written to the job log file. pub struct JobLog { + /// The `job_id` is used to keep jobs written to the job log file in the correct order pub job_id: usize, + /// The `start_time` is a measurement of when the job started, since the 1970 UNIX epoch pub start_time: Timespec, + /// The `runtime` is the actual time the application ran, in nanoseconds pub runtime: u64, + /// The `exit_value` contains the exit value that the job's process quit with pub exit_value: i32, + /// The `signal` contains a non-zero value if the job was killed by a signal pub signal: i32, + /// The actual `command` that was executed for this job pub command: String } impl JobLog { + /// Writes an individual job log to the job log file, efficiently. pub fn write_entry(&self, joblog: &mut File, id_buffer: &mut [u8], pad: usize) { // 1: JobID let mut joblog = BufWriter::new(joblog); @@ -22,11 +30,11 @@ impl JobLog { let _ = joblog.write(b" "); } - // 2: StartTime in seconds - let bytes_written = (self.start_time.sec as u64).numtoa(10, id_buffer); + // 2: StartTime in seconds, with up to two decimal places + let bytes_written = self.start_time.sec.numtoa(10, id_buffer); let _ = joblog.write(&id_buffer[0..bytes_written]); let _ = joblog.write(b"."); - let decimal = (self.start_time.nsec as u64 % 1_000_000_000) / 1_000_000; + let decimal = (self.start_time.nsec % 1_000_000_000) / 1_000_000; if decimal == 0 { let _ = joblog.write(b"000"); } else { @@ -42,7 +50,7 @@ impl JobLog { let _ = joblog.write(b" "); } - // 3: Runtime in seconds + // 3: Runtime in seconds, with up to three decimal places. let bytes_written = (self.runtime / 1_000_000_000).numtoa(10, id_buffer); for _ in 0..10-(bytes_written + 4) { let _ = joblog.write(b" "); @@ -83,6 +91,7 @@ impl JobLog { } } +/// Creates the column headers in the first line of the job log file pub fn create(file: &mut File, padding: usize) { let mut joblog = BufWriter::new(file); diff --git a/src/execute/mod.rs b/src/execute/mod.rs index 09c928f..1440056 100644 --- a/src/execute/mod.rs +++ b/src/execute/mod.rs @@ -1,3 +1,4 @@ +mod argument_splitter; mod child; mod dry; mod exec_commands; diff --git a/src/execute/receive.rs b/src/execute/receive.rs index 6395efc..1fc812e 100644 --- a/src/execute/receive.rs +++ b/src/execute/receive.rs @@ -46,12 +46,12 @@ macro_rules! open_job_files { ($stdout_path:ident, $stderr_path:ident) => {{ let stdout_file = loop { if let Ok(file) = File::open(&$stdout_path) { break file } - thread::sleep(Duration::from_millis(1)); + thread::sleep(Duration::from_millis(100)); }; let stderr_file = loop { if let Ok(file) = File::open(&$stderr_path) { break file } - thread::sleep(Duration::from_millis(1)); + thread::sleep(Duration::from_millis(100)); }; (stdout_file, stderr_file) @@ -98,7 +98,7 @@ pub fn receive_messages(input_rx: Receiver, args: Args, base: &str, proce // A buffer for buffering the outputs of temporary files on disk. let mut read_buffer = [0u8; 8192]; // A buffer for converting job ID's into a byte array representation of a string. - let mut id_buffer = [0u8; 64]; + let mut id_buffer = [0u8; 20]; // Generates the stdout and stderr paths, along with a truncation value to truncate the job ID from the paths. let (truncate_size, mut stdout_path, mut stderr_path) = filepaths::new_job(base, counter, &mut id_buffer); // If the joblog parameter was passed, open the file for writing. @@ -113,9 +113,12 @@ pub fn receive_messages(input_rx: Receiver, args: Args, base: &str, proce // The loop will only quit once all inputs have been processed while counter < args.ninputs || job_counter < args.ninputs { + // Tracks whether the next file in the queue should be trailed. let mut tail_next = false; + // First receive the next input signal from the running jobs match input_rx.recv().unwrap() { + // If the job's id matches the current counter, there's no need to buffer it -- print immediately State::Completed(id, ref name) if id == counter => { let mut stdout = stdout.lock(); let mut stderr = stderr.lock(); @@ -126,10 +129,12 @@ pub fn receive_messages(input_rx: Receiver, args: Args, base: &str, proce remove_job_files!(stdout_path, stderr_path, stderr); counter += 1; }, + // Otherwise, add the job to the job complete buffer and mark the current job for trailing State::Completed(id, name) => { buffer.push(State::Completed(id, name)); tail_next = true; }, + // If an error occured and the id matches the counter, print the error immediately. State::Error(id, ref message) if id == counter => { counter += 1; if let Err(why) = error_file.write(message.as_bytes()) { @@ -137,21 +142,29 @@ pub fn receive_messages(input_rx: Receiver, args: Args, base: &str, proce let _ = write!(stderr, "parallel: I/O error: {}", why); } }, + // Otherwise add that error to the job complete buffer as well. State::Error(id, message) => buffer.push(State::Error(id, message)), + // If the joblog parameter was set, a joblog signal can be received. + // If the job ID matches the current job counter, write the log to the job log. State::JobLog(ref data) if data.job_id == job_counter => { job_counter += 1; let mut joblog = joblog.as_mut().unwrap(); data.write_entry(&mut joblog, &mut id_buffer, id_pad_length); }, + // Otherwise, add it to the job buffer. State::JobLog(data) => job_buffer.push(data), } + // If the received job ID doesn't match the ID that we wanted, we should trail the current job's files + // and print new messages as they come available, until the completion signal has been received. if tail_next { filepaths::next_job_path(counter, truncate_size, &mut id_buffer, &mut stdout_path, &mut stderr_path); let (mut stdout_file, mut stderr_file) = open_job_files!(stdout_path, stderr_path); loop { + // If no message is received then tail the file, else handle the message match input_rx.try_recv() { + // When the completion signal is received, print remaining messages and break the loop Ok(State::Completed(id, ref name)) if id == counter => { let mut stdout = stdout.lock(); let mut stderr = stderr.lock(); @@ -161,21 +174,28 @@ pub fn receive_messages(input_rx: Receiver, args: Args, base: &str, proce counter += 1; break }, + // We are only concerned about the current job ID Ok(State::Completed(id, name)) => buffer.push(State::Completed(id, name)), + // If an error occured, print the error and break Ok(State::Error(id, ref message)) if id == counter => { counter += 1; if let Err(why) = error_file.write(message.as_bytes()) { let mut stderr = stderr.lock(); let _ = write!(stderr, "parallel: I/O error: {}", why); } + break }, + // We are only concerned about the current job ID Ok(State::Error(id, message)) => buffer.push(State::Error(id, message)), + // If the job ID matches the current job counter, write the log to the job log. Ok(State::JobLog(ref data)) if data.job_id == job_counter => { job_counter += 1; let mut joblog = joblog.as_mut().unwrap(); data.write_entry(&mut joblog, &mut id_buffer, id_pad_length); }, + // Otherwise, add it to the job buffer. Ok(State::JobLog(data)) => job_buffer.push(data), + // Tail the file and wait a specified time before checking for the next message _ => { let mut stdout = stdout.lock(); let mut stderr = stderr.lock(); @@ -190,6 +210,8 @@ pub fn receive_messages(input_rx: Receiver, args: Args, base: &str, proce } } + // Attempt to process results that have been buffered in the queue. Repeatedly check for the next sequence + // in the queue until no changes have been made. let mut changed = true; while changed { changed = false; @@ -219,6 +241,7 @@ pub fn receive_messages(input_rx: Receiver, args: Args, base: &str, proce } } + // If the joblog parameter was set, also check for job buffer for entries that can be written. if let Some(ref mut joblog) = joblog { changed = true; while changed { @@ -256,6 +279,7 @@ pub fn receive_messages(input_rx: Receiver, args: Args, base: &str, proce } } +/// Drops states that have been processed and are no longer required fn drop_used_states(buffer: &mut SmallVec<[State; 32]>, drop: &mut SmallVec<[usize; 32]>) { drop.sort(); for id in drop.drain().rev() { @@ -263,6 +287,7 @@ fn drop_used_states(buffer: &mut SmallVec<[State; 32]>, drop: &mut SmallVec<[usi } } +/// Drops job logs that have been processed and are no longer required fn drop_used_logs(buffer: &mut SmallVec<[JobLog; 32]>, drop: &mut SmallVec<[usize; 32]>) { drop.sort(); for id in drop.drain().rev() { diff --git a/src/input_iterator/iterator.rs b/src/input_iterator/iterator.rs index 6614c02..7006c89 100644 --- a/src/input_iterator/iterator.rs +++ b/src/input_iterator/iterator.rs @@ -1,6 +1,9 @@ use disk_buffer::*; -use arguments::errors::{FileErr, InputIteratorErr}; +use arguments::errors::{FileErr}; +use super::InputIteratorErr; +use itoa; use time; +use std::io::{self, Write}; use std::path::{Path, PathBuf}; use std::str; @@ -10,6 +13,34 @@ pub struct ETA { pub average: u64, } +impl ETA { + pub fn write_to_stderr(&self, completed: usize) { + let stderr = io::stderr(); + let mut stderr = &mut stderr.lock(); + + // Write the ETA to the standard error + let _ = stderr.write(b"ETA: "); + let _ = itoa::write(stderr, self.time / 1_000_000_000); + + // Write the number of inputs left to process to standard error + let _ = stderr.write(b"s Left: "); + let _ = itoa::write(stderr, self.left); + + // Write the average runtime of processes (with two decimal places) to standard error + let _ = stderr.write(b" AVG: "); + let _ = itoa::write(stderr, self.average / 1_000_000_000); + let _ = stderr.write(b"."); + let remainder = (self.average % 1_000_000_000) / 10_000_000; + let _ = itoa::write(stderr, remainder); + if remainder < 10 { let _ = stderr.write(b"0"); } + + // Write the number of completed units so far to standard error + let _ = stderr.write(b"s Completed: "); + let _ = itoa::write(stderr, completed); + let _ = stderr.write(b"\n"); + } +} + /// The `InputIterator` tracks the total number of arguments, the current argument counter, and /// takes ownership of an `InputBuffer` which buffers input arguments from the disk when arguments /// stored in memory are depleted. diff --git a/src/input_iterator/lock.rs b/src/input_iterator/lock.rs index 7513be5..041f742 100644 --- a/src/input_iterator/lock.rs +++ b/src/input_iterator/lock.rs @@ -1,5 +1,5 @@ -use arguments::{self, InputIteratorErr}; -use super::InputIterator; +use arguments; +use super::{InputIterator, InputIteratorErr}; use sys_info; use std::thread; @@ -17,21 +17,18 @@ pub struct InputsLock { } impl InputsLock { + /// Attempts to obtain the next input in the queue, returning `None` when it is finished. + /// It works the same as the `Iterator` trait's `next()` method, only re-using the same input buffer. pub fn try_next(&mut self, input: &mut String) -> Option<(usize)> { let mut inputs = self.inputs.lock().unwrap(); let job_id = inputs.curr_argument; if self.flags & arguments::ETA != 0 { - let eta = inputs.eta(); if self.completed { inputs.completed += 1; } else { self.completed = true; } - let stderr = io::stderr(); - let mut stderr = &mut stderr.lock(); - let message = format!("ETA: {}s Left: {} AVG: {:.2}s Completed: {}\n", eta.time / 1_000_000_000, - eta.left, eta.average as f64 / 1_000_000_000f64, inputs.completed); - let _ = stderr.write(message.as_bytes()); + inputs.eta().write_to_stderr(inputs.completed); } if self.has_delay { thread::sleep(self.delay); } diff --git a/src/input_iterator/mod.rs b/src/input_iterator/mod.rs index b09ede6..fc2312f 100644 --- a/src/input_iterator/mod.rs +++ b/src/input_iterator/mod.rs @@ -3,3 +3,12 @@ mod iterator; pub use self::lock::InputsLock; pub use self::iterator::{InputIterator, ETA}; + +use std::io; +use std::path::PathBuf; + +/// The `InputIterator` may possibly encounter an error with reading from the unprocessed file. +#[derive(Debug)] +pub enum InputIteratorErr { + FileRead(PathBuf, io::Error), +} diff --git a/src/main.rs b/src/main.rs index 4c52873..095e0bb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -35,7 +35,7 @@ use std::sync::mpsc::channel; use arguments::Args; use execute::pipe::disk::State; use input_iterator::{InputIterator, InputsLock}; -use tokenizer::{Token, TokenErr, tokenize}; +use tokenizer::{Token, tokenize}; /// The command string needs to be available in memory for the entirety of the application, so this /// is achievable by transmuting the lifetime of the reference into a static lifetime. To guarantee @@ -60,6 +60,8 @@ fn main() { let mut args = Args::new(); let mut comm = String::with_capacity(128); let raw_arguments = env::args().collect::>(); + + // Attempt to obtain the default tempdir base path. let mut base = match filepaths::base() { Some(base) => base, None => { @@ -69,11 +71,13 @@ fn main() { } }; + // Collect the command, arguments, and tempdir base path. args.ninputs = match args.parse(&mut comm, &raw_arguments, &mut base) { Ok(inputs) => inputs, Err(why) => why.handle(&raw_arguments) }; + // Attempt to convert the base path into a string slice. let base_path = match base.to_str() { Some(base) => String::from(base), None => { @@ -83,6 +87,7 @@ fn main() { } }; + // Construct the paths of each of the required files using the base tempdir path. let mut unprocessed_path = base.clone(); let mut processed_path = base.clone(); let mut errors_path = base; @@ -101,15 +106,8 @@ fn main() { // Attempt to tokenize the command argument into simple primitive placeholders. if let Err(error) = tokenize(&mut args.arguments, static_comm, &unprocessed_path, args.ninputs) { - let mut stderr = stderr.lock(); - match error { - TokenErr::File(why) => { - let _ = write!(stderr, "unable to obtain Nth input: {}\n", why); - }, - TokenErr::OutOfBounds => { - let _ = write!(stderr, "input token out of bounds\n"); - } - } + let stderr = &mut stderr.lock(); + let _ = writeln!(stderr, "{}", error); exit(1) } diff --git a/src/misc/digits.rs b/src/misc/digits.rs index b7a9ffd..aa163e9 100644 --- a/src/misc/digits.rs +++ b/src/misc/digits.rs @@ -4,14 +4,29 @@ pub trait Digits { fn digits(&self) -> Self; } -impl Digits for usize { - fn digits(&self) -> usize { - let mut digits = if *self == 1 || *self % 10 == 0 { 1 } else { 0 }; - let mut temp = 1; - while temp < *self { - digits += 1; - temp = (temp << 3) + (temp << 1); +macro_rules! impl_digits_for { + ($t:ty) => { + impl Digits for $t { + fn digits(&self) -> $t { + let mut digits = if *self == 1 || *self % 10 == 0 { 1 } else { 0 }; + let mut temp = 1; + while temp < *self { + digits += 1; + temp = (temp << 3) + (temp << 1); + } + digits + } } - digits } } + +impl_digits_for!(isize); +impl_digits_for!(i8); +impl_digits_for!(i16); +impl_digits_for!(i32); +impl_digits_for!(i64); +impl_digits_for!(usize); +impl_digits_for!(u8); +impl_digits_for!(u16); +impl_digits_for!(u32); +impl_digits_for!(u64); diff --git a/src/misc/numtoa.rs b/src/misc/numtoa.rs index 1fa7ca6..7b8e796 100644 --- a/src/misc/numtoa.rs +++ b/src/misc/numtoa.rs @@ -22,73 +22,76 @@ pub trait NumToA { fn numtoa(self, base: T, string: &mut [u8]) -> usize; } -impl NumToA for i32 { - fn numtoa(mut self, base: i32, string: &mut [u8]) -> usize { - let mut index = 0; - let mut is_negative = false; +// A lookup table to prevent the need for conditional branching +// The value of the remainder of each step will be used as the index +const LOOKUP: &'static [u8] = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - if self < 0 { - is_negative = true; - self = self.abs(); - } else if self == 0 { - string[0] = b'0'; - return 1; - } +macro_rules! impl_unsized_numtoa_for { + ($t:ty) => { + impl NumToA<$t> for $t { + fn numtoa(mut self, base: $t, string: &mut [u8]) -> usize { + if self == 0 { + string[0] = b'0'; + return 1; + } - while self != 0 { - let rem = (self % base) as u8; - string[index] = if rem > 9 { (rem - 10) + b'a' } else { (rem + b'0') }; - index += 1; - self /= base; - } + let mut index = 0; + while self != 0 { + let rem = self % base; + string[index] = LOOKUP[rem as usize]; + index += 1; + self /= base; + } - if is_negative { - string[index] = b'-'; - index += 1; + reverse(string, index); + index + } } - - reverse(string, index); - index } } -impl NumToA for usize { - fn numtoa(mut self, base: usize, string: &mut [u8]) -> usize { - if self == 0 { - string[0] = b'0'; - return 1; - } - - let mut index = 0; - while self != 0 { - let rem = (self % base) as u8; - string[index] = if rem > 9 { (rem - 10) + b'a' } else { (rem + b'0') }; - index += 1; - self /= base; - } +macro_rules! impl_sized_numtoa_for { + ($t:ty) => { + impl NumToA<$t> for $t { + fn numtoa(mut self, base: $t, string: &mut [u8]) -> usize { + let mut index = 0; + let mut is_negative = false; - reverse(string, index); - index - } -} + if self < 0 { + is_negative = true; + self = self.abs(); + } else if self == 0 { + string[0] = b'0'; + return 1; + } + while self != 0 { + let rem = self % base; + string[index] = LOOKUP[rem as usize]; + index += 1; + self /= base; + } -impl NumToA for u64 { - fn numtoa(mut self, base: u64, string: &mut [u8]) -> usize { - if self == 0 { - string[0] = b'0'; - return 1; - } + if is_negative { + string[index] = b'-'; + index += 1; + } - let mut index = 0; - while self != 0 { - let rem = (self % base) as u8; - string[index] = if rem > 9 { (rem - 10) + b'a' } else { (rem + b'0') }; - index += 1; - self /= base; + reverse(string, index); + index + } } - reverse(string, index); - index } } + +impl_sized_numtoa_for!(i8); +impl_sized_numtoa_for!(i16); +impl_sized_numtoa_for!(i32); +impl_sized_numtoa_for!(i64); +impl_sized_numtoa_for!(isize); +impl_unsized_numtoa_for!(u8); +impl_unsized_numtoa_for!(u16); +impl_unsized_numtoa_for!(u32); +impl_unsized_numtoa_for!(u64); +impl_unsized_numtoa_for!(usize); diff --git a/src/tokenizer/mod.rs b/src/tokenizer/mod.rs index d1ac09f..3b86a4b 100644 --- a/src/tokenizer/mod.rs +++ b/src/tokenizer/mod.rs @@ -1,6 +1,7 @@ pub mod functions; use arrayvec::ArrayVec; +use std::fmt; use std::io; use std::path::Path; use std::borrow::Cow; @@ -12,6 +13,15 @@ pub enum TokenErr { 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)] /// A token is a placeholder for the operation to be performed on the input value. pub enum Token {