Move file-specific options under OutFile (#529)
This PR is addressing #220. It's not super important but it's a breaking change, so if it's something we want to do then I thought I should get it in now before the next release. - [x] pretend can become another variant of OutFile, probably OutFile::None, as that's what it essentially is - just another output destination and not a separate option - [x] ~~backup and~~ preserve_attrs should become properties of OutFile::Path variant (so that it would contain Path { path, ~~backup,~~ preserve_attrs }) as they don't have any effect on any other output and so semantically belong there best Closes #220
This commit is contained in:
parent
b8ec65b3ca
commit
462e982784
10 changed files with 71 additions and 70 deletions
63
src/lib.rs
63
src/lib.rs
|
|
@ -76,15 +76,36 @@ pub mod internal_tests {
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum OutFile {
|
pub enum OutFile {
|
||||||
/// Path(None) means same as input
|
/// Don't actually write any output, just calculate the best results.
|
||||||
Path(Option<PathBuf>),
|
None,
|
||||||
|
/// Write output to a file.
|
||||||
|
///
|
||||||
|
/// * `path`: Path to write the output file. `None` means same as input.
|
||||||
|
/// * `preserve_attrs`: Ensure the output file has the same permissions & timestamps as the input file.
|
||||||
|
Path {
|
||||||
|
path: Option<PathBuf>,
|
||||||
|
preserve_attrs: bool,
|
||||||
|
},
|
||||||
|
/// Write to standard output.
|
||||||
StdOut,
|
StdOut,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OutFile {
|
impl OutFile {
|
||||||
|
/// Construct a new `OutFile` with the given path.
|
||||||
|
///
|
||||||
|
/// This is a convenience method for `OutFile::Path { path: Some(path), preserve_attrs: false }`.
|
||||||
|
pub fn from_path(path: PathBuf) -> Self {
|
||||||
|
OutFile::Path {
|
||||||
|
path: Some(path),
|
||||||
|
preserve_attrs: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn path(&self) -> Option<&Path> {
|
pub fn path(&self) -> Option<&Path> {
|
||||||
match *self {
|
match *self {
|
||||||
OutFile::Path(Some(ref p)) => Some(p.as_path()),
|
OutFile::Path {
|
||||||
|
path: Some(ref p), ..
|
||||||
|
} => Some(p.as_path()),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -138,18 +159,10 @@ pub struct Options {
|
||||||
///
|
///
|
||||||
/// Default: `false`
|
/// Default: `false`
|
||||||
pub check: bool,
|
pub check: bool,
|
||||||
/// Don't actually write any output, just calculate the best results.
|
|
||||||
///
|
|
||||||
/// Default: `false`
|
|
||||||
pub pretend: bool,
|
|
||||||
/// Write to output even if there was no improvement in compression.
|
/// Write to output even if there was no improvement in compression.
|
||||||
///
|
///
|
||||||
/// Default: `false`
|
/// Default: `false`
|
||||||
pub force: bool,
|
pub force: bool,
|
||||||
/// Ensure the output file has the same permissions as the input file.
|
|
||||||
///
|
|
||||||
/// Default: `false`
|
|
||||||
pub preserve_attrs: bool,
|
|
||||||
/// Which RowFilters to try on the file
|
/// Which RowFilters to try on the file
|
||||||
///
|
///
|
||||||
/// Default: `None,Sub,Entropy,Bigrams`
|
/// Default: `None,Sub,Entropy,Bigrams`
|
||||||
|
|
@ -296,10 +309,8 @@ impl Default for Options {
|
||||||
Options {
|
Options {
|
||||||
backup: false,
|
backup: false,
|
||||||
check: false,
|
check: false,
|
||||||
pretend: false,
|
|
||||||
fix_errors: false,
|
fix_errors: false,
|
||||||
force: false,
|
force: false,
|
||||||
preserve_attrs: false,
|
|
||||||
filter: indexset! {RowFilter::None, RowFilter::Sub, RowFilter::Entropy, RowFilter::Bigrams},
|
filter: indexset! {RowFilter::None, RowFilter::Sub, RowFilter::Entropy, RowFilter::Bigrams},
|
||||||
interlace: Some(Interlacing::None),
|
interlace: Some(Interlacing::None),
|
||||||
optimize_alpha: false,
|
optimize_alpha: false,
|
||||||
|
|
@ -416,7 +427,13 @@ pub fn optimize(input: &InFile, output: &OutFile, opts: &Options) -> PngResult<(
|
||||||
let opt_metadata_preserved;
|
let opt_metadata_preserved;
|
||||||
let in_data = match *input {
|
let in_data = match *input {
|
||||||
InFile::Path(ref input_path) => {
|
InFile::Path(ref input_path) => {
|
||||||
if opts.preserve_attrs {
|
if matches!(
|
||||||
|
output,
|
||||||
|
OutFile::Path {
|
||||||
|
preserve_attrs: true,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
) {
|
||||||
opt_metadata_preserved = input_path
|
opt_metadata_preserved = input_path
|
||||||
.metadata()
|
.metadata()
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
|
|
@ -458,8 +475,8 @@ pub fn optimize(input: &InFile, output: &OutFile, opts: &Options) -> PngResult<(
|
||||||
if is_fully_optimized(in_data.len(), optimized_output.len(), opts) {
|
if is_fully_optimized(in_data.len(), optimized_output.len(), opts) {
|
||||||
match (output, input) {
|
match (output, input) {
|
||||||
// if p is None, it also means same as the input path
|
// if p is None, it also means same as the input path
|
||||||
(OutFile::Path(ref p), InFile::Path(ref input_path))
|
(OutFile::Path { path, .. }, InFile::Path(ref input_path))
|
||||||
if p.as_ref().map_or(true, |p| p == input_path) =>
|
if path.as_ref().map_or(true, |p| p == input_path) =>
|
||||||
{
|
{
|
||||||
info!("{}: Could not optimize further, no change written", input);
|
info!("{}: Could not optimize further, no change written", input);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|
@ -484,20 +501,18 @@ pub fn optimize(input: &InFile, output: &OutFile, opts: &Options) -> PngResult<(
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
if opts.pretend {
|
|
||||||
info!("{}: Running in pretend mode, no output", savings);
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
match (output, input) {
|
match (output, input) {
|
||||||
(&OutFile::StdOut, _) | (&OutFile::Path(None), &InFile::StdIn) => {
|
(OutFile::None, _) => {
|
||||||
|
info!("{}: Running in pretend mode, no output", savings);
|
||||||
|
}
|
||||||
|
(&OutFile::StdOut, _) | (&OutFile::Path { path: None, .. }, &InFile::StdIn) => {
|
||||||
let mut buffer = BufWriter::new(stdout());
|
let mut buffer = BufWriter::new(stdout());
|
||||||
buffer
|
buffer
|
||||||
.write_all(&optimized_output)
|
.write_all(&optimized_output)
|
||||||
.map_err(|e| PngError::new(&format!("Unable to write to stdout: {}", e)))?;
|
.map_err(|e| PngError::new(&format!("Unable to write to stdout: {}", e)))?;
|
||||||
}
|
}
|
||||||
(OutFile::Path(ref output_path), _) => {
|
(OutFile::Path { path, .. }, _) => {
|
||||||
let output_path = output_path
|
let output_path = path
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|p| p.as_path())
|
.map(|p| p.as_path())
|
||||||
.unwrap_or_else(|| input.path().unwrap());
|
.unwrap_or_else(|| input.path().unwrap());
|
||||||
|
|
|
||||||
29
src/main.rs
29
src/main.rs
|
|
@ -376,12 +376,16 @@ fn collect_files(
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let out_file = if let Some(ref out_dir) = *out_dir {
|
let out_file =
|
||||||
let out_path = Some(out_dir.join(input.file_name().unwrap()));
|
if let (Some(out_dir), &OutFile::Path { preserve_attrs, .. }) = (out_dir, out_file) {
|
||||||
OutFile::Path(out_path)
|
let path = Some(out_dir.join(input.file_name().unwrap()));
|
||||||
} else {
|
OutFile::Path {
|
||||||
(*out_file).clone()
|
path,
|
||||||
};
|
preserve_attrs,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(*out_file).clone()
|
||||||
|
};
|
||||||
let in_file = if using_stdin {
|
let in_file = if using_stdin {
|
||||||
InFile::StdIn
|
InFile::StdIn
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -459,10 +463,15 @@ fn parse_opts_into_struct(
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let out_file = if matches.get_flag("stdout") {
|
let out_file = if matches.get_flag("pretend") {
|
||||||
|
OutFile::None
|
||||||
|
} else if matches.get_flag("stdout") {
|
||||||
OutFile::StdOut
|
OutFile::StdOut
|
||||||
} else {
|
} else {
|
||||||
OutFile::Path(matches.get_one::<PathBuf>("output_file").cloned())
|
OutFile::Path {
|
||||||
|
path: matches.get_one::<PathBuf>("output_file").cloned(),
|
||||||
|
preserve_attrs: matches.get_flag("preserve"),
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
opts.optimize_alpha = matches.get_flag("alpha");
|
opts.optimize_alpha = matches.get_flag("alpha");
|
||||||
|
|
@ -482,10 +491,6 @@ fn parse_opts_into_struct(
|
||||||
|
|
||||||
opts.check = matches.get_flag("check");
|
opts.check = matches.get_flag("check");
|
||||||
|
|
||||||
opts.pretend = matches.get_flag("pretend");
|
|
||||||
|
|
||||||
opts.preserve_attrs = matches.get_flag("preserve");
|
|
||||||
|
|
||||||
opts.bit_depth_reduction = !matches.get_flag("no-bit-reduction");
|
opts.bit_depth_reduction = !matches.get_flag("no-bit-reduction");
|
||||||
|
|
||||||
opts.color_type_reduction = !matches.get_flag("no-color-reduction");
|
opts.color_type_reduction = !matches.get_flag("no-color-reduction");
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,7 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
||||||
filter.insert(RowFilter::None);
|
filter.insert(RowFilter::None);
|
||||||
options.filter = filter;
|
options.filter = filter;
|
||||||
|
|
||||||
(
|
(OutFile::from_path(input.with_extension("out.png")), options)
|
||||||
OutFile::Path(Some(input.with_extension("out.png"))),
|
|
||||||
options,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_it_converts(
|
fn test_it_converts(
|
||||||
|
|
|
||||||
|
|
@ -26,10 +26,7 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
||||||
filter.insert(RowFilter::None);
|
filter.insert(RowFilter::None);
|
||||||
options.filter = filter;
|
options.filter = filter;
|
||||||
|
|
||||||
(
|
(OutFile::from_path(input.with_extension("out.png")), options)
|
||||||
OutFile::Path(Some(input.with_extension("out.png"))),
|
|
||||||
options,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add callback to allow checks before the output file is deleted again
|
/// Add callback to allow checks before the output file is deleted again
|
||||||
|
|
@ -518,8 +515,10 @@ fn preserve_attrs() {
|
||||||
#[cfg(feature = "filetime")]
|
#[cfg(feature = "filetime")]
|
||||||
let mtime_canon = RefCell::new(filetime::FileTime::from_unix_time(0, 0));
|
let mtime_canon = RefCell::new(filetime::FileTime::from_unix_time(0, 0));
|
||||||
|
|
||||||
let (output, mut opts) = get_opts(&input);
|
let (mut output, opts) = get_opts(&input);
|
||||||
opts.preserve_attrs = true;
|
if let OutFile::Path { preserve_attrs, .. } = &mut output {
|
||||||
|
*preserve_attrs = true;
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "filetime")]
|
#[cfg(feature = "filetime")]
|
||||||
let callback_pre = |path_in: &Path| {
|
let callback_pre = |path_in: &Path| {
|
||||||
|
|
|
||||||
|
|
@ -22,10 +22,7 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
||||||
filter.insert(RowFilter::None);
|
filter.insert(RowFilter::None);
|
||||||
options.filter = filter;
|
options.filter = filter;
|
||||||
|
|
||||||
(
|
(OutFile::from_path(input.with_extension("out.png")), options)
|
||||||
OutFile::Path(Some(input.with_extension("out.png"))),
|
|
||||||
options,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_it_converts(
|
fn test_it_converts(
|
||||||
|
|
|
||||||
|
|
@ -17,10 +17,7 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
||||||
filter.insert(RowFilter::None);
|
filter.insert(RowFilter::None);
|
||||||
options.filter = filter;
|
options.filter = filter;
|
||||||
|
|
||||||
(
|
(OutFile::from_path(input.with_extension("out.png")), options)
|
||||||
OutFile::Path(Some(input.with_extension("out.png"))),
|
|
||||||
options,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_it_converts(
|
fn test_it_converts(
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ fn optimize_from_memory_apng() {
|
||||||
fn optimize() {
|
fn optimize() {
|
||||||
let result = oxipng::optimize(
|
let result = oxipng::optimize(
|
||||||
&"tests/files/fully_optimized.png".into(),
|
&"tests/files/fully_optimized.png".into(),
|
||||||
&OutFile::Path(None),
|
&OutFile::None,
|
||||||
&Options::default(),
|
&Options::default(),
|
||||||
);
|
);
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
|
|
@ -47,7 +47,7 @@ fn optimize() {
|
||||||
fn optimize_corrupted() {
|
fn optimize_corrupted() {
|
||||||
let result = oxipng::optimize(
|
let result = oxipng::optimize(
|
||||||
&"tests/files/corrupted_header.png".into(),
|
&"tests/files/corrupted_header.png".into(),
|
||||||
&OutFile::Path(None),
|
&OutFile::None,
|
||||||
&Options::default(),
|
&Options::default(),
|
||||||
);
|
);
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
|
|
@ -57,7 +57,7 @@ fn optimize_corrupted() {
|
||||||
fn optimize_apng() {
|
fn optimize_apng() {
|
||||||
let result = oxipng::optimize(
|
let result = oxipng::optimize(
|
||||||
&"tests/files/apng_file.png".into(),
|
&"tests/files/apng_file.png".into(),
|
||||||
&OutFile::Path(None),
|
&OutFile::None,
|
||||||
&Options::from_preset(0),
|
&Options::from_preset(0),
|
||||||
);
|
);
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,7 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
||||||
filter.insert(RowFilter::None);
|
filter.insert(RowFilter::None);
|
||||||
options.filter = filter;
|
options.filter = filter;
|
||||||
|
|
||||||
(
|
(OutFile::from_path(input.with_extension("out.png")), options)
|
||||||
OutFile::Path(Some(input.with_extension("out.png"))),
|
|
||||||
options,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_it_converts(
|
fn test_it_converts(
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,7 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
||||||
filter.insert(RowFilter::None);
|
filter.insert(RowFilter::None);
|
||||||
options.filter = filter;
|
options.filter = filter;
|
||||||
|
|
||||||
(
|
(OutFile::from_path(input.with_extension("out.png")), options)
|
||||||
OutFile::Path(Some(input.with_extension("out.png"))),
|
|
||||||
options,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_it_converts(
|
fn test_it_converts(
|
||||||
|
|
@ -295,7 +292,7 @@ fn issue_92_filter_5() {
|
||||||
let input = "tests/files/issue-92.png";
|
let input = "tests/files/issue-92.png";
|
||||||
let (_, mut opts) = get_opts(Path::new(input));
|
let (_, mut opts) = get_opts(Path::new(input));
|
||||||
opts.filter = [RowFilter::MinSum].iter().cloned().collect();
|
opts.filter = [RowFilter::MinSum].iter().cloned().collect();
|
||||||
let output = OutFile::Path(Some(Path::new(input).with_extension("-f5-out.png")));
|
let output = OutFile::from_path(Path::new(input).with_extension("-f5-out.png"));
|
||||||
|
|
||||||
test_it_converts(
|
test_it_converts(
|
||||||
input,
|
input,
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,7 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
|
||||||
filter.insert(RowFilter::None);
|
filter.insert(RowFilter::None);
|
||||||
options.filter = filter;
|
options.filter = filter;
|
||||||
|
|
||||||
(
|
(OutFile::from_path(input.with_extension("out.png")), options)
|
||||||
OutFile::Path(Some(input.with_extension("out.png"))),
|
|
||||||
options,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_it_converts(
|
fn test_it_converts(
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue