├── LICENSE ├── README.md ├── _config.yml ├── cdc.plugin.bash ├── cdc.plugin.zsh └── cdc.sh /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Evan Gray 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # $ cdc [dir] 2 | > If your repositories are spread throughout your system like a pandemic, then 3 | > `cdc` is the solution! 4 | 5 | View on [GitHub](https://github.com/evanthegrayt/cdc) | 6 | [GitHub Pages](https://evanthegrayt.github.io/cdc/) 7 | 8 | # Version Update! 9 | Note that `cdc` was recently updated with breaking changes. If you're 10 | experiencing issues, please check [this section](#telling-cdc-where-to-look) of 11 | the docs, and ensure you're declaring your variables correctly. 12 | 13 | In short, the `CDC_DIRS`, `CDC_IGNORE`, and `CDC_REPO_MARKERS` used to be 14 | declared as arrays, but you can't export environmental variables as arrays; they 15 | must be a string. So if your variables looked like this: 16 | 17 | ```sh 18 | export CDC_DIRS=(/home/user/one /home/user/two) 19 | export CDC_IGNORE=(notes_directory training) 20 | export CDC_REPO_MARKERS=(.git/ .git Rakefile Makefile .hg/ .bzr/ .svn/) 21 | ``` 22 | 23 | ...they should now look like this -- a string separated by colons, like `$PATH`: 24 | 25 | ```sh 26 | export CDC_DIRS=/home/user/one:/home/user/two 27 | export CDC_IGNORE=notes_directory:training 28 | export CDC_REPO_MARKERS=.git/:.git:Rakefile:Makefile:.hg/:.bzr/:.svn/ 29 | ``` 30 | 31 | ## About 32 | ### Overview 33 | I have a few directories in which I clone repositories, so hopping from one 34 | project to another can be tedious. This plugin provides a way to change 35 | directory to any repository, regardless of where it's located, with `cdc 36 | [REPOSITORY]`. The only setup necessary is to specify which paths the plugin 37 | should check for the repository. The plugin comes with tab-completion, as long 38 | as your `zsh`/`bash` version supports it. The plugin also includes session 39 | history, and has options available that behave similar to the `pushd`, `popd`, 40 | and `dirs` commands. 41 | 42 | While this plugin was written for directories that contain repositories, you can 43 | obviously use it for adding any directories to your `cd` path. In fact, this is 44 | the default behavior, but you *can* force `cdc` to only recognize repositories 45 | with a simple [configuration change](#only-recognize-actual-repositories). 46 | 47 | ### Rationale 48 | I chose to make this function rather than editing `$CDPATH` because I don't like 49 | changing the default behavior of `cd`, but you could just as easily do the 50 | following: 51 | 52 | ```sh 53 | # Assuming `repository` exists in `/path/to/repo_dir` 54 | CDPATH=/path/to/repo_dir 55 | 56 | cd repository # will cd to /path/to/repo_dir/repository 57 | ``` 58 | 59 | Alternatively, you could make aliases: 60 | 61 | ```sh 62 | alias repository='cd /path/to/repo_dir/repository' 63 | ``` 64 | 65 | I don't like this method either, as it just pollutes your environment. In my 66 | opinion, the less aliases, the better. Also, you now have to remember an alias 67 | for each repository. `cdc` solves this issue with its tab-completion. 68 | 69 | ### Why the name "cdc"? 70 | I wanted something fast to type that wasn't already a command or builtin. You 71 | already type `cd` a million times a day, and you don't even have to move your 72 | finger to hit the c key again. You can't get much faster. 73 | 74 | ## Installation 75 | ### oh-my-zsh 76 | Clone the repository in your `$ZSH_CUSTOM/plugins` directory 77 | ```sh 78 | git clone https://github.com/evanthegrayt/cdc.git $ZSH_CUSTOM/plugins/cdc 79 | ``` 80 | Then add the plugin to your `$HOME/.zshrc` file in the `plugins` array: 81 | ```sh 82 | plugins=(cdc) # Obviously, leave your other plugins in the array. 83 | ``` 84 | 85 | ### bash-it 86 | Clone the repository in your `$BASH_IT_CUSTOM` directory 87 | ```sh 88 | git clone https://github.com/evanthegrayt/cdc.git $BASH_IT_CUSTOM/cdc 89 | ``` 90 | Files in this directory that end with `.bash` are automatically sourced, so 91 | there's nothing else to do. 92 | 93 | ### Vanilla zsh or bash 94 | Clone the repository wherever you like, and source either the `cdc.plugin.zsh` 95 | file for `zsh`, or `cdc.plugin.bash` file for `bash`, from one of your startup 96 | files, such as `~/.zshrc` or `~/.bashrc`, respectively. 97 | 98 | ```sh 99 | # Where $INSTALLATION_PATH is the path to where you installed the plugin. 100 | source $INSTALLATION_PATH/cdc.plugin.zsh # in ~/.zshrc 101 | source $INSTALLATION_PATH/cdc.plugin.bash # in ~/.bashrc 102 | ``` 103 | 104 | If you're using a version of `zsh`/`bash` that doesn't support the completion 105 | features, or you just don't want to use them, just source the `cdc.sh` file 106 | directly. 107 | 108 | ```sh 109 | source $INSTALLATION_PATH/cdc.sh # in either ~/.zshrc or ~/.bashrc 110 | ``` 111 | 112 | ## Set-up 113 | The following settings require variables to be set from a startup file, such as 114 | `~/.zshrc` or `~/.bashrc`. You can view an example of this in [my config 115 | file](https://github.com/evanthegrayt/dotfiles/blob/master/dotfiles/shellrc#L27). 116 | 117 | ### Telling cdc where to look 118 | To use this plugin, you need to `export CDC_DIRS` in a startup file. It should 119 | be a string with absolute paths to the directories to search, separated by 120 | colons (similar to `$PATH`). 121 | 122 | ```sh 123 | # Set this in ~/.zshrc or similar 124 | export CDC_DIRS=$HOME/dir_with_repos:$HOME/workspace/another_dir_with_repos 125 | ``` 126 | 127 | Note that the order of the paths in the string matters. The plugin will `cd` 128 | to the first match it finds, so if you have the same repository -- or two 129 | repositories with the same name -- in two places, the first location in the 130 | string will take precedence. There is currently an issue to better handle this 131 | "feature". Not sure how I want to go about it yet. Suggestions are very much 132 | welcome [on the issue](https://github.com/evanthegrayt/cdc/issues/6). 133 | 134 | ### Ignoring certain directories 135 | If you have directories within `CDC_DIRS` that you want the plugin to ignore, 136 | you can `export CDC_IGNORE` to a string containing those directories. These 137 | elements should only be the directory base-name, **not** the absolute path. 138 | "Ignoring" a directory will prevent it from being "seen" by `cdc`. 139 | 140 | ```sh 141 | # Assuming you never want to `cdc notes_directory`: 142 | export CDC_IGNORE=notes_directory:training 143 | ``` 144 | 145 | ### Only recognize actual repositories 146 | You can `export CDC_REPOS_ONLY` in a startup file to make `cdc` only recognize 147 | repositories as directories. This is **disabled by default**. You can also set 148 | a string of files and directories that mark what you consider a repository. 149 | Note that markers that are directories must end with a `/`, while files must 150 | not. 151 | 152 | ```sh 153 | # Enable "repos-only" mode. Note, the default is false. 154 | export CDC_REPOS_ONLY=true 155 | # Set repository markers with the following. Note, the following is already the 156 | # default, but this is how you can overwrite it in ~/.zshrc or similar. 157 | export CDC_REPO_MARKERS=.git/:.git:Rakefile:Makefile:.hg/:.bzr/:.svn/ 158 | ``` 159 | 160 | Note that this setting can be overridden with the `-r` and `-R` options. See 161 | [options](#options) below. 162 | 163 | ### Automatically pushing to the history stack 164 | By default, every `cdc` call will push the directory onto the history stack. You 165 | can disable this feature by setting `CDC_AUTO_PUSH` to `false` in a startup 166 | file. 167 | 168 | ```sh 169 | # Disable auto-pushing to history stack. 170 | export CDC_AUTO_PUSH=false 171 | ``` 172 | 173 | You can then manually push directories onto the stack with `-u`. If you have 174 | `CDC_AUTO_PUSH` set to `true`, you can still `cdc` to a directory and not push 175 | it to the stack with the `-U` option. See [options](#options) below. 176 | 177 | ### Colored Output 178 | You can enable/disable colored terminal output, and even change the colors, by 179 | adding the following lines to a startup file. 180 | 181 | ```sh 182 | export CDC_COLOR=false # Default: true. Setting to false disables colors 183 | # The following lines would make the colored output bold. 184 | export CDC_SUCCESS_COLOR='\033[1;92m' # Bold green. Default: '\033[0;32m' (green) 185 | export CDC_WARNING_COLOR='\033[1;93m' # Bold yellow. Default: '\033[0;33m' (yellow) 186 | export CDC_ERROR_COLOR='\033[1;91m' # Bold red. Default: '\033[0;31m' (red) 187 | ``` 188 | 189 | ## Usage 190 | Typing `cdc ` will list all available directories, and this list is built 191 | on the fly; nothing is hard-coded. Hit `return` after typing the directory name 192 | to change to that directory. 193 | 194 | You *can* append subdirectories, and it will work; however, this is an 195 | experimental feature, and I don't have tab-completion working for this yet (any 196 | help with [the issue](https://github.com/evanthegrayt/cdc/issues/2) would be 197 | greatly appreciated). For example: 198 | 199 | ```sh 200 | cdc repo/bin 201 | ``` 202 | 203 | If the subdirectory doesn't exist, it will `cd` to the base directory, and then 204 | print a message to `stderr`. 205 | 206 | ### Options 207 | The plugin comes with a few available options. Some are for dealing with the 208 | directory history stack, similar to `pushd`, `popd`, and `dirs`. Others are for 209 | overriding variables set in a startup file. There's also a debug mode. 210 | 211 | |Flag|What it does| 212 | |:------|:-----------| 213 | |-a|Allow the plugin to `cd` to ignored directories.| 214 | |-c|Enable colored output.| 215 | |-C|Disable colored output.| 216 | |-l|List all directories to which you can `cdc`. Same as tab-completion.| 217 | |-L|List the directories in which `cdc` will search.| 218 | |-i|List the directories that are to be ignored.| 219 | |-d|List directories in history stack. Similar to the `dirs` command.| 220 | |-n|`cd` to the current directory in the history stack.| 221 | |-t|Toggle to the last directory, similar to `cd -`. Rearranges history stack.| 222 | |-p|`cd` to previous directory in history stack. Similar to the `popd` command.| 223 | |-u|Push the directory onto the stack. Similar to the `pushd` command.| 224 | |-U|Do not push the directory onto the stack.| 225 | |-r|Only `cd` to repositories.| 226 | |-R|`cd` to the directory even if it's not a repository.| 227 | |-D|Debug mode. Enables warnings for when things aren't working as expected.| 228 | |-w|Print the directory location instead of changing to it. Like `which`.| 229 | |-h|Print help.| 230 | 231 | ## Reporting bugs 232 | If you have an idea or find a bug, please [create an 233 | issue](https://github.com/evanthegrayt/cdc/issues/new). Just make sure the topic 234 | doesn't already exist. Better yet, you can always submit a Pull Request. 235 | 236 | If you have an issue with tab-completion, make sure you have completion enabled 237 | for your shell 238 | ([bash](https://www.gnu.org/software/bash/manual/html_node/Programmable-Completion.html) 239 | / [zsh](http://zsh.sourceforge.net/Doc/Release/Completion-System.html)). If, 240 | after reading the manual, you still have problems, feel free to submit an issue. 241 | 242 | ## Self-Promotion 243 | I do these projects for fun, and I enjoy knowing that they're helpful to people. 244 | Consider starring [the repository](https://github.com/evanthegrayt/cdc) if you 245 | like it! If you love it, follow me [on github](https://github.com/evanthegrayt)! 246 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-midnight -------------------------------------------------------------------------------- /cdc.plugin.bash: -------------------------------------------------------------------------------- 1 | ## 2 | # The file to be sourced if you're using bash and want tab-completion. 3 | CDC_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 4 | 5 | ## 6 | # Source the plugin and completion functions. 7 | source $CDC_DIR/cdc.sh 8 | unset CDC_DIR 9 | 10 | ## 11 | # Bash-it plugin citations. 12 | if [[ -n $BASH_IT ]]; then 13 | cite about-plugin 14 | about-plugin '`cd` to directories from anywhere without changing $CDPATH' 15 | fi 16 | 17 | ## 18 | # Add completion arguments. 19 | complete -o nospace -W "$( _cdc_repo_list )" cdc 20 | -------------------------------------------------------------------------------- /cdc.plugin.zsh: -------------------------------------------------------------------------------- 1 | ## 2 | # The file to be sourced if you're using zsh and want tab-completion. 3 | 4 | ## 5 | # Source the plugin and completion functions. 6 | source "${0:h}/cdc.sh" 7 | 8 | ## 9 | # Add completion arguments. 10 | _cdc() { 11 | _arguments -s \ 12 | -D"[Debug mode for when unexpected things are happening]" \ 13 | - help \ 14 | -h"[Print this help]" \ 15 | - no_other_args \ 16 | -n"[cd to the current directory in the stack]" \ 17 | -p"[cd to previous directory and pop from the stack]" \ 18 | -t"[Toggle between the last two directories in the stack]" \ 19 | -i"[List all directories that are to be ignored]" \ 20 | -l"[List all directories that are cdc-able]" \ 21 | -L"[List all directories in which to search]" \ 22 | -d"[List the directories in stack]" \ 23 | - allow_arg \ 24 | -u"[Push the directory onto the stack]" \ 25 | -U"[Do not push the directory onto the stack]" \ 26 | -r"[Only cdc to repositories]" \ 27 | -R"[cd to any directory, even it is not a repository]" \ 28 | -a"[cd to the directory even if it is ignored]" \ 29 | -w"[Print directory location instead of changing to it]" \ 30 | 1::"[Directory to cd]:($(_cdc_repo_list))" 31 | } 32 | 33 | ## 34 | # Define completions. 35 | compdef '_cdc' cdc 36 | -------------------------------------------------------------------------------- /cdc.sh: -------------------------------------------------------------------------------- 1 | ## 2 | # The actual function that the user calls from the command line. 3 | # NOTE: I know this function is huge, and I hate it, but since this gets 4 | # sourced into interactive shells, I try to pollute the users' environments 5 | # with helper functions as little as possible. 6 | # 7 | # @param string $cd_dir 8 | # @return void 9 | cdc() { 10 | ## 11 | # Set local vars to avoid environment pollution. 12 | local dir 13 | local list 14 | local directory 15 | local wdir 16 | local marker 17 | local cdc_last_element 18 | local cdc_next_to_last_element 19 | local cdc_history 20 | local rc=0 21 | local cdc_list_dirs=false 22 | local cdc_list_searched_dirs=false 23 | local cdc_toggle=false 24 | local debug=false 25 | local should_return=false 26 | local allow_ignored=false 27 | local which=false 28 | local cdc_current=false 29 | local cdc_pop=false 30 | local cdc_show_history=false 31 | local cdc_list_ignored=false 32 | local print_help=false 33 | local source_config_file=false 34 | local use_color=${CDC_COLOR:-true} 35 | local cdc_dirs=($( printf "%s\n" "${CDC_DIRS//:/ }" )) 36 | local cdc_ignore=($( printf "%s\n" "${CDC_IGNORE//:/ }" )) 37 | 38 | ## 39 | # The default for auto-push is true. The user can set `CDC_AUTO_PUSH=false` 40 | # in a startup file, and manually push with `-u`. 41 | local pushdir=${CDC_AUTO_PUSH:-true} 42 | local repos_only=${CDC_REPOS_ONLY:-false} 43 | 44 | ## 45 | # When using getopts in a function, you must declare OPTIND as a local 46 | # variable, or it will only work the first time you call it. 47 | local OPTIND 48 | 49 | ## 50 | # NOTE: Experimental feature. 51 | # If argument contains a slash, it's assumed to contain subdirectories. 52 | # This splits them into the directory root and its subdirectories. 53 | if [[ $1 == */* ]]; then 54 | local subdir="${1#*/}" 55 | fi 56 | 57 | ## 58 | # Case options if present. Suppress errors because we'll supply our own. 59 | while getopts 'acCdDhilLnprRtuUw' opt 2>/dev/null; do 60 | case $opt in 61 | 62 | ## 63 | # -a: Allow cd-ing to ignored directories. 64 | a) allow_ignored=true ;; 65 | 66 | ## 67 | # -c: Enable color. 68 | c) use_color=true ;; 69 | 70 | ## 71 | # -C: Disable color. 72 | C) use_color=false ;; 73 | 74 | ## 75 | # -n: cd to the root of the current repository in the stack. 76 | n) cdc_current=true ;; 77 | 78 | ## 79 | # -l: List the directories that are cdc-able. 80 | l) cdc_list_dirs=true ;; 81 | 82 | ## 83 | # -i: List the directories that are ignored. 84 | i) cdc_list_ignored=true ;; 85 | 86 | ## 87 | # -L: List the directories that are searched. 88 | L) cdc_list_searched_dirs=true ;; 89 | 90 | ## 91 | # -t: cd to the last repo, but don't add it to the stack. 92 | t) cdc_toggle=true ;; 93 | 94 | ## 95 | # -d: List cdc history. 96 | d) cdc_show_history=true ;; 97 | 98 | ## 99 | # -p: cd to the last element in the stack and pop it from the array. 100 | p) cdc_pop=true ;; 101 | 102 | ## 103 | # -r: Force cdc to only cd to repositories. 104 | r) repos_only=true ;; 105 | 106 | ## 107 | # -R: Force cdc to NOT only cd to repositories. 108 | R) repos_only=false ;; 109 | 110 | ## 111 | # -u: Push the directory onto the history stack. 112 | u) pushdir=true ;; 113 | 114 | ## 115 | # -U: Do not push the directory onto the history stack. 116 | U) pushdir=false ;; 117 | 118 | ## 119 | # -D: Debug 120 | D) debug=true ;; 121 | 122 | ## 123 | # -w: Only display the repo's location, like which for executables. 124 | w) which=true ;; 125 | 126 | ## 127 | # -h: Print the help. 128 | h) print_help=true ;; 129 | 130 | ## 131 | # If the option isn't supported, tell the user and exit. 132 | *) 133 | _cdc_print 'error' 'Invalid option.' $debug 134 | return 1 135 | ;; 136 | esac 137 | done 138 | 139 | ## 140 | # Shift out $OPTIND so we can accurately determine how many parameters (not 141 | # options) were passed. Then, set cd_dir to $1. 142 | shift $(( OPTIND - 1 )) 143 | local cd_dir="${1%%/*}" 144 | 145 | ## 146 | # If colors are enabled, set color values if they're not already set. 147 | # TODO set a new color instead of unsetting the globals. When the globals 148 | # are unset, we can't report what they're set to in the debug screen. Pass 149 | # the variables to the _cdc_print function. 150 | if [[ $use_color == true ]]; then 151 | : ${CDC_ERROR_COLOR:='\033[0;31m'} 152 | : ${CDC_SUCCESS_COLOR:='\033[0;32m'} 153 | : ${CDC_WARNING_COLOR:='\033[0;33m'} 154 | CDC_RESET='\033[0m' 155 | ## 156 | # If colors are not enabled, unset the color variables. 157 | else 158 | unset CDC_ERROR_COLOR CDC_SUCCESS_COLOR CDC_WARNING_COLOR CDC_RESET 159 | fi 160 | 161 | if [[ $debug == true ]]; then 162 | echo "========================= ENV ===========================" 163 | printf "CDC_DIRS += ${CDC_SUCCESS_COLOR}%s$CDC_RESET\n"\ 164 | "${cdc_dirs[@]}" 165 | printf "CDC_IGNORE += ${CDC_ERROR_COLOR}%s$CDC_RESET\n"\ 166 | "${cdc_ignore[@]}" 167 | echo 168 | printf "CDC_AUTO_PUSH = %s\n" \ 169 | $( _cdc_print 'boolean' $CDC_AUTO_PUSH ) 170 | printf "CDC_REPOS_ONLY = %s\n" \ 171 | $( _cdc_print 'boolean' $CDC_REPOS_ONLY ) 172 | printf "CDC_COLOR = %s\n" \ 173 | $( _cdc_print 'boolean' $CDC_COLOR ) 174 | echo 175 | printf "CDC_SUCCESS_COLOR = $CDC_SUCCESS_COLOR%s$CDC_RESET\n"\ 176 | "$CDC_SUCCESS_COLOR" 177 | printf "CDC_WARNING_COLOR = $CDC_WARNING_COLOR%s$CDC_RESET\n"\ 178 | "$CDC_WARNING_COLOR" 179 | printf "CDC_ERROR_COLOR = $CDC_ERROR_COLOR%s$CDC_RESET\n"\ 180 | "$CDC_ERROR_COLOR" 181 | echo "======================= RUNTIME =========================" 182 | fi 183 | 184 | ## 185 | # Check for the existence of required variables that should be set in 186 | # ~/.cdcrc or a startup file. If not found, exit with non-zero return code. 187 | if (( ${#cdc_dirs[@]} == 0 )); then 188 | _cdc_print 'error' 'You must set CDC_DIRS in a config file' $debug 189 | return 1 190 | fi 191 | 192 | if [[ $print_help == true ]]; then 193 | printf "${CDC_SUCCESS_COLOR}USAGE: cdc [DIRECTORY]$CDC_RESET" 194 | printf "${CDC_WARNING_COLOR}\n\n" 195 | printf 'Flags will always override options set in ~/.cdcrc!' 196 | printf "${CDC_RESET}\n" 197 | printf " ${CDC_WARNING_COLOR}-a${CDC_RESET}" 198 | echo ' | `cd` to the directory even if it is ignored.' 199 | printf " ${CDC_WARNING_COLOR}-c${CDC_RESET}" 200 | echo ' | Enable colored output' 201 | printf " ${CDC_WARNING_COLOR}-C${CDC_RESET}" 202 | echo ' | Disable colored output' 203 | printf " ${CDC_WARNING_COLOR}-l${CDC_RESET}" 204 | echo ' | List all directories that are cdc-able.' 205 | printf " ${CDC_WARNING_COLOR}-L${CDC_RESET}" 206 | echo ' | List all directories in which to search.' 207 | printf " ${CDC_WARNING_COLOR}-i${CDC_RESET}" 208 | echo ' | List all directories that are to be ignored.' 209 | printf " ${CDC_WARNING_COLOR}-d${CDC_RESET}" 210 | echo ' | List the directories in stack.' 211 | printf " ${CDC_WARNING_COLOR}-n${CDC_RESET}" 212 | echo ' | `cd` to the current directory in the stack.' 213 | printf " ${CDC_WARNING_COLOR}-p${CDC_RESET}" 214 | echo ' | `cd` to previous directory and pop from the stack.' 215 | printf " ${CDC_WARNING_COLOR}-t${CDC_RESET}" 216 | echo ' | Toggle between the last two directories in the stack.' 217 | printf " ${CDC_WARNING_COLOR}-u${CDC_RESET}" 218 | echo ' | Push the directory onto the stack.' 219 | printf " ${CDC_WARNING_COLOR}-U${CDC_RESET}" 220 | echo ' | Do not push the directory onto the stack' 221 | printf " ${CDC_WARNING_COLOR}-r${CDC_RESET}" 222 | echo ' | 'Only cdc to repositories. 223 | printf " ${CDC_WARNING_COLOR}-R${CDC_RESET}" 224 | echo ' | cd to any directory, even it is not a repository.' 225 | printf " ${CDC_WARNING_COLOR}-D${CDC_RESET}" 226 | echo ' | Debug mode for when unexpected things are happening.' 227 | printf " ${CDC_WARNING_COLOR}-w${CDC_RESET}" 228 | echo ' | Print the directory location instead of changing to it.' 229 | printf " ${CDC_WARNING_COLOR}-h${CDC_RESET}" 230 | echo ' | Print this help.' 231 | 232 | return 0 233 | fi 234 | 235 | if [[ $cdc_list_searched_dirs == true ]]; then 236 | if [[ $debug == true ]]; then 237 | _cdc_print 'success' 'Listing searched directories.' $debug 238 | fi 239 | printf "%s\n" "${cdc_dirs[@]}" | column 240 | should_return=true 241 | fi 242 | 243 | if [[ $cdc_list_dirs == true ]]; then 244 | if [[ $debug == true ]]; then 245 | _cdc_print 'success' 'Listing available directories.' $debug 246 | fi 247 | ## 248 | # Get the list of directories. 249 | list=($( _cdc_repo_list $debug )) 250 | 251 | ## 252 | # Print the list and pipe to column for nice output. Also pad 253 | # each element to make them all at least 8 characters long. 254 | # This is done because column has issues printing strings less 255 | # than 8 bytes. 256 | printf "%-8s\n" "${list[@]}" | column 257 | 258 | should_return=true 259 | fi 260 | 261 | if [[ $cdc_toggle == true ]]; then 262 | ## 263 | # If the stack doesn't have at least two elements, tell the user. 264 | if (( ${#CDC_HISTORY[@]} < 2 )); then 265 | _cdc_print 'error' 'Not enough directories in the stack.' $debug 266 | (( rc++ )) 267 | else 268 | if [[ $debug == true ]]; then 269 | _cdc_print 'success' 'Toggling between last two directories.' \ 270 | $debug 271 | fi 272 | 273 | ## 274 | # Flip the last two elements of the array. 275 | # HACK: When you unset an element in an array, it still exists; it's 276 | # just null, so you have to re-declare the array. If anyone knows a 277 | # better way, please let me know. 278 | cdc_last_element=${CDC_HISTORY[-1]} 279 | cdc_next_to_last_element=${CDC_HISTORY[-2]} 280 | unset 'CDC_HISTORY[-1]' 281 | CDC_HISTORY=(${CDC_HISTORY[@]}) 282 | unset 'CDC_HISTORY[-1]' 283 | CDC_HISTORY=( 284 | ${CDC_HISTORY[@]} 285 | $cdc_last_element 286 | $cdc_next_to_last_element 287 | ) 288 | 289 | ## 290 | # Finally, cd to the last directory in the stack. 291 | cd ${CDC_HISTORY[-1]} 292 | fi 293 | 294 | should_return=true 295 | fi 296 | 297 | if [[ $cdc_list_ignored == true ]]; then 298 | ## 299 | # If the ignore array is empty, return. 300 | if (( ${#cdc_ignore[@]} == 0 )); then 301 | if [[ $debug == true ]]; then 302 | _cdc_print 'warn' 'No directories are being ignored.' $debug 303 | fi 304 | else 305 | if [[ $debug == true ]]; then 306 | _cdc_print 'success' 'Listing ignored directories.' $debug 307 | fi 308 | 309 | printf "%s\n" "${cdc_ignore[@]}" | column 310 | fi 311 | 312 | should_return=true 313 | fi 314 | 315 | if [[ $cdc_show_history == true ]]; then 316 | ## 317 | # If the stack is empty, tell the user. 318 | if (( ${#CDC_HISTORY[@]} == 0 )); then 319 | _cdc_print 'error' 'Stack is empty.' $debug 320 | (( rc++ )) 321 | else 322 | if [[ $debug == true ]]; then 323 | _cdc_print 'success' 'Listing directories in history.' $debug 324 | fi 325 | 326 | ## 327 | # Print the array. 328 | for cdc_history in ${CDC_HISTORY[@]}; do 329 | printf "${cdc_history##*/} " 330 | done 331 | echo 332 | fi 333 | 334 | should_return=true 335 | fi 336 | 337 | if [[ $cdc_current == true ]]; then 338 | ## 339 | # If the stack is empty, tell the user. 340 | if (( ${#CDC_HISTORY[@]} == 0 )); then 341 | _cdc_print 'error' "Stack is empty." $debug 342 | (( rc++ )) 343 | else 344 | if [[ $debug == true ]]; then 345 | _cdc_print 'success' \ 346 | 'Changing to current directory in history.' $debug 347 | fi 348 | 349 | ## 350 | # cd to the root of the last repository in the history. 351 | cd ${CDC_HISTORY[-1]} 352 | fi 353 | 354 | should_return=true 355 | fi 356 | 357 | if [[ $cdc_pop == true ]]; then 358 | ## 359 | # If there aren't enough directories to pop, notify the user. 360 | if (( ${#CDC_HISTORY[@]} == 0 )); then 361 | _cdc_print 'error' 'Stack is empty.' $debug 362 | (( rc++ )) 363 | elif (( ${#CDC_HISTORY[@]} == 1 )); then 364 | _cdc_print 'error' 'At beginning of stack.' $debug 365 | (( rc++ )) 366 | else 367 | if [[ $debug == true ]]; then 368 | _cdc_print 'success' 'Changing to last directory in history.' \ 369 | $debug 370 | fi 371 | 372 | ## 373 | # Unset the last element of the array, then re-declare it. 374 | # HACK: Again, this feels awful, but I can't get it to work for 375 | # both bash and zsh unless I do something like this. 376 | unset 'CDC_HISTORY[-1]' 377 | CDC_HISTORY=(${CDC_HISTORY[@]}) 378 | 379 | ## 380 | # cd to the previous diretory in the stack. 381 | cd ${CDC_HISTORY[-1]} 382 | fi 383 | 384 | should_return=true 385 | fi 386 | 387 | ## 388 | # If we did an action that already caused us to `cd`, return. 389 | if [[ $should_return == true ]]; then 390 | return $rc 391 | fi 392 | 393 | ## 394 | # Print usage and exit if the wrong number of arguments are passed. 395 | if (( $# != 1 )); then 396 | _cdc_print 'error' 'USAGE: cdc [DIRECTORY]' $debug 397 | _cdc_print 'error' ' Use `-h` for more help' $debug 398 | return 1 399 | fi 400 | 401 | ## 402 | # Loop through every element in $cdc_dirs. 403 | for dir in ${cdc_dirs[@]}; do 404 | 405 | ## 406 | # If a directory is in the $cdc_dirs array, but the directory doesn't 407 | # exist, print a message to stderr and move on to the next directory in 408 | # the array. 409 | if ! [[ -d $dir ]]; then 410 | if [[ $debug == true ]]; then 411 | _cdc_print 'warn' \ 412 | "$dir is in CDC_REPO_DIRS but isn't a directory." $debug 413 | fi 414 | continue 415 | fi 416 | 417 | ## 418 | # If the element is not a directory, skip it. 419 | if [[ ! -d $dir/$cd_dir ]]; then 420 | continue 421 | 422 | ## 423 | # If the directory exists, but is excluded, skip it. 424 | elif [[ $allow_ignored == false ]] && _cdc_is_excluded_dir "$cd_dir"; then 425 | if [[ $debug == true ]]; then 426 | _cdc_print 'warn' 'Match was found but it is ignored.' $debug 427 | fi 428 | continue 429 | 430 | ## 431 | # If the directory exists, but we're in repos-only mode and the 432 | # directory isn't a repo, skip it. 433 | elif [[ $repos_only == true ]] && ! _cdc_is_repo_dir "$dir/$cd_dir"; then 434 | if [[ $debug == true ]]; then 435 | _cdc_print 'warn' \ 436 | 'Match was found but it is not a repository.' $debug 437 | fi 438 | continue 439 | fi 440 | 441 | ## 442 | # By this point, the parameter obviously exists as a valid directory, 443 | # so we save it to a variable. 444 | wdir="$dir/$cd_dir" 445 | 446 | ## 447 | # If pushdir is true, add the directory to the history stack. 448 | if [[ $pushdir == true ]]; then 449 | CDC_HISTORY+=("$wdir") 450 | fi 451 | 452 | ## 453 | # If the user passed a subdirectory (if the argument had a slash in it). 454 | if [[ -n $subdir ]]; then 455 | 456 | ## 457 | # If it exists as a directory, append it to the path. 458 | if [[ -d $wdir/$subdir ]]; then 459 | wdir+="/$subdir" 460 | else 461 | 462 | ## 463 | # If it doesn't exist as a directory, print message to stderr. 464 | if [[ $debug == true ]]; then 465 | _cdc_print 'warn' "$subdir does not exist in $cd_dir." \ 466 | $debug 467 | fi 468 | fi 469 | fi 470 | 471 | ## 472 | # Finally, cd to the path, or display it if $which is true. 473 | if [[ $which == true ]]; then 474 | echo $wdir 475 | else 476 | cd "$wdir" 477 | fi 478 | 479 | ## 480 | # Return a successful code. 481 | return 0 482 | done 483 | 484 | ## 485 | # If no directory was found (the argument wasn't in the array), print 486 | # message to stderr and return unsuccessful code. 487 | _cdc_print 'error' "[$cd_dir] not found." $debug 488 | 489 | return 2 490 | } 491 | 492 | ## 493 | # Is the argument an element in $CDC_IGNORE? 494 | # 495 | # @param string $string 496 | # @return boolean 497 | _cdc_is_excluded_dir() { 498 | local element 499 | local string="$1" 500 | local cdc_ignore=($( printf "%s\n" "${CDC_IGNORE//:/ }" )) 501 | 502 | ## 503 | # If $cdc_ignore isn't defined or is empty, return "false". 504 | if [[ -z $cdc_ignore ]] || (( ${#cdc_ignore[@]} == 0 )); then 505 | return 1 506 | fi 507 | 508 | ## 509 | # Loop through each element of $CDC_IGNORE array. 510 | for element in "${cdc_ignore[@]}"; do 511 | 512 | ## 513 | # If the element matches the passed string, return "true" to indicate 514 | # it's excluded. 515 | if [[ ${element/\//} == ${string/\//} ]]; then 516 | return 0 517 | fi 518 | done 519 | 520 | ## 521 | # If nothing matched, return "false". 522 | return 1 523 | } 524 | 525 | ## 526 | # Lists repositories found in $cdc_dirs that aren't excluded. 527 | # 528 | # @param string $string 529 | # @return array 530 | _cdc_repo_list() { 531 | local dir 532 | local subdir 533 | local fulldir 534 | local directories=() 535 | local debug=${1:-false} 536 | local cdc_dirs=($( printf "%s\n" "${CDC_DIRS//:/ }" )) 537 | 538 | ## 539 | # Loop through all elements of $cdc_dirs array. 540 | for dir in "${cdc_dirs[@]}"; do 541 | 542 | ## 543 | # If the element isn't a directory that exists, move on. 544 | if ! [[ -d $dir ]]; then 545 | if [[ $debug == true ]]; then 546 | _cdc_print 'warn' \ 547 | "$dir is in CDC_REPO_DIRS but isn't a directory." 548 | fi 549 | continue 550 | fi 551 | 552 | ## 553 | # Loop through all subdirectories in the directory. 554 | for fulldir in "$dir"/*/; do 555 | 556 | ## 557 | # Remove trailing slash from directory. 558 | subdir=${fulldir%/} 559 | 560 | ## 561 | # Remove preceding directories from subdir. 562 | subdir=${subdir##*/} 563 | 564 | ## 565 | # If in repos-only mode, and directory isn't a repo, skip it. 566 | if [[ $CDC_REPOS_ONLY == true ]] && ! _cdc_is_repo_dir "$fulldir"; then 567 | continue 568 | fi 569 | 570 | ## 571 | # If the directory isn't excluded, add it to the array. 572 | if ! _cdc_is_excluded_dir "$subdir"; then 573 | directories+=("$subdir") 574 | fi 575 | done 576 | done 577 | 578 | ## 579 | # "Return" the array. 580 | echo "${directories[@]}" 581 | } 582 | 583 | ## 584 | # Is the directory a repository? 585 | # 586 | # @param string $dir 587 | # @return boolean 588 | _cdc_is_repo_dir() { 589 | local id 590 | local marker 591 | local dir="$1" 592 | local repo_markers 593 | 594 | if [[ -n $CDC_REPO_MARKERS ]]; then 595 | repo_markers=($( printf "%s\n" "${CDC_REPO_MARKERS//:/ }" )) 596 | else 597 | repo_markers=(.git/ .git Rakefile Makefile .hg/ .bzr/ .svn/) 598 | fi 599 | 600 | 601 | ## 602 | # Spin through all known repository markers. 603 | for marker in ${repo_markers[@]}; do 604 | 605 | ## 606 | # Repo identifier is the passed directory plus the known marker. 607 | id="$dir/$marker" 608 | 609 | ## 610 | # If the marker ends with a slash and it's a valid directory, or if it 611 | # doesn't end with a slash and it's a valid file, then the directory is 612 | # a repository. 613 | if [[ $id == */ && -d $id ]] || [[ $id != */ && -f $id ]]; then 614 | return 0 615 | fi 616 | done 617 | 618 | return 1 619 | } 620 | 621 | ## 622 | # Print a message with colored output. 623 | # TODO This function can definitely be DRY-ed up. 624 | # 625 | # @param string $level 626 | # @param string $message 627 | # @return void 628 | _cdc_print() { 629 | local level="$1" 630 | local message="$2" 631 | local debug="$3" 632 | 633 | ## 634 | # If we're not debugging, just print the message and return. 635 | if [[ $debug == false ]]; then 636 | echo $message 637 | return 638 | fi 639 | 640 | ## 641 | # Case the level of the message and print the appropriate color and message. 642 | case $level in 643 | 'success') 644 | printf "${CDC_SUCCESS_COLOR}SUCCESS:${CDC_RESET} $message\n" 645 | ;; 646 | 'warn') 647 | printf "${CDC_WARNING_COLOR}WARNING:${CDC_RESET} $message\n" >&2 648 | ;; 649 | 'error') 650 | printf "${CDC_ERROR_COLOR}ERROR:${CDC_RESET} $message\n" >&2 651 | ;; 652 | ## 653 | # Hijacking this function to also print our debug booleans. 654 | 'boolean') 655 | ## 656 | # If the variable is true, return with success color. 657 | if [[ $message == true ]]; then 658 | printf "${CDC_SUCCESS_COLOR}true$CDC_RESET" 659 | else 660 | printf "${CDC_ERROR_COLOR}false$CDC_RESET" 661 | fi 662 | ;; 663 | esac 664 | } 665 | 666 | ## 667 | # Source the user's config file. 668 | if [[ -f $HOME/.cdcrc ]]; then 669 | _cdc_print 'warn' "Using ~/.cdcrc is deprecated and will be removed " \ 670 | "in a future version.\nPlease set values in a config file instead." 671 | source $HOME/.cdcrc 672 | fi 673 | 674 | ## 675 | # Set the array that will remember the history. Needs to persist. 676 | CDC_HISTORY=() 677 | --------------------------------------------------------------------------------