diff --git a/src/common.rs b/src/common.rs index 8512802..24d4fb4 100644 --- a/src/common.rs +++ b/src/common.rs @@ -41,7 +41,7 @@ pub(crate) use url::{Host, Url}; pub(crate) use walkdir::WalkDir; // modules -pub(crate) use crate::{consts, error, use_color}; +pub(crate) use crate::{consts, error}; // traits pub(crate) use crate::{ diff --git a/src/lint.rs b/src/lint.rs index 0bbe06c..12a8776 100644 --- a/src/lint.rs +++ b/src/lint.rs @@ -6,14 +6,16 @@ pub(crate) enum Lint { SmallPieceLength, } -const UNEVEN_PIECE_LENGTH: &str = "uneven-piece-length"; -const SMALL_PIECE_LENGTH: &str = "small-piece-length"; - impl Lint { + const SMALL_PIECE_LENGTH: &'static str = "small-piece-length"; + const UNEVEN_PIECE_LENGTH: &'static str = "uneven-piece-length"; + pub(crate) const VALUES: &'static [&'static str] = + &[Self::SMALL_PIECE_LENGTH, Self::UNEVEN_PIECE_LENGTH]; + pub(crate) fn name(self) -> &'static str { match self { - Self::UnevenPieceLength => UNEVEN_PIECE_LENGTH, - Self::SmallPieceLength => SMALL_PIECE_LENGTH, + Self::SmallPieceLength => Self::SMALL_PIECE_LENGTH, + Self::UnevenPieceLength => Self::UNEVEN_PIECE_LENGTH, } } } @@ -23,8 +25,8 @@ impl FromStr for Lint { fn from_str(text: &str) -> Result { match text.replace('_', "-").to_lowercase().as_str() { - UNEVEN_PIECE_LENGTH => Ok(Self::UnevenPieceLength), - SMALL_PIECE_LENGTH => Ok(Self::SmallPieceLength), + Self::SMALL_PIECE_LENGTH => Ok(Self::SmallPieceLength), + Self::UNEVEN_PIECE_LENGTH => Ok(Self::UnevenPieceLength), _ => Err(Error::LintUnknown { text: text.to_string(), }), diff --git a/src/options.rs b/src/options.rs index cd2b402..5d5e93d 100644 --- a/src/options.rs +++ b/src/options.rs @@ -13,13 +13,14 @@ pub(crate) struct Options { unstable: bool, #[structopt( long = "color", - default_value = use_color::AUTO, + value_name = "WHEN", + default_value = UseColor::AUTO, set = ArgSettings::CaseInsensitive, - possible_values = use_color::VALUES, - help = "Print colorful output. When `auto`, the default, colored output is only enabled \ - if imdl detects that it is connected to a terminal, the `NO_COLOR` environment \ - variable is not set, and the `TERM` environment variable is not set with a \ - value of `dumb`.", + possible_values = UseColor::VALUES, + help = "Print colorful output according to `WHEN`. When `auto`, the default, colored output \ + is only enabled if imdl detects that it is connected to a terminal, the `NO_COLOR` \ + environment variable is not set, and the `TERM` environment variable is not set to \ + `dumb`.", )] pub(crate) use_color: UseColor, } diff --git a/src/subcommand/torrent/create.rs b/src/subcommand/torrent/create.rs index f31d670..0db619c 100644 --- a/src/subcommand/torrent/create.rs +++ b/src/subcommand/torrent/create.rs @@ -8,24 +8,27 @@ use crate::common::*; )] pub(crate) struct Create { #[structopt( - name = "ANNOUNCE", long = "announce", + value_name = "URL", required(true), - help = "Use `ANNOUNCE` as the primary tracker announce URL. To supply multiple announce URLs, \ - also use `--announce-tier`." + help = "Use `URL` as the primary tracker announce URL. To supply multiple announce URLs, also \ + use `--announce-tier`." )] announce: Url, #[structopt( - name = "ALLOW", long = "allow", - help = "Use `ANNOUNCE` as the primary tracker announce URL. To supply multiple announce URLs, \ - also use `--announce-tier`." + value_name = "LINT", + possible_values = Lint::VALUES, + help = "Allow `LINT`. Lints check for conditions which, although permitted, are not usually \ + desirable. For example, piece length can be any non-zero value, but probably \ + shouldn't be below 16 KiB. The lint `small-piece-size` checks for this, and \ + `--allow small-piece-size` can be used to disable this check.", )] allowed_lints: Vec, #[structopt( long = "announce-tier", - name = "ANNOUNCE-TIER", - help = "Add `ANNOUNCE-TIER` to list of tracker announce tiers. Each instance adds a new \ + value_name = "URL-LIST", + help = "Use `URL-LIST` as a tracker announce tier. Each instance adds a new \ tier. To add multiple trackers to a given tier, separate their announce URLs \ with commas:\n\ \n\ @@ -41,15 +44,15 @@ pub(crate) struct Create { )] announce_tiers: Vec, #[structopt( - name = "COMMENT", long = "comment", - help = "Include `COMMENT` in generated `.torrent` file. Stored under `comment` key of \ - top-level metainfo dictionary." + value_name = "TEXT", + help = "Include `TEXT` as the comment for generated `.torrent` file. Stored under `comment` \ + key of top-level metainfo dictionary." )] comment: Option, #[structopt( - name = "NODE", long = "dht-node", + value_name = "NODE", help = "Add DHT bootstrap node `NODE` to torrent. `NODE` should be in the form `HOST:PORT`, \ where `HOST` is a domain name, an IPv4 address, or an IPv6 address surrounded by \ brackets. May be given more than once to add multiple bootstrap nodes. Examples: @@ -59,95 +62,85 @@ pub(crate) struct Create { )] dht_nodes: Vec, #[structopt( - name = "FOLLOW-SYMLINKS", long = "follow-symlinks", help = "Follow symlinks in torrent input. By default, symlinks to files and directories are \ not included in torrent contents." )] follow_symlinks: bool, #[structopt( - name = "FORCE", long = "force", help = "Overwrite the destination `.torrent` file, if it exists." )] force: bool, #[structopt( - name = "GLOB", long = "glob", + value_name = "GLOB", help = "Include or exclude files that match `GLOB`. Multiple glob may be provided, with the \ - last one taking precedence. Precede a glob with a ! to exclude it." + last one taking precedence. Precede a glob with `!` to exclude it." )] globs: Vec, #[structopt( - name = "INCLUDE-HIDDEN", long = "include-hidden", help = "Include hidden files that would otherwise be skipped, such as files that start with a \ `.`, and files hidden by file attributes on macOS and Windows." )] include_hidden: bool, #[structopt( - name = "INCLUDE-JUNK", long = "include-junk", help = "Include junk files that would otherwise be skipped." )] include_junk: bool, #[structopt( - name = "INPUT", long = "input", - 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.", + value_name = "PATH", + help = "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.", parse(from_os_str) )] input: PathBuf, #[structopt( - name = "MD5SUM", long = "md5sum", help = "Include MD5 checksum of each file in the torrent. N.B. MD5 is cryptographically \ broken and only suitable for checking for accidental corruption." )] md5sum: bool, #[structopt( - name = "NAME", long = "name", - help = "Set name of torrent to `NAME`. Defaults to the filename of `--input`." + value_name = "TEXT", + help = "Set name of torrent to `TEXT`. Defaults to the filename of the argument to `--input`." )] name: Option, #[structopt( - name = "NO-CREATED-BY", long = "no-created-by", help = "Do not populate `created by` key of generated torrent with imdl version information." )] no_created_by: bool, #[structopt( - name = "NO-CREATION-DATE", long = "no-creation-date", help = "Do not populate `creation date` key of generated torrent with current time." )] no_creation_date: bool, #[structopt( - name = "OPEN", long = "open", help = "Open `.torrent` file after creation. Uses `xdg-open`, `gnome-open`, or `kde-open` on \ Linux; `open` on macOS; and `cmd /C start on Windows" )] open: bool, #[structopt( - name = "OUTPUT", long = "output", - help = "Save `.torrent` file to `OUTPUT`, or `-` for standard output. Defaults to \ - `$INPUT.torrent`.", + value_name = "TARGET", + help = "Save `.torrent` file to `TARGET`, or print to standard output if `TARGET` is `-`. \ + Defaults to `$INPUT.torrent`.", parse(from_os_str) )] output: Option, #[structopt( - name = "PIECE-LENGTH", long = "piece-length", - help = "Set piece length to `PIECE-LENGTH` bytes. Accepts SI units, e.g. kib, mib, and gib." + value_name = "BYTES", + help = "Set piece length to `BYTES`. Accepts SI units, e.g. kib, mib, and gib." )] piece_length: Option, #[structopt( - name = "PRIVATE", long = "private", help = "Set the `private` flag. Torrent clients that understand the flag and participate in \ the swarm of a torrent with the flag set will only announce themselves to the \ @@ -157,10 +150,14 @@ pub(crate) struct Create { )] private: bool, #[structopt( - name = "SOURCE", long = "source", - help = "Include `SOURCE` in generated `.torrent` file. Stored under `info.source` key of \ - metainfo dictionary." + value_name = "TEXT", + help = "Set torrent source to `TEXT`. Stored under `source` key of info dictionary. This is \ + useful for keeping statistics from being mis-reported when participating in swarms \ + with the same contents, but with different trackers. When source is set to a unique \ + value for torrents with the same contents, torrent clients will treat them as \ + distinct torrents, and not share peers between them, and will correctly report \ + download and upload statistics to multiple trackers." )] source: Option, } diff --git a/src/subcommand/torrent/show.rs b/src/subcommand/torrent/show.rs index b5f9a9f..816e5b4 100644 --- a/src/subcommand/torrent/show.rs +++ b/src/subcommand/torrent/show.rs @@ -8,9 +8,9 @@ use crate::common::*; )] pub(crate) struct Show { #[structopt( - name = "TORRENT", long = "input", - help = "Show information about `TORRENT`.", + value_name = "PATH", + help = "Show information about torrent at `PATH`.", parse(from_os_str) )] input: PathBuf, diff --git a/src/subcommand/torrent/stats.rs b/src/subcommand/torrent/stats.rs index 3714c85..e6e0b0c 100644 --- a/src/subcommand/torrent/stats.rs +++ b/src/subcommand/torrent/stats.rs @@ -8,17 +8,17 @@ use crate::common::*; )] pub(crate) struct Stats { #[structopt( - name = "COUNT", long = "limit", short = "l", - help = "Stop after processing the first `COUNT` torrents. Useful when processing large \ - collections of `.torrent` files." + value_name = "N", + help = "Stop after processing `N` torrents. Useful when processing large collections of \ + `.torrent` files." )] limit: Option, #[structopt( - name = "REGEX", long = "extract-pattern", short = "e", + value_name = "REGEX", help = "Extract and display values under key paths that match `REGEX`. Subkeys of a \ bencodeded dictionary are delimited by `/`, and values of a bencoded list are \ delmited by `*`. For example, given the following bencoded dictionary `{\"foo\": \ @@ -28,10 +28,10 @@ pub(crate) struct Stats { )] extract_patterns: Vec, #[structopt( - name = "INPUT", long = "input", short = "i", - help = "Search `INPUT` for torrents. May be a directory to search or a single torrent file.", + value_name = "PATH", + help = "Search `PATH` for torrents. May be a directory or a single torrent file.", parse(from_os_str) )] input: PathBuf, diff --git a/src/subcommand/torrent/verify.rs b/src/subcommand/torrent/verify.rs index 5d4dbf9..d9fa9fa 100644 --- a/src/subcommand/torrent/verify.rs +++ b/src/subcommand/torrent/verify.rs @@ -8,16 +8,17 @@ use crate::common::*; )] pub(crate) struct Verify { #[structopt( - name = "TORRENT", long = "metainfo", - help = "Verify input data against torrent metainfo in `TORRENT`.", + value_name = "FILE", + help = "Verify torrent contents against torrent metainfo in `FILE`.", parse(from_os_str) )] metainfo: PathBuf, #[structopt( - name = "INPUT", long = "input", - help = "Verify `INPUT`. Defaults to `info.name` field of torrent metainfo.", + value_name = "PATH", + help = "Verify torrent contents at `PATH` against torrent metainfo. Defaults to `name` field \ + of torrent info dictionary.", parse(from_os_str) )] input: Option, diff --git a/src/use_color.rs b/src/use_color.rs index 84525f1..1591787 100644 --- a/src/use_color.rs +++ b/src/use_color.rs @@ -1,11 +1,5 @@ use crate::common::*; -pub(crate) const AUTO: &str = "auto"; -pub(crate) const ALWAYS: &str = "always"; -pub(crate) const NEVER: &str = "never"; - -pub(crate) const VALUES: &[&str] = &[AUTO, ALWAYS, NEVER]; - #[derive(Copy, Clone, Debug, PartialEq)] pub(crate) enum UseColor { Auto, @@ -13,14 +7,21 @@ pub(crate) enum UseColor { Never, } +impl UseColor { + pub(crate) const ALWAYS: &'static str = "always"; + pub(crate) const AUTO: &'static str = "auto"; + pub(crate) const NEVER: &'static str = "never"; + pub(crate) const VALUES: &'static [&'static str] = &[Self::AUTO, Self::ALWAYS, Self::NEVER]; +} + impl FromStr for UseColor { type Err = Infallible; fn from_str(text: &str) -> Result { match text.to_lowercase().as_str() { - AUTO => Ok(Self::Auto), - ALWAYS => Ok(Self::Always), - NEVER => Ok(Self::Never), + Self::AUTO => Ok(Self::Auto), + Self::ALWAYS => Ok(Self::Always), + Self::NEVER => Ok(Self::Never), _ => unreachable!(), } } @@ -32,11 +33,20 @@ mod tests { #[test] fn from_str() { - assert_eq!(UseColor::Auto, AUTO.parse().unwrap()); - assert_eq!(UseColor::Always, ALWAYS.parse().unwrap()); - assert_eq!(UseColor::Never, NEVER.parse().unwrap()); - assert_eq!(UseColor::Auto, AUTO.to_uppercase().parse().unwrap()); - assert_eq!(UseColor::Always, ALWAYS.to_uppercase().parse().unwrap()); - assert_eq!(UseColor::Never, NEVER.to_uppercase().parse().unwrap()); + assert_eq!(UseColor::Auto, UseColor::AUTO.parse().unwrap()); + assert_eq!(UseColor::Always, UseColor::ALWAYS.parse().unwrap()); + assert_eq!(UseColor::Never, UseColor::NEVER.parse().unwrap()); + assert_eq!( + UseColor::Auto, + UseColor::AUTO.to_uppercase().parse().unwrap() + ); + assert_eq!( + UseColor::Always, + UseColor::ALWAYS.to_uppercase().parse().unwrap() + ); + assert_eq!( + UseColor::Never, + UseColor::NEVER.to_uppercase().parse().unwrap() + ); } }