Compare commits

..

10 commits

Author SHA1 Message Date
Nathan Fisher
dcc571c6d2 Slight refactor for clippy lints; Add Creator type 2024-12-15 02:52:53 -05:00
Nathan Fisher
dd8daa26ed Use CreateFlags to parse flags for archive creation 2024-12-15 02:01:32 -05:00
Nathan Fisher
5420684061 Binary: begin moving flag discovery out of program logic 2024-12-15 01:30:38 -05:00
Nathan Fisher
84a0f2c049 Update deps 2024-12-15 01:30:11 -05:00
Nathan Fisher
2b34d9ed01 Binary: Add "verbose" flag 2024-12-14 18:57:46 -05:00
Nathan Fisher
22faadc34e Add verbose option for extracting and creating archives to list files
(to be implemented)
2024-03-31 23:17:59 -04:00
Nathan Fisher
3bc8b9aa80 Use Listing as message type for parallel actions 2024-03-31 23:12:07 -04:00
Nathan Fisher
e3dddc3dd9 Display sizes in human readable form 2024-02-24 01:32:13 -05:00
Nathan Fisher
3e9a34af7e Add ability to display total sizes when displaying listings or
extracting archives
2024-02-24 00:29:23 -05:00
Nathan Fisher
44e2741727 Update Roadmap 2024-02-23 19:18:07 -05:00
9 changed files with 723 additions and 481 deletions

409
Cargo.lock generated
View file

