From ecaa306d4976e2a52d6e8efec3a33dc13cdf54e9 Mon Sep 17 00:00:00 2001 From: Aaron Shim Date: Fri, 28 Apr 2017 00:30:32 -0700 Subject: [PATCH 1/3] debug settings to test a workflow (cherry picked from commit 41fa73ac17d62dddff1daef075bdef54d8da1e4f) --- .vscode/launch.json | 2 +- test.zip | Bin 0 -> 3884 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 test.zip diff --git a/.vscode/launch.json b/.vscode/launch.json index 02db983..359ee63 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -6,7 +6,7 @@ "type": "gdb", "request": "launch", "target": "./target/debug/zi", - "arguments": "--help", + "arguments": "-p test.zip", "cwd": "${workspaceRoot}" } ] diff --git a/test.zip b/test.zip new file mode 100644 index 0000000000000000000000000000000000000000..6bfc586fc9d7b1afe744caeeae9b4bec330c26a3 GIT binary patch literal 3884 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 RKd!;_YtVkJk|+PD{sTC77asrs literal 0 HcmV?d00001 From 9b18c19adefc9b02103994131b5bbc5c5c5e1109 Mon Sep 17 00:00:00 2001 From: Aaron Shim Date: Mon, 8 May 2017 00:11:48 -0700 Subject: [PATCH 2/3] command line arguments for different stats work now --- .vscode/launch.json | 2 +- src/flat_writer.rs | 29 ++++++++----- src/json_writer.rs | 100 ++++++++++++++++++++++++++++---------------- src/main.rs | 23 +++++----- src/zip_info.rs | 56 ++++++++++++++++++++++++- 5 files changed, 151 insertions(+), 59 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 359ee63..be1db4a 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -6,7 +6,7 @@ "type": "gdb", "request": "launch", "target": "./target/debug/zi", - "arguments": "-p test.zip", + "arguments": "test.zip", "cwd": "${workspaceRoot}" } ] diff --git a/src/flat_writer.rs b/src/flat_writer.rs index 99eeaca..226a2a6 100644 --- a/src/flat_writer.rs +++ b/src/flat_writer.rs @@ -1,7 +1,9 @@ use ::glob; use ::zip; use std::fs; + use zip_info::WriteZipInfo; +use zip_info::StatArgs; /// Info Writer for multiple archive files: pub struct MultiArchiveFlatWriter<'a> { @@ -17,12 +19,12 @@ impl<'a> MultiArchiveFlatWriter<'a> { impl<'a> WriteZipInfo for MultiArchiveFlatWriter<'a> { /// Given path names for multiple archives, concatenate their /// names, contents, and stats: - fn write_zip_info(&mut self, exclude: &str) -> String { + fn write_zip_info(&mut self, exclude: &str, stat_args: &StatArgs) -> String { let mut output = Vec::new(); for path_name in self.path_names { let archive_info = ZipInfoFlatWriter::new(path_name.as_str()) - .write_zip_info(exclude); + .write_zip_info(exclude, stat_args); output.push(archive_info); } @@ -49,7 +51,7 @@ impl<'a> ZipInfoFlatWriter<'a> { impl<'a> WriteZipInfo for ZipInfoFlatWriter<'a> { /// Concatenate Zip file name with indented stats for an archive: - fn write_zip_info(&mut self, exclude: &str) -> String { + fn write_zip_info(&mut self, exclude: &str, stat_args: &StatArgs) -> String { let mut info = format!("{}", self.path_name); let exclude_pattern = glob::Pattern::new(exclude).unwrap(); @@ -58,7 +60,7 @@ impl<'a> WriteZipInfo for ZipInfoFlatWriter<'a> { let archive_item = self.archive.by_index(i).unwrap(); if !exclude_pattern.matches(archive_item.name()) { - info = format!("{}{}", info, info_for_archive_item(archive_item)); + info = format!("{}{}", info, info_for_archive_item(archive_item, stat_args)); } } @@ -66,21 +68,28 @@ impl<'a> WriteZipInfo for ZipInfoFlatWriter<'a> { } } -fn info_for_archive_item(archive_item: zip::read::ZipFile) -> String { +fn info_for_archive_item(archive_item: zip::read::ZipFile, stat_args: &StatArgs) -> String { let mut info = String::new(); let item_path = archive_item.name(); info = format!("{}\n\t{}", info, item_path); - let compression_type = archive_item.compression(); - info = format!("{}\n\t\tCompression type: {}", info, compression_type); + if stat_args.flag_compression_type { + let compression_type = archive_item.compression(); + info = format!("{}\n\t\tCompression type: {}", info, compression_type); + } + // Can't think of a way to avoid evaluating original_size or compressed_size without dependent typing... let original_size = archive_item.size(); - info = format!("{}\n\t\tOriginal size: {}", info, original_size); + if stat_args.flag_original_size { + info = format!("{}\n\t\tOriginal size: {}", info, original_size); + } let compressed_size = archive_item.compressed_size(); - info = format!("{}\n\t\tCompressed size: {}", info, compressed_size); + if stat_args.flag_compressed_size { + info = format!("{}\n\t\tCompressed size: {}", info, compressed_size); + } - if original_size > 0 { + if stat_args.flag_compression_rate && original_size > 0 { let compression_rate = (original_size as f64 - compressed_size as f64) / original_size as f64; diff --git a/src/json_writer.rs b/src/json_writer.rs index d0862a5..3fd3be0 100644 --- a/src/json_writer.rs +++ b/src/json_writer.rs @@ -4,14 +4,20 @@ use ::zip; use std::collections::HashMap; use std::fs; -pub fn zips_to_json(file_paths: &[&str], exclude: &str) -> String { - let multi_archive = MultiArchiveJsonWriter::from(file_paths, exclude); - serde_json::to_string(&multi_archive).unwrap() +use zip_info::StatArgs; + +pub fn zips_to_json(file_paths: &[&str], exclude: &str, stat_args: &StatArgs) -> String { + zips_to_json_with_printer(file_paths, exclude, stat_args, serde_json::to_string) +} + +pub fn zips_to_json_pretty(file_paths: &[&str], exclude: &str, stat_args: &StatArgs) -> String { + zips_to_json_with_printer(file_paths, exclude, stat_args, serde_json::to_string_pretty) } -pub fn zips_to_json_pretty(file_paths: &[&str], exclude: &str) -> String { - let multi_archive = MultiArchiveJsonWriter::from(file_paths,exclude); - serde_json::to_string_pretty(&multi_archive).unwrap() +fn zips_to_json_with_printer(file_paths: &[&str], exclude: &str, stat_args: &StatArgs, printer: F) -> String + where F: Fn(&MultiArchiveJsonWriter) -> serde_json::Result { + let multi_archive = MultiArchiveJsonWriter::from(file_paths, exclude, stat_args); + printer(&multi_archive).unwrap() } #[derive(Serialize, Deserialize, Debug, PartialEq)] @@ -28,13 +34,13 @@ impl MultiArchiveJsonWriter { /// Create and fill MultiArchiveJsonWriter representing /// zero to many .zip files: pub fn from( - file_paths: &[&str], exclude: &str) -> MultiArchiveJsonWriter { + file_paths: &[&str], exclude: &str, stat_args: &StatArgs) -> MultiArchiveJsonWriter { let mut multi_archive = MultiArchiveJsonWriter::new(); for path in file_paths { multi_archive.archives.insert( String::from(*path), - ZipArchiveJsonWriter::from(*path, exclude), + ZipArchiveJsonWriter::from(*path, exclude, stat_args), ); } @@ -55,7 +61,7 @@ impl ZipArchiveJsonWriter { /// Create and fill ZipArchiveJsonWriter representing a /// .zip file: - pub fn from(file_path: &str, exclude: &str) -> ZipArchiveJsonWriter { + pub fn from(file_path: &str, exclude: &str, stat_args: &StatArgs) -> ZipArchiveJsonWriter { let mut archive_writer = ZipArchiveJsonWriter::new(); let exclude_pattern = glob::Pattern::new(exclude).unwrap(); @@ -71,10 +77,15 @@ impl ZipArchiveJsonWriter { if !exclude_pattern.matches(zip_object.name()) { archive_writer.objects.insert( String::from(zip_object.name()), + // I wanted to prevent the evaluation of as many of these parameters at this level as possible + // but it seems that because compression rate depends on both .size() and .compressed_size(), we + // need both to be passed in to calculate compression_rate if needed. (No way to encode this + // dependency here without dependent types.) ZipObjectJsonWriter::new( - zip_object.compression(), + if stat_args.flag_compression_type { Some(zip_object.compression()) } else { None }, zip_object.size(), zip_object.compressed_size(), + stat_args, ), ); } @@ -84,33 +95,47 @@ impl ZipArchiveJsonWriter { } } +// We are going to use Option types here to denote that not +// all of these are going to be calculated, based on certain +// command-line flags given to this program. +// +// TODO: Figure out how to get the JSON serializer to dump fields +// that are set to null, if this is something that we want in the +// feature. #[derive(Serialize, Deserialize, Debug, PartialEq)] struct ZipObjectJsonWriter { - compression_type: String, - original_size: u64, - compressed_size: u64, - compression_rate: String, + compression_type: Option, + original_size: Option, + compressed_size: Option, + compression_rate: Option, } impl ZipObjectJsonWriter { pub fn new( - compression_type: zip::CompressionMethod, + compression_type: Option, original_size: u64, compressed_size: u64, + stat_args: &StatArgs, ) -> ZipObjectJsonWriter { - let compression_rate = match original_size { - 0 => 0.0 as f64, - _ => { - (original_size as f64 - compressed_size as f64) - / original_size as f64 - }, - }; + let compression_rate = + if stat_args.flag_compression_rate { + Some(match original_size { + 0 => 0.0 as f64, + _ => { + (original_size as f64 - compressed_size as f64) + / original_size as f64 + }, + }) + } + else { + None + }; ZipObjectJsonWriter { - compression_type: format!("{}", compression_type), - original_size: original_size, - compressed_size: compressed_size, - compression_rate: format!("{:.*}%", 2, compression_rate * 100.0), + compression_type: compression_type.and_then(|v| Some(format!("{}", v))), + original_size: if stat_args.flag_original_size { Some(original_size) } else { None }, + compressed_size: if stat_args.flag_compressed_size { Some(compressed_size) } else { None }, + compression_rate: compression_rate.and_then(|v| Some(format!("{:.*}%", 2, v * 100.0))), } } } @@ -121,10 +146,10 @@ mod tests { fn get_zip_object() -> ZipObjectJsonWriter { ZipObjectJsonWriter { - compression_type: format!("{}", zip::CompressionMethod::Deflated), - original_size: 100, - compressed_size: 50, - compression_rate: String::from("50%"), + compression_type: Some(format!("{}", zip::CompressionMethod::Deflated)), + original_size: Some(100), + compressed_size: Some(50), + compression_rate: Some(String::from("50%")), } } @@ -137,28 +162,31 @@ mod tests { #[test] fn test_new_zip_object_calculates_percentages() { let zip_object = ZipObjectJsonWriter::new( - zip::CompressionMethod::Deflated, + Some(zip::CompressionMethod::Deflated), 100, 50, + &StatArgs::default(), ); - assert_eq!("50.00%", zip_object.compression_rate); + assert_eq!("50.00%", zip_object.compression_rate.unwrap_or_default()); let zip_object_empty = ZipObjectJsonWriter::new( - zip::CompressionMethod::Stored, + Some(zip::CompressionMethod::Stored), 0, 0, + &StatArgs::default(), ); - assert_eq!("0.00%", zip_object_empty.compression_rate); + assert_eq!("0.00%", zip_object_empty.compression_rate.unwrap_or_default()); let zip_object_grew = ZipObjectJsonWriter::new( - zip::CompressionMethod::Deflated, + Some(zip::CompressionMethod::Deflated), 100, 150, + &StatArgs::default(), ); - assert_eq!("-50.00%", zip_object_grew.compression_rate); + assert_eq!("-50.00%", zip_object_grew.compression_rate.unwrap_or_default()); } #[test] diff --git a/src/main.rs b/src/main.rs index 125c1a7..450a044 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,10 +15,12 @@ mod flat_writer; mod json_writer; use zip_info::WriteZipInfo; +use zip_info::Args; +use zip_info::StatArgs; /// The Docopt usage string const USAGE: &'static str = " -Usage: zi [-j | -p] [--exclude=] ... +Usage: zi [-j | -p] [--exclude=] [options] ... zi --help zi presents information about Zip archives. @@ -29,21 +31,20 @@ Common options: -p, --pretty-json Structure the output in easy-to-read JSON --exclude= Ignore objects in the archives whose name is like this glob pattern. +More options: + --compression-type Show the compression type of each file. + --original-size Show the original size of each file. + --compressed-size Show the compressed size of each file. + --compression-rate Show the compression rate of each file. "; -#[derive(Debug, RustcDecodable)] -struct Args { - arg_path: Vec, - flag_json: bool, - flag_pretty_json: bool, - flag_exclude: String, -} - fn main() { let args: Args = Docopt::new(USAGE) .and_then(|d| d.decode()) .unwrap_or_else(|e| e.exit()); + let stat_args = StatArgs::new(&args); + // XXX We divide up the display functionality here since // the original flat writer works differently than the JSON // writer. Ultimately, the JSON writer should be a generic @@ -54,7 +55,7 @@ fn main() { args.arg_path.as_slice() ); - println!("{}", multiwriter.write_zip_info(args.flag_exclude.as_str())); + println!("{}", multiwriter.write_zip_info(args.flag_exclude.as_str(), &stat_args)); } else { // Convert String to &str for json printing since // Docopt appears not to be able to handle Vec<&str>: @@ -69,6 +70,6 @@ fn main() { _ => json_writer::zips_to_json_pretty, }; - println!("{}", s(path_names.as_slice(), args.flag_exclude.as_str())); + println!("{}", s(path_names.as_slice(), args.flag_exclude.as_str(), &stat_args)); } } diff --git a/src/zip_info.rs b/src/zip_info.rs index 16f638d..e290201 100644 --- a/src/zip_info.rs +++ b/src/zip_info.rs @@ -1,5 +1,59 @@ pub trait WriteZipInfo { - fn write_zip_info(&mut self, exclude: &str) -> String; + fn write_zip_info(&mut self, exclude: &str, stat_args: &StatArgs) -> String; +} + +// Struct for our command-line arguments +#[derive(Debug, RustcDecodable)] +pub struct Args { + pub arg_path: Vec, + pub flag_json: bool, + pub flag_pretty_json: bool, + pub flag_exclude: String, + pub flag_compression_type: bool, + pub flag_original_size: bool, + pub flag_compressed_size: bool, + pub flag_compression_rate: bool, +} + +// We will need to pass this down in the main thread, to the individual printers, +// and there is no point in passing down the whole Args struct, in the spirit of encapsulation. +#[derive(Debug)] +pub struct StatArgs { + pub flag_compression_type: bool, + pub flag_original_size: bool, + pub flag_compressed_size: bool, + pub flag_compression_rate: bool, +} + +impl StatArgs { + pub fn new(args: &Args) -> StatArgs { + let mut stat_args = StatArgs { + flag_compression_type: args.flag_compression_type, + flag_original_size: args.flag_original_size, + flag_compressed_size: args.flag_compressed_size, + flag_compression_rate: args.flag_compression_rate, + }; + + // If none of these are explicitly specified, we print all of them. + if stat_args.is_all_false() { + stat_args = StatArgs::default(); + } + + stat_args + } + + pub fn default() -> StatArgs { + StatArgs { + flag_compression_type: true, + flag_original_size: true, + flag_compressed_size: true, + flag_compression_rate: true, + } + } + + fn is_all_false(&self) -> bool { + !(self.flag_compression_type || self.flag_original_size || self.flag_compressed_size || self.flag_compression_rate) + } } #[cfg(test)] From 0a4c7c380d42ef87e5561c4b02a0ed72f910fdfa Mon Sep 17 00:00:00 2001 From: Aaron Shim Date: Thu, 1 Jun 2017 23:23:56 -0700 Subject: [PATCH 3/3] ditching fields in the JSON serialization if they are optional (rather than having them null) --- src/json_writer.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/json_writer.rs b/src/json_writer.rs index 3fd3be0..6fdd86b 100644 --- a/src/json_writer.rs +++ b/src/json_writer.rs @@ -104,9 +104,13 @@ impl ZipArchiveJsonWriter { // feature. #[derive(Serialize, Deserialize, Debug, PartialEq)] struct ZipObjectJsonWriter { + #[serde(skip_serializing_if="Option::is_none")] compression_type: Option, + #[serde(skip_serializing_if="Option::is_none")] original_size: Option, + #[serde(skip_serializing_if="Option::is_none")] compressed_size: Option, + #[serde(skip_serializing_if="Option::is_none")] compression_rate: Option, }