├── .gitattributes ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── Contributing.md ├── LICENSE ├── README.md ├── changelog.txt ├── docker-tests ├── Dockerfile-bashtest └── Dockerfile-zshtest ├── docs ├── README.md ├── dev │ └── release.md ├── manual │ ├── add.md │ ├── config.md │ ├── get.md │ ├── goto.md │ ├── help.md │ ├── install.md │ ├── list.md │ ├── prefix.md │ ├── profile.md │ ├── profiles.md │ ├── remove.md │ └── rmprofile.md └── tldr │ ├── README.md │ ├── add.gif │ ├── list.gif │ └── remove.gif ├── src ├── embedded.rs ├── main.rs ├── paths.rs ├── shell │ ├── goto │ ├── goto-bash.sh │ ├── goto-powershell.ps1 │ └── goto-zsh.sh └── storage.rs └── tests └── test_shell.sh /.gitattributes: -------------------------------------------------------------------------------- 1 | ./tests/test_shell.sh eol=lf 2 | ./src/shell/goto eol=lf 3 | ./src/shell/goto-bash.sh eol=lf 4 | ./src/shell/goto-zsh.sh eol=lf 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | dist 3 | goto_cd.egg-info 4 | **.pyc 5 | venv 6 | htmlcov 7 | .coverage 8 | .tox 9 | 10 | # Added by cargo 11 | 12 | /target 13 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "ansi_term" 5 | version = "0.11.0" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 8 | dependencies = [ 9 | "winapi", 10 | ] 11 | 12 | [[package]] 13 | name = "arrayref" 14 | version = "0.3.6" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" 17 | 18 | [[package]] 19 | name = "arrayvec" 20 | version = "0.5.2" 21 | source = "registry+https://github.com/rust-lang/crates.io-index" 22 | checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" 23 | 24 | [[package]] 25 | name = "atty" 26 | version = "0.2.14" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 29 | dependencies = [ 30 | "hermit-abi", 31 | "libc", 32 | "winapi", 33 | ] 34 | 35 | [[package]] 36 | name = "autocfg" 37 | version = "1.0.1" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 40 | 41 | [[package]] 42 | name = "base64" 43 | version = "0.13.0" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" 46 | 47 | [[package]] 48 | name = "bitflags" 49 | version = "1.2.1" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 52 | 53 | [[package]] 54 | name = "blake2b_simd" 55 | version = "0.5.11" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587" 58 | dependencies = [ 59 | "arrayref", 60 | "arrayvec", 61 | "constant_time_eq", 62 | ] 63 | 64 | [[package]] 65 | name = "block-buffer" 66 | version = "0.9.0" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" 69 | dependencies = [ 70 | "generic-array", 71 | ] 72 | 73 | [[package]] 74 | name = "cfg-if" 75 | version = "1.0.0" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 78 | 79 | [[package]] 80 | name = "clap" 81 | version = "2.33.3" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" 84 | dependencies = [ 85 | "ansi_term", 86 | "atty", 87 | "bitflags", 88 | "strsim", 89 | "textwrap", 90 | "unicode-width", 91 | "vec_map", 92 | ] 93 | 94 | [[package]] 95 | name = "constant_time_eq" 96 | version = "0.1.5" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" 99 | 100 | [[package]] 101 | name = "cpufeatures" 102 | version = "0.2.1" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" 105 | dependencies = [ 106 | "libc", 107 | ] 108 | 109 | [[package]] 110 | name = "crossbeam-utils" 111 | version = "0.8.1" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d" 114 | dependencies = [ 115 | "autocfg", 116 | "cfg-if", 117 | "lazy_static", 118 | ] 119 | 120 | [[package]] 121 | name = "digest" 122 | version = "0.9.0" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" 125 | dependencies = [ 126 | "generic-array", 127 | ] 128 | 129 | [[package]] 130 | name = "dirs" 131 | version = "3.0.1" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "142995ed02755914747cc6ca76fc7e4583cd18578746716d0508ea6ed558b9ff" 134 | dependencies = [ 135 | "dirs-sys", 136 | ] 137 | 138 | [[package]] 139 | name = "dirs-next" 140 | version = "2.0.0" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" 143 | dependencies = [ 144 | "cfg-if", 145 | "dirs-sys-next", 146 | ] 147 | 148 | [[package]] 149 | name = "dirs-sys" 150 | version = "0.3.5" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "8e93d7f5705de3e49895a2b5e0b8855a1c27f080192ae9c32a6432d50741a57a" 153 | dependencies = [ 154 | "libc", 155 | "redox_users 0.3.5", 156 | "winapi", 157 | ] 158 | 159 | [[package]] 160 | name = "dirs-sys-next" 161 | version = "0.1.2" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" 164 | dependencies = [ 165 | "libc", 166 | "redox_users 0.4.0", 167 | "winapi", 168 | ] 169 | 170 | [[package]] 171 | name = "dunce" 172 | version = "1.0.2" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "453440c271cf5577fd2a40e4942540cb7d0d2f85e27c8d07dd0023c925a67541" 175 | 176 | [[package]] 177 | name = "generic-array" 178 | version = "0.14.4" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" 181 | dependencies = [ 182 | "typenum", 183 | "version_check", 184 | ] 185 | 186 | [[package]] 187 | name = "getrandom" 188 | version = "0.1.16" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" 191 | dependencies = [ 192 | "cfg-if", 193 | "libc", 194 | "wasi 0.9.0+wasi-snapshot-preview1", 195 | ] 196 | 197 | [[package]] 198 | name = "getrandom" 199 | version = "0.2.3" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" 202 | dependencies = [ 203 | "cfg-if", 204 | "libc", 205 | "wasi 0.10.2+wasi-snapshot-preview1", 206 | ] 207 | 208 | [[package]] 209 | name = "goto-cd" 210 | version = "2.1.1" 211 | dependencies = [ 212 | "clap", 213 | "dirs", 214 | "dirs-next", 215 | "dunce", 216 | "rust-embed", 217 | "serde", 218 | "term", 219 | "toml", 220 | ] 221 | 222 | [[package]] 223 | name = "hermit-abi" 224 | version = "0.1.17" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8" 227 | dependencies = [ 228 | "libc", 229 | ] 230 | 231 | [[package]] 232 | name = "lazy_static" 233 | version = "1.4.0" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 236 | 237 | [[package]] 238 | name = "libc" 239 | version = "0.2.101" 240 | source = "registry+https://github.com/rust-lang/crates.io-index" 241 | checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21" 242 | 243 | [[package]] 244 | name = "opaque-debug" 245 | version = "0.3.0" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" 248 | 249 | [[package]] 250 | name = "proc-macro2" 251 | version = "1.0.28" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612" 254 | dependencies = [ 255 | "unicode-xid", 256 | ] 257 | 258 | [[package]] 259 | name = "quote" 260 | version = "1.0.9" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" 263 | dependencies = [ 264 | "proc-macro2", 265 | ] 266 | 267 | [[package]] 268 | name = "redox_syscall" 269 | version = "0.1.57" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" 272 | 273 | [[package]] 274 | name = "redox_syscall" 275 | version = "0.2.9" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "5ab49abadf3f9e1c4bc499e8845e152ad87d2ad2d30371841171169e9d75feee" 278 | dependencies = [ 279 | "bitflags", 280 | ] 281 | 282 | [[package]] 283 | name = "redox_users" 284 | version = "0.3.5" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d" 287 | dependencies = [ 288 | "getrandom 0.1.16", 289 | "redox_syscall 0.1.57", 290 | "rust-argon2", 291 | ] 292 | 293 | [[package]] 294 | name = "redox_users" 295 | version = "0.4.0" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" 298 | dependencies = [ 299 | "getrandom 0.2.3", 300 | "redox_syscall 0.2.9", 301 | ] 302 | 303 | [[package]] 304 | name = "rust-argon2" 305 | version = "0.8.3" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb" 308 | dependencies = [ 309 | "base64", 310 | "blake2b_simd", 311 | "constant_time_eq", 312 | "crossbeam-utils", 313 | ] 314 | 315 | [[package]] 316 | name = "rust-embed" 317 | version = "6.0.1" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | checksum = "d058b84555f8dbf848b5fdf8d3340cbed66167d616232a5ddbffe6fb476db9d2" 320 | dependencies = [ 321 | "rust-embed-impl", 322 | "rust-embed-utils", 323 | "walkdir", 324 | ] 325 | 326 | [[package]] 327 | name = "rust-embed-impl" 328 | version = "6.0.1" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "7a888d6e9c2ef8ce7691500edef64ad184a1fc0984805d3f825653d62df9f9eb" 331 | dependencies = [ 332 | "proc-macro2", 333 | "quote", 334 | "rust-embed-utils", 335 | "syn", 336 | "walkdir", 337 | ] 338 | 339 | [[package]] 340 | name = "rust-embed-utils" 341 | version = "6.0.0" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "5b5f2909061b856ce587f2496fc149a71d576cb2e0842c79f0dc82741622c6d0" 344 | dependencies = [ 345 | "sha2", 346 | "walkdir", 347 | ] 348 | 349 | [[package]] 350 | name = "rustversion" 351 | version = "1.0.5" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088" 354 | 355 | [[package]] 356 | name = "same-file" 357 | version = "1.0.6" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 360 | dependencies = [ 361 | "winapi-util", 362 | ] 363 | 364 | [[package]] 365 | name = "serde" 366 | version = "1.0.118" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800" 369 | dependencies = [ 370 | "serde_derive", 371 | ] 372 | 373 | [[package]] 374 | name = "serde_derive" 375 | version = "1.0.118" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df" 378 | dependencies = [ 379 | "proc-macro2", 380 | "quote", 381 | "syn", 382 | ] 383 | 384 | [[package]] 385 | name = "sha2" 386 | version = "0.9.6" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "9204c41a1597a8c5af23c82d1c921cb01ec0a4c59e07a9c7306062829a3903f3" 389 | dependencies = [ 390 | "block-buffer", 391 | "cfg-if", 392 | "cpufeatures", 393 | "digest", 394 | "opaque-debug", 395 | ] 396 | 397 | [[package]] 398 | name = "strsim" 399 | version = "0.8.0" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 402 | 403 | [[package]] 404 | name = "syn" 405 | version = "1.0.75" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | checksum = "b7f58f7e8eaa0009c5fec437aabf511bd9933e4b2d7407bd05273c01a8906ea7" 408 | dependencies = [ 409 | "proc-macro2", 410 | "quote", 411 | "unicode-xid", 412 | ] 413 | 414 | [[package]] 415 | name = "term" 416 | version = "0.7.0" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" 419 | dependencies = [ 420 | "dirs-next", 421 | "rustversion", 422 | "winapi", 423 | ] 424 | 425 | [[package]] 426 | name = "textwrap" 427 | version = "0.11.0" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 430 | dependencies = [ 431 | "unicode-width", 432 | ] 433 | 434 | [[package]] 435 | name = "toml" 436 | version = "0.5.8" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" 439 | dependencies = [ 440 | "serde", 441 | ] 442 | 443 | [[package]] 444 | name = "typenum" 445 | version = "1.13.0" 446 | source = "registry+https://github.com/rust-lang/crates.io-index" 447 | checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" 448 | 449 | [[package]] 450 | name = "unicode-width" 451 | version = "0.1.8" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" 454 | 455 | [[package]] 456 | name = "unicode-xid" 457 | version = "0.2.1" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 460 | 461 | [[package]] 462 | name = "vec_map" 463 | version = "0.8.2" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 466 | 467 | [[package]] 468 | name = "version_check" 469 | version = "0.9.3" 470 | source = "registry+https://github.com/rust-lang/crates.io-index" 471 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" 472 | 473 | [[package]] 474 | name = "walkdir" 475 | version = "2.3.1" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" 478 | dependencies = [ 479 | "same-file", 480 | "winapi", 481 | "winapi-util", 482 | ] 483 | 484 | [[package]] 485 | name = "wasi" 486 | version = "0.9.0+wasi-snapshot-preview1" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 489 | 490 | [[package]] 491 | name = "wasi" 492 | version = "0.10.2+wasi-snapshot-preview1" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 495 | 496 | [[package]] 497 | name = "winapi" 498 | version = "0.3.9" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 501 | dependencies = [ 502 | "winapi-i686-pc-windows-gnu", 503 | "winapi-x86_64-pc-windows-gnu", 504 | ] 505 | 506 | [[package]] 507 | name = "winapi-i686-pc-windows-gnu" 508 | version = "0.4.0" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 511 | 512 | [[package]] 513 | name = "winapi-util" 514 | version = "0.1.5" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 517 | dependencies = [ 518 | "winapi", 519 | ] 520 | 521 | [[package]] 522 | name = "winapi-x86_64-pc-windows-gnu" 523 | version = "0.4.0" 524 | source = "registry+https://github.com/rust-lang/crates.io-index" 525 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 526 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "goto-cd" 3 | version = "2.1.1" 4 | authors = ["Henrik 'CatEars' Adolfsson "] 5 | description = "Teleportation for the command-line" 6 | homepage = "https://github.com/CatEars/goto" 7 | documentation = "https://github.com/CatEars/goto/tree/master/docs" 8 | repository = "https://github.com/CatEars/goto" 9 | readme = "README.md" 10 | license = "MIT" 11 | edition = "2018" 12 | keywords = ["bookmark", "CLI", "terminal"] 13 | categories = ["command-line-utilities"] 14 | include = ["src/**/*", "README.md", "changelog.txt", "LICENSE", "docs/manual/*"] 15 | 16 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 17 | 18 | [dependencies] 19 | dirs = "3.0.1" 20 | dirs-next = "2.0.0" 21 | toml = "0.5.3" 22 | serde = { version = "1.0.117", features = ["derive"] } 23 | clap = "2.33.3" 24 | term = "0.7.0" 25 | rust-embed = "6.0.1" 26 | dunce = "1.0.2" 27 | -------------------------------------------------------------------------------- /Contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Any contributions are welcome. As with any open source repo the rule is to be 4 | sensible. Use Pull Requests to ask for changes to the code that you made, open 5 | an issue for suggestions and issues/bugs. Do understand however that I have a 6 | full time job and I can't fix everything. In other words, if you really really 7 | really want something to happen I am afraid the best way is to do it yourself 8 | (and then send a PR my way of course!). Below are some lists that should give an 9 | idea of how to best contribute. 10 | 11 | ## Very Welcome Contributions 12 | 13 | * Adaptations for other shells (I've heard `fish` is popular). 14 | * Improvements for currently supported shells 15 | * Tests (Unit tests, Tests formulated like user stories etc.) 16 | * MacOS testing 17 | * PRs that solve obvious bugs 18 | * Documentation (More Pictures/Videos/Screencasts = More Better) 19 | * Developer Documentation 20 | * Issues with reproducible bugs 21 | * Positive and Professional Discussions 22 | 23 | ## Quite Welcome Contributions 24 | 25 | * Feature Suggestions 26 | * Issues where minimal brain capacity is needed to reproduce bugs 27 | 28 | ## Kinda Welcome Contributions 29 | 30 | * Windows Integration 31 | * Issues with bugs that only say what went wrong 32 | 33 | ## Not So Welcome Contributions 34 | 35 | * Big changes to the underlying code base 36 | * Issues with bugs that is impossible to understand why they would be bugs and are 100% impossible to reproduce 37 | * Angry and Unprofessional Discussions 38 | 39 | ## "Not even going to answer" level "Contributions" 40 | 41 | * "Please hire me/Looking for a developer with X/Are you experienced in X?" LinkedIn posts disguised as Issues 42 | 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Henrik Adolfsson 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # goto 2 | 3 | 🔖 🔖 🔖 Bookmarks for the terminal 🔖 🔖 🔖 4 | 5 | The tldr usage of `goto`: 6 | 7 | 1. Bookmark with `goto --add ./my_folder` 8 | 2. Use `cd` to go somewhere else, or open a new terminal, or restart your computer 9 | 3. Run `goto my_folder` and you're teleported back to `my_folder` 10 | 11 | Usage gifs and usage documentation can be found in the 12 | [docs folder](https://github.com/CatEars/goto/blob/master/docs/README.md) 13 | 14 | ## Key Features 15 | 16 | * Like browser bookmarks, but for the commandline! 17 | * Add commonly visited places, like `code/my_project` and teleport to it from anywhere! 18 | * Did you say you want auto-completion with that? Of course there is auto-completion! 19 | * Works with bash, zsh and powershell! 20 | * Got several folders with similar names? Use an alias for the bookmark! 21 | 22 | ## Unnecessary slogan 23 | 24 | Goto - The good way to program 25 | 26 | ## Installing 27 | 28 | #### bash 29 | 30 | ```sh 31 | cargo install --locked goto-cd 32 | goto-cd --install >> ~/.bashrc 33 | ``` 34 | 35 | #### zsh 36 | 37 | ```sh 38 | cargo install --locked goto-cd 39 | goto-cd --install >> ~/.zshrc 40 | ``` 41 | 42 | #### powershell 43 | 44 | ```sh 45 | cargo install --locked goto-cd 46 | goto-cd --powershell-install >> $PROFILE 47 | ``` 48 | 49 | #### Finally 50 | 51 | Restart your shell for effects to take place 52 | 53 | ## Basic Usage 54 | 55 | Note: The installed binary is called `goto-cd`, but `goto` is the name of the command 56 | loaded into your shell. `goto-cd` is only referenced when installing the first time. 57 | 58 | ```sh 59 | goto --add . 60 | # Prints "Added 'catears' which points to '/home/catears'" 61 | cd / 62 | goto catears 63 | # Ends up at /home/catears 64 | ``` 65 | 66 | ## Updating 67 | 68 | ```sh 69 | cargo install --locked goto-cd 70 | # Installs the newest version of `goto-cd` 71 | goto --install 72 | # Installs the latest version of shell scripts for both unix/windows 73 | ``` 74 | 75 | ## Documentation 76 | 77 | See the [docs](https://github.com/CatEars/goto/blob/master/docs/README.md) folder. 78 | 79 | ## Contributing 80 | 81 | See [contributing.md](https://github.com/CatEars/goto/blob/master/Contributing.md). 82 | 83 | ## License 84 | 85 | MIT - see [LICENSE](https://github.com/CatEars/goto/blob/master/LICENSE) 86 | -------------------------------------------------------------------------------- /changelog.txt: -------------------------------------------------------------------------------- 1 | Version 1.0.1 - First public release 2 | 3 | First public release of goto 4 | 5 | Features: 6 | - You can now traverse with subfolders, 7 | meaning you can tab complete and reach subfolders of your teleport target. 8 | 9 | Version 0.2.2 - First Py2/Py3 fix 10 | 11 | Bugfix: 12 | - Fix breaking py2 problem with --prefix 13 | 14 | Version 0.2.1 - First Major update 15 | 16 | Features: 17 | - Add tab completion for the empty string 18 | - Add output to commands that were missing output 19 | 20 | Documentation upgrades: 21 | - Add tldr for the simplest commands 22 | - Add manual for all commands 23 | - Do not load a ton of gifs when looking in docs folder 24 | 25 | Version 0.1.1 - First Public PyPI Release (beta) 26 | 27 | First public release of goto. 28 | 29 | The basic functionality is there with --add,--list,--remove,--profile,--profiles 30 | and bash completion. The coverage of the code is rather meager so far, but most 31 | of the feature development for this cycle is finished and so we can focus on 32 | testing and quality assurance. 33 | -------------------------------------------------------------------------------- /docker-tests/Dockerfile-bashtest: -------------------------------------------------------------------------------- 1 | FROM rust as builder 2 | 3 | WORKDIR /build 4 | COPY src src/ 5 | COPY Cargo.toml Cargo.toml ./ 6 | RUN cargo build --release 7 | 8 | FROM ubuntu:18.04 as runner 9 | WORKDIR /run 10 | COPY --from=builder /build/target/ /build/target 11 | RUN ln -s /build/target/release/goto-cd /usr/bin/goto-cd 12 | COPY tests tests/ 13 | 14 | ENV RCFILE="/root/.bashrc" 15 | 16 | CMD ["bash", "tests/test_shell.sh"] 17 | -------------------------------------------------------------------------------- /docker-tests/Dockerfile-zshtest: -------------------------------------------------------------------------------- 1 | FROM rust as builder 2 | 3 | WORKDIR /build 4 | COPY src src/ 5 | COPY Cargo.toml Cargo.toml ./ 6 | RUN cargo build --release 7 | 8 | FROM ubuntu:18.04 as runner 9 | WORKDIR /run 10 | COPY --from=builder /build/target/ /build/target 11 | RUN ln -s /build/target/release/goto-cd /usr/bin/goto-cd && \ 12 | apt update && apt install zsh 13 | COPY tests tests/ 14 | 15 | ENV RCFILE="/root/.bashrc" 16 | 17 | CMD ["zsh", "tests/test_shell.sh"] 18 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | ![PNG Normal Usage](https://github.com/CatEars/goto/raw/master/docs/tldrusage.png) 4 | 5 | To see usage in the form of GIFs, look in the [tldr folder](https://github.com/CatEars/goto/blob/master/docs/tldr/README.md) 6 | 7 | To see usage for each command in a `man` style manual, look in the [manual folder](https://github.com/CatEars/goto/tree/master/docs/manual) 8 | 9 | ## F.A.Q 10 | 11 | #### `goto` is too long to type. I just want to type `abc` instead. How to? 12 | 13 | The easiest way to accomplish this is to modify your `.zshrc` or `.bashrc` and add something like this: 14 | 15 | ```bash 16 | function abc() { 17 | goto $@ 18 | } 19 | ``` 20 | 21 | This would work for bash atleast, but you could probably find what works for zsh 22 | [here](https://unix.stackexchange.com/questions/337800/on-the-relative-merits-of-and). 23 | 24 | Of course you probably want to have tab completion as well. The above won't give 25 | you that. For bash you would add the following: 26 | 27 | ``` 28 | complete -F _GotoHelperFunction abc 29 | ``` 30 | 31 | For zsh you would add the following: 32 | 33 | ``` 34 | compdef _GotoHelperFunction abc 35 | ``` 36 | 37 | IMPORTANT NOTE: The above will have to be placed after `goto` is `source`d. 38 | Otherwise the `_GotoHelperFunction` will not be defined yet. 39 | -------------------------------------------------------------------------------- /docs/dev/release.md: -------------------------------------------------------------------------------- 1 | Developer checklist on new major release 2 | 3 | [ ] Make sure all unit tests pass 4 | [ ] Make sure all bash tests pass 5 | [ ] Make sure all zsh tests pass 6 | [ ] Make sure Travis passes all above tests 7 | [ ] Up affected version and compile 8 | [ ] Make sure uploading to test pypi can install 9 | 10 | * At this point we are confident that the commit is an okay release, it is time to release it. 11 | 12 | [ ] Create new coverage badge 13 | [ ] Update changelog with included updates 14 | [ ] Commit both above 15 | [ ] Push and make sure that Travis still passes 16 | [ ] Add tag to commit and push the tag, Travis should update PyPI 17 | [ ] Make sure that PyPI page is updated 18 | [ ] Make sure that PyPI goto-cd can be installed locally with newest version 19 | [ ] Make sure that bash/zsh tests pass with newest version 20 | 21 | -------------------------------------------------------------------------------- /docs/manual/add.md: -------------------------------------------------------------------------------- 1 | # Add 2 | 3 | ### Tags 4 | 5 | Basic 6 | 7 | ### Usage 8 | 9 | ```bash 10 | $ goto --add path/to/folder 11 | ``` 12 | 13 | or 14 | 15 | ```bash 16 | $ goto --add my_special_name:path/to/folder 17 | ``` 18 | 19 | ### Effect 20 | 21 | Will add `folder` as a teleportation target. After adding the folder you should 22 | be able to teleport to it using `$ goto folder` or `$ goto my_special_name`. 23 | Additionally, there should now also be tab completion activated for `folder` or 24 | `my_special_name`. 25 | 26 | ### Notes 27 | 28 | `Add` will only add the target to the current profile, so if you switch profiles 29 | and want to have this teleportation target, then you will need to either switch 30 | back to the same profile or `add` the teleportation target again. 31 | -------------------------------------------------------------------------------- /docs/manual/config.md: -------------------------------------------------------------------------------- 1 | # CONFIG 2 | 3 | ### Tags 4 | 5 | Basic 6 | 7 | ### Usage 8 | 9 | ```bash 10 | $ goto --config set attribute value 11 | $ goto --config get attribute 12 | $ goto --config remove attribute 13 | ``` 14 | 15 | ### Effect 16 | 17 | 1. Will set and store `attribute`/`value` in `_setting.toml` 18 | 2. Will get the `attribute` (if it exists) from `_setting.toml` 19 | 3. Will remove the `attribute` (if it exists) from `_setting.toml` 20 | 21 | ### Notes 22 | 23 | -------------------------------------------------------------------------------- /docs/manual/get.md: -------------------------------------------------------------------------------- 1 | # Get 2 | 3 | ### Tags 4 | 5 | Internals 6 | 7 | ### Usage 8 | 9 | ```bash 10 | $ goto --get nginx_logs 11 | ``` 12 | ### Effect 13 | 14 | If there is a teleportation target with the name `nginx_logs`, then this command 15 | will print the full path to that target. For example it might print 16 | `/var/log/nginx`. 17 | 18 | ### Notes 19 | 20 | This is mainly used by the internals 21 | -------------------------------------------------------------------------------- /docs/manual/goto.md: -------------------------------------------------------------------------------- 1 | # goto 2 | 3 | ### Tags 4 | 5 | Basic 6 | 7 | ### Usage 8 | 9 | ```bash 10 | $ goto teleportation_name 11 | ``` 12 | 13 | or 14 | 15 | ```bash 16 | $ goto t[tab] 17 | $ goto teleportation_name 18 | ``` 19 | 20 | ### Effect 21 | 22 | Will teleport the user to the path that `teleportation_name` points to, like a 23 | long distance `cd`. Also supports tab completion for previously added 24 | teleportation targets. 25 | 26 | ### Notes 27 | 28 | -------------------------------------------------------------------------------- /docs/manual/help.md: -------------------------------------------------------------------------------- 1 | # Help 2 | 3 | ### Tags 4 | 5 | Basic 6 | 7 | ### Usage 8 | 9 | ```bash 10 | $ goto --help 11 | ``` 12 | 13 | ### Effect 14 | 15 | Will print the help page for `goto`, listing all commands and a brief summary of 16 | what they do and how to use them. 17 | 18 | ### Notes 19 | -------------------------------------------------------------------------------- /docs/manual/install.md: -------------------------------------------------------------------------------- 1 | # Install 2 | 3 | ### Tags 4 | 5 | Basic, Run Once 6 | 7 | ### Usage 8 | 9 | ```bash 10 | $ _gotohelper --install bash 11 | ``` 12 | 13 | or 14 | 15 | ```zsh 16 | $ _gotohelper --install zsh 17 | ``` 18 | 19 | ### Effect 20 | 21 | Will install goto either under `bash` or under `zsh`. This command uses 22 | `_gotohelper` in order to bootstrap `goto` into your shell. It should only be 23 | necessary to run once and should be the first command you run. 24 | 25 | ### Notes 26 | 27 | If you want to install goto under both `bash` and `zsh` then you just run both 28 | of the commands. They use the same "backend" and only differ in that one targets 29 | your `.bashrc` and the other targets your `.zshrc`. If it helps you can think of 30 | it like two different `git` user interfaces (porcelains). Both use the same 31 | underlying data to work properly. 32 | -------------------------------------------------------------------------------- /docs/manual/list.md: -------------------------------------------------------------------------------- 1 | # List 2 | 3 | ### Tags 4 | 5 | Basic 6 | 7 | ### Usage 8 | 9 | ```bash 10 | $ goto --list 11 | ``` 12 | 13 | ### Effect 14 | 15 | Will list all different teleports you have for this profile. The left hand side 16 | will show the teleportation names and the right hand side will show the paths 17 | you teleport to. An example of what will be printed: 18 | 19 | ``` 20 | home => /home/catears 21 | ``` 22 | 23 | ### Notes 24 | -------------------------------------------------------------------------------- /docs/manual/prefix.md: -------------------------------------------------------------------------------- 1 | # Prefix 2 | 3 | ### Tags 4 | 5 | Internals 6 | 7 | ### Usage 8 | 9 | ```bash 10 | $ goto --prefix hom 11 | ``` 12 | 13 | ### Effect 14 | 15 | Will print a space separated list of all teleportation targets that are prefixed 16 | by `hom`. For example: `home homie homer_simpson`. 17 | 18 | ### Notes 19 | 20 | An internal command used to get tab-completion suggestions. 21 | -------------------------------------------------------------------------------- /docs/manual/profile.md: -------------------------------------------------------------------------------- 1 | # Profile 2 | 3 | ### Tags 4 | 5 | Advanced, Profile 6 | 7 | ### Usage 8 | 9 | ```bash 10 | $ goto --profile some_profile 11 | ``` 12 | 13 | ### Effect 14 | 15 | Will switch to using `some_profile`. This will (if not yet created) be a 16 | completely blank profile. If you need to divide your teleportations into 17 | different buckets, where you sometimes want to use teleportations from bucket #1 18 | and sometimes use teleportations from bucket #2 then this is the command for 19 | you! Every user starts out with the `default` profile, which can never be 20 | removed, so profiles is just a way to namespace your teleports. 21 | 22 | ### Notes 23 | 24 | Anything with profiles is considered advanced usage, because you normally don't 25 | need to learn that much about it to use `goto`. 26 | -------------------------------------------------------------------------------- /docs/manual/profiles.md: -------------------------------------------------------------------------------- 1 | # Profiles 2 | 3 | ### Tags 4 | 5 | Advanced, Profile 6 | 7 | ### Usage 8 | 9 | ```bash 10 | $ goto --profiles 11 | ``` 12 | 13 | ### Effect 14 | 15 | Lists all profiles and shows which one is the currently active one. 16 | 17 | 18 | ### Notes 19 | 20 | Anything with profiles is considered advanced usage, because you normally don't 21 | need to learn that much about it to use `goto`. 22 | 23 | -------------------------------------------------------------------------------- /docs/manual/remove.md: -------------------------------------------------------------------------------- 1 | # Remove 2 | 3 | ### Tags 4 | 5 | Basic 6 | 7 | ### Usage 8 | 9 | ```bash 10 | $ goto --remove some_name 11 | ``` 12 | 13 | ### Effect 14 | 15 | Will remove the teleportation target with the name `some_name`. Used to clean up 16 | and remove old teleports. Very useful if you have moved a folder to a new place 17 | and want to move your teleportation target as well. 18 | 19 | ### Notes 20 | -------------------------------------------------------------------------------- /docs/manual/rmprofile.md: -------------------------------------------------------------------------------- 1 | # Rmprofile 2 | 3 | ### Tags 4 | 5 | Advanced, Profile 6 | 7 | ### Usage 8 | 9 | ```bash 10 | $ goto --rmprofile some_profile 11 | ``` 12 | 13 | ### Effect 14 | 15 | Will remove `some_profile` so that it is no longer usable. 16 | 17 | ### Notes 18 | 19 | Rmprofile will remove a profile so think extra hard about wheter you want to 20 | remove it or not before doing so! 21 | 22 | Anything with profiles is considered advanced usage, because you normally don't 23 | need to learn that much about it to use `goto`. 24 | 25 | 26 | -------------------------------------------------------------------------------- /docs/tldr/README.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | #### TLDR for each command 4 | 5 | `goto --list` 6 | 7 | ![GIF Usage of List Command](https://github.com/CatEars/goto/raw/master/docs/tldr/list.gif) 8 | 9 | `goto --add [name:]folder` 10 | 11 | ![GIF Usage of Add Command](https://github.com/CatEars/goto/raw/master/docs/tldr/add.gif) 12 | 13 | `goto --remove name` 14 | 15 | ![GIF Usage of Remove Command](https://github.com/CatEars/goto/raw/master/docs/tldr/remove.gif) 16 | -------------------------------------------------------------------------------- /docs/tldr/add.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CatEars/goto/788e08483910b2ae2e26fd5afbe9d6fc0ce03b5c/docs/tldr/add.gif -------------------------------------------------------------------------------- /docs/tldr/list.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CatEars/goto/788e08483910b2ae2e26fd5afbe9d6fc0ce03b5c/docs/tldr/list.gif -------------------------------------------------------------------------------- /docs/tldr/remove.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CatEars/goto/788e08483910b2ae2e26fd5afbe9d6fc0ce03b5c/docs/tldr/remove.gif -------------------------------------------------------------------------------- /src/embedded.rs: -------------------------------------------------------------------------------- 1 | use rust_embed::RustEmbed; 2 | 3 | #[derive(RustEmbed)] 4 | #[folder = "src/shell/"] 5 | struct Asset; 6 | 7 | 8 | fn get_internal_shell_script(fname: &str) -> String { 9 | let asset = Asset::get(fname).unwrap(); 10 | String::from(std::str::from_utf8(asset.data.as_ref()).unwrap()) 11 | } 12 | 13 | pub fn get_bash_goto_enable_script_str() -> String { 14 | get_internal_shell_script("goto-bash.sh") 15 | } 16 | 17 | pub fn get_zsh_goto_enable_script_str() -> String { 18 | get_internal_shell_script("goto-zsh.sh") 19 | } 20 | 21 | pub fn get_selector_script_str() -> String { 22 | get_internal_shell_script("goto") 23 | } 24 | 25 | pub fn get_powershell_enable_script_str() -> String { 26 | get_internal_shell_script("goto-powershell.ps1") 27 | } 28 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate term; 2 | 3 | mod storage; 4 | mod embedded; 5 | mod paths; 6 | use clap::{App, Arg}; 7 | use std::cmp::max; 8 | use storage::{ensure_directory_structure, get_current_profile}; 9 | 10 | fn parse_opts<'a, 'b>() -> clap::App<'a, 'b> { 11 | return App::new("Goto") 12 | .version("2.1.1") 13 | .author("'CatEars' ") 14 | .about("Give your terminal teleporting powers") 15 | .arg( 16 | Arg::with_name("add") 17 | .short("a") 18 | .long("add") 19 | .help("Add a new teleport to the current profile") 20 | .takes_value(true), 21 | ) 22 | .arg( 23 | Arg::with_name("get") 24 | .short("g") 25 | .long("get") 26 | .help("Print a teleport target.") 27 | .takes_value(true), 28 | ) 29 | .arg( 30 | Arg::with_name("prefix") 31 | .long("prefix") 32 | .help("List all targets that have X as prefix") 33 | .takes_value(true), 34 | ) 35 | .arg( 36 | Arg::with_name("all-prefix") 37 | .long("all-prefix") 38 | .help("List all targets for CLI usage") 39 | .takes_value(false), 40 | ) 41 | .arg( 42 | Arg::with_name("remove") 43 | .short("r") 44 | .long("remove") 45 | .help("Remove a teleport from the current profile") 46 | .takes_value(true), 47 | ) 48 | .arg( 49 | Arg::with_name("list") 50 | .short("l") 51 | .long("list") 52 | .help("Lists all teleports") 53 | .takes_value(false), 54 | ) 55 | .arg( 56 | Arg::with_name("install") 57 | .long("install") 58 | .help("Installs `goto` function into linux shell.") 59 | .takes_value(false), 60 | ) 61 | .arg( 62 | Arg::with_name("install-powershell") 63 | .long("install-powershell") 64 | .help("Installs `goto` function into powershell") 65 | .takes_value(false), 66 | ) 67 | .arg( 68 | Arg::with_name("config-dir") 69 | .long("config-dir") 70 | .help("Prints the directory where goto configuration is stored") 71 | .takes_value(false) 72 | ); 73 | 74 | } 75 | 76 | fn list_profile() { 77 | let msg = "Need access to the terminal, but couldn't get that"; 78 | let mut t = term::stdout().expect(msg); 79 | 80 | let profile = get_current_profile().unwrap(); 81 | let max_key_length = profile.keys().map(|x| x.len()).fold(0, |a, b| max(a, b)); 82 | 83 | for k in profile.keys() { 84 | t.fg(term::color::GREEN).unwrap(); 85 | write!(t, "{: ").unwrap(); 88 | t.fg(term::color::GREEN).unwrap(); 89 | let x = profile[k].as_str().unwrap(); 90 | writeln!(t, "{}", x).unwrap(); 91 | } 92 | 93 | t.reset().unwrap(); 94 | } 95 | 96 | fn get_from_profile(key: &str) { 97 | let mut t = term::stdout().unwrap(); 98 | 99 | let mut real_key = String::from(key); 100 | if real_key.ends_with("/") { 101 | real_key.pop(); 102 | } 103 | 104 | let profile = get_current_profile().unwrap(); 105 | let ans = profile.get(&real_key); 106 | 107 | match ans { 108 | Some(x) => { 109 | let v = x.as_str().unwrap(); 110 | writeln!(t, "{}", v).unwrap(); 111 | } 112 | _ => { 113 | t.fg(term::color::RED).unwrap(); 114 | writeln!(t, "{} is not a valid teleport.", key).unwrap(); 115 | t.reset().unwrap(); 116 | std::process::exit(1); 117 | } 118 | } 119 | } 120 | 121 | 122 | fn add_teleport_to_profile(teleport_name: &str, directory_name: &str) { 123 | let term_msg = "Need access to the terminal, but couldn't get that"; 124 | let mut t = term::stdout().expect(term_msg); 125 | let path = std::path::Path::new(directory_name); 126 | 127 | if !path.is_dir() { 128 | t.fg(term::color::RED).unwrap(); 129 | writeln!(t, "Could not find {}.", directory_name).unwrap(); 130 | writeln!(t, "Is it really a directory").unwrap(); 131 | t.reset().unwrap(); 132 | std::process::exit(1); 133 | } 134 | 135 | let canonical_path = paths::canonicalize_path(&path.to_path_buf()).display().to_string(); 136 | storage::save_to_current_profile(teleport_name, &canonical_path); 137 | 138 | t.fg(term::color::GREEN).unwrap(); 139 | writeln!(t, "Added '{}' which points to '{}'", teleport_name, canonical_path).unwrap(); 140 | t.reset().unwrap(); 141 | } 142 | 143 | fn do_remove_from_profile(key: &str) { 144 | let term_msg = "Need access to the terminal, but couldn't get that"; 145 | let mut t = term::stdout().expect(term_msg); 146 | 147 | if storage::remove_from_profile(key) { 148 | t.fg(term::color::GREEN).unwrap(); 149 | writeln!(t, "'{}' is no longer a valid teleport", key).unwrap(); 150 | } else { 151 | t.fg(term::color::RED).unwrap(); 152 | writeln!(t, "'{}' is not a teleport, could not remove", key).unwrap(); 153 | t.reset().unwrap(); 154 | std::process::exit(1); 155 | } 156 | 157 | t.reset().unwrap(); 158 | } 159 | 160 | fn add_to_profile(key: &str) { 161 | let term_msg = "Need access to the terminal, but couldn't get that"; 162 | let mut t = term::stdout().expect(term_msg); 163 | 164 | let items = key.split(":").collect::>(); 165 | if items.len() == 1 { 166 | let telepath = std::path::Path::new(items[0]).to_path_buf(); 167 | let canonical = paths::canonicalize_path(&telepath); 168 | let fname = canonical.file_name().unwrap(); 169 | let as_str = fname.to_str().unwrap(); 170 | add_teleport_to_profile(as_str, items[0]); 171 | } else if items.len() == 2 { 172 | add_teleport_to_profile(items[0], items[1]); 173 | } else { 174 | t.fg(term::color::RED).unwrap(); 175 | writeln!( 176 | t, 177 | "'{}' is not formatted like expected. Format is 'teleport:directory'", 178 | key 179 | ) 180 | .unwrap(); 181 | t.reset().unwrap(); 182 | std::process::exit(1); 183 | } 184 | } 185 | 186 | fn do_prefix(key: &str) { 187 | let profile = storage::get_current_profile().unwrap(); 188 | 189 | let matches: Vec = profile 190 | .keys() 191 | .filter(|x| x.starts_with(key)) 192 | .map(|x| String::from(x)) 193 | .collect(); 194 | if matches.len() == 0 { 195 | return; 196 | } else if matches.len() == 1 { 197 | println!("{}/", matches[0]); 198 | } else { 199 | print!("{}/", matches[0]); 200 | for k in 1..matches.len() { 201 | print!(" {}/", matches[k]); 202 | } 203 | println!(""); 204 | } 205 | } 206 | 207 | fn print_source_install() { 208 | let path = paths::get_config_script_path("goto").display().to_string(); 209 | println!(""); 210 | println!("# add `goto` command to shell"); 211 | println!("if [ -f {} ]; then", path); 212 | println!(" source {}", path); 213 | println!("fi"); 214 | println!(""); 215 | } 216 | 217 | fn print_powershell_source_install() { 218 | let path = paths::get_config_script_path("goto-powershell.ps1").display().to_string(); 219 | println!(""); 220 | println!("# goto profile"); 221 | println!("$GotoPath = \"{}\"", path); 222 | println!("if (Test-Path($GotoPath)) {{"); 223 | println!(" . \"$GotoPath\""); 224 | println!("}}"); 225 | println!(""); 226 | } 227 | 228 | fn do_install() { 229 | storage::install_latest_scripts(); 230 | print_source_install() 231 | } 232 | 233 | fn do_powershell_install() { 234 | storage::install_latest_scripts(); 235 | print_powershell_source_install(); 236 | } 237 | 238 | fn do_config_dir() { 239 | let path = paths::get_config_dir().display().to_string(); 240 | println!("{}", path); 241 | } 242 | 243 | fn main() { 244 | ensure_directory_structure(); 245 | let app = parse_opts(); 246 | let matches = app.get_matches(); 247 | 248 | if let Some(x) = matches.value_of("add") { 249 | add_to_profile(x); 250 | } else if let Some(x) = matches.value_of("get") { 251 | get_from_profile(x); 252 | } else if let Some(x) = matches.value_of("remove") { 253 | do_remove_from_profile(x); 254 | } else if matches.occurrences_of("list") == 1 { 255 | list_profile(); 256 | } else if let Some(x) = matches.value_of("prefix") { 257 | do_prefix(x); 258 | } else if matches.occurrences_of("all-prefix") == 1 { 259 | do_prefix(""); 260 | } else if matches.occurrences_of("install") == 1 { 261 | do_install(); 262 | } else if matches.occurrences_of("install-powershell") == 1 { 263 | do_powershell_install(); 264 | } else if matches.occurrences_of("config-dir") == 1 { 265 | do_config_dir(); 266 | } else { 267 | parse_opts().write_help(&mut std::io::stdout()).unwrap(); 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /src/paths.rs: -------------------------------------------------------------------------------- 1 | use dirs::config_dir; 2 | 3 | pub fn get_config_dir() -> std::path::PathBuf { 4 | let mut shell_dir = config_dir().unwrap(); 5 | shell_dir.push("goto-cd"); 6 | shell_dir 7 | } 8 | 9 | pub fn get_config_toml_path(config_name: &str) -> std::path::PathBuf { 10 | let mut cfg = get_config_dir(); 11 | cfg.push(format!("{}.toml", config_name)); 12 | cfg 13 | } 14 | 15 | pub fn get_config_shell_dir() -> std::path::PathBuf { 16 | let mut shell_dir = get_config_dir(); 17 | shell_dir.push("shell"); 18 | shell_dir 19 | } 20 | 21 | pub fn get_config_script_path(shell_script_name: &str) -> std::path::PathBuf { 22 | let mut shell_path = get_config_shell_dir(); 23 | shell_path.push(shell_script_name); 24 | shell_path 25 | } 26 | 27 | #[cfg(target_family = "windows")] 28 | pub fn canonicalize_path(path: &std::path::PathBuf) -> std::path::PathBuf { 29 | dunce::canonicalize(path).unwrap() 30 | } 31 | 32 | #[cfg(target_family = "unix")] 33 | pub fn canonicalize_path(path: &std::path::PathBuf) -> std::path::PathBuf { 34 | path.canonicalize().unwrap() 35 | } -------------------------------------------------------------------------------- /src/shell/goto: -------------------------------------------------------------------------------- 1 | # This file is meant to be sourced 2 | # Entrypoint for any application on unix 3 | 4 | if [ -n "$ZSH_VERSION" ]; then 5 | __thisdir=${0:a:h} 6 | if [ -f $__thisdir/goto-zsh.sh ]; then 7 | source $__thisdir/goto-zsh.sh 8 | else 9 | echo "Inside zsh but could not find 'goto-zsh.sh!'" 10 | echo "To remove this comment you should edit .zshrc" 11 | echo "and remove any reference to goto" 12 | fi 13 | elif [ -n "$BASH_VERSION" ]; then 14 | __thisdir="$(dirname "${BASH_SOURCE[0]}")" 15 | if [ -f $__thisdir/goto-bash.sh ]; then 16 | source $__thisdir/goto-bash.sh 17 | else 18 | echo "Inside bash but could not find 'goto-bash.sh!'" 19 | echo "To remove this comment you should edit .bashrc" 20 | echo "and remove any reference to goto" 21 | fi 22 | fi 23 | -------------------------------------------------------------------------------- /src/shell/goto-bash.sh: -------------------------------------------------------------------------------- 1 | # This file is meant to be sourced 2 | # Entry point for bash based shells 3 | 4 | _GotoHelperFunction() { 5 | local cur 6 | DRIVER=goto-cd 7 | COMPREPLY=() 8 | cur=${COMP_WORDS[COMP_CWORD]} 9 | 10 | if [ $COMP_CWORD -eq 1 ]; then 11 | # User is trying to jump 12 | answers="$($DRIVER --prefix "$cur")" 13 | COMPREPLY=($answers) 14 | 15 | # Dirty hack to not expand with a space after subfolder expansion 16 | len=${#COMPREPLY[@]} 17 | if [[ $len -eq 1 ]] && [[ $answers == *"/"* ]]; then 18 | fake="x" 19 | ans="$answers $answers$fake" 20 | COMPREPLY=($ans) 21 | fi 22 | 23 | return 24 | fi 25 | 26 | if [ $COMP_CWORD -eq 2 ]; then 27 | command=${COMP_WORDS[1]} 28 | if [ "$command" = "--remove" ] && [ -n "$cur" ]; then 29 | # User is trying to remove 30 | answers="$($DRIVER --prefix "$cur")" 31 | COMPREPLY=($answers) 32 | return 33 | fi 34 | fi 35 | } 36 | 37 | function goto() { 38 | GT=goto-cd 39 | A=$1 40 | B=$2 41 | if [[ ! $A == -* ]] && [ -n "$A" ]; then 42 | # We are dealing with a goto that wants to go somewhere 43 | # The first argument is non-null and does not start with a dash! 44 | target=$($GT --get $A) 45 | if [ $? -ne 0 ]; then 46 | echo "$target" 47 | elif [ -d $target ]; then 48 | cd $target 49 | else 50 | echo "'$target' seems to not be a directory..." 51 | fi 52 | else 53 | # We are dealing with a command + argument 54 | $GT $A $B $3 $4 55 | fi 56 | } 57 | 58 | complete -F _GotoHelperFunction goto 59 | -------------------------------------------------------------------------------- /src/shell/goto-powershell.ps1: -------------------------------------------------------------------------------- 1 | 2 | function Goto() { 3 | if (($args.Count -eq 1) -and (-not ($args[0].StartsWith("-")))) { 4 | Set-Location $(goto-cd --get $args[0]) 5 | } else { 6 | goto-cd $args 7 | } 8 | } 9 | 10 | $gotoCompletionScriptBlock = { 11 | param($wordToComplete, $commandAst, $cursorPosition) 12 | if ([String]::IsNullOrEmpty($wordToComplete)) { 13 | $(goto-cd.exe --all-prefix).Split(" ") 14 | } else { 15 | $(goto-cd.exe --prefix $wordToComplete).Split(" ") 16 | } 17 | } 18 | Register-ArgumentCompleter -CommandName Goto -Native -ScriptBlock $gotoCompletionScriptBlock -------------------------------------------------------------------------------- /src/shell/goto-zsh.sh: -------------------------------------------------------------------------------- 1 | # This file is meant to be sourced 2 | # Entry point for zsh based shells 3 | 4 | function goto() { 5 | DRIVER=goto-cd 6 | A=$1 7 | B=$2 8 | if [[ ! $A == -* ]] && [ -n "$A" ]; then 9 | # We are dealing with a goto that wants to go somewhere 10 | # The first argument is non-null and does not start with a dash! 11 | target=$($DRIVER --get $A) 12 | if [ $? -ne 0 ]; then 13 | echo "$target" 14 | elif [ -d $target ]; then 15 | cd $target 16 | else 17 | echo "'$target' seems to not be a directory..." 18 | fi 19 | else 20 | # We are dealing with a command + argument 21 | $DRIVER $A $B $3 $4 22 | fi 23 | } 24 | 25 | function _GotoHelperFunction() { 26 | local context state state_descr line 27 | typeset -A opt_args 28 | 29 | _arguments -C \ 30 | "-h[Show help information]" \ 31 | "--help[Show help information]" \ 32 | "--add[Add a teleport]" \ 33 | "--remove[Remove a teleport]" \ 34 | "--install[Install for either bash or zsh]" \ 35 | "--profile[Change to a different profile]" \ 36 | "--profiles[List all profiles]" \ 37 | "--rmprofile[Remove a profile]" \ 38 | "*::arg:->string" 39 | 40 | A=$line 41 | if [[ ! $A == -* ]]; then 42 | target=($($DRIVER --prefix "$A")) 43 | _describe -t target 'teleports' target 44 | fi 45 | } 46 | 47 | compdef _GotoHelperFunction goto 48 | -------------------------------------------------------------------------------- /src/storage.rs: -------------------------------------------------------------------------------- 1 | use crate::embedded; 2 | use crate::paths; 3 | 4 | use serde::{Deserialize, Serialize}; 5 | use std::fs; 6 | use std::io::Write; 7 | use toml::value::Map; 8 | use toml::Value; 9 | 10 | #[derive(Serialize, Deserialize)] 11 | pub struct Config { 12 | pub current_profile: String, 13 | pub profiles: Vec, 14 | } 15 | 16 | fn parse_config_toml(config_name: &str) -> String { 17 | let cfg = paths::get_config_toml_path(config_name); 18 | fs::read_to_string(cfg).unwrap() 19 | } 20 | 21 | fn write_to_current_profile(profile_name: &str, profile: &Map) { 22 | let serialized = toml::to_string(profile).unwrap(); 23 | let cfg = paths::get_config_toml_path(profile_name); 24 | 25 | let mut file = fs::File::create(cfg).unwrap(); 26 | file.write_all(serialized.as_bytes()).unwrap(); 27 | } 28 | 29 | fn parse_config() -> Result { 30 | return toml::from_str(&parse_config_toml("_setting")); 31 | } 32 | 33 | fn parse_profile(profile_name: &str) -> Result, toml::de::Error> { 34 | let profile = parse_config_toml(profile_name); 35 | return toml::from_str(&profile); 36 | } 37 | 38 | fn get_current_profile_name() -> Result { 39 | return Ok(parse_config()?.current_profile); 40 | } 41 | 42 | pub fn get_current_profile() -> Result, toml::de::Error> { 43 | let current_profile = get_current_profile_name()?; 44 | return parse_profile(¤t_profile); 45 | } 46 | 47 | pub fn save_to_current_profile(teleport: &str, path: &str) { 48 | let profile_name = get_current_profile_name().unwrap(); 49 | let mut profile = parse_profile(&profile_name).unwrap(); 50 | profile.insert(String::from(teleport), toml::Value::from(path)); 51 | write_to_current_profile(&profile_name, &profile); 52 | } 53 | 54 | pub fn remove_from_profile(teleport: &str) -> bool { 55 | let profile_name = get_current_profile_name().unwrap(); 56 | let mut profile = parse_profile(&profile_name).unwrap(); 57 | 58 | let res = profile.remove(teleport); 59 | write_to_current_profile(&profile_name, &profile); 60 | match res { 61 | Some(_x) => return true, 62 | None => return false, 63 | } 64 | } 65 | 66 | pub fn ensure_directory_structure() { 67 | let cfg1 = paths::get_config_dir(); 68 | if !cfg1.exists() { 69 | fs::create_dir_all(cfg1).unwrap(); 70 | } 71 | 72 | let cfg2 = paths::get_config_toml_path("_setting"); 73 | if !cfg2.exists() { 74 | let default_config = Config { 75 | current_profile: String::from("default"), 76 | profiles: vec![String::from("default")], 77 | }; 78 | let config_str = toml::to_string(&default_config).unwrap(); 79 | let mut file = fs::File::create(cfg2).unwrap(); 80 | write!(file, "{}", config_str).unwrap(); 81 | 82 | let profile = paths::get_config_toml_path("default"); 83 | fs::File::create(profile).unwrap(); 84 | } 85 | } 86 | 87 | fn install_local_shell(fname: &str, content: String) { 88 | let executable = paths::get_config_script_path(fname); 89 | let mut file = fs::File::create(executable).unwrap(); 90 | write!(file, "{}", &content).unwrap(); 91 | } 92 | 93 | pub fn install_latest_scripts() { 94 | let shell_dir = paths::get_config_shell_dir(); 95 | if !shell_dir.exists() { 96 | fs::create_dir_all(shell_dir).unwrap(); 97 | } 98 | 99 | install_local_shell("goto", embedded::get_selector_script_str()); 100 | install_local_shell("goto-zsh.sh", embedded::get_zsh_goto_enable_script_str()); 101 | install_local_shell("goto-bash.sh", embedded::get_bash_goto_enable_script_str()); 102 | install_local_shell("goto-powershell.ps1", embedded::get_powershell_enable_script_str()); 103 | } 104 | -------------------------------------------------------------------------------- /tests/test_shell.sh: -------------------------------------------------------------------------------- 1 | export LC_ALL=C.UTF-8 2 | export LANG=C.UTF-8 3 | 4 | EXEC=$(which goto-cd) 5 | if [ -z "$EXEC" ]; then 6 | echo "Shell test failed. goto-cd not installed!" 7 | exit 1 8 | fi 9 | 10 | 11 | goto-cd --install >> $RCFILE 12 | export PS1="fake interactivity. otherwise we get kicked out of .bashrc" 13 | source $RCFILE 14 | 15 | ### Setup complete 16 | 17 | cd /tmp 18 | # Make a/b/c/d, deep levels and top levels a, b, c and d 19 | mkdir -p a/b/c/d b c d 20 | 21 | goto --add ./a 22 | 23 | if [ -z "$(goto --get a)" ]; then 24 | echo "Simple test failed, could not get 'goto --get a'" 25 | exit 1 26 | fi 27 | 28 | cd / 29 | goto a 30 | if [ "$(pwd)" != "/tmp/a" ]; then 31 | echo "Could not teleport using goto" 32 | exit 1 33 | fi 34 | 35 | goto --add t:b/c/d 36 | if ! goto --get t; then 37 | echo "Cannot use different name for teleports" 38 | exit 1 39 | fi 40 | 41 | echo "Removing teleportation 't'" 42 | goto --remove t 43 | 44 | if goto --get t; then 45 | echo "Removed teleport 't' but it is still here!" 46 | exit 1 47 | fi 48 | 49 | echo "Trying to list all teleports" 50 | goto --list 51 | 52 | cd /tmp 53 | 54 | echo "Adding aaa, aab, aac and aad and expecting prefix 'aa' to return these" 55 | goto --add aaa:a 56 | goto --add aab:b 57 | goto --add aac:c 58 | goto --add aad:d 59 | 60 | if ! goto --prefix aa; then 61 | echo "Prefix for 'aa' failed" 62 | exit 1 63 | fi 64 | 65 | echo "Getting prefix for aa and testing aaa, aab, aac and aad" 66 | RES="$(goto --prefix aa)" 67 | if [[ $RES != *"aaa"* ]] || [[ $RES != *"aab"* ]] || [[ $RES != *"aac"* ]] || [[ $RES != *"aad"* ]]; then 68 | echo "Prefix operation did not return everything!" 69 | exit 1 70 | fi 71 | 72 | echo "Getting prefix for aab, should be an exact string" 73 | RES="$(goto --prefix aab)" 74 | if [[ $RES != "aab/" ]]; then 75 | echo "Prefix could not find when a perfectly matching prefix existed" 76 | exit 1 77 | fi --------------------------------------------------------------------------------