Completely parse CLI options

This commit is contained in:
Joshua Holmer 2016-01-08 15:09:00 -05:00
parent 273880ea0b
commit 5f866d9844
3 changed files with 253 additions and 28 deletions

1
rustfmt.toml Normal file
View file

@ -0,0 +1 @@
format_strings = false

View file

@ -4,6 +4,7 @@ extern crate libz_sys;
extern crate libc;
use std::path::Path;
use std::path::PathBuf;
use std::collections::HashSet;
pub mod deflate {
@ -12,11 +13,13 @@ pub mod deflate {
}
pub mod png;
pub struct Options<'a> {
pub struct Options {
pub backup: bool,
pub out_file: &'a Path,
pub out_file: PathBuf,
pub out_dir: Option<PathBuf>,
pub stdout: bool,
pub fix_errors: bool,
pub force: bool,
pub pretend: bool,
pub clobber: bool,
pub create: bool,
pub preserve_attrs: bool,
@ -31,6 +34,7 @@ pub struct Options<'a> {
pub color_type_reduction: bool,
pub palette_reduction: bool,
pub idat_recoding: bool,
pub strip: bool,
}
pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), String> {
@ -65,7 +69,6 @@ pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), String> {
// TODO: Bit depth/palette reduction
//
// TODO: Apply interlacing changes
// TODO: Force reencoding if interlacing was changed
//
// Go through selected permutations and determine the best
let mut best: Option<(u8, u8, u8, u8)> = None;
@ -73,6 +76,7 @@ pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), String> {
let combinations = opts.filter.len() * opts.compression.len() * opts.memory.len() *
opts.strategies.len();
println!("Trying: {} combinations", combinations);
// TODO: Force reencoding if interlacing was changed
// TODO: Multithreading
for f in &opts.filter {
for zc in &opts.compression {

View file

@ -2,16 +2,13 @@ extern crate optipng;
extern crate clap;
extern crate regex;
use clap::{App, Arg};
use clap::{App, Arg, ArgMatches};
use regex::Regex;
use std::collections::HashSet;
use std::env;
use std::path::Path;
use std::path::PathBuf;
fn main() {
// TODO: Handle wildcards
let filename = env::args().skip(1).next().unwrap();
let infile = Path::new(&filename);
let mut filter = HashSet::new();
filter.insert(0);
filter.insert(5);
@ -26,9 +23,11 @@ fn main() {
let default_opts = optipng::Options {
backup: false,
out_file: infile,
out_file: PathBuf::new(),
out_dir: None,
stdout: false,
pretend: false,
fix_errors: false,
force: false,
clobber: true,
create: true,
preserve_attrs: false,
@ -43,9 +42,9 @@ fn main() {
color_type_reduction: true,
palette_reduction: true,
idat_recoding: true,
strip: false,
};
// TODO: Handle command line args
let matches = App::new("optipng")
.version("2.0.0-alpha.1")
.author("Joshua Holmer <jholmer.in@gmail.com>")
@ -53,6 +52,7 @@ fn main() {
.arg(Arg::with_name("files")
.help("File(s) to compress")
.index(1)
.multiple(true)
.required(true))
.arg(Arg::with_name("optimization")
.help("Optimization level - Default: 2")
@ -72,11 +72,20 @@ fn main() {
.arg(Arg::with_name("output_dir")
.help("Write output file(s) to <directory>")
.long("dir")
.takes_value(true))
.takes_value(true)
.conflicts_with("output_file")
.conflicts_with("stdout"))
.arg(Arg::with_name("output_file")
.help("Write output file to <file>")
.long("out")
.takes_value(true))
.takes_value(true)
.conflicts_with("output_dir")
.conflicts_with("stdout"))
.arg(Arg::with_name("stdout")
.help("Write output to stdout")
.long("stdout")
.conflicts_with("output_dir")
.conflicts_with("output_file"))
.arg(Arg::with_name("fix")
.help("Enable error recovery")
.long("fix"))
@ -101,9 +110,6 @@ fn main() {
.short("v")
.long("verbose")
.conflicts_with("quiet"))
.arg(Arg::with_name("version")
.help("Show copyright and version info")
.short("V"))
.arg(Arg::with_name("filters")
.help("PNG delta filters (0-5) - Default: 0,5")
.short("f")
@ -170,14 +176,14 @@ fn main() {
.arg(Arg::with_name("no-color-reduction")
.help("No color type reduction")
.long("nc"))
.arg(Arg::with_name("no-bit-depth")
.arg(Arg::with_name("no-palette-reduction")
.help("No palette reduction")
.long("np"))
.arg(Arg::with_name("no-reductions")
.help("No reductions")
.long("nx"))
.arg(Arg::with_name("no-recoding")
.help("No IDAT recoding")
.help("No IDAT recoding unless necessary")
.long("nz"))
.arg(Arg::with_name("strip")
.help("Strip all metadata objects")
@ -201,13 +207,11 @@ fn main() {
let mut opts = default_opts;
if !matches.is_present("files") {
return ();
}
parse_opts_into_struct(&matches, &mut opts);
// TODO: Handle optimization presets
for input in matches.values_of("files").unwrap() {
opts.out_file = Path::new(input);
opts.out_file = PathBuf::from(input);
// TODO: Handle wildcards
match optipng::optimize(Path::new(input), &opts) {
Ok(_) => (),
Err(x) => println!("{}", x),
@ -215,7 +219,212 @@ fn main() {
}
}
fn parse_numeric_range_opts(input: &str, min_value: u8, max_value: u8) -> Result<Vec<u8>, String> {
fn parse_opts_into_struct(matches: &ArgMatches, opts: &mut optipng::Options) {
match matches.value_of("optimization") {
Some("0") => {
opts.idat_recoding = false;
let mut compression = HashSet::new();
compression.insert(3);
opts.compression = compression;
}
Some("1") => {
let mut filter = HashSet::new();
filter.insert(0);
opts.filter = filter;
let mut strategies = HashSet::new();
strategies.insert(0);
opts.strategies = strategies;
}
// 2 is the default
Some("3") => {
let mut filter = HashSet::new();
filter.insert(0);
filter.insert(5);
opts.filter = filter;
let mut compression = HashSet::new();
compression.insert(9);
opts.compression = compression;
let mut memory = HashSet::new();
for i in 8..10 {
memory.insert(i);
}
opts.memory = memory;
let mut strategies = HashSet::new();
for i in 0..4 {
strategies.insert(i);
}
opts.strategies = strategies;
}
Some("4") => {
let mut filter = HashSet::new();
for i in 0..6 {
filter.insert(i);
}
opts.filter = filter;
let mut compression = HashSet::new();
compression.insert(9);
opts.compression = compression;
let mut memory = HashSet::new();
for i in 8..10 {
memory.insert(i);
}
opts.memory = memory;
let mut strategies = HashSet::new();
for i in 0..4 {
strategies.insert(i);
}
opts.strategies = strategies;
}
Some("5") => {
let mut filter = HashSet::new();
for i in 0..6 {
filter.insert(i);
}
opts.filter = filter;
let mut compression = HashSet::new();
for i in 3..10 {
compression.insert(i);
}
opts.compression = compression;
let mut memory = HashSet::new();
for i in 8..10 {
memory.insert(i);
}
opts.memory = memory;
let mut strategies = HashSet::new();
for i in 0..4 {
strategies.insert(i);
}
opts.strategies = strategies;
}
Some("6") => {
let mut filter = HashSet::new();
for i in 0..6 {
filter.insert(i);
}
opts.filter = filter;
let mut compression = HashSet::new();
for i in 1..10 {
compression.insert(i);
}
opts.compression = compression;
let mut memory = HashSet::new();
for i in 7..10 {
memory.insert(i);
}
opts.memory = memory;
let mut strategies = HashSet::new();
for i in 0..4 {
strategies.insert(i);
}
opts.strategies = strategies;
}
_ => (), // Use default
}
if let Some(x) = matches.value_of("interlace") {
opts.interlace = x.parse::<u8>().ok();
}
if let Some(x) = matches.value_of("filters") {
opts.filter = parse_numeric_range_opts(x, 0, 5).unwrap();
}
if let Some(x) = matches.value_of("compression") {
opts.compression = parse_numeric_range_opts(x, 1, 9).unwrap();
}
if let Some(x) = matches.value_of("memory") {
opts.memory = parse_numeric_range_opts(x, 1, 9).unwrap();
}
if let Some(x) = matches.value_of("strategies") {
opts.strategies = parse_numeric_range_opts(x, 0, 3).unwrap();
}
match matches.value_of("window") {
Some("256") => opts.window = 8,
Some("512") => opts.window = 9,
Some("1k") => opts.window = 10,
Some("2k") => opts.window = 11,
Some("4k") => opts.window = 12,
Some("8k") => opts.window = 13,
Some("16k") => opts.window = 14,
// 32k is default
_ => (),
}
if let Some(x) = matches.value_of("output_dir") {
opts.out_dir = Some(PathBuf::from(x));
}
if let Some(x) = matches.value_of("output_file") {
opts.out_file = PathBuf::from(x);
}
if matches.is_present("stdout") {
opts.stdout = true;
}
if matches.is_present("backup") {
opts.backup = true;
}
if matches.is_present("fix") {
opts.fix_errors = true;
}
if matches.is_present("clobber") {
opts.clobber = false;
}
if matches.is_present("pretend") {
opts.pretend = true;
}
if matches.is_present("preserve") {
opts.preserve_attrs = true;
}
if matches.is_present("quiet") {
opts.verbosity = None;
}
if matches.is_present("verbose") {
opts.verbosity = Some(1);
}
if matches.is_present("no-bit-reduction") {
opts.bit_depth_reduction = false;
}
if matches.is_present("no-color-reduction") {
opts.color_type_reduction = false;
}
if matches.is_present("no-palette-reduction") {
opts.palette_reduction = false;
}
if matches.is_present("no-reductions") {
opts.bit_depth_reduction = false;
opts.color_type_reduction = false;
opts.palette_reduction = false;
}
if matches.is_present("no-recoding") {
opts.idat_recoding = false;
}
if matches.is_present("strip") {
opts.strip = true;
}
}
fn parse_numeric_range_opts(input: &str,
min_value: u8,
max_value: u8)
-> Result<HashSet<u8>, String> {
let one_item = Regex::new(format!("^[{}-{}]$", min_value, max_value).as_ref()).unwrap();
let multiple_items = Regex::new(format!("^([{}-{}])(,|-)([{}-{}])$",
min_value,
@ -224,9 +433,11 @@ fn parse_numeric_range_opts(input: &str, min_value: u8, max_value: u8) -> Result
max_value)
.as_ref())
.unwrap();
let mut items = HashSet::new();
if one_item.is_match(input) {
return Ok(vec![input.parse::<u8>().unwrap()]);
items.insert(input.parse::<u8>().unwrap());
return Ok(items);
}
if let Some(captures) = multiple_items.captures(input) {
@ -237,8 +448,17 @@ fn parse_numeric_range_opts(input: &str, min_value: u8, max_value: u8) -> Result
}
return Ok(match &captures[2] {
"," => vec![first, second],
"-" => (first..second + 1).collect(),
"," => {
items.insert(first);
items.insert(second);
return Ok(items);
}
"-" => {
for i in first..second + 1 {
items.insert(i);
}
return Ok(items);
}
_ => panic!("Unreachable"),
});
}