pub use self::flags::cflag::CFlag;
pub use self::flags::iflag::IFlag;
pub use self::flags::oflag::OFlag;
pub use self::error::{Error, Result};
pub use self::number::parse;
mod number;
mod error;
mod flags;
#[derive(Clone, Debug, Default)]
pub struct Opts {
pub cflags: CFlag,
pub iflags: IFlag,
pub oflags: OFlag,
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,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Kind {
BasicOption,
CFlag,
IFlag,
OFlag,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum StatusLevel {
TransferOnly,
SuppressAll,
NoTransfer,
Progress,
}
#[doc(hidden)]
pub trait Unimplemented {
const KIND: Kind;
const UNIMPLEMENTED: &'static [&'static str];
fn check_if_implemented(flag: &str) -> Result<()> {
for s in Self::UNIMPLEMENTED {
if flag == *s {
return Err(Error::Unimplemented(Self::KIND, *s));
}
}
Ok(())
}
fn unknown<T>(flag: String) -> Result<T> { Err(Error::Unknown(Self::KIND, flag)) }
}
impl Unimplemented for Opts {
const KIND: Kind = Kind::BasicOption;
const UNIMPLEMENTED: &'static [&'static str] = &["count", "files"];
}
impl Default for StatusLevel {
fn default() -> Self { StatusLevel::TransferOnly }
}
impl std::str::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(block: bool, unblock: bool, conversion_record_size: Option<usize>) -> Result<Self> {
match (block, 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 'unblock' 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 = CFlag::default();
let mut conv_block_size = None;
let (mut iflags, mut oflags) = (IFlag::default(), OFlag::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 = CFlag::new(val)?,
"iflag" => iflags = IFlag::new(val)?,
"oflag" => oflags = OFlag::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(IFlag::DIRECTORY))?;
validate_directory(&output_file, oflags.contains(OFlag::DIRECTORY))?;
Ok(Opts {
mode: Mode::new(
cflags.contains(CFlag::BLOCK),
cflags.contains(CFlag::UNBLOCK),
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,
})
}
pub fn iflag(&self, iflag: IFlag) -> bool { self.iflags.contains(iflag) }
pub fn oflag(&self, oflag: OFlag) -> bool { self.oflags.contains(oflag) }
pub fn cflag(&self, cflag: CFlag) -> bool { self.cflags.contains(cflag) }
}
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(()),
}
}
pub fn keyval(s: &str) -> Option<(&str, &str)> {
let mut split = s.split("=");
if let (Some(key), Some(val), None) = (split.next(), split.next(), split.next()) {
Some((key, val))
} else {
None
}
}