├── 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 |
--------------------------------------------------------------------------------