├── .github └── workflows │ └── test.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE-Apache-2.0.txt ├── LICENSE.txt ├── README.md └── src └── main.rs /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Build and test 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | CARGO_TERM_COLOR: always 7 | 8 | jobs: 9 | test: 10 | runs-on: ubuntu-latest 11 | 12 | strategy: 13 | matrix: 14 | rust: [stable, "1.56"] 15 | 16 | steps: 17 | - uses: actions/checkout@v3 18 | - name: Install Rust 19 | uses: actions-rs/toolchain@v1 20 | with: 21 | toolchain: ${{ matrix.rust }} 22 | override: true 23 | - name: Build 24 | run: cargo build --all 25 | - name: Run tests 26 | run: cargo test --all 27 | 28 | lint: 29 | runs-on: ubuntu-latest 30 | 31 | strategy: 32 | matrix: 33 | rust: ["1.59"] 34 | 35 | steps: 36 | - uses: actions/checkout@v3 37 | - name: Install Rust 38 | uses: actions-rs/toolchain@v1 39 | with: 40 | toolchain: ${{ matrix.rust }} 41 | override: true 42 | components: clippy, rustfmt 43 | - name: Lint 44 | run: cargo clippy --all -- -D warnings 45 | - name: Check format 46 | run: cargo fmt --all --check 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "0.7.18" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "atty" 16 | version = "0.2.14" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 19 | dependencies = [ 20 | "hermit-abi", 21 | "libc", 22 | "winapi", 23 | ] 24 | 25 | [[package]] 26 | name = "autocfg" 27 | version = "1.1.0" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 30 | 31 | [[package]] 32 | name = "bitflags" 33 | version = "1.3.2" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 36 | 37 | [[package]] 38 | name = "cfg-if" 39 | version = "1.0.0" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 42 | 43 | [[package]] 44 | name = "clap" 45 | version = "3.1.6" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "d8c93436c21e4698bacadf42917db28b23017027a4deccb35dbe47a7e7840123" 48 | dependencies = [ 49 | "atty", 50 | "bitflags", 51 | "clap_derive", 52 | "indexmap", 53 | "lazy_static", 54 | "os_str_bytes", 55 | "strsim", 56 | "termcolor", 57 | "textwrap", 58 | ] 59 | 60 | [[package]] 61 | name = "clap_derive" 62 | version = "3.1.4" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "da95d038ede1a964ce99f49cbe27a7fb538d1da595e4b4f70b8c8f338d17bf16" 65 | dependencies = [ 66 | "heck", 67 | "proc-macro-error", 68 | "proc-macro2", 69 | "quote", 70 | "syn", 71 | ] 72 | 73 | [[package]] 74 | name = "difflib" 75 | version = "0.4.0" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" 78 | 79 | [[package]] 80 | name = "dont" 81 | version = "0.1.0" 82 | dependencies = [ 83 | "cfg-if", 84 | "clap", 85 | "mockall", 86 | "which", 87 | ] 88 | 89 | [[package]] 90 | name = "downcast" 91 | version = "0.11.0" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" 94 | 95 | [[package]] 96 | name = "either" 97 | version = "1.6.1" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" 100 | 101 | [[package]] 102 | name = "float-cmp" 103 | version = "0.9.0" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" 106 | dependencies = [ 107 | "num-traits", 108 | ] 109 | 110 | [[package]] 111 | name = "fragile" 112 | version = "1.1.0" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "8da1b8f89c5b5a5b7e59405cfcf0bb9588e5ed19f0b57a4cd542bbba3f164a6d" 115 | 116 | [[package]] 117 | name = "hashbrown" 118 | version = "0.11.2" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" 121 | 122 | [[package]] 123 | name = "heck" 124 | version = "0.4.0" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" 127 | 128 | [[package]] 129 | name = "hermit-abi" 130 | version = "0.1.19" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 133 | dependencies = [ 134 | "libc", 135 | ] 136 | 137 | [[package]] 138 | name = "indexmap" 139 | version = "1.8.0" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" 142 | dependencies = [ 143 | "autocfg", 144 | "hashbrown", 145 | ] 146 | 147 | [[package]] 148 | name = "itertools" 149 | version = "0.10.3" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" 152 | dependencies = [ 153 | "either", 154 | ] 155 | 156 | [[package]] 157 | name = "lazy_static" 158 | version = "1.4.0" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 161 | 162 | [[package]] 163 | name = "libc" 164 | version = "0.2.121" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f" 167 | 168 | [[package]] 169 | name = "memchr" 170 | version = "2.4.1" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 173 | 174 | [[package]] 175 | name = "mockall" 176 | version = "0.11.0" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "3d4d70639a72f972725db16350db56da68266ca368b2a1fe26724a903ad3d6b8" 179 | dependencies = [ 180 | "cfg-if", 181 | "downcast", 182 | "fragile", 183 | "lazy_static", 184 | "mockall_derive", 185 | "predicates", 186 | "predicates-tree", 187 | ] 188 | 189 | [[package]] 190 | name = "mockall_derive" 191 | version = "0.11.0" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "79ef208208a0dea3f72221e26e904cdc6db2e481d9ade89081ddd494f1dbaa6b" 194 | dependencies = [ 195 | "cfg-if", 196 | "proc-macro2", 197 | "quote", 198 | "syn", 199 | ] 200 | 201 | [[package]] 202 | name = "normalize-line-endings" 203 | version = "0.3.0" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" 206 | 207 | [[package]] 208 | name = "num-traits" 209 | version = "0.2.14" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 212 | dependencies = [ 213 | "autocfg", 214 | ] 215 | 216 | [[package]] 217 | name = "os_str_bytes" 218 | version = "6.0.0" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" 221 | dependencies = [ 222 | "memchr", 223 | ] 224 | 225 | [[package]] 226 | name = "predicates" 227 | version = "2.1.1" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "a5aab5be6e4732b473071984b3164dbbfb7a3674d30ea5ff44410b6bcd960c3c" 230 | dependencies = [ 231 | "difflib", 232 | "float-cmp", 233 | "itertools", 234 | "normalize-line-endings", 235 | "predicates-core", 236 | "regex", 237 | ] 238 | 239 | [[package]] 240 | name = "predicates-core" 241 | version = "1.0.3" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "da1c2388b1513e1b605fcec39a95e0a9e8ef088f71443ef37099fa9ae6673fcb" 244 | 245 | [[package]] 246 | name = "predicates-tree" 247 | version = "1.0.5" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "4d86de6de25020a36c6d3643a86d9a6a9f552107c0559c60ea03551b5e16c032" 250 | dependencies = [ 251 | "predicates-core", 252 | "termtree", 253 | ] 254 | 255 | [[package]] 256 | name = "proc-macro-error" 257 | version = "1.0.4" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 260 | dependencies = [ 261 | "proc-macro-error-attr", 262 | "proc-macro2", 263 | "quote", 264 | "syn", 265 | "version_check", 266 | ] 267 | 268 | [[package]] 269 | name = "proc-macro-error-attr" 270 | version = "1.0.4" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 273 | dependencies = [ 274 | "proc-macro2", 275 | "quote", 276 | "version_check", 277 | ] 278 | 279 | [[package]] 280 | name = "proc-macro2" 281 | version = "1.0.36" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" 284 | dependencies = [ 285 | "unicode-xid", 286 | ] 287 | 288 | [[package]] 289 | name = "quote" 290 | version = "1.0.16" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "b4af2ec4714533fcdf07e886f17025ace8b997b9ce51204ee69b6da831c3da57" 293 | dependencies = [ 294 | "proc-macro2", 295 | ] 296 | 297 | [[package]] 298 | name = "regex" 299 | version = "1.5.5" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" 302 | dependencies = [ 303 | "aho-corasick", 304 | "memchr", 305 | "regex-syntax", 306 | ] 307 | 308 | [[package]] 309 | name = "regex-syntax" 310 | version = "0.6.25" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 313 | 314 | [[package]] 315 | name = "strsim" 316 | version = "0.10.0" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 319 | 320 | [[package]] 321 | name = "syn" 322 | version = "1.0.89" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "ea297be220d52398dcc07ce15a209fce436d361735ac1db700cab3b6cdfb9f54" 325 | dependencies = [ 326 | "proc-macro2", 327 | "quote", 328 | "unicode-xid", 329 | ] 330 | 331 | [[package]] 332 | name = "termcolor" 333 | version = "1.1.3" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" 336 | dependencies = [ 337 | "winapi-util", 338 | ] 339 | 340 | [[package]] 341 | name = "termtree" 342 | version = "0.2.4" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "507e9898683b6c43a9aa55b64259b721b52ba226e0f3779137e50ad114a4c90b" 345 | 346 | [[package]] 347 | name = "textwrap" 348 | version = "0.15.0" 349 | source = "registry+https://github.com/rust-lang/crates.io-index" 350 | checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" 351 | 352 | [[package]] 353 | name = "unicode-xid" 354 | version = "0.2.2" 355 | source = "registry+https://github.com/rust-lang/crates.io-index" 356 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 357 | 358 | [[package]] 359 | name = "version_check" 360 | version = "0.9.4" 361 | source = "registry+https://github.com/rust-lang/crates.io-index" 362 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 363 | 364 | [[package]] 365 | name = "which" 366 | version = "4.2.4" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "2a5a7e487e921cf220206864a94a89b6c6905bfc19f1057fa26a4cb360e5c1d2" 369 | dependencies = [ 370 | "either", 371 | "lazy_static", 372 | "libc", 373 | ] 374 | 375 | [[package]] 376 | name = "winapi" 377 | version = "0.3.9" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 380 | dependencies = [ 381 | "winapi-i686-pc-windows-gnu", 382 | "winapi-x86_64-pc-windows-gnu", 383 | ] 384 | 385 | [[package]] 386 | name = "winapi-i686-pc-windows-gnu" 387 | version = "0.4.0" 388 | source = "registry+https://github.com/rust-lang/crates.io-index" 389 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 390 | 391 | [[package]] 392 | name = "winapi-util" 393 | version = "0.1.5" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 396 | dependencies = [ 397 | "winapi", 398 | ] 399 | 400 | [[package]] 401 | name = "winapi-x86_64-pc-windows-gnu" 402 | version = "0.4.0" 403 | source = "registry+https://github.com/rust-lang/crates.io-index" 404 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 405 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dont" 3 | description = "swiss army knife for everything you do not want to do" 4 | version = "0.1.0" 5 | edition = "2021" 6 | 7 | authors = [ 8 | "Masaki Hara ", 9 | ] 10 | homepage = "https://github.com/qnighy/dont" 11 | repository = "https://github.com/qnighy/dont" 12 | license = "MIT OR Apache-2.0" 13 | keywords = ["command", "joke", "tool", "utility"] 14 | categories = ["command-line-utilities"] 15 | 16 | [dependencies] 17 | cfg-if = "1.0.0" 18 | clap = { version = "3.1.6", features = ["derive"] } 19 | which = "4.2.4" 20 | 21 | [dev-dependencies] 22 | mockall = "0.11.0" 23 | -------------------------------------------------------------------------------- /LICENSE-Apache-2.0.txt: -------------------------------------------------------------------------------- 1 | Copyright 2022 Masaki Hara 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2022 Masaki Hara 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | 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. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## `dont` command: swiss army knife for everything you do not want to do 2 | 3 | ### Installation 4 | 5 | ``` 6 | cargo install dont 7 | ``` 8 | 9 | ### Usage 10 | 11 | ``` 12 | dont echo "hello world" 13 | ``` 14 | 15 | It doesn't print `hello world`. 16 | 17 | ``` 18 | dont do-release-upgrade 19 | ``` 20 | 21 | It doesn't upgrade your operating system. 22 | 23 | ``` 24 | dont ls 25 | ``` 26 | 27 | It doesn't list the contents of the current directory. 28 | 29 | ``` 30 | dont dont echo "hello world" 31 | ``` 32 | 33 | It doesn't follow your second `dont`. That means... uh oh. 34 | 35 | ### License 36 | 37 | MIT or Apache-2.0. See LICENSE.txt and LICENSE-Apache-2.0.txt. 38 | 39 | ### Contributing 40 | 41 | If you find cases where `dont` doesn't properly negate your intentions, feel free to submit a pull request. Be sure to include a test case. 42 | 43 | Check your code by executing the following: 44 | 45 | ``` 46 | cargo test 47 | cargo fmt 48 | cargo clippy 49 | ``` 50 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Masaki Hara 2 | // See LICENSE.txt and LICENSE-Apache-2.0.txt for the license. 3 | 4 | use std::ffi::OsString; 5 | use std::io; 6 | 7 | use cfg_if::cfg_if; 8 | use clap::Parser; 9 | 10 | #[derive(Parser, Debug)] 11 | #[clap(author, version, about, long_about = None)] 12 | struct Args { 13 | #[clap(allow_hyphen_values = true)] 14 | command: Vec, 15 | } 16 | 17 | fn main() { 18 | let args = Args::parse(); 19 | match execute(&DefaultController, &args) { 20 | Conclusion::Exit(code) => { 21 | std::process::exit(code); 22 | } 23 | Conclusion::Exec(args) => { 24 | use std::process::Command; 25 | let mut command = Command::new(&args[0]); 26 | command.args(&args[1..]); 27 | #[allow(clippy::needless_late_init)] 28 | let result: Result<(), io::Error>; 29 | cfg_if! { 30 | if #[cfg(unix)] { 31 | use std::os::unix::process::CommandExt; 32 | result = Err(command.exec()); 33 | } else { 34 | result = command.spawn(); 35 | } 36 | } 37 | if let Err(e) = result { 38 | let command_description = args 39 | .into_iter() 40 | .map(|x| x.to_string_lossy().into_owned()) 41 | .collect::>() 42 | .join(" "); 43 | eprintln!("Failed to run {}: {}", command_description, e); 44 | std::process::exit(1); 45 | } 46 | } 47 | } 48 | } 49 | 50 | fn execute(ctl: &C, args: &Args) -> Conclusion { 51 | if args.command.is_empty() { 52 | // Just "dont". What is the right reaction to the command? 53 | return Conclusion::Exit(0); 54 | } 55 | if args.command[0] == "true" { 56 | return Conclusion::Exit(1); 57 | } else if args.command[0] == "false" { 58 | return Conclusion::Exit(0); 59 | } else if args.command[0] == "dont" { 60 | if args.command.len() == 1 { 61 | // Just "dont dont". What is the right reaction to the command? 62 | return Conclusion::Exit(0); 63 | } 64 | return Conclusion::Exec(args.command[1..].to_owned()); 65 | } else if args.command[0] == "ls" && ctl.has_command("sl") { 66 | return Conclusion::Exec( 67 | vec![OsString::from("sl")] 68 | .into_iter() 69 | .chain(args.command[1..].iter().cloned()) 70 | .collect(), 71 | ); 72 | } else if args.command[0] == "sl" { 73 | return Conclusion::Exec( 74 | vec![OsString::from("ls")] 75 | .into_iter() 76 | .chain(args.command[1..].iter().cloned()) 77 | .collect(), 78 | ); 79 | } else if args.command[0] == "vim" && ctl.has_command("emacs") { 80 | return Conclusion::Exec( 81 | vec![OsString::from("emacs")] 82 | .into_iter() 83 | .chain(args.command[1..].iter().cloned()) 84 | .collect(), 85 | ); 86 | } else if args.command[0] == "emacs" && ctl.has_command("vim") { 87 | return Conclusion::Exec( 88 | vec![OsString::from("vim")] 89 | .into_iter() 90 | .chain(args.command[1..].iter().cloned()) 91 | .collect(), 92 | ); 93 | } 94 | Conclusion::Exit(0) 95 | } 96 | 97 | #[derive(Debug, Clone, PartialEq, Eq)] 98 | enum Conclusion { 99 | Exit(i32), 100 | Exec(Vec), 101 | } 102 | 103 | #[cfg_attr(test, mockall::automock)] 104 | trait Controller { 105 | fn has_command(&self, name: &str) -> bool; 106 | } 107 | 108 | #[derive(Debug)] 109 | struct DefaultController; 110 | 111 | impl Controller for DefaultController { 112 | fn has_command(&self, name: &str) -> bool { 113 | which::which(name).is_ok() 114 | } 115 | } 116 | 117 | #[cfg(test)] 118 | mod tests { 119 | use super::*; 120 | use mockall::predicate::*; 121 | 122 | fn main(ctl: &MockController, args: &[&str]) -> Result { 123 | // TODO: resolve unwrap correctly 124 | let args = Args::try_parse_from(args)?; 125 | Ok(execute(ctl, &args)) 126 | } 127 | 128 | #[test] 129 | fn test_help() { 130 | let ctl = MockController::new(); 131 | let e = main(&ctl, &["dont", "--help"]).unwrap_err(); 132 | let msg = e.to_string(); 133 | assert!( 134 | msg.contains("USAGE:"), 135 | "Expected message to contain \"USAGE:\", got {}", 136 | msg 137 | ); 138 | } 139 | 140 | #[test] 141 | fn test_true() { 142 | let ctl = MockController::new(); 143 | let concl = main(&ctl, &["dont", "true"]).unwrap(); 144 | assert_eq!(concl, Conclusion::Exit(1)); 145 | } 146 | 147 | #[test] 148 | fn test_true_with_dashes() { 149 | let ctl = MockController::new(); 150 | let concl = main(&ctl, &["dont", "--", "true"]).unwrap(); 151 | assert_eq!(concl, Conclusion::Exit(1)); 152 | } 153 | 154 | #[test] 155 | fn test_false() { 156 | let ctl = MockController::new(); 157 | let concl = main(&ctl, &["dont", "false"]).unwrap(); 158 | assert_eq!(concl, Conclusion::Exit(0)); 159 | } 160 | 161 | #[test] 162 | fn test_dont() { 163 | let ctl = MockController::new(); 164 | let concl = main(&ctl, &["dont", "dont", "ls"]).unwrap(); 165 | assert_eq!(concl, Conclusion::Exec(vec!["ls".into()])); 166 | } 167 | 168 | #[test] 169 | fn test_dont_with_dashes() { 170 | let ctl = MockController::new(); 171 | let concl = main(&ctl, &["dont", "--", "dont", "ls"]).unwrap(); 172 | assert_eq!(concl, Conclusion::Exec(vec!["ls".into()])); 173 | } 174 | 175 | #[test] 176 | fn test_dont_with_wrong_dashes() { 177 | let ctl = MockController::new(); 178 | let concl = main(&ctl, &["dont", "dont", "--", "ls"]).unwrap(); 179 | assert_eq!(concl, Conclusion::Exec(vec!["--".into(), "ls".into()])); 180 | } 181 | 182 | #[test] 183 | fn test_ls() { 184 | let mut ctl = MockController::new(); 185 | ctl.expect_has_command().with(eq("sl")).returning(|_| false); 186 | let concl = main(&ctl, &["dont", "ls"]).unwrap(); 187 | assert_eq!(concl, Conclusion::Exit(0)); 188 | } 189 | 190 | #[test] 191 | fn test_ls_when_sl_exists() { 192 | let mut ctl = MockController::new(); 193 | ctl.expect_has_command().with(eq("sl")).returning(|_| true); 194 | let concl = main(&ctl, &["dont", "ls"]).unwrap(); 195 | assert_eq!(concl, Conclusion::Exec(vec!["sl".into()])); 196 | } 197 | 198 | #[test] 199 | fn test_ls_with_args_when_sl_exists() { 200 | let mut ctl = MockController::new(); 201 | ctl.expect_has_command().with(eq("sl")).returning(|_| true); 202 | let concl = main(&ctl, &["dont", "ls", "foo"]).unwrap(); 203 | assert_eq!(concl, Conclusion::Exec(vec!["sl".into(), "foo".into()])); 204 | } 205 | 206 | #[test] 207 | fn test_sl() { 208 | let ctl = MockController::new(); 209 | let concl = main(&ctl, &["dont", "sl"]).unwrap(); 210 | assert_eq!(concl, Conclusion::Exec(vec!["ls".into()])); 211 | } 212 | 213 | #[test] 214 | fn test_sl_with_args() { 215 | let ctl = MockController::new(); 216 | let concl = main(&ctl, &["dont", "sl", "foo"]).unwrap(); 217 | assert_eq!(concl, Conclusion::Exec(vec!["ls".into(), "foo".into()])); 218 | } 219 | 220 | #[test] 221 | fn test_vim() { 222 | let mut ctl = MockController::new(); 223 | ctl.expect_has_command() 224 | .with(eq("emacs")) 225 | .returning(|_| false); 226 | let concl = main(&ctl, &["dont", "vim"]).unwrap(); 227 | assert_eq!(concl, Conclusion::Exit(0)); 228 | } 229 | 230 | #[test] 231 | fn test_vim_when_emacs_exists() { 232 | let mut ctl = MockController::new(); 233 | ctl.expect_has_command() 234 | .with(eq("emacs")) 235 | .returning(|_| true); 236 | let concl = main(&ctl, &["dont", "vim"]).unwrap(); 237 | assert_eq!(concl, Conclusion::Exec(vec!["emacs".into()])); 238 | } 239 | 240 | #[test] 241 | fn test_vim_with_args_when_emacs_exists() { 242 | let mut ctl = MockController::new(); 243 | ctl.expect_has_command() 244 | .with(eq("emacs")) 245 | .returning(|_| true); 246 | let concl = main(&ctl, &["dont", "vim", "foo"]).unwrap(); 247 | assert_eq!(concl, Conclusion::Exec(vec!["emacs".into(), "foo".into()])); 248 | } 249 | 250 | #[test] 251 | fn test_emacs() { 252 | let mut ctl = MockController::new(); 253 | ctl.expect_has_command() 254 | .with(eq("vim")) 255 | .returning(|_| false); 256 | let concl = main(&ctl, &["dont", "emacs"]).unwrap(); 257 | assert_eq!(concl, Conclusion::Exit(0)); 258 | } 259 | 260 | #[test] 261 | fn test_emacs_when_vim_exists() { 262 | let mut ctl = MockController::new(); 263 | ctl.expect_has_command().with(eq("vim")).returning(|_| true); 264 | let concl = main(&ctl, &["dont", "emacs"]).unwrap(); 265 | assert_eq!(concl, Conclusion::Exec(vec!["vim".into()])); 266 | } 267 | 268 | #[test] 269 | fn test_emacs_with_args_when_vim_exists() { 270 | let mut ctl = MockController::new(); 271 | ctl.expect_has_command().with(eq("vim")).returning(|_| true); 272 | let concl = main(&ctl, &["dont", "emacs", "foo"]).unwrap(); 273 | assert_eq!(concl, Conclusion::Exec(vec!["vim".into(), "foo".into()])); 274 | } 275 | } 276 | --------------------------------------------------------------------------------