initial commit
This commit is contained in:
commit
540bb97629
12 changed files with 1601 additions and 0 deletions
20
.gitignore
vendored
Normal file
20
.gitignore
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
# Created by https://www.toptal.com/developers/gitignore/api/rust
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=rust
|
||||
|
||||
### Rust ###
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
|
||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||
# Cargo.lock
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/rust
|
748
Cargo.lock
generated
Normal file
748
Cargo.lock
generated
Normal file
|
@ -0,0 +1,748 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atomic"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d818003e740b63afc82337e3160717f4f63078720a810b7b903e70a5d1d2994"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "5.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
|
||||
dependencies = [
|
||||
"dirs-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||
|
||||
[[package]]
|
||||
name = "figment"
|
||||
version = "0.10.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8cb01cd46b0cf372153850f4c6c272d9cbea2da513e07538405148f95bd789f3"
|
||||
dependencies = [
|
||||
"atomic",
|
||||
"pear",
|
||||
"serde",
|
||||
"toml",
|
||||
"uncased",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inlinable_string"
|
||||
version = "0.1.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb"
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.168"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d"
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
||||
|
||||
[[package]]
|
||||
name = "option-ext"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pear"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bdeeaa00ce488657faba8ebf44ab9361f9365a97bd39ffb8a60663f57ff4b467"
|
||||
dependencies = [
|
||||
"inlinable_string",
|
||||
"pear_codegen",
|
||||
"yansi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pear_codegen"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4bab5b985dc082b345f812b7df84e1bef27e7207b39e448439ba8bd69c93f147"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"proc-macro2-diagnostics",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2-diagnostics"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"version_check",
|
||||
"yansi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"libredox",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.216"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.216"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shadow-rs"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"dirs",
|
||||
"figment",
|
||||
"serde",
|
||||
"temp-env",
|
||||
"tempfile",
|
||||
"thiserror 2.0.6",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
||||
|
||||
[[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.90"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "temp-env"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96374855068f47402c3121c6eed88d29cb1de8f3ab27090e273e420bdabcf050"
|
||||
dependencies = [
|
||||
"parking_lot",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||
dependencies = [
|
||||
"thiserror-impl 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fec2a1820ebd077e2b90c4df007bebf344cd394098a13c563957d0afc83ea47"
|
||||
dependencies = [
|
||||
"thiserror-impl 2.0.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uncased"
|
||||
version = "0.9.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697"
|
||||
dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
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.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.52.6",
|
||||
"windows_aarch64_msvc 0.52.6",
|
||||
"windows_i686_gnu 0.52.6",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc 0.52.6",
|
||||
"windows_x86_64_gnu 0.52.6",
|
||||
"windows_x86_64_gnullvm 0.52.6",
|
||||
"windows_x86_64_msvc 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.6.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yansi"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
|
20
Cargo.toml
Normal file
20
Cargo.toml
Normal file
|
@ -0,0 +1,20 @@
|
|||
[package]
|
||||
name = "shadow-rs"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.5", features = ["derive", "env"] }
|
||||
dirs = "5.0"
|
||||
figment = { version = "0.10", features = ["env", "toml"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
thiserror = "2.0"
|
||||
toml = "0.8"
|
||||
|
||||
[dev-dependencies]
|
||||
temp-env = "0.3"
|
||||
tempfile = "3.14"
|
||||
|
||||
[[bin]]
|
||||
name = "shadow"
|
||||
path = "src/main.rs"
|
102
Justfile
Normal file
102
Justfile
Normal file
|
@ -0,0 +1,102 @@
|
|||
set dotenv-load := true
|
||||
set unstable := true
|
||||
|
||||
# List all available commands
|
||||
[private]
|
||||
default:
|
||||
@just --list
|
||||
|
||||
[private]
|
||||
test-cleanup:
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
rm -rf test_bin
|
||||
echo "Test cleanup complete"
|
||||
|
||||
[private]
|
||||
test-setup:
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
cargo build
|
||||
|
||||
TEST_DIR="test_bin"
|
||||
mkdir -p $TEST_DIR
|
||||
cd $TEST_DIR
|
||||
|
||||
ln -sf ../target/debug/shadow .
|
||||
ln -sf shadow ls
|
||||
ln -sf shadow tree
|
||||
ln -sf shadow cat
|
||||
|
||||
mkdir -p other_bin
|
||||
|
||||
cd ..
|
||||
echo "Test setup complete"
|
||||
|
||||
clean:
|
||||
rm -rf target/
|
||||
|
||||
lint:
|
||||
@just --fmt
|
||||
cargo fmt
|
||||
cargo clippy
|
||||
|
||||
|
||||
test:
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
just test-setup
|
||||
|
||||
cd test_bin
|
||||
export PATH="$PWD:$PATH"
|
||||
|
||||
echo -e "\nTesting shadow add commands:"
|
||||
./shadow add ls eza
|
||||
./shadow add tree "eza --tree"
|
||||
./shadow add cat bat --bin-path ./other_bin
|
||||
|
||||
echo -e "\nTesting shadow list command:"
|
||||
./shadow list
|
||||
|
||||
echo -e "\nTesting shadowed commands:"
|
||||
./ls --version
|
||||
./tree --version
|
||||
./cat --version
|
||||
|
||||
echo -e "\nTesting shadow remove command:"
|
||||
./shadow remove ls
|
||||
echo "After removing ls:"
|
||||
./shadow list
|
||||
|
||||
echo -e "\nTesting remove with specific bin path:"
|
||||
./shadow remove cat --bin-path ./other_bin
|
||||
echo "After removing cat:"
|
||||
./shadow list
|
||||
|
||||
echo -e "\nTesting command with --raw flag:"
|
||||
if ! ./tree --raw --version; then
|
||||
echo "Raw command should execute original binary"
|
||||
fi
|
||||
|
||||
echo -e "\nTesting non-existent shadow:"
|
||||
if ./shadow remove nonexistent; then
|
||||
echo "Error: remove should fail for non-existent shadow"
|
||||
exit 1
|
||||
else
|
||||
echo "Successfully detected non-existent shadow"
|
||||
fi
|
||||
|
||||
echo -e "\nTesting invalid commands:"
|
||||
if ./shadow add; then
|
||||
echo "Error: add should fail without arguments"
|
||||
exit 1
|
||||
else
|
||||
echo "Successfully detected invalid add command"
|
||||
fi
|
||||
|
||||
cd ..
|
||||
just test-cleanup
|
||||
|
10
LICENSE
Normal file
10
LICENSE
Normal file
|
@ -0,0 +1,10 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2024 Josh Thomas
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
3
README.md
Normal file
3
README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# shadow
|
||||
|
||||
A CLI tool to manage your shell aliases.
|
170
src/cli.rs
Normal file
170
src/cli.rs
Normal file
|
@ -0,0 +1,170 @@
|
|||
use crate::commands::Commands;
|
||||
use crate::config::Config;
|
||||
use crate::error::ExitCode;
|
||||
use clap::Parser;
|
||||
use std::env;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
pub struct Cli {
|
||||
#[command(subcommand)]
|
||||
command: Option<Commands>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ShadowedArgs {
|
||||
args: Vec<String>,
|
||||
is_raw: bool,
|
||||
}
|
||||
|
||||
impl ShadowedArgs {
|
||||
pub fn from_env() -> Self {
|
||||
let args: Vec<String> = env::args().skip(1).collect();
|
||||
let is_raw = args.contains(&"--raw".to_string()) || args.contains(&"-R".to_string());
|
||||
let args = args
|
||||
.into_iter()
|
||||
.filter(|arg| arg != "--raw" && arg != "-R")
|
||||
.collect();
|
||||
|
||||
Self { args, is_raw }
|
||||
}
|
||||
}
|
||||
|
||||
impl Cli {
|
||||
pub fn execute(config: Config) -> ExitCode {
|
||||
let cli = Self::parse();
|
||||
match &cli.command {
|
||||
Some(cmd) => cmd.execute(config),
|
||||
None => {
|
||||
println!("Use --help for usage information");
|
||||
ExitCode::InvalidArguments
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn execute_shadowed(config: Config, command: &str) -> ExitCode {
|
||||
let args = ShadowedArgs::from_env();
|
||||
match config
|
||||
.shadows()
|
||||
.find(command)
|
||||
.map(|shadow| shadow.execute(&args.args, args.is_raw))
|
||||
{
|
||||
Ok(code) => code,
|
||||
Err(e) => {
|
||||
eprintln!("{}", e);
|
||||
e.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use tempfile::TempDir;
|
||||
|
||||
impl ShadowedArgs {
|
||||
pub fn new(args: Vec<String>) -> Self {
|
||||
let is_raw = args.contains(&"--raw".to_string()) || args.contains(&"-R".to_string());
|
||||
let args = args
|
||||
.into_iter()
|
||||
.filter(|arg| arg != "--raw" && arg != "-R")
|
||||
.collect();
|
||||
|
||||
Self { args, is_raw }
|
||||
}
|
||||
}
|
||||
|
||||
impl Cli {
|
||||
pub fn execute_shadowed_with_args(
|
||||
config: Config,
|
||||
_temp_dir: TempDir, // Add this to keep it alive
|
||||
command: &str,
|
||||
args: ShadowedArgs,
|
||||
) -> ExitCode {
|
||||
match config
|
||||
.shadows()
|
||||
.find(command)
|
||||
.map(|shadow| shadow.execute(&args.args, args.is_raw))
|
||||
{
|
||||
Ok(code) => code,
|
||||
Err(e) => {
|
||||
eprintln!("{}", e);
|
||||
e.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn execute_with_args(config: Config, _temp_dir: TempDir, args: Vec<&str>) -> ExitCode {
|
||||
let cli = Self::try_parse_from(args).unwrap();
|
||||
match &cli.command {
|
||||
Some(cmd) => cmd.execute(config),
|
||||
None => {
|
||||
println!("Use --help for usage information");
|
||||
ExitCode::InvalidArguments
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod shadowed_args {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_normal_args() {
|
||||
let args = ShadowedArgs::new(vec!["arg1".to_string(), "arg2".to_string()]);
|
||||
assert_eq!(args.args, vec!["arg1", "arg2"]);
|
||||
assert!(!args.is_raw);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_raw_long_flag() {
|
||||
let args = ShadowedArgs::new(vec!["--raw".to_string(), "arg1".to_string()]);
|
||||
assert_eq!(args.args, vec!["arg1"]);
|
||||
assert!(args.is_raw);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_raw_short_flag() {
|
||||
let args = ShadowedArgs::new(vec!["-R".to_string(), "arg1".to_string()]);
|
||||
assert_eq!(args.args, vec!["arg1"]);
|
||||
assert!(args.is_raw);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_raw_flag_middle() {
|
||||
let args = ShadowedArgs::new(vec![
|
||||
"arg1".to_string(),
|
||||
"--raw".to_string(),
|
||||
"arg2".to_string(),
|
||||
]);
|
||||
assert_eq!(args.args, vec!["arg1", "arg2"]);
|
||||
assert!(args.is_raw);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiple_raw_flags() {
|
||||
let args = ShadowedArgs::new(vec![
|
||||
"--raw".to_string(),
|
||||
"-R".to_string(),
|
||||
"arg1".to_string(),
|
||||
]);
|
||||
assert_eq!(args.args, vec!["arg1"]);
|
||||
assert!(args.is_raw);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_args() {
|
||||
let args = ShadowedArgs::new(vec![]);
|
||||
assert!(args.args.is_empty());
|
||||
assert!(!args.is_raw);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_only_raw_flag() {
|
||||
let args = ShadowedArgs::new(vec!["--raw".to_string()]);
|
||||
assert!(args.args.is_empty());
|
||||
assert!(args.is_raw);
|
||||
}
|
||||
}
|
||||
}
|
129
src/commands.rs
Normal file
129
src/commands.rs
Normal file
|
@ -0,0 +1,129 @@
|
|||
use crate::config::Config;
|
||||
use crate::error::ExitCode;
|
||||
use crate::shadows::Shadow;
|
||||
use clap::Parser;
|
||||
use clap::Subcommand;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
pub enum Commands {
|
||||
/// Add a new shadow
|
||||
Add(Add),
|
||||
/// Remove a shadow
|
||||
Remove(Remove),
|
||||
/// List all shadows
|
||||
List(List),
|
||||
}
|
||||
|
||||
impl Commands {
|
||||
pub fn execute(&self, config: Config) -> ExitCode {
|
||||
match self {
|
||||
Commands::Add(cmd) => cmd.execute(config),
|
||||
Commands::Remove(cmd) => cmd.execute(config),
|
||||
Commands::List(cmd) => cmd.execute(config),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
pub struct Add {
|
||||
/// Original command to shadow
|
||||
original: String,
|
||||
/// Replacement command
|
||||
replacement: String,
|
||||
/// Directory to create symlink in
|
||||
#[arg(long)]
|
||||
bin_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl Add {
|
||||
pub fn execute(&self, mut config: Config) -> ExitCode {
|
||||
if config.shadows().contains(&self.original) {
|
||||
eprintln!("Command already shadowed: {}", self.original);
|
||||
return ExitCode::DuplicateCommand;
|
||||
}
|
||||
|
||||
if Command::new(&self.original).output().is_err() {
|
||||
eprintln!("Command not found: {}", self.original);
|
||||
return ExitCode::CommandFailed;
|
||||
}
|
||||
|
||||
let bin_path = match &self.bin_path {
|
||||
Some(p) if p == config.settings().bin_path() => None,
|
||||
Some(p) => Some(p.clone()),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let shadow = Shadow::new(self.original.clone(), self.replacement.clone(), bin_path);
|
||||
|
||||
if let Err(e) = shadow.create_symlink(config.settings()) {
|
||||
eprintln!("{}", e);
|
||||
return e.into();
|
||||
}
|
||||
|
||||
match config.add(shadow) {
|
||||
Ok(()) => {
|
||||
println!("Added shadow: {}", self.original);
|
||||
ExitCode::Success
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("{}", e);
|
||||
e.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
pub struct Remove {
|
||||
/// Command to un-shadow
|
||||
original: String,
|
||||
/// Directory containing the symlink
|
||||
#[arg(long)]
|
||||
bin_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl Remove {
|
||||
pub fn execute(&self, mut config: Config) -> ExitCode {
|
||||
let shadow = match config.shadows().find(&self.original) {
|
||||
Ok(shadow) => shadow,
|
||||
Err(e) => {
|
||||
eprintln!("{}", e);
|
||||
return e.into();
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(e) = shadow.remove_symlink(config.settings()) {
|
||||
eprintln!("{}", e);
|
||||
return e.into();
|
||||
}
|
||||
|
||||
match config.remove(&self.original) {
|
||||
Ok(()) => {
|
||||
println!("Removed shadow: {}", self.original);
|
||||
ExitCode::Success
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("{}", e);
|
||||
e.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
pub struct List;
|
||||
|
||||
impl List {
|
||||
pub fn execute(&self, config: Config) -> ExitCode {
|
||||
match config.shadows().is_empty() {
|
||||
true => println!("No shadows configured"),
|
||||
false => config
|
||||
.shadows()
|
||||
.iter()
|
||||
.for_each(|shadow| println!("{}", shadow)),
|
||||
}
|
||||
ExitCode::Success
|
||||
}
|
||||
}
|
110
src/config.rs
Normal file
110
src/config.rs
Normal file
|
@ -0,0 +1,110 @@
|
|||
use crate::error::{Result, ShadowError};
|
||||
use crate::shadows::{Shadow, Shadows};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Config {
|
||||
#[serde(default)]
|
||||
settings: Settings,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Shadows::is_empty")]
|
||||
shadows: Shadows,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn new() -> Result<Self> {
|
||||
let config = Config {
|
||||
settings: Settings::default(),
|
||||
shadows: Shadows::default(),
|
||||
};
|
||||
config.save()?;
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
pub fn load() -> Result<Self> {
|
||||
if Self::config_path().exists() {
|
||||
let contents = std::fs::read_to_string(Self::config_path())?;
|
||||
toml::from_str(&contents).map_err(|e| ShadowError::ConfigError(e.to_string()))
|
||||
} else {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save(&self) -> Result<()> {
|
||||
if let Some(parent) = Self::config_path().parent() {
|
||||
std::fs::create_dir_all(parent)?;
|
||||
}
|
||||
|
||||
let contents =
|
||||
toml::to_string_pretty(self).map_err(|e| ShadowError::ConfigError(e.to_string()))?;
|
||||
|
||||
std::fs::write(Self::config_path(), contents)
|
||||
.map_err(|e| ShadowError::ConfigError(e.to_string()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn settings(&self) -> &Settings {
|
||||
&self.settings
|
||||
}
|
||||
|
||||
pub fn shadows(&self) -> &Shadows {
|
||||
&self.shadows
|
||||
}
|
||||
|
||||
pub fn add(&mut self, shadow: Shadow) -> Result<()> {
|
||||
self.shadows.push(shadow);
|
||||
self.save()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, original: &str) -> Result<()> {
|
||||
let position = self
|
||||
.shadows
|
||||
.iter()
|
||||
.position(|shadow| shadow.original() == original)
|
||||
.ok_or_else(|| ShadowError::ShadowNotFound(original.to_string()))?;
|
||||
|
||||
self.shadows.remove(position);
|
||||
self.save()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn config_path() -> PathBuf {
|
||||
dirs::config_dir()
|
||||
.expect("Could not find config directory")
|
||||
.join("shadow/config.toml")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
||||
pub struct Settings {
|
||||
#[serde(default = "Settings::default_bin_path")]
|
||||
bin_path: PathBuf,
|
||||
#[serde(default)]
|
||||
always_use_raw: bool,
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
pub fn new(bin_path: PathBuf, always_use_raw: bool) -> Self {
|
||||
Self {
|
||||
bin_path,
|
||||
always_use_raw,
|
||||
}
|
||||
}
|
||||
|
||||
fn default() -> Self {
|
||||
Self::new(Self::default_bin_path(), false)
|
||||
}
|
||||
|
||||
pub fn bin_path(&self) -> &PathBuf {
|
||||
&self.bin_path
|
||||
}
|
||||
|
||||
fn default_bin_path() -> PathBuf {
|
||||
dirs::executable_dir()
|
||||
.or_else(|| dirs::home_dir().map(|h| h.join(".local/bin")))
|
||||
.expect("Could not determine binary directory")
|
||||
}
|
||||
}
|
46
src/error.rs
Normal file
46
src/error.rs
Normal file
|
@ -0,0 +1,46 @@
|
|||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum ExitCode {
|
||||
Success = 0,
|
||||
GeneralError = 1,
|
||||
ConfigError = 2,
|
||||
InvalidArguments = 64,
|
||||
CommandNotFound = 127,
|
||||
CommandFailed = 128,
|
||||
DuplicateCommand = 129,
|
||||
}
|
||||
|
||||
impl From<ExitCode> for i32 {
|
||||
fn from(code: ExitCode) -> i32 {
|
||||
code as i32
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ShadowError> for ExitCode {
|
||||
fn from(error: ShadowError) -> Self {
|
||||
match error {
|
||||
ShadowError::ShadowNotFound(_) => ExitCode::CommandNotFound,
|
||||
ShadowError::CommandExecutionError(_) => ExitCode::CommandFailed,
|
||||
ShadowError::ConfigError(_) => ExitCode::ConfigError,
|
||||
ShadowError::InvalidReplacement(_) => ExitCode::InvalidArguments,
|
||||
ShadowError::DuplicateCommand(_) => ExitCode::DuplicateCommand,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ShadowError {
|
||||
#[error("No shadow found for command: {0}")]
|
||||
ShadowNotFound(String),
|
||||
#[error("Command already shadowed: {0}")]
|
||||
DuplicateCommand(String),
|
||||
#[error("Failed to execute command: {0}")]
|
||||
CommandExecutionError(#[from] std::io::Error),
|
||||
#[error("Failed to load config: {0}")]
|
||||
ConfigError(String),
|
||||
#[error("Invalid replacement command: {0}")]
|
||||
InvalidReplacement(String),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, ShadowError>;
|
38
src/main.rs
Normal file
38
src/main.rs
Normal file
|
@ -0,0 +1,38 @@
|
|||
mod cli;
|
||||
mod commands;
|
||||
mod config;
|
||||
mod error;
|
||||
mod shadows;
|
||||
|
||||
use crate::cli::Cli;
|
||||
use crate::config::Config;
|
||||
use crate::error::ExitCode;
|
||||
use std::env;
|
||||
use std::path::Path;
|
||||
use std::process::exit;
|
||||
|
||||
fn main() {
|
||||
// For debugging
|
||||
println!("Args: {:?}", std::env::args().collect::<Vec<_>>());
|
||||
println!("Program: {:?}", std::env::current_exe().unwrap());
|
||||
|
||||
let args = env::args().next().unwrap();
|
||||
let program_name = Path::new(&args)
|
||||
.file_name()
|
||||
.and_then(|name| name.to_str())
|
||||
.unwrap_or("shadow");
|
||||
let config = match Config::load() {
|
||||
Ok(config) => config,
|
||||
Err(e) => {
|
||||
eprintln!("Failed to load config: {}", e);
|
||||
exit(ExitCode::ConfigError.into());
|
||||
}
|
||||
};
|
||||
|
||||
let exit_code = match program_name {
|
||||
"shadow" => Cli::execute(config),
|
||||
command => Cli::execute_shadowed(config, command),
|
||||
};
|
||||
|
||||
exit(exit_code.into())
|
||||
}
|
205
src/shadows.rs
Normal file
205
src/shadows.rs
Normal file
|
@ -0,0 +1,205 @@
|
|||
use crate::config::Settings;
|
||||
use crate::error::{ExitCode, Result, ShadowError};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::env;
|
||||
use std::fmt;
|
||||
use std::fs;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
pub struct Shadows(Vec<Shadow>);
|
||||
|
||||
impl Shadows {
|
||||
pub fn find<S: AsRef<str>>(&self, original: S) -> Result<&Shadow> {
|
||||
self.0
|
||||
.iter()
|
||||
.find(|s| s.original == original.as_ref())
|
||||
.ok_or_else(|| ShadowError::ShadowNotFound(original.as_ref().to_string()))
|
||||
}
|
||||
|
||||
pub fn contains<S: AsRef<str>>(&self, original: S) -> bool {
|
||||
self.find(original).is_ok()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Shadows {
|
||||
type Target = Vec<Shadow>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for Shadows {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for Shadows {
|
||||
type Item = Shadow;
|
||||
type IntoIter = std::vec::IntoIter<Self::Item>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a Shadows {
|
||||
type Item = &'a Shadow;
|
||||
type IntoIter = std::slice::Iter<'a, Shadow>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.iter()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct Shadow {
|
||||
original: String,
|
||||
replacement: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
bin_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl Shadow {
|
||||
pub fn new(original: String, replacement: String, bin_path: Option<PathBuf>) -> Self {
|
||||
Self {
|
||||
original,
|
||||
replacement,
|
||||
bin_path,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn original(&self) -> &String {
|
||||
&self.original
|
||||
}
|
||||
|
||||
pub fn replacement(&self) -> &String {
|
||||
&self.replacement
|
||||
}
|
||||
|
||||
pub fn bin_path(&self) -> &Option<PathBuf> {
|
||||
&self.bin_path
|
||||
}
|
||||
|
||||
pub fn create_symlink(&self, settings: &Settings) -> Result<()> {
|
||||
let bin_path = self
|
||||
.bin_path
|
||||
.as_deref()
|
||||
.unwrap_or_else(|| settings.bin_path());
|
||||
|
||||
fs::create_dir_all(bin_path).map_err(|e| {
|
||||
ShadowError::ConfigError(format!("Failed to create bin directory: {}", e))
|
||||
})?;
|
||||
|
||||
let target = env::current_exe().map_err(|e| {
|
||||
ShadowError::ConfigError(format!("Failed to get executable path: {}", e))
|
||||
})?;
|
||||
|
||||
let link_path = self.link_path(bin_path);
|
||||
|
||||
if link_path.exists() {
|
||||
fs::remove_file(&link_path)?;
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
std::os::unix::fs::symlink(&target, &link_path)?;
|
||||
|
||||
#[cfg(windows)]
|
||||
std::os::windows::fs::symlink_file(&target, &link_path)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn remove_symlink(&self, settings: &Settings) -> Result<()> {
|
||||
let bin_path = self
|
||||
.bin_path
|
||||
.as_deref()
|
||||
.unwrap_or_else(|| settings.bin_path());
|
||||
|
||||
let link_path = self.link_path(bin_path);
|
||||
|
||||
if link_path.exists() {
|
||||
fs::remove_file(&link_path)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn link_path(&self, bin_path: &Path) -> PathBuf {
|
||||
let link_name = if cfg!(windows) {
|
||||
format!("{}.exe", self.original)
|
||||
} else {
|
||||
self.original.clone()
|
||||
};
|
||||
bin_path.join(link_name)
|
||||
}
|
||||
|
||||
pub fn execute(&self, args: &[String], raw: bool) -> ExitCode {
|
||||
if raw {
|
||||
self.execute_original(args)
|
||||
} else {
|
||||
self.execute_shadow(args)
|
||||
}
|
||||
}
|
||||
|
||||
fn execute_original(&self, args: &[String]) -> ExitCode {
|
||||
match Command::new(&self.original).args(args).status() {
|
||||
Ok(status) => match status.code() {
|
||||
Some(0) => ExitCode::Success,
|
||||
Some(_) => ExitCode::CommandFailed,
|
||||
None => ExitCode::CommandFailed,
|
||||
},
|
||||
Err(e) => {
|
||||
eprintln!("Failed to execute {}: {}", self.original, e);
|
||||
ExitCode::CommandFailed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn execute_shadow(&self, args: &[String]) -> ExitCode {
|
||||
let parts: Vec<&str> = self.replacement.split_whitespace().collect();
|
||||
let (cmd, base_args) = match parts.split_first() {
|
||||
Some(parts) => parts,
|
||||
None => {
|
||||
eprintln!("Invalid replacement command: {}", self.replacement);
|
||||
return ExitCode::InvalidArguments;
|
||||
}
|
||||
};
|
||||
|
||||
let all_args: Vec<String> = base_args
|
||||
.iter()
|
||||
.map(|&s| s.to_string())
|
||||
.chain(args.iter().cloned())
|
||||
.collect();
|
||||
|
||||
match Command::new(cmd).args(all_args).status() {
|
||||
Ok(status) => match status.code() {
|
||||
Some(0) => ExitCode::Success,
|
||||
Some(_) => ExitCode::CommandFailed,
|
||||
None => ExitCode::CommandFailed,
|
||||
},
|
||||
Err(e) => {
|
||||
eprintln!("Failed to execute {}: {}", cmd, e);
|
||||
ExitCode::CommandFailed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Shadow {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{} → {}", self.original, self.replacement)?;
|
||||
if let Some(path) = &self.bin_path {
|
||||
write!(f, " (in {})", path.display())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue