├── templates ├── perl ├── ruby ├── zsh ├── python ├── bash └── default ├── completion.zsh ├── utils.sh ├── completion.fish ├── completion.sh ├── CHANGELOG.md ├── LICENSE.md ├── s ├── s_test.sh ├── s.sh └── README.md /templates/perl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | -------------------------------------------------------------------------------- /templates/ruby: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | -------------------------------------------------------------------------------- /templates/zsh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zsh 2 | -------------------------------------------------------------------------------- /templates/python: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | -------------------------------------------------------------------------------- /completion.zsh: -------------------------------------------------------------------------------- 1 | compdef '_files -g "$(s)/*(:t)"' s 2 | -------------------------------------------------------------------------------- /templates/bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o nounset 4 | set -o errexit 5 | -------------------------------------------------------------------------------- /templates/default: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o nounset 4 | set -o errexit 5 | -------------------------------------------------------------------------------- /utils.sh: -------------------------------------------------------------------------------- 1 | EX_USAGE=64 2 | EX_CONFIG=78 3 | 4 | # Prints line to stderr 5 | err() { 6 | printf "$1\n" "${@:2}" >&2 7 | } 8 | -------------------------------------------------------------------------------- /completion.fish: -------------------------------------------------------------------------------- 1 | function _s_complete 2 | set opts (s)/* 3 | for i in $opts 4 | echo (basename $i) 5 | end 6 | end 7 | 8 | complete -c s -f -a '(_s_complete)' 9 | -------------------------------------------------------------------------------- /completion.sh: -------------------------------------------------------------------------------- 1 | _s() { 2 | cur="${COMP_WORDS[COMP_CWORD]}" 3 | opts=("$(s)"/*) 4 | COMPREPLY=( $(compgen -W "${opts[*]##*/}" -- $cur) ) 5 | } 6 | 7 | complete -F _s s 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 3.0 4 | 5 | * Adds fish completion script. 6 | * Adds test suite to verify funcionality. 7 | * Simplifies API for copying/moving scripts. 8 | 9 | ## 2.0 10 | 11 | * Switches to using stand-alone, executable script file instead of shell 12 | functions 13 | * `s` is now activated by adding its repo directory to the shell `PATH` 14 | variable 15 | 16 | ## 1.0 17 | 18 | * Incorporates some suggestions from reddit followers 19 | * Improves default bash template 20 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Colin T.A. Gray and David Sanders 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /s: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | 6 | S_SCRIPT_PATH=$(dirname "$0") 7 | S_SCRIPT_NAME=$(basename "$0") 8 | 9 | source "$S_SCRIPT_PATH/utils.sh" 10 | source "$S_SCRIPT_PATH/s.sh" 11 | 12 | function __s_help { 13 | cat <<'EOF' 14 | s, a simple shell script manager 15 | 16 | usage: s [-t [template name] [script name]] 17 | [-bzpre [script name]] 18 | [-- cmd [arg ...]] 19 | [-t -- cmd [arg ...]] 20 | [-h|--help] 21 | 22 | With no args, `s` lists all scripts in $S_BIN_PATH. In a non-terminal 23 | environment, $S_BIN_PATH is printed to stdout. 24 | 25 | adding/editing: 26 | -t [template name] [script name] 27 | If no extra arguments are given, lists available templates. In a 28 | non-terminal environment, prints $S_TEMPLATES_PATH to stdout. 29 | 30 | If only a template name is given, edits or creates and edits that 31 | template in $EDITOR. In a non-terminal environment, prints the path of 32 | the template to stdout. 33 | 34 | If a template name and script name are given, edits or creates and edits 35 | the script with the given template. 36 | 37 | -b [script] Shorthand for `-t bash [script]` 38 | -z [script] Shorthand for `-t zsh [script]` 39 | -p [script] Shorthand for `-t python [script]` 40 | -r [script] Shorthand for `-t ruby [script]` 41 | -e [script] Shorthand for `-t perl [script]` 42 | 43 | info/manipulation: 44 | -- cmd [arg ...] 45 | Performs a command with given args in directory specified by $S_BIN_PATH. 46 | 47 | -t -- cmd [arg ...] 48 | Performs a command with given args in directory specified by $S_TEMPLATES_PATH. 49 | 50 | etc: 51 | -h, --help Show this help screen. 52 | 53 | EOF 54 | } 55 | 56 | __s "$@" 57 | -------------------------------------------------------------------------------- /s_test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o nounset 4 | 5 | EX_CONFIG=78 6 | S_SCRIPT_NAME=s 7 | 8 | source ./utils.sh 9 | source ./s.sh 10 | 11 | 12 | faketty() { 13 | if [[ $(uname) == Darwin ]]; then 14 | script -q /dev/null bash -c "$1" 15 | else 16 | script -qc "bash -c '$1'" /dev/null 17 | fi 18 | } 19 | 20 | 21 | # err tests 22 | testErrPrintsLineToStdErr() { 23 | IFS= read -rd '' output < <(err 'arst' 2>&1) 24 | assertEquals $'arst\n' "$output" 25 | } 26 | 27 | testErrTakesArgumentsJustLikePrintf() { 28 | local output=$(err 'foo %s' 'bar' 2>&1) 29 | assertEquals 'foo bar' "$output" 30 | } 31 | 32 | 33 | # serr tests 34 | testSerrPrintsLineWithScriptName() { 35 | local output=$(S_SCRIPT_NAME=s serr 'foo' 2>&1) 36 | assertEquals 's: foo' "$output" 37 | } 38 | 39 | 40 | # __s_init tests 41 | testSInitAbortsWhenEditorNotSet() { 42 | local EDITOR= 43 | 44 | __s_init &> /dev/null 45 | assertEquals $EX_CONFIG $? 46 | 47 | local output=$(__s_init 2>&1) 48 | assertTrue 'output does not match regex' \ 49 | "[[ '$output' =~ ^.+EDITOR\ environment ]]" 50 | } 51 | 52 | testSInitSetsDefaultTemplatesPathIfNoneFound() { 53 | local EDITOR=foo S_TEMPLATES_PATH= S_SCRIPT_PATH=bar 54 | 55 | __s_init &> /dev/null 56 | assertEquals 'bar/templates' "$S_TEMPLATES_PATH" 57 | } 58 | 59 | testSInitAbortsIfTemplatesPathIsNotDir() { 60 | local EDITOR=foo S_TEMPLATES_PATH=./foo_bar_probably_doesnt_exist_dir_9871937298712394874 61 | 62 | __s_init &> /dev/null 63 | assertEquals $EX_CONFIG $? 64 | 65 | local output=$(__s_init 2>&1) 66 | assertTrue 'output does not match regex' \ 67 | "[[ '$output' =~ does\ not\ exist$ ]]" 68 | } 69 | 70 | testSInitSetsDefaultBinPathIfNoneFound() { 71 | local EDITOR=foo S_TEMPLATES_PATH=. S_BIN_PATH= HOME=bar 72 | 73 | __s_init &> /dev/null 74 | assertEquals 'bar/.bin' "$S_BIN_PATH" 75 | } 76 | 77 | testSInitAbortsIfBinPathIsNotDir() { 78 | local EDITOR=foo S_TEMPLATES_PATH=. S_BIN_PATH=./foo_bar_probably_doesnt_exist_dir_9871937298712394874 79 | 80 | __s_init &> /dev/null 81 | assertEquals $EX_CONFIG $? 82 | 83 | local output=$(__s_init 2>&1) 84 | assertTrue 'output does not match regex' \ 85 | "[[ '$output' =~ does\ not\ exist$ ]]" 86 | } 87 | 88 | testSInitAbortsIfSBinPathNotInPath() { 89 | local EDITOR=foo S_TEMPLATES_PATH=. S_BIN_PATH=$(mktemp -d) 90 | 91 | __s_init &> /dev/null 92 | assertEquals $EX_CONFIG $? 93 | 94 | local output=$(__s_init 2>&1) 95 | assertTrue 'output does not match regex' \ 96 | "[[ '$output' =~ not\ in\ PATH$ ]]" 97 | 98 | rmdir "$S_BIN_PATH" 99 | } 100 | 101 | 102 | # __s_do tests 103 | testSDoRequiresAtLeastTwoArguments() { 104 | __s_do &> /dev/null 105 | assertEquals $EX_USAGE $? 106 | 107 | __s_do test &> /dev/null 108 | assertEquals $EX_USAGE $? 109 | 110 | __s_do . ls &> /dev/null 111 | } 112 | 113 | testSDoDoesTheGivenCommandAtTheGivenPath() { 114 | local output=$(__s_do . ls s_test.sh 2> /dev/null) 115 | assertEquals 's_test.sh' "$output" 116 | } 117 | 118 | 119 | # __s_list tests 120 | testSListPrintsArgIfStdOutNotATerminal() { 121 | local output=$(__s_list arst) 122 | assertEquals 'arst' "$output" 123 | } 124 | 125 | testSListPrintsFilesAtPath() { 126 | local tmp_dir=$(mktemp -d) 127 | touch "$tmp_dir/tmp_file" 128 | 129 | local output=$( \ 130 | faketty "source utils.sh && source s.sh && __s_list $tmp_dir 2> /dev/null" | \ 131 | tr -d '\r' \ 132 | ) 133 | assertEquals 'tmp_file' "$output" 134 | 135 | rm "$tmp_dir/tmp_file" 136 | rmdir "$tmp_dir" 137 | } 138 | 139 | 140 | # __s_open tests 141 | testSOpenPrintsArgIfStdOutNotATerminal() { 142 | local output=$(__s_open arst) 143 | assertEquals 'arst' "$output" 144 | } 145 | 146 | testSOpenUsesEditorToOpenFiles() { 147 | local EDITOR=printf 148 | 149 | export S_EDITOR_ARGS= 150 | local output=$(faketty 'source utils.sh && source s.sh && __s_open args 2> /dev/null') 151 | assertEquals 'args' "$output" 152 | 153 | export S_EDITOR_ARGS='("lots of %s %s" extra)' 154 | local output=$(faketty 'source utils.sh && source s.sh && __s_open args 2> /dev/null') 155 | assertEquals 'lots of extra args' "$output" 156 | } 157 | 158 | 159 | if [[ -z "${SHUNIT_PATH:-}" ]]; then 160 | printf "%s: must set SHUNIT_PATH environment var to run tests\n" "$(basename "$0")" >&2 161 | exit $EX_CONFIG 162 | fi 163 | 164 | source "$SHUNIT_PATH" 165 | -------------------------------------------------------------------------------- /s.sh: -------------------------------------------------------------------------------- 1 | # Prints line to stderr with script name 2 | serr() { 3 | err "%s: $1" "$S_SCRIPT_NAME" "${@:2}" 4 | } 5 | 6 | # Initializes s 7 | __s_init() { 8 | # Ensure EDITOR is set 9 | if [[ -z "${EDITOR:-}" ]]; then 10 | serr 'EDITOR environment variable is not set' 11 | return $EX_CONFIG 12 | fi 13 | 14 | # Set default template path 15 | if [[ -z "${S_TEMPLATES_PATH:-}" ]]; then 16 | S_TEMPLATES_PATH="$S_SCRIPT_PATH/templates" 17 | fi 18 | 19 | # Ensure directory at S_TEMPLATES_PATH exists 20 | if [[ ! -d "$S_TEMPLATES_PATH" ]]; then 21 | serr 'directory specified by S_TEMPLATES_PATH (%s) does not exist' "$S_TEMPLATES_PATH" 22 | return $EX_CONFIG 23 | fi 24 | 25 | # Set default bin path 26 | if [[ -z "${S_BIN_PATH:-}" ]]; then 27 | S_BIN_PATH="$HOME/.bin" 28 | fi 29 | 30 | # Ensure directory at S_BIN_PATH exists 31 | if [[ ! -d "$S_BIN_PATH" ]]; then 32 | serr 'directory specified by S_BIN_PATH (%s) does not exist' "$S_BIN_PATH" 33 | return $EX_CONFIG 34 | fi 35 | 36 | # Ensure S_BIN_PATH is in PATH 37 | printf '%s' "$PATH" | grep -qF -- "$S_BIN_PATH" 38 | if [[ $? -eq 1 ]]; then 39 | serr 'directory specified by S_BIN_PATH (%s) is not in PATH' "$S_BIN_PATH" 40 | return $EX_CONFIG 41 | fi 42 | } 43 | 44 | # Does something at given path 45 | __s_do() { 46 | if [[ $# -lt 2 ]]; then 47 | serr 'path or command not specified' 48 | return $EX_USAGE 49 | fi 50 | 51 | local path=$1 52 | local cmd=("${@:2}") 53 | 54 | pushd "$path" &> /dev/null 55 | "${cmd[@]}" 56 | err 'did `%s` in %s' "${cmd[*]}" "$path" 57 | popd &> /dev/null 58 | } 59 | 60 | # Lists files at path or prints path 61 | __s_list() { 62 | # Print if not a terminal 63 | if [[ ! -t 1 ]]; then 64 | printf '%s' "$1" 65 | return 0 66 | fi 67 | 68 | err 'Available %s:' "$2" 69 | ls -- "$1" 70 | } 71 | 72 | # Opens a file at path or prints path 73 | __s_open() { 74 | # Print if not a terminal 75 | if [[ ! -t 1 ]]; then 76 | printf '%s' "$1" 77 | return 0 78 | fi 79 | 80 | local cmd=("$EDITOR") 81 | 82 | if [[ -n "${S_EDITOR_ARGS:-}" ]]; then 83 | eval 'local -a s_editor_args='"$S_EDITOR_ARGS" 84 | cmd+=("${s_editor_args[@]}") 85 | fi 86 | 87 | "${cmd[@]}" "$1" 88 | } 89 | 90 | # Edits/adds a script or template 91 | __s_edit() { 92 | local t_name=$1 93 | local s_name=${2:-} 94 | local t_loc="$S_TEMPLATES_PATH/$t_name" 95 | local s_loc="$S_BIN_PATH/${s_name:-}" 96 | 97 | if [[ -z "${s_name:-}" ]]; then 98 | # Edit template 99 | __s_open "$t_loc" 100 | return 0 101 | elif [[ ! -e "$t_loc" ]]; then 102 | serr 'template "%s" not found' "$t_name" 103 | return 1 104 | fi 105 | 106 | # Create script from template if it doesn't exist 107 | if [[ ! -e "$s_loc" ]]; then 108 | cp -- "$t_loc" "$s_loc" 109 | chmod -- 755 "$s_loc" 110 | local created=1 111 | fi 112 | 113 | # Edit script 114 | __s_open "$s_loc" 115 | 116 | # Remove script if not different from template 117 | if [[ "${created:-}" -eq 1 && "$(<"$s_loc")" == "$(<"$t_loc")" ]]; then 118 | rm -- "$s_loc" 119 | fi 120 | } 121 | 122 | # Switch board 123 | __s() { 124 | if ! __s_init; then 125 | serr "failed to initialize" 126 | return $EX_CONFIG 127 | fi 128 | 129 | if [[ $# -eq 0 ]]; then 130 | __s_list "$S_BIN_PATH" "scripts" 131 | return 0 132 | fi 133 | 134 | local cmd=$1 135 | shift 1 136 | 137 | case "$cmd" in 138 | "-h"|"--help") 139 | __s_help;; 140 | 141 | "-b"|"-z"|"-p"|"-r"|"-e") 142 | if [[ $# -lt 1 ]]; then 143 | err 'usage: %s %s