@ -19,63 +19,64 @@ dependencies = [
[[package]]
name = "anstream"
version = "0.6.12"
version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96b09b5178381e0874812a9b157f7fe84982617e48f71f4e3235482775e5b540"
checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.6"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
[[package]]
name = "anstyle-parse"
version = "0.2.3"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.0.2"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
dependencies = [
"windows-sys",
"windows-sys 0.59.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.2"
version = "3.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125"
dependencies = [
"anstyle",
"windows-sys",
"windows-sys 0.59.0",
]
[[package]]
name = "arrayvec"
version = "0.7.4"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
[[package]]
name = "autocfg"
version = "1.1.0"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "block-buffer"
@ -88,18 +89,19 @@ dependencies = [
[[package]]
name = "bumpalo"
version = "3.14.0"
version = "3.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
[[package]]
name = "cc"
version = "1.0.83"
version = "1.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
checksum = "9157bbaa6b165880c27a4293a474c91cdcf265cc68cc829bf10be0964a391caf"
dependencies = [
"jobserver",
"libc",
"shlex",
]
[[package]]
@ -110,32 +112,32 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.31"
version = "0.4.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
"wasm-bindgen",
"windows-targets 0.48.5",
"windows-targets",
]
[[package]]
name = "clap"
version = "4.5.1"
version = "4.5.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da"
checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84"
dependencies = [
"clap_builder",
]
[[package]]
name = "clap_builder"
version = "4.5.1"
version = "4.5.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb"
checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838"
dependencies = [
"anstream",
"anstyle",
@ -145,18 +147,18 @@ dependencies = [
[[package]]
name = "clap_complete"
version = "4.5.1"
version = "4.5.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "885e4d7d5af40bfb99ae6f9433e292feac98d452dcb3ec3d25dfe7552b77da8c"
checksum = "d9647a559c112175f17cf724dc72d3645680a883c58481332779192b0d8e7a01"
dependencies = [
"clap",
]
[[package]]
name = "clap_complete_nushell"
version = "4.5.1"
version = "4.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80d0e48e026ce7df2040239117d25e4e79714907420c70294a5ce4b6bbe6a7b6"
checksum = "315902e790cc6e5ddd20cbd313c1d0d49db77f191e149f96397230fb82a17677"
dependencies = [
"clap",
"clap_complete",
@ -164,15 +166,15 @@ dependencies = [
[[package]]
name = "clap_lex"
version = "0.7.0"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
[[package]]
name = "clap_mangen"
version = "0.2.20"
version = "0.2.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1dd95b5ebb5c1c54581dd6346f3ed6a79a3eef95dd372fc2ac13d535535300e"
checksum = "fbae9cbfdc5d4fa8711c09bd7b83f644cb48281ac35bf97af3e47b0675864bdf"
dependencies = [
"clap",
"roff",
@ -180,9 +182,9 @@ dependencies = [
[[package]]
name = "colorchoice"
version = "1.0.0"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
[[package]]
name = "console"
@ -193,21 +195,21 @@ dependencies = [
"encode_unicode",
"lazy_static",
"libc",
"unicode-width",
"windows-sys",
"unicode-width 0.1.14",
"windows-sys 0.52.0",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.6"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "cpufeatures"
version = "0.2.12"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3"
dependencies = [
"libc",
]
@ -233,9 +235,9 @@ dependencies = [
[[package]]
name = "crossbeam-utils"
version = "0.8.19"
version = "0.8.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
[[package]]
name = "crypto-common"
@ -259,9 +261,9 @@ dependencies = [
[[package]]
name = "either"
version = "1.9.0"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "encode_unicode"
@ -299,9 +301,9 @@ dependencies = [
[[package]]
name = "iana-time-zone"
version = "0.1.59"
version = "0.1.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539"
checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
dependencies = [
"android_system_properties",
"core-foundation-sys",
@ -322,69 +324,67 @@ dependencies = [
[[package]]
name = "indicatif"
version = "0.17.8"
version = "0.17.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3"
checksum = "cbf675b85ed934d3c67b5c5469701eec7db22689d0a2139d856e0925fa28b281"
dependencies = [
"console",
"instant",
"number_prefix",
"portable-atomic",
"unicode-segmentation",
"unicode-width",
"unicode-width 0.2.0",
"vt100",
"web-time",
]
[[package]]
name = "instant"
version = "0.1.12"
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if",
]
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "itoa"
version = "1.0.10"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
[[package]]
name = "jobserver"
version = "0.1.28"
version = "0.1.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6"
checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
dependencies = [
"libc",
]
[[package]]
name = "js-sys"
version = "0.3.67"
version = "0.3.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1"
checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7"
dependencies = [
"once_cell",
"wasm-bindgen",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.152"
version = "0.2.168"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d"
[[package]]
name = "log"
version = "0.4.20"
version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
[[package]]
name = "md-5"
@ -398,9 +398,9 @@ dependencies = [
[[package]]
name = "num-traits"
version = "0.2.17"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
@ -413,9 +413,9 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
[[package]]
name = "once_cell"
version = "1.19.0"
version = "1.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
[[package]]
name = "package-bootstrap"
@ -431,39 +431,39 @@ dependencies = [
[[package]]
name = "pkg-config"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
[[package]]
name = "portable-atomic"
version = "1.6.0"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0"
checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6"
[[package]]
name = "proc-macro2"
version = "1.0.76"
version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c"
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.35"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rayon"
version = "1.8.0"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1"
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
dependencies = [
"either",
"rayon-core",
@ -471,9 +471,9 @@ dependencies = [
[[package]]
name = "rayon-core"
version = "1.12.0"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed"
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
@ -481,9 +481,9 @@ dependencies = [
[[package]]
name = "roff"
version = "0.2.1"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316"
checksum = "88f8660c1ff60292143c98d08fc6e2f654d722db50410e3f3797d40baaf9d8f3"
[[package]]
name = "same-file"
@ -517,16 +517,22 @@ dependencies = [
]
[[package]]
name = "strsim"
version = "0.11.0"
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.48"
version = "2.0.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
dependencies = [
"proc-macro2",
"quote",
@ -550,33 +556,39 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "unicode-ident"
version = "1.0.12"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
[[package]]
name = "unicode-segmentation"
version = "1.11.0"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
[[package]]
name = "unicode-width"
version = "0.1.11"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
[[package]]
name = "unicode-width"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
[[package]]
name = "utf8parse"
version = "0.2.1"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "version_check"
version = "0.9.4"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "vt100"
@ -586,7 +598,7 @@ checksum = "84cd863bf0db7e392ba3bd04994be3473491b31e66340672af5d11943c6274de"
dependencies = [
"itoa",
"log",
"unicode-width",
"unicode-width 0.1.14",
"vte",
]
@ -603,9 +615,9 @@ dependencies = [
[[package]]
name = "vte_generate_state_changes"
version = "0.1.1"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d257817081c7dffcdbab24b9e62d2def62e2ff7d00b1c20062551e6cccc145ff"
checksum = "2e369bee1b05d510a7b4ed645f5faa90619e05437111783ea5848f28d97d3c2e"
dependencies = [
"proc-macro2",
"quote",
@ -613,9 +625,9 @@ dependencies = [
[[package]]
name = "walkdir"
version = "2.4.0"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
"same-file",
"winapi-util",
@ -623,23 +635,23 @@ dependencies = [
[[package]]
name = "wasm-bindgen"
version = "0.2.90"
version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406"
checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396"
dependencies = [
"cfg-if",
"once_cell",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.90"
version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd"
checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
@ -648,9 +660,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.90"
version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999"
checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@ -658,9 +670,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.90"
version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7"
checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2"
dependencies = [
"proc-macro2",
"quote",
@ -671,48 +683,36 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.90"
version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b"
checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6"
[[package]]
name = "winapi"
version = "0.3.9"
name = "web-time"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.6"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"winapi",
"windows-sys 0.59.0",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-core"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
dependencies = [
"windows-targets 0.52.0",
"windows-targets",
]
[[package]]
@ -721,146 +721,105 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.0",
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm 0.48.5",
"windows_aarch64_msvc 0.48.5",
"windows_i686_gnu 0.48.5",
"windows_i686_msvc 0.48.5",
"windows_x86_64_gnu 0.48.5",
"windows_x86_64_gnullvm 0.48.5",
"windows_x86_64_msvc 0.48.5",
]
[[package]]
name = "windows-targets"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
dependencies = [
"windows_aarch64_gnullvm 0.52.0",
"windows_aarch64_msvc 0.52.0",
"windows_i686_gnu 0.52.0",
"windows_i686_msvc 0.52.0",
"windows_x86_64_gnu 0.52.0",
"windows_x86_64_gnullvm 0.52.0",
"windows_x86_64_msvc 0.52.0",
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnu"
version = "0.52.0"
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "zstd"
version = "0.13.0"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bffb3309596d527cfcba7dfc6ed6052f1d39dfbd7c867aa2e865e4a449c10110"
checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9"
dependencies = [
"zstd-safe",
]
[[package]]
name = "zstd-safe"
version = "7.0.0"
version = "7.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43747c7422e2924c11144d5229878b98180ef8b06cca4ab5af37afc8a8d8ea3e"
checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059"
dependencies = [
"zstd-sys",
]
[[package]]
name = "zstd-sys"
version = "2.0.9+zstd.1.5.5"
version = "2.0.13+zstd.1.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656"
checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa"
dependencies = [
"cc",
"pkg-config",

View file

@ -153,3 +153,5 @@ to see Haggis implemented in other languages.
- [x] Add path to error message when passing between threads
- [x] Add ability to write archives to stdout
- [x] Add ability to read archives from stdin
- [x] Add option to display total size to archive listings
- [x] Optionally display sizes in human readable form

View file

@ -40,7 +40,7 @@ fn main() -> Result<(), Box<dyn Error>> {
let bs = Bootstrap::new("haggis", cli::haggis(), &outdir);
bs.install(target_dir, 1)?;
if matches.get_flag("meta") {
bs.docfiles(&["README.md", "LICENSE.md"], &Path::new("haggis"))?;
bs.docfiles(&["README.md", "LICENSE.md"], Path::new("haggis"))?;
}
Bootstrap::new("haggis-create", cli::create(), &outdir).manpage(1)?;
Bootstrap::new("haggis-extract", cli::extract(), &outdir).manpage(1)?;

View file

@ -27,6 +27,11 @@ pub fn extract() -> Command {
.long("quiet")
.visible_alias("silent")
.action(ArgAction::SetFalse),
Arg::new("verbose")
.help("List the extracted files")
.short('v')
.long("verbose")
.action(ArgAction::SetTrue),
Arg::new("stdin")
.help("Read archive from stdin")
.short('i')
@ -53,6 +58,11 @@ pub fn extract() -> Command {
.long("gid")
.value_parser(clap::value_parser!(u32))
.num_args(1),
Arg::new("total")
.help("Display the total size of extracted files")
.short('t')
.long("total")
.action(ArgAction::SetTrue),
Arg::new("archive")
.num_args(1)
.required_unless_present("stdin")
@ -94,6 +104,11 @@ pub fn create() -> Command {
.long("quiet")
.visible_alias("silent")
.action(ArgAction::SetFalse),
Arg::new("verbose")
.help("List the archived files")
.short('v')
.long("verbose")
.action(ArgAction::SetTrue),
Arg::new("stdout")
.help("Write archive to stdout")
.short('o')
@ -154,6 +169,16 @@ pub fn list() -> Command {
.short('n')
.long("no-sort")
.action(ArgAction::SetTrue),
Arg::new("total")
.help("Display the total size if extracted")
.short('t')
.long("total")
.action(ArgAction::SetTrue),
Arg::new("human")
.help("Display sizes in human-readable form")
.short('H')
.long("human")
.action(ArgAction::SetTrue),
Arg::new("archive")
.num_args(1)
.required(true)

View file

@ -1,14 +1,20 @@
#![warn(clippy::all, clippy::pedantic)]
use {
clap::ArgMatches,
haggis::{Algorithm, Listing, ListingKind, ListingStream, NodeStream, Message, StreamMessage},
haggis::{
Algorithm, HumanSize, Listing, ListingKind, ListingStream, Message, NodeStream,
StreamMessage,
},
indicatif::{ProgressBar, ProgressStyle},
std::{
fs::{self, File},
io::{self, BufReader, BufWriter},
os::fd::{AsRawFd, FromRawFd},
process,
sync::mpsc,
sync::{
atomic::{AtomicU64, Ordering},
mpsc, Arc,
},
thread,
},
walkdir::WalkDir,
@ -19,6 +25,72 @@ mod cli;
static TEMPLATE: &str = "[ {prefix:^30!} ] {wide_bar}{pos:>5.cyan}/{len:5.green}";
#[allow(clippy::struct_excessive_bools)]
struct ListingFlags {
files: bool,
color: bool,
long: bool,
human: bool,
}
impl From<&ArgMatches> for ListingFlags {
fn from(matches: &ArgMatches) -> Self {
Self {
files: matches.get_flag("files"),
color: matches.get_flag("color"),
long: matches.get_flag("long"),
human: matches.get_flag("human"),
}
}
}
#[derive(PartialEq, Eq)]
enum Verbosity {
Quiet,
Normal,
Verbose,
}
impl From<&ArgMatches> for Verbosity {
fn from(matches: &ArgMatches) -> Self {
if matches.get_flag("quiet") {
Self::Quiet
} else if matches.get_flag("verbose") && !matches.get_flag("stdout") {
Self::Verbose
} else {
Self::Normal
}
}
}
struct CreateFlags {
algorithm: Algorithm,
zstd_compression: Option<i32>,
verbosity: Verbosity,
stdout: bool,
uid: Option<u32>,
gid: Option<u32>,
}
impl TryFrom<&ArgMatches> for CreateFlags {
type Error = haggis::Error;
fn try_from(matches: &ArgMatches) -> Result<Self, Self::Error> {
Ok(Self {
algorithm: matches.get_one::<String>("algorithm").unwrap().parse()?,
zstd_compression: if matches.get_flag("zstd") {
Some(matches.get_one::<i32>("level").copied().unwrap_or(3))
} else {
None
},
verbosity: Verbosity::from(matches),
stdout: matches.get_flag("stdout"),
uid: matches.get_one::<u32>("uid").copied(),
gid: matches.get_one::<u32>("gid").copied(),
})
}
}
fn main() {
let matches = cli::haggis().get_matches();
match matches.subcommand() {
@ -51,10 +123,7 @@ fn main() {
#[allow(clippy::similar_names)]
fn create(matches: &ArgMatches) -> Result<(), haggis::Error> {
let verbose = !matches.get_flag("stdout") || matches.get_flag("quiet");
let algorithm: Algorithm = matches.get_one::<String>("algorithm").unwrap().parse()?;
let uid = matches.get_one::<u32>("uid").copied();
let gid = matches.get_one::<u32>("gid").copied();
let flags = CreateFlags::try_from(matches)?;
let mut files = vec![];
if let Some(f) = matches.get_many::<String>("files") {
for f in f {
@ -79,24 +148,30 @@ fn create(matches: &ArgMatches) -> Result<(), haggis::Error> {
let (sender, receiver) = mpsc::channel();
let len = files.len();
let mut handle = None;
if verbose {
if flags.verbosity == Verbosity::Verbose {
let pb = ProgressBar::new(len as u64);
pb.set_style(ProgressStyle::with_template(TEMPLATE).unwrap());
pb.set_prefix("Adding files");
if let Some(o) = output {
pb.println(format!("Creating archive {o}"));
}
let m = matches.clone();
handle = Some(thread::spawn(move || {
for msg in &receiver {
match msg {
Message::NodeCreated(s) => {
pb.set_prefix(s.split('/').last().unwrap().to_string());
pb.inc(1);
}
Message::NodeSaved { name, size } => {
let name = name.split('/').last().unwrap();
pb.set_prefix(format!("{name} added, {size} bytes"));
Message::NodeSaved(n) => {
let name = n.name.split('/').last().unwrap();
if let ListingKind::Normal(size) = n.kind {
pb.set_prefix(format!("{name} added, {size} bytes"));
} else {
pb.set_prefix(format!("{name} added"));
}
pb.inc(1);
if m.get_flag("verbose") {
pb.suspend(|| {
let _res = print_listing(&n, &m);
});
}
}
Message::Eof => {
pb.finish_and_clear();
@ -109,34 +184,54 @@ fn create(matches: &ArgMatches) -> Result<(), haggis::Error> {
}
}));
}
if matches.get_flag("zstd") {
let level = matches.get_one::<i32>("level").copied().unwrap_or(3);
if matches.get_flag("stdout") {
if let Some(level) = flags.zstd_compression {
if flags.stdout {
let stdout = io::stdout();
let mut writer = Encoder::new(stdout, level)?;
haggis::par_stream_archive(&mut writer, &files, algorithm, &sender, uid, gid)?;
haggis::par_stream_archive(
&mut writer,
&files,
flags.algorithm,
&sender,
flags.uid,
flags.gid,
)?;
let _fd = writer.finish();
} else if let Some(o) = output {
let fd = File::create(o)?;
let mut writer = Encoder::new(fd, level)?;
haggis::par_stream_archive(&mut writer, &files, algorithm, &sender, uid, gid)?;
haggis::par_stream_archive(
&mut writer,
&files,
flags.algorithm,
&sender,
flags.uid,
flags.gid,
)?;
let _fd = writer.finish()?;
} else {
unreachable!();
}
} else if matches.get_flag("stdout") {
} else if flags.stdout {
let stdout = io::stdout();
let mut writer = BufWriter::new(stdout);
haggis::par_stream_archive(&mut writer, &files, algorithm, &sender, uid, gid)?;
haggis::par_stream_archive(
&mut writer,
&files,
flags.algorithm,
&sender,
flags.uid,
flags.gid,
)?;
} else if let Some(o) = output {
haggis::par_create_archive(o, &files, algorithm, &sender, uid, gid)?;
haggis::par_create_archive(o, &files, flags.algorithm, &sender, flags.uid, flags.gid)?;
} else {
unreachable!();
}
if let Some(handle) = handle {
match handle.join() {
Ok(()) => {
if verbose {
if flags.verbosity == Verbosity::Verbose {
println!("Archive created successfully");
}
Ok(())
@ -153,6 +248,7 @@ fn create(matches: &ArgMatches) -> Result<(), haggis::Error> {
#[allow(clippy::similar_names)]
fn extract(matches: &ArgMatches) -> Result<(), haggis::Error> {
let total = Arc::new(AtomicU64::new(0));
let file = matches.get_one::<String>("archive");
let uid = matches.get_one::<u32>("uid").copied();
let gid = matches.get_one::<u32>("gid").copied();
@ -174,12 +270,15 @@ fn extract(matches: &ArgMatches) -> Result<(), haggis::Error> {
let dir = matches.get_one::<String>("change");
let (sender, receiver) = mpsc::channel();
let file = file.cloned().unwrap_or("stdin".to_string());
let total_ref = total.clone();
let handle = if zst {
let reader = Decoder::new(fd)?;
let mut stream = NodeStream::new(reader)?;
let handle = if matches.get_flag("quiet") {
let matches = matches.clone();
Some(thread::spawn(move || {
progress(&file, &receiver, u64::from(stream.length));
let t = progress(&file, &receiver, u64::from(stream.length), &matches);
total_ref.store(t, Ordering::Relaxed);
Ok::<(), haggis::Error>(())
}))
} else {
@ -191,8 +290,10 @@ fn extract(matches: &ArgMatches) -> Result<(), haggis::Error> {
let reader = BufReader::new(fd);
let mut stream = NodeStream::new(reader)?;
let handle = if matches.get_flag("quiet") {
let matches = matches.clone();
Some(thread::spawn(move || {
progress(&file, &receiver, u64::from(stream.length));
let t = progress(&file, &receiver, u64::from(stream.length), &matches);
total_ref.store(t, Ordering::Relaxed);
Ok::<(), haggis::Error>(())
}))
} else {
@ -204,7 +305,12 @@ fn extract(matches: &ArgMatches) -> Result<(), haggis::Error> {
if let Some(handle) = handle {
match handle.join() {
Ok(_) => {
if matches.get_flag("quiet") {
if matches.get_flag("total") {
println!(
"{} extracted",
HumanSize::from(total.load(Ordering::Relaxed))
);
} else if matches.get_flag("quiet") {
println!("Archive extracted successfully");
}
Ok(())
@ -219,33 +325,53 @@ fn extract(matches: &ArgMatches) -> Result<(), haggis::Error> {
}
}
fn progress(file: &str, receiver: &mpsc::Receiver<StreamMessage>, len: u64) {
fn progress(
file: &str,
receiver: &mpsc::Receiver<StreamMessage>,
len: u64,
matches: &ArgMatches,
) -> u64 {
let mut total: u64 = 0;
let pb = ProgressBar::new(len);
pb.set_style(ProgressStyle::with_template(TEMPLATE).unwrap());
pb.set_prefix("Extracting files");
pb.println(format!("Extracting archive {file}"));
for msg in receiver {
match msg {
StreamMessage::FileExtracted { name, size } => {
let name = name.split('/').last().unwrap();
pb.set_prefix(format!("{name} extracted, {size} bytes"));
pb.inc(1);
}
StreamMessage::LinkCreated { name, target } => {
let name = name.split('/').last().unwrap();
let target = target.split('/').last().unwrap();
pb.set_prefix(format!("{name} -> {target}"));
pb.inc(1);
}
StreamMessage::DirectoryCreated { name } => {
let name = name.split('/').last().unwrap();
pb.set_prefix(format!("mkdir {name}"));
pb.inc(1);
}
StreamMessage::DeviceCreated { name } => {
let name = name.split('/').last().unwrap();
pb.set_prefix(format!("mknod {name}"));
pb.inc(1);
StreamMessage::NodeExtracted(n) => {
let name = n.name.split('/').last().unwrap();
match n.kind {
ListingKind::Normal(size) => {
pb.set_prefix(format!("{name} extracted, {size} bytes"));
pb.inc(1);
total += size;
}
ListingKind::SoftLink(ref t) | ListingKind::HardLink(ref t) => {
pb.set_prefix(format!("{name} -> {t}"));
pb.inc(1);
}
ListingKind::Directory => {
pb.set_prefix(format!("mkdir {name}"));
pb.inc(1);
}
ListingKind::Block(_) | ListingKind::Character(_) => {
pb.set_prefix(format!("mknod {name}"));
pb.inc(1);
}
ListingKind::Fifo => {
pb.set_prefix(format!("mkfifo {name}"));
pb.inc(1);
}
ListingKind::Eof => {
pb.finish_and_clear();
break;
}
}
if matches.get_flag("verbose") {
pb.suspend(|| {
let _res = print_listing(&n, matches);
});
}
}
StreamMessage::Eof => {
pb.finish_and_clear();
@ -256,20 +382,22 @@ fn progress(file: &str, receiver: &mpsc::Receiver<StreamMessage>, len: u64) {
}
}
}
total
}
fn print_listing(li: &Listing, matches: &ArgMatches) -> Result<(), haggis::Error> {
if matches.get_flag("files") && li.kind == ListingKind::Directory {
let flags = ListingFlags::from(matches);
if flags.files && li.kind == ListingKind::Directory {
return Ok(());
}
if matches.get_flag("color") {
if matches.get_flag("long") {
li.print_color()?;
if flags.color {
if flags.long {
li.print_color(flags.human)?;
} else {
li.print_color_simple()?;
}
} else if matches.get_flag("long") {
println!("{li}");
} else if flags.long {
li.print(flags.human);
} else {
println!("{}", li.name);
}
@ -277,15 +405,22 @@ fn print_listing(li: &Listing, matches: &ArgMatches) -> Result<(), haggis::Error
}
fn list_unsorted(matches: &ArgMatches) -> Result<(), haggis::Error> {
let mut total: u64 = 0;
let file = matches.get_one::<String>("archive").unwrap();
let fd = File::open(file)?;
if matches.get_flag("zstd") {
let mut fd = File::open(file)?;
let zst = matches.get_flag("zstd") || haggis::detect_zstd(&mut fd)?;
if zst {
let reader = Decoder::new(fd)?;
let stream = NodeStream::new(reader)?;
for node in stream {
let node = node?;
let li = Listing::from(node);
print_listing(&li, matches)?;
if matches.get_flag("total") {
if let ListingKind::Normal(s) = li.kind {
total += s;
}
}
}
} else {
let reader = BufReader::new(fd);
@ -293,12 +428,21 @@ fn list_unsorted(matches: &ArgMatches) -> Result<(), haggis::Error> {
for li in stream {
let li = li?;
print_listing(&li, matches)?;
if matches.get_flag("total") {
if let ListingKind::Normal(s) = li.kind {
total += s;
}
}
}
}
if matches.get_flag("total") {
println!("Total: {}", HumanSize::from(total));
}
Ok(())
}
fn list(matches: &ArgMatches) -> Result<(), haggis::Error> {
let mut total: u64 = 0;
let file = matches.get_one::<String>("archive").unwrap();
let mut fd = File::open(file)?;
let zst = matches.get_flag("zstd") || haggis::detect_zstd(&mut fd)?;
@ -320,6 +464,14 @@ fn list(matches: &ArgMatches) -> Result<(), haggis::Error> {
};
for li in list {
print_listing(&li, matches)?;
if matches.get_flag("total") {
if let ListingKind::Normal(s) = li.kind {
total += s;
}
}
}
if matches.get_flag("total") {
println!("Total: {}", HumanSize::from(total));
}
Ok(())
}

40
src/humansize.rs Normal file
View file

@ -0,0 +1,40 @@
use std::fmt;
const KILOS: f64 = 1024.0;
const MEGAS: f64 = KILOS * 1024.0;
const GIGAS: f64 = MEGAS * 1024.0;
const TERAS: f64 = GIGAS * 1024.0;
#[derive(Clone, Copy, Debug)]
pub enum HumanSize {
Bytes(u64),
KiloBytes(f64),
MegaBytes(f64),
GigaBytes(f64),
TeraBytes(f64),
}
impl fmt::Display for HumanSize {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Bytes(b) => write!(f, "{b:>7}"),
Self::KiloBytes(k) => write!(f, "{k:>6.1}K"),
Self::MegaBytes(m) => write!(f, "{m:>6.1}M"),
Self::GigaBytes(g) => write!(f, "{g:>6.1}G"),
Self::TeraBytes(t) => write!(f, "{t:>6.1}T"),
}
}
}
#[allow(clippy::cast_precision_loss)]
impl From<u64> for HumanSize {
fn from(value: u64) -> Self {
match value {
n if n as f64 > TERAS => Self::GigaBytes(n as f64 / TERAS),
n if n as f64 > GIGAS => Self::GigaBytes(n as f64 / GIGAS),
n if n as f64 > MEGAS => Self::MegaBytes(n as f64 / MEGAS),
n if n as f64 > KILOS => Self::KiloBytes(n as f64 / KILOS),
_ => Self::Bytes(value),
}
}
}

View file

@ -16,6 +16,7 @@ mod checksum;
mod error;
mod file;
mod filetype;
mod humansize;
mod listing;
mod listing_stream;
pub(crate) mod nix;
@ -28,6 +29,7 @@ pub use {
error::Error,
file::File,
filetype::FileType,
humansize::HumanSize,
listing::Kind as ListingKind,
listing::Listing,
listing_stream::ListingStream,
@ -64,6 +66,130 @@ pub(crate) fn load_string<R: Read>(reader: &mut R) -> Result<String, Error> {
Ok(String::from_utf8(buf)?)
}
pub struct Creator<W: Write> {
pub writer: W,
pub algorithm: Algorithm,
pub uid: Option<u32>,
pub gid: Option<u32>,
}
impl Creator<BufWriter<std::fs::File>> {
pub fn new_for_path(
path: &str,
algorithm: Algorithm,
uid: Option<u32>,
gid: Option<u32>,
) -> Result<Self, Error> {
let fd = fs::OpenOptions::new()
.create(true)
.truncate(true)
.open(path)?;
let writer = BufWriter::new(fd);
Ok(Self {
writer,
algorithm,
uid,
gid,
})
}
}
impl Creator<BufWriter<std::io::StdoutLock<'_>>> {
#[allow(clippy::must_use_candidate)]
pub fn new_for_stdout(algorithm: Algorithm, uid: Option<u32>, gid: Option<u32>) -> Self {
let stdout = io::stdout().lock();
let writer = BufWriter::new(stdout);
Self {
writer,
algorithm,
uid,
gid,
}
}
}
impl<W: Write> Creator<W> {
/// Creates and streams a haggis archive over something which implements `Write`
/// # Errors
/// Returns `crate::Error` if io fails or several other error conditions
pub fn stream_archive(&mut self, files: &[String]) -> Result<(), Error> {
self.writer.write_all(&MAGIC)?;
let len = u32::try_from(files.len())?;
self.writer.write_all(&len.to_le_bytes())?;
let links = Mutex::new(HashMap::new());
for f in files {
let mut node = Node::from_path(f, self.algorithm, &links)?;
if let Some(n) = self.uid {
node.uid = n;
}
if let Some(n) = self.gid {
node.gid = n;
}
node.write(&mut self.writer)?;
}
self.writer.write_all(&[0; 8])?;
Ok(())
}
}
#[cfg(feature = "parallel")]
impl<W: Write + Send> Creator<W> {
/// Creates and streams a Haggis archive from a list of files, processing each
/// file in parallel
/// # Errors
/// Returns `crate::Error` if io fails or several other error conditions
pub fn par_stream_archive(
mut self,
files: &[String],
sender: &Sender<Message>,
) -> Result<(), Error> {
self.writer.write_all(&MAGIC)?;
let len = u32::try_from(files.len())?;
self.writer.write_all(&len.to_le_bytes())?;
let links = Mutex::new(HashMap::<u64, String>::new());
let writer = Mutex::new(self.writer);
let s = sender.clone();
files.par_iter().try_for_each_with(s, |s, f| {
let mut node = match Node::from_path(f, self.algorithm, &links) {
Ok(n) => n,
Err(error) => {
s.send(Message::Err {
name: f.to_string(),
error,
})
.map_err(|_| Error::SenderError)?;
return Ok(());
}
};
if let Some(n) = self.uid {
node.uid = n;
}
if let Some(n) = self.gid {
node.gid = n;
}
if let Ok(mut writer) = writer.lock() {
let mut writer = &mut *writer;
if let Err(error) = node.write(&mut writer) {
s.send(Message::Err {
name: node.name.clone(),
error,
})
.map_err(|_| Error::SenderError)?;
}
s.send(Message::NodeSaved(node.into()))
.map_err(|_| Error::SenderError)?;
Ok(())
} else {
Err(Error::MutexError)
}
})?;
let mut writer = writer.into_inner().map_err(|_| Error::MutexError)?;
writer.write_all(&[0; 8])?;
sender.send(Message::Eof).map_err(|_| Error::SenderError)?;
Ok(())
}
}
#[allow(clippy::similar_names)]
/// Creates a haggis archive from a list of files
/// # Errors
@ -135,18 +261,8 @@ pub fn stream_archive<W: Write>(
#[cfg(feature = "parallel")]
#[derive(Debug)]
pub enum Message {
/// A `Node` has successfully been created from this path
NodeCreated(
/// The path of the created `Node`
String,
),
/// The `Node` has successfully been written into the writer
NodeSaved {
/// The path of the saved `Node`
name: String,
/// The size in bytes of the created `Node`
size: u64,
},
NodeSaved(Listing),
/// An error occurred creating or writing out the node
Err {
/// The pathname of the node
@ -239,17 +355,8 @@ pub fn par_stream_archive<W: Write + Send>(
})
.map_err(|_| Error::SenderError)?;
}
match node.filetype {
FileType::Normal(n) => s
.send(Message::NodeSaved {
name: node.name.clone(),
size: n.len,
})
.map_err(|_| Error::SenderError)?,
_ => s
.send(Message::NodeCreated(node.name.clone()))
.map_err(|_| Error::SenderError)?,
}
s.send(Message::NodeSaved(node.into()))
.map_err(|_| Error::SenderError)?;
Ok(())
} else {
Err(Error::MutexError)

View file

@ -5,11 +5,10 @@ use {
};
use {
crate::{filetype::Flag, Error, FileType, Node, Special},
chrono::NaiveDateTime,
crate::{filetype::Flag, Error, FileType, HumanSize, Node, Special},
chrono::DateTime,
std::{
cmp::Ordering,
fmt,
io::{Read, Seek, SeekFrom},
},
};
@ -172,84 +171,6 @@ impl Ord for Listing {
}
}
impl fmt::Display for Listing {
#[allow(clippy::cast_possible_wrap)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}{}{}{}{}{}{}{}{}{} {:>4}:{:<4} ",
match self.kind {
Kind::Normal(_) => "-",
Kind::HardLink(_) => "L",
Kind::SoftLink(_) => "l",
Kind::Directory => "d",
Kind::Character(_) => "c",
Kind::Block(_) => "b",
Kind::Fifo => "p",
Kind::Eof => return Ok(()),
},
match self.mode {
m if m & 0o400 != 0 => "r",
_ => "-",
},
match self.mode {
m if m & 0o200 != 0 => "w",
_ => "-",
},
match self.mode {
m if m & 0o4000 != 0 => "S",
m if m & 0o100 != 0 => "x",
_ => "-",
},
match self.mode {
m if m & 0o40 != 0 => "r",
_ => "-",
},
match self.mode {
m if m & 0o20 != 0 => "w",
_ => "-",
},
match self.mode {
m if m & 0o2000 != 0 => "S",
m if m & 0o10 != 0 => "x",
_ => "-",
},
match self.mode {
m if m & 0o4 != 0 => "r",
_ => "-",
},
match self.mode {
m if m & 0o2 != 0 => "w",
_ => "-",
},
match self.mode {
m if m & 0o1000 != 0 => "t",
m if m & 0o1 != 0 => "x",
_ => "-",
},
self.uid,
self.gid,
)?;
match self.kind {
Kind::Normal(s) => write!(f, "{s:>10} "),
_ => write!(f, "{:>10}", "-"),
}?;
match NaiveDateTime::from_timestamp_opt(self.mtime as i64, 0) {
Some(dt) => write!(f, "{dt} ")?,
_ => write!(f, "{:>19} ", self.mtime)?,
}
match self.kind {
Kind::Directory | Kind::Fifo | Kind::Normal(_) => write!(f, "{}", self.name),
Kind::HardLink(ref tgt) => write!(f, "{}=>{}", self.name, tgt),
Kind::SoftLink(ref tgt) => write!(f, "{}->{}", self.name, tgt),
Kind::Character(ref sp) | Kind::Block(ref sp) => {
write!(f, "{} {},{}", self.name, sp.major, sp.minor)
}
Kind::Eof => unreachable!(),
}
}
}
impl Listing {
/// Reads all metadata from a haggis Node without reading the file's actual
/// data.
@ -292,6 +213,92 @@ impl Listing {
})
}
pub fn print(&self, human: bool) {
print!(
"{}{}{}{}{}{}{}{}{}{} {:>4}:{:<4} ",
match self.kind {
Kind::Normal(_) => "-",
Kind::HardLink(_) => "L",
Kind::SoftLink(_) => "l",
Kind::Directory => "d",
Kind::Character(_) => "c",
Kind::Block(_) => "b",
Kind::Fifo => "p",
Kind::Eof => return,
},
match self.mode {
m if m & 0o400 != 0 => "r",
_ => "-",
},
match self.mode {
m if m & 0o200 != 0 => "w",
_ => "-",
},
match self.mode {
m if m & 0o4000 != 0 => "S",
m if m & 0o100 != 0 => "x",
_ => "-",
},
match self.mode {
m if m & 0o40 != 0 => "r",
_ => "-",
},
match self.mode {
m if m & 0o20 != 0 => "w",
_ => "-",
},
match self.mode {
m if m & 0o2000 != 0 => "S",
m if m & 0o10 != 0 => "x",
_ => "-",
},
match self.mode {
m if m & 0o4 != 0 => "r",
_ => "-",
},
match self.mode {
m if m & 0o2 != 0 => "w",
_ => "-",
},
match self.mode {
m if m & 0o1000 != 0 => "t",
m if m & 0o1 != 0 => "x",
_ => "-",
},
self.uid,
self.gid,
);
match self.kind {
Kind::Normal(s) => {
if human {
print!("{} ", HumanSize::from(s));
} else {
print!("{s:>10} ");
}
}
_ => {
if human {
print!("{:>7} ", "-");
} else {
print!("{:>10} ", "-");
}
}
};
match DateTime::from_timestamp(self.mtime as i64, 0) {
Some(dt) => print!("{dt} "),
_ => print!("{:>19} ", self.mtime),
}
match self.kind {
Kind::Directory | Kind::Fifo | Kind::Normal(_) => println!("{}", self.name),
Kind::HardLink(ref tgt) => println!("{}=>{}", self.name, tgt),
Kind::SoftLink(ref tgt) => println!("{}->{}", self.name, tgt),
Kind::Character(ref sp) | Kind::Block(ref sp) => {
println!("{} {},{}", self.name, sp.major, sp.minor);
}
Kind::Eof => unreachable!(),
}
}
/// Prints a line with relavent metadata included,
/// colorizing the filename based on the filetype.
/// Similar to GNU `ls --long --color auto`
@ -299,7 +306,7 @@ impl Listing {
/// Can return an `Error` if stdout is unavailable
#[allow(clippy::cast_possible_wrap)]
#[cfg(feature = "color")]
pub fn print_color(&self) -> Result<(), Error> {
pub fn print_color(&self, human: bool) -> Result<(), Error> {
print!(
"{}{}{}{}{}{}{}{}{}{} {:>4}:{:<4} ",
match self.kind {
@ -355,10 +362,22 @@ impl Listing {
self.gid,
);
match self.kind {
Kind::Normal(s) => print!("{s:>10} "),
_ => print!("{:>10} ", "-"),
Kind::Normal(s) => {
if human {
print!("{:>10} ", HumanSize::from(s));
} else {
print!("{s:>10} ");
}
}
_ => {
if human {
print!("{:>7} ", "-");
} else {
print!("{:>10} ", "-");
}
}
}
match NaiveDateTime::from_timestamp_opt(self.mtime as i64, 0) {
match DateTime::from_timestamp(self.mtime as i64, 0) {
Some(dt) => print!("{dt} "),
_ => print!("{:>19} ", self.mtime),
}

View file

@ -1,11 +1,5 @@
#![allow(clippy::similar_names)]
#[cfg(feature = "parallel")]
use {
crate::FileType,
rayon::{iter::ParallelBridge, prelude::ParallelIterator},
std::sync::mpsc::Sender,
};
use {
crate::{Error, Node, MAGIC},
std::{
@ -15,6 +9,12 @@ use {
os::fd::{AsRawFd, FromRawFd},
},
};
#[cfg(feature = "parallel")]
use {
crate::{FileType, Listing},
rayon::{iter::ParallelBridge, prelude::ParallelIterator},
std::sync::mpsc::Sender,
};
/// An iterator over a series of archive `Node`'s. This struct is generic over any
/// type which implements `Read`, such as a file or a network stream.
@ -44,30 +44,8 @@ impl<R: Read> Iterator for Stream<R> {
#[cfg(feature = "parallel")]
#[derive(Debug)]
pub enum Message {
/// A File node has been successfully extracted
FileExtracted {
/// The path of the extracted `Node`
name: String,
/// The size in bytes of the created file
size: u64,
},
/// A link has been created from a `Node`
LinkCreated {
/// The path of the link
name: String,
/// The path to this link's target
target: String,
},
/// A directory has been created from this `Node`
DirectoryCreated {
/// The directory's path
name: String,
},
/// A device file has been created from this `Node`
DeviceCreated {
/// The path of the device file
name: String,
},
/// A Node has been successfully extracted
NodeExtracted(Listing),
/// An error occurred while extracting this `Node`
Err {
/// The pathame of the `Node` being extracted
@ -135,12 +113,12 @@ impl<R: Read> Stream<R> {
f: F,
) -> Result<(), Error>
where
F: FnOnce(Node, Option<u32>, Option<u32>) + Copy,
F: FnOnce(&Node, Option<u32>, Option<u32>) + Copy,
{
for node in self {
let node = node?;
node.extract(prefix, uid, gid)?;
f(node, uid, gid);
f(&node, uid, gid);
}
Ok(())
}
@ -148,7 +126,7 @@ impl<R: Read> Stream<R> {
#[cfg(feature = "parallel")]
impl<R: Read + Send> Stream<R> {
/// Extracts and archive in parallel
/// Extracts an archive in parallel
/// # Errors
/// Returns `crate::Error` if io fails or several other error conditions
pub fn par_extract(
@ -170,35 +148,13 @@ impl<R: Read + Send> Stream<R> {
return Err(error);
}
match n.filetype {
FileType::Normal(f) => {
s.send(Message::FileExtracted {
name: n.name.clone(),
size: f.len,
})
.map_err(|_| Error::SenderError)?;
}
FileType::SoftLink(t) | FileType::HardLink(t) => {
s.send(Message::LinkCreated {
name: n.name.clone(),
target: t.clone(),
})
.map_err(|_| Error::SenderError)?;
}
FileType::Directory => {
s.send(Message::DirectoryCreated {
name: n.name.clone(),
})
.map_err(|_| Error::SenderError)?;
}
FileType::Block(_) | FileType::Character(_) | FileType::Fifo => {
s.send(Message::DeviceCreated {
name: n.name.clone(),
})
.map_err(|_| Error::SenderError)?;
}
FileType::Eof => {
s.send(Message::Eof).map_err(|_| Error::SenderError)?;
}
_ => {
s.send(Message::NodeExtracted(n.into()))
.map_err(|_| Error::SenderError)?;
}
}
Ok::<(), Error>(())
})?;
@ -206,7 +162,7 @@ impl<R: Read + Send> Stream<R> {
Ok(())
}
/// Extracts and archive in parallel and runs the passed in function for
/// Extracts an archive in parallel and runs the passed in function for
/// each `Node`
/// # Errors
/// Returns `crate::Error` if io fails or several other error conditions
@ -219,44 +175,26 @@ impl<R: Read + Send> Stream<R> {
f: F,
) -> Result<(), Error>
where
F: FnOnce(Node, Option<u32>, Option<u32>) + Copy + Send + Sync,
F: FnOnce(&Node, Option<u32>, Option<u32>) + Copy + Send + Sync,
{
let s = sender.clone();
self.into_iter().par_bridge().try_for_each_with(s, |s, n| {
let n = n?;
n.extract(prefix, uid, gid)?;
match n.filetype {
FileType::Normal(ref f) => {
s.send(Message::FileExtracted {
name: n.name.clone(),
size: f.len,
})
.map_err(|_| Error::SenderError)?;
}
FileType::SoftLink(ref t) | FileType::HardLink(ref t) => {
s.send(Message::LinkCreated {
name: n.name.clone(),
target: t.clone(),
})
.map_err(|_| Error::SenderError)?;
}
FileType::Directory => {
s.send(Message::DirectoryCreated {
name: n.name.clone(),
})
.map_err(|_| Error::SenderError)?;
}
FileType::Block(_) | FileType::Character(_) | FileType::Fifo => {
s.send(Message::DeviceCreated {
name: n.name.clone(),
})
.map_err(|_| Error::SenderError)?;
}
FileType::Eof => {
s.send(Message::Eof).map_err(|_| Error::SenderError)?;
}
if let Err(error) = n.extract(prefix, uid, gid) {
s.send(Message::Err {
name: n.name.clone(),
error: error.clone(),
})
.map_err(|_| Error::SenderError)?;
return Err(error);
}
if let FileType::Eof = n.filetype {
s.send(Message::Eof).map_err(|_| Error::SenderError)?;
} else {
f(&n, uid, gid);
s.send(Message::NodeExtracted(n.into()))
.map_err(|_| Error::SenderError)?;
}
f(n, uid, gid);
Ok::<(), Error>(())
})?;
sender.send(Message::Eof).map_err(|_| Error::SenderError)?;