diff --git a/src/capture.rs b/src/capture.rs index 88661fd..dcb8c42 100644 --- a/src/capture.rs +++ b/src/capture.rs @@ -17,6 +17,10 @@ impl Capture { .unwrap() .to_owned() } + + pub(crate) fn bytes(&self) -> Vec { + self.cursor.borrow().get_ref().clone() + } } impl Write for Capture { diff --git a/src/common.rs b/src/common.rs index db2a76d..31e79d6 100644 --- a/src/common.rs +++ b/src/common.rs @@ -49,7 +49,7 @@ pub(crate) use crate::{ bytes::Bytes, env::Env, error::Error, file_info::FileInfo, files::Files, hasher::Hasher, info::Info, lint::Lint, linter::Linter, metainfo::Metainfo, mode::Mode, opt::Opt, piece_length_picker::PieceLengthPicker, platform::Platform, style::Style, table::Table, - torrent_summary::TorrentSummary, use_color::UseColor, + target::Target, torrent_summary::TorrentSummary, use_color::UseColor, }; // test stdlib types diff --git a/src/main.rs b/src/main.rs index 57d522a..dbe4438 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,6 +16,7 @@ clippy::result_expect_used, clippy::result_unwrap_used, clippy::shadow_reuse, + clippy::too_many_lines, clippy::unreachable, clippy::unseparated_literal_suffix, clippy::wildcard_enum_match_arm @@ -76,6 +77,7 @@ mod platform_interface; mod reckoner; mod style; mod table; +mod target; mod torrent_summary; mod use_color; diff --git a/src/opt.rs b/src/opt.rs index 1e6aa3a..5214e6e 100644 --- a/src/opt.rs +++ b/src/opt.rs @@ -2,19 +2,6 @@ use crate::common::*; mod torrent; -#[derive(StructOpt)] -pub(crate) enum Subcommand { - Torrent(torrent::Torrent), -} - -impl Subcommand { - pub(crate) fn run(self, env: &mut Env, unstable: bool) -> Result<(), Error> { - match self { - Self::Torrent(torrent) => torrent.run(env, unstable), - } - } -} - #[derive(StructOpt)] #[structopt( about(consts::ABOUT), @@ -51,3 +38,16 @@ impl Opt { self.subcommand.run(env, self.unstable) } } + +#[derive(StructOpt)] +pub(crate) enum Subcommand { + Torrent(torrent::Torrent), +} + +impl Subcommand { + pub(crate) fn run(self, env: &mut Env, unstable: bool) -> Result<(), Error> { + match self { + Self::Torrent(torrent) => torrent.run(env, unstable), + } + } +} diff --git a/src/opt/torrent/create.rs b/src/opt/torrent/create.rs index 2ca4eef..80041c9 100644 --- a/src/opt/torrent/create.rs +++ b/src/opt/torrent/create.rs @@ -47,7 +47,8 @@ Note: Many BitTorrent clients do not implement the behavior described in BEP 12. name = "INPUT", long = "input", help = "Read torrent contents from `INPUT`.", - long_help = "Read torrent contents from `INPUT`. If `INPUT` is a file, torrent will be a single-file torrent, otherwise if `INPUT` is a directory, torrent will be a multi-file torrent." + long_help = "Read torrent contents from `INPUT`. If `INPUT` is a file, torrent will be a single-file torrent, otherwise if `INPUT` is a directory, torrent will be a multi-file torrent.", + parse(from_os_str) )] input: PathBuf, #[structopt( @@ -85,9 +86,10 @@ Note: Many BitTorrent clients do not implement the behavior described in BEP 12. #[structopt( name = "OUTPUT", long = "output", - help = "Save `.torrent` file to `OUTPUT`. Defaults to `$INPUT.torrent`." + help = "Save `.torrent` file to `OUTPUT`, or `-` for standard output. Defaults to `$INPUT.torrent`.", + parse(from_os_str) )] - output: Option, + output: Option, #[structopt( name = "PIECE-LENGTH", long = "piece-length", @@ -175,12 +177,12 @@ impl Create { let output = self .output .as_ref() - .map(|output| env.resolve(&output)) + .map(|output| output.resolve(env)) .unwrap_or_else(|| { let mut torrent_name = name.to_owned(); torrent_name.push_str(".torrent"); - input.parent().unwrap().join(torrent_name) + Target::File(input.parent().unwrap().join(torrent_name)) }); let private = if self.private { Some(1) } else { None }; @@ -228,12 +230,15 @@ impl Create { let bytes = metainfo.serialize()?; - fs::write(&output, &bytes).context(error::Filesystem { path: &output })?; - - TorrentSummary::from_metainfo(metainfo)?.write(env)?; - - if self.open { - Platform::open(&output)?; + match &output { + Target::File(path) => { + fs::write(path, &bytes).context(error::Filesystem { path })?; + TorrentSummary::from_metainfo(metainfo)?.write(env)?; + if self.open { + Platform::open(&path)?; + } + } + Target::Stdio => env.out.write_all(&bytes).context(error::Stdout)?, } Ok(()) @@ -890,4 +895,21 @@ Content Size 0 bytes "; assert_eq!(have, want); } + + #[test] + fn write_to_stdout() { + let mut env = environment(&[ + "--input", + "foo", + "--announce", + "http://bar", + "--output", + "-", + ]); + fs::write(env.resolve("foo"), "").unwrap(); + env.run().unwrap(); + let bytes = env.out_bytes(); + let value = bencode::Value::decode(&bytes).unwrap(); + assert!(matches!(value, bencode::Value::Dict(_))); + } } diff --git a/src/opt/torrent/show.rs b/src/opt/torrent/show.rs index 9054ea3..0cde0a6 100644 --- a/src/opt/torrent/show.rs +++ b/src/opt/torrent/show.rs @@ -10,7 +10,8 @@ pub(crate) struct Show { #[structopt( name = "TORRENT", long = "input", - help = "Show information about `TORRENT`." + help = "Show information about `TORRENT`.", + parse(from_os_str) )] input: PathBuf, } diff --git a/src/opt/torrent/stats.rs b/src/opt/torrent/stats.rs index ba2de34..eda6c85 100644 --- a/src/opt/torrent/stats.rs +++ b/src/opt/torrent/stats.rs @@ -29,7 +29,8 @@ Extract and display values under key paths that match `REGEX`. Subkeys of a benc long = "input", short = "i", help = "Search `INPUT` for torrents.", - long_help = "Search `INPUT` for torrents. May be a directory to search or a single torrent file." + long_help = "Search `INPUT` for torrents. May be a directory to search or a single torrent file.", + parse(from_os_str) )] input: PathBuf, #[structopt( diff --git a/src/target.rs b/src/target.rs new file mode 100644 index 0000000..f887de0 --- /dev/null +++ b/src/target.rs @@ -0,0 +1,41 @@ +use crate::common::*; + +#[derive(PartialEq, Debug)] +pub(crate) enum Target { + File(PathBuf), + Stdio, +} + +impl Target { + pub(crate) fn resolve(&self, env: &Env) -> Self { + match self { + Self::File(path) => Self::File(env.resolve(path)), + Self::Stdio => Self::Stdio, + } + } +} + +impl From<&OsStr> for Target { + fn from(text: &OsStr) -> Self { + if text == OsStr::new("-") { + Self::Stdio + } else { + Self::File(text.into()) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn file() { + assert_eq!(Target::from(OsStr::new("foo")), Target::File("foo".into())); + } + + #[test] + fn stdio() { + assert_eq!(Target::from(OsStr::new("-")), Target::Stdio); + } +} diff --git a/src/test_env.rs b/src/test_env.rs index 5419b39..172345b 100644 --- a/src/test_env.rs +++ b/src/test_env.rs @@ -18,6 +18,10 @@ impl TestEnv { pub(crate) fn out(&self) -> String { self.out.string() } + + pub(crate) fn out_bytes(&self) -> Vec { + self.out.bytes() + } } impl Deref for TestEnv {