Take input to imdl torrent create as positional

Allow taking the `--input` argument to `imdl torrent create` as a
positional argument, so the following now works:

    imdl torrent create foo

Taking input by flag `--input` still works.

type: changed
fixes:
- https://github.com/casey/intermodal/issues/375
This commit is contained in:
Casey Rodarmor 2020-04-21 22:20:36 -07:00
parent c22df5a083
commit 5ba885dbc4
No known key found for this signature in database
GPG Key ID: 556186B153EC6FE0
7 changed files with 169 additions and 22 deletions

View File

@ -2,9 +2,10 @@ Changelog
========= =========
UNRELEASED - 2020-04-21 UNRELEASED - 2020-04-22
----------------------- -----------------------
- :wrench: [`xxxxxxxxxxxx`](https://github.com/casey/intermodal/commits/master) Don't commit man pages - _Casey Rodarmor <casey@rodarmor.com>_ - :zap: [`xxxxxxxxxxxx`](https://github.com/casey/intermodal/commits/master) Take input to `imdl torrent create` as positional - Fixes [#375](https://github.com/casey/intermodal/issues/375) - _Casey Rodarmor <casey@rodarmor.com>_
- :wrench: [`c22df5a08326`](https://github.com/casey/intermodal/commit/c22df5a083265b03abd5531b1f5b2aad60aa68cd) Don't commit man pages - _Casey Rodarmor <casey@rodarmor.com>_
- :wrench: [`4d67d3a10d17`](https://github.com/casey/intermodal/commit/4d67d3a10d17db3c63af092a936eb5994ee107b1) Don't commit the book - _Casey Rodarmor <casey@rodarmor.com>_ - :wrench: [`4d67d3a10d17`](https://github.com/casey/intermodal/commit/4d67d3a10d17db3c63af092a936eb5994ee107b1) Don't commit the book - _Casey Rodarmor <casey@rodarmor.com>_
- :wrench: [`28114c3d64dd`](https://github.com/casey/intermodal/commit/28114c3d64dde5e0275c936b0019eaf4760ba559) Don't commit shell completion scripts - _Casey Rodarmor <casey@rodarmor.com>_ - :wrench: [`28114c3d64dd`](https://github.com/casey/intermodal/commit/28114c3d64dde5e0275c936b0019eaf4760ba559) Don't commit shell completion scripts - _Casey Rodarmor <casey@rodarmor.com>_
- :art: [`4f4464e3a2a7`](https://github.com/casey/intermodal/commit/4f4464e3a2a7f4aaffea8dbe38dd110ad9be4393) Get `st_flags` from `MetadataExt` on MacOS - _Casey Rodarmor <casey@rodarmor.com>_ - :art: [`4f4464e3a2a7`](https://github.com/casey/intermodal/commit/4f4464e3a2a7f4aaffea8dbe38dd110ad9be4393) Get `st_flags` from `MetadataExt` on MacOS - _Casey Rodarmor <casey@rodarmor.com>_

View File

@ -1 +0,0 @@
# Notes

View File

@ -53,6 +53,9 @@ pub(crate) use log::trace;
// modules // modules
pub(crate) use crate::{consts, error, host_port_parse_error}; pub(crate) use crate::{consts, error, host_port_parse_error};
// functions
pub(crate) use crate::xor_args::xor_args;
// traits // traits
pub(crate) use crate::{ pub(crate) use crate::{
input_stream::InputStream, into_u64::IntoU64, into_usize::IntoUsize, invariant::Invariant, input_stream::InputStream, into_u64::IntoU64, into_usize::IntoUsize, invariant::Invariant,

View File

@ -100,6 +100,7 @@ mod torrent_summary;
mod use_color; mod use_color;
mod verifier; mod verifier;
mod walker; mod walker;
mod xor_args;
fn main() { fn main() {
if let Err(code) = Env::main().status() { if let Err(code) = Env::main().status() {

View File

@ -5,6 +5,16 @@ use create_step::CreateStep;
mod create_content; mod create_content;
mod create_step; mod create_step;
const INPUT_HELP: &str = "Read torrent contents from `PATH`. If `PATH` is a file, torrent will be \
a single-file torrent. If `PATH` is a directory, torrent will be a \
multi-file torrent. If `PATH` is `-`, read from standard input. Piece \
length defaults to 256KiB when reading from standard input if \
`--piece-length` is not given.";
const INPUT_FLAG: &str = "input-flag";
const INPUT_POSITIONAL: &str = "<INPUT>";
#[derive(StructOpt)] #[derive(StructOpt)]
#[structopt( #[structopt(
help_message(consts::HELP_MESSAGE), help_message(consts::HELP_MESSAGE),
@ -115,17 +125,25 @@ Examples:
)] )]
include_junk: bool, include_junk: bool,
#[structopt( #[structopt(
name = INPUT_POSITIONAL,
value_name = "INPUT",
empty_values = false,
required_unless = INPUT_FLAG,
conflicts_with = INPUT_FLAG,
parse(try_from_os_str = InputTarget::try_from_os_str),
help = INPUT_HELP,
)]
input_positional: Option<InputTarget>,
#[structopt(
name = INPUT_FLAG,
long = "input", long = "input",
short = "i", short = "i",
value_name = "PATH", value_name = "PATH",
empty_values(false), empty_values = false,
parse(try_from_os_str = InputTarget::try_from_os_str), parse(try_from_os_str = InputTarget::try_from_os_str),
help = "Read torrent contents from `PATH`. If `PATH` is a file, torrent will be a single-file \ help = INPUT_HELP,
torrent. If `PATH` is a directory, torrent will be a multi-file torrent. If `PATH` \
is `-`, read from standard input. Piece length defaults to 256KiB when reading from \
standard input if `--piece-length` is not given.",
)] )]
input: InputTarget, input_flag: Option<InputTarget>,
#[structopt( #[structopt(
long = "link", long = "link",
help = "Print created torrent `magnet:` URL to standard output" help = "Print created torrent `magnet:` URL to standard output"
@ -144,7 +162,8 @@ Examples:
value_name = "TEXT", value_name = "TEXT",
help = "Set name of torrent to `TEXT`. Defaults to the filename of the argument to `--input`. \ help = "Set name of torrent to `TEXT`. Defaults to the filename of the argument to `--input`. \
Required when `--input -`.", Required when `--input -`.",
required_if("input", "-") required_if(INPUT_FLAG, "-"),
required_if(INPUT_POSITIONAL, "-")
)] )]
name: Option<String>, name: Option<String>,
#[structopt( #[structopt(
@ -192,7 +211,8 @@ Sort in ascending order by size, break ties in descending path order:
value_name = "TARGET", value_name = "TARGET",
empty_values(false), empty_values(false),
parse(try_from_os_str = OutputTarget::try_from_os_str), parse(try_from_os_str = OutputTarget::try_from_os_str),
required_if("input", "-"), required_if(INPUT_FLAG, "-"),
required_if(INPUT_POSITIONAL, "-"),
help = "Save `.torrent` file to `TARGET`, or print to standard output if `TARGET` is `-`. \ help = "Save `.torrent` file to `TARGET`, or print to standard output if `TARGET` is `-`. \
Defaults to the argument to `--input` with an `.torrent` extension appended. Required \ Defaults to the argument to `--input` with an `.torrent` extension appended. Required \
when `--input -`.", when `--input -`.",
@ -250,6 +270,13 @@ Sort in ascending order by size, break ties in descending path order:
impl Create { impl Create {
pub(crate) fn run(self, env: &mut Env, options: &Options) -> Result<(), Error> { pub(crate) fn run(self, env: &mut Env, options: &Options) -> Result<(), Error> {
let input = xor_args(
"input_positional",
&self.input_positional,
"input_flag",
&self.input_flag,
)?;
let mut linter = Linter::new(); let mut linter = Linter::new();
linter.allow(self.allowed_lints.iter().cloned()); linter.allow(self.allowed_lints.iter().cloned());
@ -281,10 +308,10 @@ impl Create {
}; };
if !options.quiet { if !options.quiet {
CreateStep::Searching { input: &self.input }.print(env)?; CreateStep::Searching { input: &input }.print(env)?;
} }
let content = CreateContent::from_create(&self, env)?; let content = CreateContent::from_create(&self, &input, env)?;
let output = content.output.resolve(env)?; let output = content.output.resolve(env)?;
@ -398,7 +425,7 @@ impl Create {
#[cfg(test)] #[cfg(test)]
{ {
if let InputTarget::Path(path) = &self.input { if let InputTarget::Path(path) = &input {
let deserialized = bendy::serde::de::from_bytes::<Metainfo>(&bytes).unwrap(); let deserialized = bendy::serde::de::from_bytes::<Metainfo>(&bytes).unwrap();
assert_eq!(deserialized, metainfo); assert_eq!(deserialized, metainfo);
@ -451,6 +478,79 @@ mod tests {
assert!(matches!(env.run(), Err(Error::Clap { .. }))); assert!(matches!(env.run(), Err(Error::Clap { .. })));
} }
#[test]
fn input_arguments_conflict() {
let mut env = test_env! {
args: [
"torrent",
"create",
"--input",
"foo",
"bar",
],
tree: {},
};
assert_matches!(env.run(), Err(Error::Clap { .. }));
}
#[test]
fn require_name_if_input_flag_stdin() {
let mut env = test_env! {
args: [
"torrent",
"create",
"--input",
"-",
],
tree: {},
};
assert_matches!(env.run(), Err(Error::Clap { .. }));
}
#[test]
fn require_name_if_input_positional_stdin() {
let mut env = test_env! {
args: [
"torrent",
"create",
"-",
],
tree: {},
};
assert_matches!(env.run(), Err(Error::Clap { .. }));
}
#[test]
fn require_output_if_input_flag_stdin() {
let mut env = test_env! {
args: [
"torrent",
"create",
"--input",
"-",
"--name",
"foo",
],
tree: {},
};
assert_matches!(env.run(), Err(Error::Clap { .. }));
}
#[test]
fn require_output_if_input_positional_stdin() {
let mut env = test_env! {
args: [
"torrent",
"create",
"-",
"--name",
"foo",
],
tree: {},
};
assert_matches!(env.run(), Err(Error::Clap { .. }));
}
#[test] #[test]
fn require_input_present() { fn require_input_present() {
let mut env = test_env! { let mut env = test_env! {
@ -507,6 +607,30 @@ mod tests {
Ok(()) Ok(())
} }
#[test]
fn input_positional() {
let mut env = test_env! {
args: [
"torrent",
"create",
"foo",
],
tree: {
foo: {
bar: "",
baz: "",
},
}
};
env.assert_ok();
let metainfo = env.load_metainfo("foo.torrent");
assert_eq!(metainfo.info.name, "foo");
assert_matches!(
metainfo.info.mode,
Mode::Multiple{files} if files.len() == 2
);
}
#[test] #[test]
fn input_dot() { fn input_dot() {
let mut env = test_env! { let mut env = test_env! {
@ -526,10 +650,12 @@ mod tests {
} }
}; };
env.assert_ok(); env.assert_ok();
// let metainfo = env.load_metainfo("../dir.torrent"); let metainfo = env.load_metainfo("../dir.torrent");
// assert_eq!(metainfo.info.name, "dir"); assert_eq!(metainfo.info.name, "dir");
// assert_matches!(metainfo.info.mode, Mode::Multiple{files} if files.len() assert_matches!(
// == 1); metainfo.info.mode,
Mode::Multiple{files} if files.len() == 1
);
} }
#[test] #[test]
@ -2700,7 +2826,7 @@ Content Size 9 bytes
], ],
tree: {}, tree: {},
}; };
assert!(matches!(env.run(), Err(Error::Clap { .. }))); assert_matches!(env.run(), Err(Error::Clap { .. }));
} }
#[test] #[test]
@ -2718,7 +2844,7 @@ Content Size 9 bytes
], ],
tree: {}, tree: {},
}; };
assert!(matches!(env.run(), Err(Error::Clap { .. }))); assert_matches!(env.run(), Err(Error::Clap { .. }));
} }
#[test] #[test]

View File

@ -11,8 +11,8 @@ pub(crate) struct CreateContent {
} }
impl CreateContent { impl CreateContent {
pub(crate) fn from_create(create: &Create, env: &mut Env) -> Result<Self> { pub(crate) fn from_create(create: &Create, input: &InputTarget, env: &mut Env) -> Result<Self> {
match &create.input { match input {
InputTarget::Path(path) => { InputTarget::Path(path) => {
let spinner = if env.err().is_styled_term() { let spinner = if env.err().is_styled_term() {
let style = ProgressStyle::default_spinner() let style = ProgressStyle::default_spinner()

17
src/xor_args.rs Normal file
View File

@ -0,0 +1,17 @@
use crate::common::*;
pub(crate) fn xor_args<T: Clone>(
a_name: &str,
a: &Option<T>,
b_name: &str,
b: &Option<T>,
) -> Result<T> {
let target = a.as_ref().xor(b.as_ref()).ok_or_else(|| {
Error::internal(format!(
"Expected exactly one of the arguments `{}` or `{}` to be set",
a_name, b_name
))
})?;
Ok(target.clone())
}