├── .gitignore
├── MIT-LICENSE
├── README.md
├── doc
├── list.png
├── show.png
└── which.png
├── shy
└── test
├── fixtures
├── .hidden
│ └── plugin4
├── plugin1.sh
├── plugin2.sh
└── plugin3.sh
├── shy.bats
└── test_helper.bash
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
--------------------------------------------------------------------------------
/MIT-LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013-2014 Aaron Royer
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # shy - minimal shell "plugins"
2 |
3 | Shy is a small tool for managing shell (bash/zsh) confiuration that is split into several files. A "plugin" is merely a file with plain old shell aliases, functions, variables, or anything else - they work the same whether or not Shy is installed.
4 |
5 | Using Shy allows you to do the following:
6 |
7 | * Organize your shell config into groups of related functionality (plugins) in a lightweight way
8 | * Quickly find where things (functions, aliases, variables) are defined
9 | * Open your editor with the appropriate file, to make a quick change
10 | * And (not much) more!
11 |
12 | [Installation](#installation)
13 | [Usage](#usage)
14 | [Why Use This?](#why)
15 | [Command Reference](#command-reference)
16 | [Advanced Configuration](#advanced-configuration)
17 | [Credits](#credits)
18 |
19 |
20 | ## Installation
21 |
22 | 1. Download the [shy script](https://raw2.github.com/aaronroyer/shy/master/shy) and place it somewhere on your PATH (make sure it is executable)
23 | 1. Add ```eval "$(shy init)"``` in .bashrc (or .zshrc if using zsh), somewhere after configuring your PATH
24 | 1. Add some plugins! See [usage](#usage) for more info.
25 |
26 |
27 | ## Usage
28 |
29 | Once you're installed then you can used ```shy load``` to load plugin files. You will usually do this in your .bashrc/.zshrc file. Make sure Shy is initialized before loading plugins.
30 |
31 | ```
32 | shy load ~/path/to/plugin.sh
33 | ```
34 | Or maybe something like
35 |
36 | ```
37 | for plugin in ~/.shell_plugins/* do
38 | shy load $plugin
39 | done
40 | ```
41 |
42 | Shy will source the files as normal, but also record all of the aliases, functions, and variables first defined in the file.
43 |
44 | Now you can view a list of your plugins. The name of a plugin is the base file name with any extension removed.
45 |
46 | 
47 |
48 | You can examine the details of a plugin.
49 |
50 | 
51 |
52 | Use ```which``` if you want to know where something is defined.
53 |
54 | ```
55 | $ shy which glb
56 | glb is a function in the plugin git
57 | $ shy which gd
58 | gs is an alias in the plugin git
59 | ```
60 |
61 | You can open a plugin (or anything else) in your EDITOR.
62 |
63 | ```
64 | $ shy edit git
65 | # (opens the git plugin source file in your editor)
66 |
67 | $ shy edit gs
68 | # (opens the git plugin source file, where the alias gs is defined)
69 | ```
70 |
71 |
72 | ## Why Use This?
73 |
74 | You should try Shy if you like to maintain your own shell config, want to split things into separate files, want to be able to keep track of it all and make tweaks with ease, and don't want something heavy to do it.
75 |
76 | If you want lots of crazy/awesome power-user features you might like something like [composure](https://github.com/erichs/composure) instead. If you use zsh and just want to dump a ton of functionality that someone else wrote into your shell and be done with it then you could use [oh-my-zsh](https://github.com/robbyrussell/oh-my-zsh).
77 |
78 | I prefer to keep a set of often-used tools that I've put together myself (with little bits I've stolen) and manage them with simple tools.
79 |
80 |
81 | ## Command Reference
82 |
83 | #### shy load
84 |
85 | Load a plugin file. This simply sources the file and Shy records the aliases, functions, and variables first defined in the file.
86 |
87 | This should normally be used in your shell configuration files (like .bashrc or .zshrc) and not interactively.
88 |
89 | ---
90 |
91 | #### shy list
92 |
93 | List the plugins that that have previously been loaded with ```load```. Names of plugins are the plugin file names with any extension removed.
94 |
95 | For example:
96 |
97 | ```~/.shell_plugins/git.sh``` is a plugin named ```git```
98 |
99 | ```~/.shell_plugins/rails``` is a plugin named ```rails```
100 |
101 | ---
102 |
103 | #### shy show \
104 |
105 | Show the details for the plugin. This prints the path of the plugin file, and lists the aliases, functions, and variables defined in the plugin.
106 |
107 | ---
108 |
109 | #### shy edit \
110 |
111 | Open a plugin file in your editor. When given a plugin name, the plugin file is opened. When given the name of an alias, function, or variable the plugin file where that item is defined is opened in the editor.
112 |
113 | To find an editor to use, Shy first checks the variable ```SHY_EDITOR```, then ```EDITOR```, then falls back on ``vim```.
114 |
115 | ---
116 |
117 | #### shy which \
118 |
119 | Print what plugin an alias, function, or variable is defined in.
120 |
121 | ---
122 |
123 | #### shy help \[\\]
124 |
125 | Prints help for a specific command, or general help with no command name given.
126 |
127 |
128 | ## Advanced Configuration
129 |
130 | If you want everything to work even if Shy is not installed (like if you sync your dotfiles but don't sync Shy along with it) then you can add a fallback function that simply sources your plugin files. This way your shell works the same even if ```shy``` isn't on your ```PATH```.
131 |
132 | ```
133 | if which shy &> /dev/null; then
134 | eval "$(shy init)"
135 | else
136 | shy() { [ "$1" = 'load' ] && source "$2"; }
137 | fi
138 | ```
139 |
140 | ### Environment Variables
141 |
142 | ```SHY_EDITOR``` - sets the editor to use with Shy
143 |
144 | ```SHY_DEBUG``` - prints debug info to standard out when loading plugins
145 |
146 | ```SHY_CACHE_DIR``` - Shy caches plugins in ```~/.shy_plugin_cache```for faster startup; this overrides the default location
147 |
148 | ```SHY_NO_COLOR``` - Set this to anything not empty to prevent Shy from coloring any output
149 |
150 |
151 | ## Credits
152 |
153 | Shy uses [Bats](https://github.com/sstephenson/bats) for automated testing. You should check it out if you're doing anything non-trivial with Bash.
154 |
155 | ## License
156 |
157 | Shy is MIT licensed
158 |
--------------------------------------------------------------------------------
/doc/list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronroyer/shy/3367bb3a9b84f7e73b33fb209d4493a0e985bda9/doc/list.png
--------------------------------------------------------------------------------
/doc/show.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronroyer/shy/3367bb3a9b84f7e73b33fb209d4493a0e985bda9/doc/show.png
--------------------------------------------------------------------------------
/doc/which.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronroyer/shy/3367bb3a9b84f7e73b33fb209d4493a0e985bda9/doc/which.png
--------------------------------------------------------------------------------
/shy:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | #
3 | # Installation: Make this file is executable and add it to your PATH somewhere.
4 | # Then place the following in your shell rc file.
5 | #
6 | # eval "$(shy init)"
7 | #
8 | #
9 | # shy 0.0.2
10 | # Usage: shy []
11 | #
12 | # Shy enables minimal shell plugin management
13 | #
14 | # Most useful commands:
15 | # load Load a plugin file
16 | # list List the plugins that have been loaded
17 | # show Show the contents of a plugin
18 | # edit Open a plugin file with your editor
19 | # which Show which plugin a function, alias, or variable belongs to
20 | #
21 | # Use `shy help ' for information on a specific command.
22 | # Full documentation: https://github.com/aaronroyer/shy#readme
23 | set -e
24 |
25 | if [ -t 1 ] && [ -z "$SHY_NO_COLOR" ]; then
26 | c_reset=$(tput sgr0)
27 | c_bold=$(tput bold)
28 | c_blue=$(tput setaf 4)
29 | c_white=$(tput setaf 7)
30 | fi
31 |
32 | ### Utilities
33 |
34 | warn() {
35 | echo "shy: $@" 1>&2;
36 | }
37 |
38 | die() {
39 | warn "$@"
40 | exit 1
41 | }
42 |
43 | debug() {
44 | [ -n "$SHY_DEBUG" ] && warn "$@"
45 | return 0
46 | }
47 |
48 | # Get data for a plugin with the given name
49 | get_plugin_data() {
50 | local name=$1
51 | echo $SHY_PLUGIN_DATA | tr '|' '\n' | grep "^${name};"
52 | }
53 |
54 | plugin_field() {
55 | echo $1 | cut -d ';' -f $2
56 | }
57 |
58 | plugin_name() { plugin_field "$1" 1; }
59 | plugin_file() { plugin_field "$1" 2; }
60 | plugin_aliases() { plugin_field "$1" 3 | tr ':' '\n'; }
61 | plugin_functions() { plugin_field "$1" 4 | tr ':' '\n'; }
62 | plugin_variables() { plugin_field "$1" 5 | tr ':' '\n'; }
63 |
64 | realpath() {
65 | local native=$(type -p grealpath realpath | head -1)
66 | if [ -n "$native" ]; then
67 | $native "$1"
68 | else
69 | [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"
70 | fi
71 | }
72 |
73 | # Returns 0 if an exact match is included in a list, non-zero otherwise.
74 | includes() {
75 | echo "$1" | grep -q "^$2$"
76 | }
77 |
78 | # Takes a semicolon-delimited list and prints it with a nice title. Prints
79 | # nothing if the list is empty.
80 | fancy_list() {
81 | local items="$1" title="$2"
82 | [ -z "$items" ] && return
83 | echo -e "\n${c_bold}${c_blue}== ${c_white}$title${c_blue} ==${c_reset}"
84 | echo "$items"
85 | }
86 |
87 | singularize_type() {
88 | case "$1" in
89 | aliases) echo alias ;;
90 | functions) echo function ;;
91 | variables) echo variable ;;
92 | esac
93 | }
94 |
95 | # Takes the name of an item (function, alias, or variable) and echos the names
96 | # of any plugins it is included in, if any, followed by the type of item. The
97 | # plugin name and type of item are separated by a colon.
98 | #
99 | # Example
100 | # find_item my_func
101 | # => my_plugin:function
102 | find_item() {
103 | local item_name="$1"
104 | for plugin_name in $(list_plugins); do
105 | local plugin=$(get_plugin_data $plugin_name)
106 | for item_type in aliases functions variables; do
107 | if includes "$(plugin_${item_type} "$plugin")" $item_name; then
108 | echo "$plugin_name:$(singularize_type $item_type)"
109 | return
110 | fi
111 | done
112 | done
113 | return 1
114 | }
115 |
116 | plugin_cache_dir() {
117 | if [ -n "$SHY_CACHE_DIR" ]; then
118 | echo "$SHY_CACHE_DIR"
119 | return
120 | fi
121 |
122 | echo "$HOME/.shy_plugin_cache"
123 | }
124 |
125 | initialize_cache() {
126 | local cache_dir=$(plugin_cache_dir)
127 | if [ ! -d "$cache_dir" ]; then
128 | mkdir "$cache_dir" || { warn "Could not create cache directory: $cache_dir"; return 1; }
129 | fi
130 | }
131 |
132 | plugin_name_for_path() {
133 | basename "${1%.*}"
134 | }
135 |
136 | cache_file_path() {
137 | local plugin_name="$(plugin_name_for_path "$1")"
138 | echo "$(plugin_cache_dir)/$plugin_name"
139 | }
140 |
141 | write_cache() {
142 | local plugin_path="$1" plugin_content="$2"
143 | initialize_cache || return 1
144 | local plugin_cache_file=$(cache_file_path "$plugin_path")
145 | echo "$plugin_content" > "$plugin_cache_file"
146 | }
147 |
148 | read_cache() {
149 | local plugin_path="$1"
150 | [ -d "$(plugin_cache_dir)" ] || return 1
151 | local plugin_cache_file=$(cache_file_path "$plugin_path")
152 | [ -f "$plugin_cache_file" ] || return 1
153 | [ "$plugin_path" -nt "$plugin_cache_file" ] && return 1
154 | cat "$plugin_cache_file"
155 | }
156 |
157 | # Prints comments from this file starting at the given line number and stopping
158 | # when the next line does not start with a comment.
159 | print_comments_starting_at_line() {
160 | cat "$BASH_SOURCE" | tail -n "+$1" | awk '{if (/^#/) print; else exit}' | cut -c3-
161 | echo ''
162 | }
163 |
164 | ### Top-level functions
165 |
166 | # Usage: shy list
167 | #
168 | # Lists the names of plugins that have been previously loaded with
169 | # `shy load '. The name of a plugin is the file name with
170 | # any extension removed.
171 | list_plugins() {
172 | echo $SHY_PLUGIN_DATA | tr '|' '\n' | cut -d ';' -f 1
173 | }
174 |
175 | # Usage: shy show
176 | #
177 | # Prints information about the plugin with the given name, including the
178 | # name and file path, as well as the functions, aliases, and variables
179 | # defined in the plugin.
180 | show_plugin() {
181 | local plugin_data=$(get_plugin_data $1)
182 | [ -z "$plugin_data" ] && die "Unknown plugin name: $1"
183 | local name source_file aliases funcs vars
184 | name=$(plugin_name "$plugin_data")
185 | source_file=$(plugin_file "$plugin_data")
186 | aliases=$(plugin_aliases "$plugin_data")
187 | funcs=$(plugin_functions "$plugin_data")
188 | vars=$(plugin_variables "$plugin_data")
189 |
190 | echo "${c_bold}Plugin name:${c_reset} $name"
191 | echo "${c_bold}Source file:${c_reset} $source_file"
192 | fancy_list "$aliases" 'Aliases'
193 | fancy_list "$funcs" 'Functions'
194 | fancy_list "$vars" 'Variables'
195 | echo ''
196 | }
197 |
198 | # Usage: shy edit
199 | #
200 | # Opens a plugin file in your editor.
201 | #
202 | # If the name of a plugin is given, the file for the plugin with with that
203 | # name is opened.
204 | #
205 | # If the names of a function, alias, or variable defined in a function is
206 | # given, then the plugin file containing the item is opened.
207 | #
208 | # The editor used will be chosen from the SHY_EDITOR environment variable
209 | # or the EDITOR environment variable, in that order. If neither is set then
210 | # vi is used.
211 | edit_plugin() {
212 | local item_name="$1"
213 | local plugin_data=$(get_plugin_data $item_name)
214 | if [ -z "$plugin_data" ]; then
215 | local plugin_name_and_item_type=$(find_item $item_name)
216 | if [ -n "$plugin_name_and_item_type" ]; then
217 | plugin_data=$(get_plugin_data ${plugin_name_and_item_type%:*})
218 | fi
219 | fi
220 | [ -z "$plugin_data" ] && die "Unknown plugin, function, alias, or variable: $1"
221 |
222 | local editor=$SHY_EDITOR
223 | [ -z "$editor" ] && editor=$EDITOR
224 | [ -z "$editor" ] && editor=vi
225 |
226 | $editor $(plugin_file "$plugin_data")
227 | }
228 |
229 | # Usage: shy which
230 | #
231 | # Finds which plugin a given function, alias, or variable is defined in.
232 | which_plugin() {
233 | local item_name="$1"
234 | local plugin_name_and_item_type=$(find_item $item_name)
235 | if [ -n "$plugin_name_and_item_type" ]; then
236 | local plugin_name=${plugin_name_and_item_type%:*}
237 | local item_type=${plugin_name_and_item_type##*:}
238 | local article=a
239 | [ "$item_type" = "alias" ] && article=an
240 | echo "$item_name is $article $item_type in the plugin $plugin_name"
241 | else
242 | echo "($item_name not found in any plugin)" >&2
243 | return 1
244 | fi
245 | }
246 |
247 | # Usage: shy help []
248 | #
249 | # Prints help information.
250 | #
251 | # With no command name given, basic commands and usage are printed.
252 | #
253 | # If a command name is given, then usage for that command is printed.
254 | print_help() {
255 | local cmd="$1"
256 | if [ -n "$cmd" ]; then
257 | local doc_first_line=$(grep -n "^# Usage: shy $cmd" "$BASH_SOURCE")
258 | if [ -n "$doc_first_line" ]; then
259 | local doc_first_line_number=$(echo "$doc_first_line" | cut -d ':' -f 1)
260 | print_comments_starting_at_line $doc_first_line_number
261 | else
262 | die "No such command '$cmd'"
263 | fi
264 | else
265 | print_comments_starting_at_line 9
266 | fi
267 | }
268 |
269 | # (Documentation comments for load command included here, even though it is defined
270 | # in the init section, so that it is parseable with the rest of the top-level commands)
271 |
272 | # Usage: shy load
273 | #
274 | # Loads a plugin from the given file.
275 | #
276 | # A plugin is merely a file with normal shell code. When loaded, Shy sources the
277 | # file and records the names of the functions, aliases, and environment variables
278 | # defined (for the first time) while sourcing the file.
279 |
280 | # Usage: shy init
281 | #
282 | # Prints Shy code that must be sourced in the interactive shell environment.
283 | #
284 | # This should generally only be used in the following initial configuration of
285 | # Shy, which should be placed in your shell's rc file, after the main shy
286 | # executable is placed on the PATH.
287 | #
288 | # eval "$(shy init)"
289 | print_initialization() {
290 | cat <&2
338 | fi
339 | ;;
340 |
341 | _err)
342 | shift
343 | [ -n "\$@" ] && { echo "\$@" >&2; }
344 | return 1
345 | ;;
346 |
347 | _pathify)
348 | echo \$2 | tr "\$IFS" ':' | sed 's/:\$//'
349 | ;;
350 |
351 | _aliases)
352 | local raw_aliases
353 | if [ -n "\$ZSH_VERSION" ]; then
354 | raw_aliases="\$(alias)"
355 | else
356 | raw_aliases="\$(alias | cut -d ' ' -f 2-)"
357 | fi
358 | shy _pathify "\$(echo "\$raw_aliases" | cut -d '=' -f 1 | sort)"
359 | ;;
360 |
361 | _functions)
362 | local funcs
363 | if [ -n "\$ZSH_VERSION" ]; then
364 | funcs="\$(print -l \${(ok)functions})"
365 | else
366 | funcs="\$(typeset -F | cut -d ' ' -f 3)"
367 | fi
368 | shy _pathify "\$funcs"
369 | ;;
370 |
371 | _variables)
372 | shy _pathify "\$(env | cut -d '=' -f 1 | sort)"
373 | ;;
374 |
375 | # Capture names of aliases, functions, and variables in the current environment
376 | _capture-env)
377 | echo "\$(shy _aliases);\$(shy _functions);\$(shy _variables)"
378 | ;;
379 |
380 | # Run the given command command and detect additions to the environment that occurred
381 | _detect-env-additions)
382 | local before after additions additions_for_type
383 | before=\$(shy _capture-env)
384 | shift
385 | "\$@"
386 | after=\$(shy _capture-env)
387 | # TODO clean this up
388 | for n in 1 2 3; do
389 | additions_for_type=\$(comm -13 <(echo \$before | cut -d ';' -f "\$n" | tr ':' '\n') <(echo \$after | cut -d ';' -f "\$n" | tr ':' '\n'))
390 | additions="\${additions}\$(shy _pathify "\$additions_for_type")"
391 | [ "\$n" -lt "3" ] && additions="\$additions;"
392 | done
393 | # Ugh, figure out a way to not use this?
394 | SHY_TMP_DATA="\$additions"
395 | ;;
396 |
397 | *)
398 | command shy "\$@"
399 | ;;
400 |
401 | esac
402 | }
403 | EOS
404 | }
405 |
406 | if [ -z "$1" ]; then
407 | print_help
408 | else
409 | case "$1" in
410 | init) print_initialization ;;
411 |
412 | list) list_plugins ;;
413 | show|edit|which) ${1}_plugin "$2" ;;
414 | help) shift; print_help "$@" ;;
415 |
416 | _realpath|_read_cache|_write_cache)
417 | cmd=${1:1}
418 | shift
419 | $cmd "$@" ;;
420 |
421 | esac
422 | fi
423 |
--------------------------------------------------------------------------------
/test/fixtures/.hidden/plugin4:
--------------------------------------------------------------------------------
1 | alias plugin4_alias1='echo "plugin4 alias1"'
2 |
3 | plugin4_func1() {
4 | echo 'plugin4 func1'
5 | }
6 |
7 | export PLUGIN4_VAR1='plugin4 var1'
8 |
--------------------------------------------------------------------------------
/test/fixtures/plugin1.sh:
--------------------------------------------------------------------------------
1 | alias plugin1_alias1='echo "plugin1 alias1"'
2 | alias plugin1_alias2='echo "plugin1 alias2"'
3 |
4 | plugin1_func1() {
5 | echo 'plugin1 func1'
6 | }
7 |
8 | plugin1_func2() {
9 | echo 'plugin1 func2'
10 | }
11 |
12 | export PLUGIN1_VAR1='plugin1 var1'
13 | export PLUGIN1_VAR2='plugin1 var2'
14 |
--------------------------------------------------------------------------------
/test/fixtures/plugin2.sh:
--------------------------------------------------------------------------------
1 | alias plugin2_alias1='echo "plugin2 alias1"'
2 |
3 | plugin2_func1() {
4 | echo 'plugin2 func1'
5 | }
6 |
--------------------------------------------------------------------------------
/test/fixtures/plugin3.sh:
--------------------------------------------------------------------------------
1 | alias plugin3_alias1='echo "plugin3 alias1"'
2 |
3 | export PLUGIN3_VAR1='plugin3 var1'
4 |
--------------------------------------------------------------------------------
/test/shy.bats:
--------------------------------------------------------------------------------
1 | load test_helper
2 |
3 | @test "_detect-env-additions detects added aliases, functions, and variables from sourced files" {
4 | shy _detect-env-additions source "$FIXTURES_DIR/plugin1.sh"
5 | assert_equal "plugin1_alias1:plugin1_alias2;plugin1_func1:plugin1_func2;PLUGIN1_VAR1:PLUGIN1_VAR2" "$SHY_TMP_DATA"
6 |
7 | shy _detect-env-additions source "$FIXTURES_DIR/plugin2.sh"
8 | assert_equal "plugin2_alias1;plugin2_func1;" "$SHY_TMP_DATA"
9 | }
10 |
11 | @test "_write_cache writes plugin cache for plugin file path" {
12 | local plugin_file='/Users/aaron/.shell.d/plugin2.sh'
13 | local plugin_content='plugin2_alias1;plugin2_func1;'
14 | local cached_plugin_file="$SHY_CACHE_DIR/plugin2"
15 | [ -d "$SHY_CACHE_DIR" ] && rmdir "$SHY_CACHE_DIR"
16 |
17 | run shy _write_cache $plugin_file "$plugin_content"
18 | assert_success
19 | [ -d "$SHY_CACHE_DIR" ]
20 |
21 | assert_equal $plugin_content "$(cat $cached_plugin_file)"
22 | }
23 |
24 | @test "_read_cache reads plugin cache for plugin file path, if fresh" {
25 | local plugin_file="$BATS_TMPDIR/plugin2.sh"
26 | cat "$FIXTURES_DIR/plugin1.sh" > $plugin_file
27 | local plugin_content='plugin2_alias1;plugin2_func1;'
28 | local cached_plugin_file="$SHY_CACHE_DIR/plugin2"
29 | echo $plugin_content > "$cached_plugin_file"
30 |
31 | make_modified_in_past $plugin_file
32 | touch $cached_plugin_file
33 |
34 | run shy _read_cache $plugin_file
35 | assert_success
36 | assert_output $plugin_content
37 | }
38 |
39 | @test "_read_cache does not read plugin cache for plugin file path, if stale" {
40 | local plugin_file="$BATS_TMPDIR/plugin2.sh"
41 | cat "$FIXTURES_DIR/plugin1.sh" > $plugin_file
42 | local plugin_content='plugin2_alias1;plugin2_func1;'
43 | local cached_plugin_file="$SHY_CACHE_DIR/plugin2"
44 | echo $plugin_content > "$cached_plugin_file"
45 |
46 | touch $plugin_file
47 | make_modified_in_past $cached_plugin_file
48 |
49 | run shy _read_cache $plugin_file
50 | assert_failure
51 | assert_output ""
52 | }
53 |
54 | @test "load saves plugin information for first plugin" {
55 | local plugin_file="$FIXTURES_DIR/plugin1.sh"
56 | shy load $plugin_file
57 | assert_equal "plugin1;$plugin_file;plugin1_alias1:plugin1_alias2;plugin1_func1:plugin1_func2;PLUGIN1_VAR1:PLUGIN1_VAR2" "$SHY_PLUGIN_DATA"
58 | }
59 |
60 | @test "load saves plugin information for additional plugins" {
61 | load_plugin plugin1
62 | local plugin_file="$FIXTURES_DIR/plugin2.sh"
63 | local new_plugin_data="$SHY_PLUGIN_DATA|plugin2;$plugin_file;plugin2_alias1;plugin2_func1;"
64 |
65 | shy load $plugin_file
66 | assert_equal "$new_plugin_data" "$SHY_PLUGIN_DATA"
67 |
68 | plugin_file="$FIXTURES_DIR/plugin3.sh"
69 | new_plugin_data="$SHY_PLUGIN_DATA|plugin3;$plugin_file;plugin3_alias1;;PLUGIN3_VAR1"
70 |
71 | shy load $plugin_file
72 | assert_equal "$new_plugin_data" "$SHY_PLUGIN_DATA"
73 | }
74 |
75 | @test "plugins do not need a file extension (and work in hidden directories)" {
76 | local plugin_file="$FIXTURES_DIR/.hidden/plugin4"
77 | shy load $plugin_file
78 | assert_equal "plugin4;$plugin_file;plugin4_alias1;plugin4_func1;PLUGIN4_VAR1" "$SHY_PLUGIN_DATA"
79 | }
80 |
81 | @test "load returns failure status without a file name" {
82 | run shy load
83 | assert_failure "Usage: shy load PLUGIN_NAME"
84 | }
85 |
86 | @test "load returns failure status with nonexistent file" {
87 | local bogus_file="$FIXTURES_DIR/file_that_does_not_exist.sh"
88 | run shy load "$bogus_file"
89 | assert_failure "shy: file does not exist: $bogus_file"
90 | }
91 |
92 | @test "load reads data from cache if it exists and is fresh" {
93 | local plugin_file="$FIXTURES_DIR/plugin1.sh"
94 | local cached_plugin_content='cached_alias1;cached_func1;'
95 | local cached_plugin_file="$SHY_CACHE_DIR/plugin1"
96 | echo $cached_plugin_content > "$cached_plugin_file"
97 |
98 | make_modified_in_past $plugin_file
99 | touch $cached_plugin_file
100 |
101 | shy load $plugin_file
102 | assert_equal "plugin1;$plugin_file;$cached_plugin_content" "$SHY_PLUGIN_DATA"
103 | }
104 |
105 | @test "load does not read data from cache if it exists and is stale" {
106 | local plugin_file="$FIXTURES_DIR/plugin1.sh"
107 | local cached_plugin_content='cached_alias1;cached_func1;'
108 | local cached_plugin_file="$SHY_CACHE_DIR/plugin1"
109 | echo $cached_plugin_content > "$cached_plugin_file"
110 |
111 | make_modified_in_past $cached_plugin_file
112 | touch $plugin_file
113 |
114 | shy load $plugin_file
115 | assert_equal "plugin1;$plugin_file;plugin1_alias1:plugin1_alias2;plugin1_func1:plugin1_func2;PLUGIN1_VAR1:PLUGIN1_VAR2" "$SHY_PLUGIN_DATA"
116 | }
117 |
118 | @test "load writes to cache if not reading from cache" {
119 | local plugin_file="$FIXTURES_DIR/plugin1.sh"
120 | local cached_plugin_file="$SHY_CACHE_DIR/plugin1"
121 | rmdir "$SHY_CACHE_DIR"
122 | shy load $plugin_file
123 | [ -d "$SHY_CACHE_DIR" ]
124 | [ -f "$cached_plugin_file" ]
125 | assert_equal "plugin1_alias1:plugin1_alias2;plugin1_func1:plugin1_func2;PLUGIN1_VAR1:PLUGIN1_VAR2" "$(cat $cached_plugin_file)"
126 | }
127 |
128 | @test "list lists all loaded plugin names" {
129 | load_plugins plugin1 plugin2 plugin3
130 |
131 | run shy list
132 | assert_success
133 | assert_output "plugin1
134 | plugin2
135 | plugin3"
136 | }
137 |
138 | @test "show prints info for a plugin" {
139 | load_plugins plugin1 plugin2 plugin3
140 | run shy show plugin1
141 | assert_success
142 | assert_output "Plugin name: plugin1
143 | Source file: $FIXTURES_DIR/plugin1.sh
144 |
145 | == Aliases ==
146 | plugin1_alias1
147 | plugin1_alias2
148 |
149 | == Functions ==
150 | plugin1_func1
151 | plugin1_func2
152 |
153 | == Variables ==
154 | PLUGIN1_VAR1
155 | PLUGIN1_VAR2"
156 | }
157 |
158 | @test "show returns failure status for nonexistent plugin" {
159 | load_plugins plugin1 plugin2 plugin3
160 | run shy show bogus_plugin
161 | assert_failure
162 | assert_output "shy: Unknown plugin name: bogus_plugin"
163 | }
164 |
165 | @test "edit opens editor for plugin" {
166 | load_plugins plugin1 plugin2 plugin3
167 | # echo the file path to make sure we get the right one in the output
168 | export SHY_EDITOR=echo
169 | run shy edit plugin1
170 | assert_success
171 | assert_output "$FIXTURES_DIR/plugin1.sh"
172 | }
173 |
174 | @test "edit opens editor with plugin containing a function" {
175 | load_plugins plugin1 plugin2 plugin3
176 | export SHY_EDITOR=echo
177 | run shy edit plugin1_func1
178 | assert_success
179 | assert_output "$FIXTURES_DIR/plugin1.sh"
180 | }
181 |
182 | @test "edit opens editor with plugin containing an alias" {
183 | load_plugins plugin1 plugin2 plugin3
184 | export SHY_EDITOR=echo
185 | run shy edit plugin2_alias1
186 | assert_success
187 | assert_output "$FIXTURES_DIR/plugin2.sh"
188 | }
189 |
190 | @test "edit opens editor with plugin containing a variable" {
191 | load_plugins plugin1 plugin2 plugin3
192 | export SHY_EDITOR=echo
193 | run shy edit PLUGIN3_VAR1
194 | assert_success
195 | assert_output "$FIXTURES_DIR/plugin3.sh"
196 | }
197 |
198 | @test "edit returns failure status for nonexistent plugin or item" {
199 | load_plugins plugin1 plugin2 plugin3
200 | export SHY_EDITOR=echo
201 | run shy edit bogus_plugin
202 | assert_failure
203 | assert_output "shy: Unknown plugin, function, alias, or variable: bogus_plugin"
204 | }
205 |
206 | @test "which finds where an alias is defined" {
207 | load_plugins plugin1 plugin2 plugin3
208 | run shy which plugin3_alias1
209 | assert_success
210 | assert_output "plugin3_alias1 is an alias in the plugin plugin3"
211 | }
212 |
213 | @test "which finds where an function is defined" {
214 | load_plugins plugin1 plugin2 plugin3
215 | run shy which plugin2_func1
216 | assert_success
217 | assert_output "plugin2_func1 is a function in the plugin plugin2"
218 | }
219 |
220 | @test "which finds where a variable is defined" {
221 | load_plugins plugin1 plugin2 plugin3
222 | run shy which PLUGIN1_VAR1
223 | assert_success
224 | assert_output "PLUGIN1_VAR1 is a variable in the plugin plugin1"
225 | }
226 |
227 | @test "which returns failure status and error message when nothing is found" {
228 | load_plugins plugin1 plugin2 plugin3
229 | run shy which bogus_something_or_other
230 | assert_failure
231 | assert_output "(bogus_something_or_other not found in any plugin)"
232 | }
233 |
--------------------------------------------------------------------------------
/test/test_helper.bash:
--------------------------------------------------------------------------------
1 | # Much of this is inspired by or straight stolen from rbenv's tests
2 | # https://github.com/sstephenson/rbenv/blob/master/test/test_helper.bash
3 |
4 | # Guard against bats executing this twice
5 | if [ -z "$TEST_PATH_INITIALIZED" ]; then
6 | export TEST_PATH_INITIALIZED=true
7 |
8 | PATH=/usr/bin:/bin:/usr/sbin:/sbin
9 | PATH="$(dirname $BATS_TEST_DIRNAME):$PATH"
10 |
11 | export FIXTURES_DIR="$BATS_TEST_DIRNAME/fixtures"
12 | export SHY_CACHE_DIR="$BATS_TMPDIR/.shy_plugin_cache"
13 | fi
14 |
15 | eval "$(shy init)"
16 |
17 | setup() {
18 | unset SHY_PLUGIN_DATA
19 | unset SHY_TMP_DATA
20 |
21 | if [ -d "$SHY_CACHE_DIR" ]; then
22 | find "$SHY_CACHE_DIR" -type f -exec rm {} \;
23 | else
24 | mkdir "$SHY_CACHE_DIR"
25 | fi
26 | }
27 |
28 | flunk() {
29 | { if [ "$#" -eq 0 ]; then cat -
30 | else echo "$@"
31 | fi
32 | } | sed "s:${BATS_TEST_DIRNAME}:TEST_DIR:g" >&2
33 | return 1
34 | }
35 |
36 | assert_success() {
37 | if [ "$status" -ne 0 ]; then
38 | flunk "command failed with exit status $status"
39 | elif [ "$#" -gt 0 ]; then
40 | assert_output "$1"
41 | fi
42 | }
43 |
44 | assert_failure() {
45 | if [ "$status" -eq 0 ]; then
46 | flunk "expected failed exit status"
47 | elif [ "$#" -gt 0 ]; then
48 | assert_output "$1"
49 | fi
50 | }
51 |
52 | assert_equal() {
53 | if [ "$1" != "$2" ]; then
54 | { echo "expected: $1"
55 | echo "actual: $2"
56 | } | flunk
57 | fi
58 | }
59 |
60 | assert_output() {
61 | local expected
62 | if [ $# -eq 0 ]; then expected="$(cat -)"
63 | else expected="$1"
64 | fi
65 | assert_equal "$expected" "$output"
66 | }
67 |
68 | load_plugins() {
69 | for arg in "$@"; do
70 | shy load "$FIXTURES_DIR/$arg.sh"
71 | done
72 | }
73 |
74 | load_plugin() {
75 | load_plugins "$@"
76 | }
77 |
78 | make_modified_in_past() {
79 | local current_timestamp=$(date +%Y%m%d%H%M)
80 | local past_timestamp=$(expr $current_timestamp - 10)
81 | touch -t $past_timestamp "$1"
82 | }
83 |
--------------------------------------------------------------------------------