Completely parse CLI options
This commit is contained in:
parent
273880ea0b
commit
5f866d9844
3 changed files with 253 additions and 28 deletions
1
rustfmt.toml
Normal file
1
rustfmt.toml
Normal file
|
|
@ -0,0 +1 @@
|
|||
format_strings = false
|
||||
12
src/lib.rs
12
src/lib.rs
|
|
@ -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 {
|
||||
|
|
|
|||
268
src/main.rs
268
src/main.rs
|
|
@ -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"),
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue