Skip to content
This repository has been archived by the owner on Jul 11, 2019. It is now read-only.

Commit

Permalink
QoL: Refactoring & Comments
Browse files Browse the repository at this point in the history
- Code comments were added in a number of areas
- A lookup optimization was performed to the NumToA trait
- The NumToA trait is now feature complete
- ETA now has integer to string optimizations via itoa
- Some error types were improved to feature a Display implementation
- Some refactoring of the codebase
  • Loading branch information
mmstick committed Jan 15, 2017
1 parent 9ba7f41 commit b5e61d5
Show file tree
Hide file tree
Showing 18 changed files with 287 additions and 180 deletions.
40 changes: 23 additions & 17 deletions src/arguments/errors.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::fmt;
use std::io::{self, Write, stderr, stdout};
use std::path::PathBuf;
use std::process::exit;
Expand All @@ -10,37 +11,50 @@ 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),
/// The value for `max_args` was not set to a number.
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,
}

Expand All @@ -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]);
},
Expand Down
5 changes: 3 additions & 2 deletions src/arguments/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down Expand Up @@ -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<usize, ParseErr> {
-> Result<usize, ParseErr>
{
// Each list will consist of a series of input arguments
let mut lists: Vec<Vec<String>> = Vec::new();
// The `current_inputs` variable will contain all the inputs that have been collected for the first list.
Expand Down
63 changes: 63 additions & 0 deletions src/execute/argument_splitter.rs
Original file line number Diff line number Diff line change
@@ -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<String> {
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::<Vec<String>>(), expected);

let argument = ArgumentSplitter::new("one\\ two\\\\ three");
let expected = vec!["one two\\", "three"];
assert_eq!(argument.collect::<Vec<String>>(), expected);
}
2 changes: 2 additions & 0 deletions src/execute/child.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<State>, flags: u16, job_id: usize, input: String,
has_timeout: bool, timeout: Duration, base: &str, buffer: &mut [u8]) -> (Timespec, Timespec, i32, i32)
{
Expand Down
65 changes: 1 addition & 64 deletions src/execute/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -150,67 +151,3 @@ fn shell_output<S: AsRef<OsStr>>(args: S, flags: u16) -> io::Result<Child> {
.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<String> {
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::<Vec<String>>(), expected);

let argument = ArgumentSplitter::new("one\\ two\\\\ three");
let expected = vec!["one two\\", "three"];
assert_eq!(argument.collect::<Vec<String>>(), expected);
}
8 changes: 4 additions & 4 deletions src/execute/dry.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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];

Expand Down
6 changes: 3 additions & 3 deletions src/execute/exec_commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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];

Expand Down
2 changes: 1 addition & 1 deletion src/execute/exec_inputs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
17 changes: 13 additions & 4 deletions src/execute/job_log.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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 {
Expand All @@ -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" ");
Expand Down Expand Up @@ -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);

Expand Down
1 change: 1 addition & 0 deletions src/execute/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod argument_splitter;
mod child;
mod dry;
mod exec_commands;
Expand Down
Loading

0 comments on commit b5e61d5

Please sign in to comment.