pub mod error;
pub mod flags;
pub mod number;
pub use self::error::{Error, Result, Unimplemented};
use std::str::FromStr;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Kind {
BasicOption,
CFlag,
IFlag,
OFlag,
}
#[derive(Clone, Debug, Default)]
pub struct Opts {
pub cflags: flags::Conv,
pub iflags: flags::In,
pub oflags: flags::Out,
pub mode: Mode,
pub count: Option<usize>,
pub files: Option<usize>,
pub input_block_size: usize,
pub input_file: Option<String>,
pub input_seek: Option<usize>,
pub output_block_size: usize,
pub output_file: Option<String>,
pub output_seek: Option<usize>,
pub status: StatusLevel,
}
impl Unimplemented for Opts {
const KIND: Kind = Kind::BasicOption;
const UNIMPLEMENTED: &'static [&'static str] = &["count", "files"];
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum StatusLevel {
TransferOnly,
SuppressAll,
NoTransfer,
Progress,
}
impl Default for StatusLevel {
fn default() -> Self { StatusLevel::TransferOnly }
}
impl FromStr for StatusLevel {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
match s {
"none" => Ok(StatusLevel::SuppressAll),
"noxfer" => Ok(StatusLevel::NoTransfer),
"progress" => Ok(StatusLevel::Progress),
s => Err(Error::Unknown(Kind::BasicOption, format!("status={}", s))),
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Mode {
Standard,
Block(usize),
Unblock(usize),
}
impl Default for Mode {
fn default() -> Self { Mode::Standard }
}
impl Mode {
fn new(cflags: flags::Conv, conversion_record_size: Option<usize>) -> Result<Self> {
match (
cflags.contains(flags::Conv::BLOCK),
cflags.contains(flags::Conv::UNBLOCK),
conversion_record_size,
) {
(false, false, None) => Ok(Mode::Standard),
(true, false, Some(n)) => Ok(Mode::Block(n)),
(false, true, Some(n)) => Ok(Mode::Unblock(n)),
(false, false, Some(_)) => Err(Error::ConflictingOption(
"a conversion record size option is meaningless if 'block' or 'unblock' are not specified",
)),
(true, true, _) => Err(Error::ConflictingOption(
"can't specify both 'block' and 'unblock' conversion options",
)),
(true, false, None) | (false, true, None) => Err(Error::ConflictingOption(
"must specify a conversion record size if 'block' or 'u'nblock' is selected",
)),
}
}
}
impl Opts {
const DEFAULT_INPUT_BLOCKSIZE: usize = 512;
const DEFAULT_OUTPUT_BLOCKSIZE: usize = 512;
#[allow(unreachable_patterns)]
pub fn new<I>(args: I) -> Result<Self>
where
I: IntoIterator<Item = String>,
{
let mut block_size = <Option<usize>>::default();
let (mut count, mut files) = <(Option<usize>, Option<usize>)>::default(); let mut cflags = flags::Conv::default();
let mut conv_block_size = None;
let (mut iflags, mut oflags) = (flags::In::default(), flags::Out::default());
let (mut in_block_size, mut out_block_size) = (Self::DEFAULT_INPUT_BLOCKSIZE, Self::DEFAULT_OUTPUT_BLOCKSIZE);
let (mut input_file, mut output_file) = <(Option<String>, Option<String>)>::default();
let (mut input_seek, mut output_seek) = <(Option<usize>, Option<usize>)>::default();
let mut status = StatusLevel::default();
for arg in args.into_iter().skip(1) {
if let Some((key, val)) = keyval(&arg) {
Self::check_if_implemented(key)?;
match key {
"conv" => cflags = flags::Conv::new(val)?,
"iflag" => iflags = flags::In::new(val)?,
"oflag" => oflags = flags::Out::new(val)?,
"bs" => block_size = number::parse_opt(val)?,
"cbs" => conv_block_size = number::parse_opt(val)?,
"count" => count = number::parse_opt(val)?, "files" => files = number::parse_opt(val)?, "ibs" => in_block_size = number::parse(val)?,
"if" => input_file = Some(val.to_string()),
"iseek" | "skip" => input_seek = number::parse_opt(val)?,
"obs" => out_block_size = val.parse()?,
"of" => output_file = Some(val.to_string()),
"os" | "seek" => output_seek = number::parse_opt(val)?,
"status" => status = val.parse()?,
_ => return Self::unknown(key.to_owned()),
}
continue;
};
return Self::unknown(arg);
}
validate_directory(&input_file, iflags.contains(flags::In::DIRECTORY))?;
validate_directory(&output_file, oflags.contains(flags::Out::DIRECTORY))?;
Ok(Opts {
mode: Mode::new(cflags, conv_block_size)?,
count,
files,
status,
cflags,
iflags,
oflags,
input_block_size: block_size.unwrap_or(in_block_size),
output_block_size: block_size.unwrap_or(out_block_size),
input_file,
output_file,
input_seek,
output_seek,
})
}
#[inline]
pub fn iflag(&self, flag: flags::In) -> bool { self.iflags.contains(flag) }
#[inline]
pub fn oflag(&self, flag: flags::Out) -> bool { self.oflags.contains(flag) }
#[inline]
pub fn cflag(&self, flag: flags::Conv) -> bool { self.cflags.contains(flag) }
}
fn validate_directory(path: &Option<String>, dir_flag: bool) -> Result<()> {
match path {
Some(path) if !std::path::Path::is_dir(path.as_ref()) && dir_flag => Err(Error::NotDirectory(path.to_string())),
_ => Ok(()),
}
}
fn keyval(s: &String) -> Option<(&str, &str)> {
let mut split = s.split("=");
if let (Some(key), Some(val)) = (split.next(), split.next()) {
Some((key, val))
} else {
None
}
}