Overhaul help, making use of long descriptions
This commit is contained in:
parent
0b3302316b
commit
cb9f6a25d8
1 changed files with 144 additions and 48 deletions
192
src/main.rs
192
src/main.rs
|
|
@ -35,13 +35,16 @@ use std::process::exit;
|
|||
use std::time::Duration;
|
||||
|
||||
fn main() {
|
||||
// Note: Long help descriptions should wrap at 70 characters (column 71)
|
||||
// Indentation is 10 spaces, so this allows it to fit on an 80 character terminal
|
||||
// Short help descriptions should ideally be no more than 54 characters
|
||||
let matches = Command::new("oxipng")
|
||||
.version(env!("CARGO_PKG_VERSION"))
|
||||
.author("Joshua Holmer <jholmer.in@gmail.com>")
|
||||
.about("Losslessly improves compression of PNG files")
|
||||
.about("Losslessly improve compression of PNG files")
|
||||
.arg(
|
||||
Arg::new("files")
|
||||
.help("File(s) to compress (use \"-\" for stdin)")
|
||||
.help("File(s) to compress (use '-' for stdin)")
|
||||
.index(1)
|
||||
.num_args(1..)
|
||||
.use_value_delimiter(false)
|
||||
|
|
@ -50,11 +53,31 @@ fn main() {
|
|||
)
|
||||
.arg(
|
||||
Arg::new("optimization")
|
||||
.help("Optimization level - Default: 2")
|
||||
.help("Optimization level (0-6, or max)")
|
||||
.long_help("\
|
||||
Set the optimization level preset. The default level 2 is quite fast
|
||||
and provides good compression. Lower levels are faster, higher levels
|
||||
provide better compression, though with increasingly diminishing
|
||||
returns.
|
||||
|
||||
0 => --zc 5 --fast (1 trial, determined heuristically)
|
||||
1 => --zc 10 --fast (1 trial, determined heuristically)
|
||||
2 => --zc 11 -f 0,1,6,7 --fast (4 fast trials, 1 main trial)
|
||||
3 => --zc 11 -f 0,7,8,9 (4 trials)
|
||||
4 => --zc 12 -f 0,7,8,9 (4 trials)
|
||||
5 => --zc 12 -f 0,1,2,5,6,7,8,9 (8 trials)
|
||||
6 => --zc 12 -f 0-9 (10 trials)
|
||||
max => (stable alias for the max level)
|
||||
|
||||
Manually specifying a compression option (zc, f, etc.) will override
|
||||
the optimization preset, regardless of the order you write the
|
||||
arguments.")
|
||||
.short('o')
|
||||
.long("opt")
|
||||
.value_name("level")
|
||||
.value_parser(["0", "1", "2", "3", "4", "5", "6", "max"]),
|
||||
.default_value("2")
|
||||
.value_parser(["0", "1", "2", "3", "4", "5", "6", "max"])
|
||||
.hide_possible_values(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("backup")
|
||||
|
|
@ -66,7 +89,10 @@ fn main() {
|
|||
)
|
||||
.arg(
|
||||
Arg::new("recursive")
|
||||
.help("Recurse into subdirectories and optimize all *.png/*.apng files")
|
||||
.help("Recurse input directories, optimizing all PNG files")
|
||||
.long_help("\
|
||||
When directories are given as input, traverse the directory trees and
|
||||
optimize all PNG files found (files with “.png” or “.apng” extension).")
|
||||
.short('r')
|
||||
.long("recursive")
|
||||
.action(ArgAction::SetTrue),
|
||||
|
|
@ -74,6 +100,10 @@ fn main() {
|
|||
.arg(
|
||||
Arg::new("output_dir")
|
||||
.help("Write output file(s) to <directory>")
|
||||
.long_help("\
|
||||
Write output file(s) to <directory>. If the directory does not exist,
|
||||
it will be created. Note that this will not preserve the directory
|
||||
structure of the input files when used with '--recursive'.")
|
||||
.long("dir")
|
||||
.value_name("directory")
|
||||
.value_parser(value_parser!(PathBuf))
|
||||
|
|
@ -99,35 +129,54 @@ fn main() {
|
|||
)
|
||||
.arg(
|
||||
Arg::new("preserve")
|
||||
.help("Preserve file attributes if possible")
|
||||
.help("Preserve file permissions and timestamps if possible")
|
||||
.long_help("\
|
||||
Preserve file permissions and timestamps if possible.
|
||||
|
||||
Timestamps can only be preserved if oxipng was compiled with the
|
||||
`filetime` feature.")
|
||||
.short('p')
|
||||
.long("preserve")
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("pretend")
|
||||
.help("Do not write any files, only calculate compression gains")
|
||||
.help("Do not write any files, only show compression results")
|
||||
.short('P')
|
||||
.long("pretend")
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("strip-safe")
|
||||
.help("Strip safely-removable metadata objects")
|
||||
.help("Strip safely-removable chunks, same as '--strip safe'")
|
||||
.short('s')
|
||||
.action(ArgAction::SetTrue)
|
||||
.conflicts_with("strip"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("strip")
|
||||
.help("Strip metadata objects ['safe', 'all', or comma-separated list]\nCAUTION: stripping 'all' will convert APNGs to standard PNGs")
|
||||
.help("Strip metadata (safe, all, or comma-separated list)\nCAUTION: 'all' will convert APNGs to standard PNGs")
|
||||
.long_help(format!("\
|
||||
Strip metadata chunks, where <mode> is one of:
|
||||
|
||||
safe => Strip all non-critical chunks, except for the following:
|
||||
{}
|
||||
all => Strip all non-critical chunks
|
||||
<list> => Strip chunks in the comma-separated list, e.g. 'bKGD,cHRM'
|
||||
|
||||
CAUTION: 'all' will convert APNGs to standard PNGs.",
|
||||
StripChunks::KEEP_SAFE
|
||||
.iter()
|
||||
.map(|c| String::from_utf8_lossy(c))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")))
|
||||
.long("strip")
|
||||
.value_name("mode")
|
||||
.conflicts_with("strip-safe"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("keep")
|
||||
.help("Strip all optional metadata except objects in the comma-separated list")
|
||||
.help("Strip all metadata except in the comma-separated list")
|
||||
.long("keep")
|
||||
.value_name("list")
|
||||
.conflicts_with("strip")
|
||||
|
|
@ -135,28 +184,50 @@ fn main() {
|
|||
)
|
||||
.arg(
|
||||
Arg::new("alpha")
|
||||
.help("Perform additional alpha optimizations")
|
||||
.help("Perform additional alpha channel optimization")
|
||||
.long_help("\
|
||||
Perform additional optimization on images with an alpha channel, by
|
||||
altering the color values of fully transparent pixels. This is
|
||||
generally recommended for better compression, but take care as this is
|
||||
technically a lossy transformation and may be unsuitable for some
|
||||
applications.")
|
||||
.short('a')
|
||||
.long("alpha")
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("interlace")
|
||||
.help("PNG interlace type - Default: 0")
|
||||
.help("Set PNG interlacing type (0, 1, keep)")
|
||||
.long_help("\
|
||||
Set the PNG interlacing type, where <type> is one of:
|
||||
|
||||
0 => Remove interlacing from all images that are processed
|
||||
1 => Apply Adam7 interlacing on all images that are processed
|
||||
keep => Keep the existing interlacing type of each image
|
||||
|
||||
Note that interlacing can add 25-50% to the size of an optimized
|
||||
image. Only use it if you believe the benefits outweigh the costs for
|
||||
your use case.")
|
||||
.short('i')
|
||||
.long("interlace")
|
||||
.value_name("type")
|
||||
.value_parser(["0", "1", "keep"]),
|
||||
.default_value("0")
|
||||
.value_parser(["0", "1", "keep"])
|
||||
.hide_possible_values(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("scale16")
|
||||
.help("Forcibly reduce 16-bit images to 8-bit")
|
||||
.long_help("\
|
||||
Forcibly reduce 16-bit images to 8-bit. Reduction is performed by
|
||||
scaling the values, such that e.g. 0x00FF is reduced to 0x01 rather
|
||||
than 0x00.")
|
||||
.long("scale16")
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("verbose")
|
||||
.help("Run in verbose mode (use multiple times to increase verbosity)")
|
||||
.help("Run in verbose mode (use twice to increase verbosity)")
|
||||
.short('v')
|
||||
.long("verbose")
|
||||
.action(ArgAction::Count)
|
||||
|
|
@ -172,9 +243,29 @@ fn main() {
|
|||
)
|
||||
.arg(
|
||||
Arg::new("filters")
|
||||
.help(format!("PNG delta filters (0-{})", RowFilter::LAST))
|
||||
.help(format!("Filters to try (0-{}; see '--help' for details)", RowFilter::LAST))
|
||||
.long_help("\
|
||||
Peform compression trials with each of the given filter types. You can
|
||||
specify a comma-separated list, or a range of values. E.g. '-f 0-3' is
|
||||
the same as '-f 0,1,2,3'.
|
||||
|
||||
PNG delta filters (apply the same filter to every line)
|
||||
0 => None (recommended to always include this filter)
|
||||
1 => Sub
|
||||
2 => Up
|
||||
3 => Average
|
||||
4 => Paeth
|
||||
Heuristic strategies (try to find the best delta filter for each line)
|
||||
5 => MinSum Minimum sum of absolute differences
|
||||
6 => Entropy Highest Shannon entropy
|
||||
7 => Bigrams Lowest count of distinct bigrams
|
||||
8 => BigEnt Highest Shannon entropy of bigrams
|
||||
9 => Brute Smallest compressed size (slow)
|
||||
|
||||
The default value depends on the optimization level preset.")
|
||||
.short('f')
|
||||
.long("filters")
|
||||
.value_name("list")
|
||||
.value_parser(|x: &str| {
|
||||
parse_numeric_range_opts(x, 0, RowFilter::LAST)
|
||||
.map_err(|_| "Invalid option for filters")
|
||||
|
|
@ -182,7 +273,11 @@ fn main() {
|
|||
)
|
||||
.arg(
|
||||
Arg::new("fast")
|
||||
.help("Use fast filter evaluation (helpful when you have more filters enabled than CPU cores)")
|
||||
.help("Use fast filter evaluation")
|
||||
.long_help("\
|
||||
Perform a fast compression evaluation of each enabled filter, followed
|
||||
by a single main compression trial of the best result. Recommended
|
||||
if you have more filters enabled than CPU cores.")
|
||||
.long("fast")
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
|
|
@ -226,13 +321,21 @@ fn main() {
|
|||
)
|
||||
.arg(
|
||||
Arg::new("no-recoding")
|
||||
.help("No recoding of IDAT or other compressed chunks unless necessary")
|
||||
.help("No recompression unless reductions occur")
|
||||
.long_help("\
|
||||
No recompression of IDAT unless reductions occur. Recompression of
|
||||
other compressed chunks (such as iCCP) will also be disabled. Note
|
||||
that the combination of '--nx' and '--nz' will fully disable all
|
||||
optimization.")
|
||||
.long("nz")
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("fix")
|
||||
.help("Enable error recovery")
|
||||
.help("Disable checksum validation")
|
||||
.long_help("\
|
||||
Do not perform checksum validation of PNG chunks. This may allow some
|
||||
files with errors to be processed successfully.")
|
||||
.long("fix")
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
|
|
@ -244,53 +347,46 @@ fn main() {
|
|||
)
|
||||
.arg(
|
||||
Arg::new("zopfli")
|
||||
.help("Use the slow but stronger Zopfli compressor (recommended use is with all filters and `--fast` enabled)")
|
||||
.help("Use the slow but stronger Zopfli compressor")
|
||||
.long_help("\
|
||||
Use the slow but stronger Zopfli compressor. Recommended use is with
|
||||
'-o max' and '--fast'.
|
||||
|
||||
This option is only available if oxipng was compiled with the `zopfli`
|
||||
feature.")
|
||||
.short('Z')
|
||||
.long("zopfli")
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("timeout")
|
||||
.help("Maximum amount of time, in seconds, to spend on optimizations (currently of limited use due to the shift away from zlib)")
|
||||
.help("Maximum amount of time to spend on optimizations")
|
||||
.long_help("\
|
||||
Maximum amount of time, in seconds, to spend on optimizations. Oxipng
|
||||
will check the timeout before each reduction or compression trial, and
|
||||
will stop trying to optimize the file if the timeout is exceeded. Note
|
||||
that this does not cut short any compression trials that are already
|
||||
in progress, so it is currently of limited effectiveness for large
|
||||
files or high compression levels.")
|
||||
.value_name("secs")
|
||||
.long("timeout")
|
||||
.value_parser(value_parser!(u64)),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("threads")
|
||||
.help("Set number of threads to use - Default: num CPU cores")
|
||||
.help("Set number of threads to use [default: num CPU cores]")
|
||||
.long_help("\
|
||||
Set number of threads to use [default: num CPU cores]
|
||||
|
||||
This option is only available if oxipng was compiled with the
|
||||
`parallel` feature.")
|
||||
.long("threads")
|
||||
.short('t')
|
||||
.value_name("num")
|
||||
.value_parser(value_parser!(usize)),
|
||||
)
|
||||
.after_help(
|
||||
"Optimization levels:
|
||||
-o 0 => --zc 5 --fast (1 trial, determined heuristically)
|
||||
-o 1 => --zc 10 --fast (1 trial, determined heuristically)
|
||||
-o 2 => --zc 11 -f 0,1,6,7 --fast (1 trial, determined by fast evaluation)
|
||||
-o 3 => --zc 11 -f 0,7,8,9 (4 trials)
|
||||
-o 4 => --zc 12 -f 0,7,8,9 (4 trials; same as `-o 3` for zopfli)
|
||||
-o 5 => --zc 12 -f 0,1,2,5,6,7,8,9 (8 trials)
|
||||
-o 6 => --zc 12 -f 0-9 (10 trials)
|
||||
-o max => (stable alias for the max compression)
|
||||
|
||||
Manually specifying a compression option (zc, f, etc.) will override the optimization preset,
|
||||
regardless of the order you write the arguments.
|
||||
|
||||
PNG delta filters:
|
||||
0 => None
|
||||
1 => Sub
|
||||
2 => Up
|
||||
3 => Average
|
||||
4 => Paeth
|
||||
Heuristic filter selection strategies:
|
||||
5 => MinSum Minimum sum of absolute differences
|
||||
6 => Entropy Highest Shannon entropy
|
||||
7 => Bigrams Lowest count of distinct bigrams
|
||||
8 => BigEnt Highest Shannon entropy of bigrams
|
||||
9 => Brute Smallest compressed size (slow)",
|
||||
)
|
||||
.after_help("Run `oxipng --help` to see full details of all options")
|
||||
.after_long_help("")
|
||||
.get_matches_from(std::env::args());
|
||||
|
||||
if matches.get_flag("backup") {
|
||||
|
|
|
|||
Loading…
Reference in a new issue