├── LICENSE ├── Makefile ├── README.mkd ├── bin ├── bashful ├── bashful-doc ├── bashful-execute ├── bashful-files ├── bashful-input ├── bashful-messages ├── bashful-modes ├── bashful-profile ├── bashful-terminfo ├── bashful-utils └── shdoc ├── package.json └── share ├── man └── man1 │ ├── bashful.1 │ └── shdoc.1 └── zsh └── functions └── _shdoc /LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CWD := $(shell pwd) 2 | PREFIX ?= /usr/local 3 | 4 | DIRS=bin share/man/man1 share/zsh/functions 5 | 6 | install: 7 | install -d $(foreach d,$(DIRS),$(PREFIX)/$(d)) 8 | $(foreach d,$(DIRS),install $(d)/* $(PREFIX)/$(d);) 9 | -------------------------------------------------------------------------------- /README.mkd: -------------------------------------------------------------------------------- 1 | Bashful 2 | ======= 3 | 4 | A collection of libraries to simplify writing bash scripts. 5 | 6 | It's separated into the following libraries: 7 | 8 | bashful-doc 9 | bashful-execute 10 | bashful-files 11 | bashful-input 12 | bashful-messages 13 | bashful-modes 14 | bashful-profile 15 | bashful-terminfo 16 | bashful-utils 17 | 18 | To get full documentation on each, just invoke it at the command line with 19 | the first argument as 'help'. For example, to get documentation on the input 20 | library: 21 | 22 | bashful-input help 23 | -------------------------------------------------------------------------------- /bin/bashful: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Filename: bashful 4 | # Description: An interface to bashful for non-bash scripts. 5 | # Maintainer: Jeremy Cantrell 6 | # Last Modified: Tue 2011-07-05 19:38:57 (-0400) 7 | 8 | # 9 | # 10 | # Bashful is a collection of libraries to simplify writing bash scripts. 11 | # 12 | # It's separated into the following libraries: 13 | # 14 | # bashful-doc 15 | # bashful-execute 16 | # bashful-files 17 | # bashful-input 18 | # bashful-messages 19 | # bashful-modes 20 | # bashful-profile 21 | # bashful-terminfo 22 | # bashful-utils 23 | # 24 | # To get full documentation on each, just invoke it at the command line with 25 | # the first argument as 'help'. For example, to get documentation on the input 26 | # library: 27 | # 28 | # $ bashful-input help 29 | # 30 | # 31 | 32 | if (( ${BASH_LINENO:-0} == 0 )); then 33 | source bashful-doc 34 | doc_execute "$0" "$@" 35 | exit 36 | fi 37 | 38 | source bashful-doc 39 | source bashful-execute 40 | source bashful-files 41 | source bashful-input 42 | source bashful-messages 43 | source bashful-modes 44 | source bashful-profile 45 | source bashful-terminfo 46 | source bashful-utils 47 | -------------------------------------------------------------------------------- /bin/bashful-doc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Filename: bashful-doc 4 | # Description: Functions for extracting embedded documentation. 5 | # Maintainer: Jeremy Cantrell 6 | # Last Modified: Tue 2011-07-05 00:56:08 (-0400) 7 | 8 | # 9 | # 10 | # The doc library provides a way to extract documentation from scripts. 11 | # 12 | # Normally, I would prefer to use getopts to setup a -h/--help option, but in 13 | # some cases it isn't practical or it can conflict with other functions. This 14 | # provides a nice alternative with no side-effects. 15 | # 16 | # Within the script, a section of documentation is denoted like this: 17 | # 18 | # # 19 | # # 20 | # # DOCUMENTATION TEXT GOES HERE 21 | # # 22 | # # 23 | # 24 | # 25 | 26 | if (( ${BASH_LINENO:-0} == 0 )); then 27 | source bashful-doc 28 | doc_execute "$0" "$@" 29 | exit 30 | fi 31 | 32 | [[ $BASHFUL_DOC_LOADED ]] && return 33 | 34 | source bashful-utils 35 | 36 | doc() 37 | { 38 | # 39 | # 40 | # Retrieve embedded documentation from scripts. 41 | # 42 | # Usage: doc NAME [FILE...] 43 | # 44 | # 45 | 46 | local name=$1; shift 47 | embedded_tag "doc:$name" "$@" 48 | } 49 | 50 | doc_help() 51 | { 52 | # 53 | # 54 | # Display full documentation for a given script/topic. 55 | # 56 | # Usage: doc_help SCRIPT [TOPIC] 57 | # 58 | # 59 | 60 | local src=$(type -p "$1") 61 | local cmd=$2 62 | local cmds 63 | 64 | { 65 | if [[ $cmd ]]; then 66 | doc "$cmd" "$src" 67 | else 68 | doc "$(basename "$src" .sh)" "$src" 69 | cmds=$(doc_topics "$src") 70 | if [[ $cmds ]]; then 71 | echo -e "\nAvailable topics:\n" 72 | echo "$cmds" | sed 's/^/ /' 73 | fi 74 | fi 75 | } | pager 76 | } 77 | 78 | doc_execute() 79 | { 80 | # 81 | # 82 | # Display the documentation for a given script if there are no arguments 83 | # or the only argument is "help". 84 | # 85 | # Display the documentation for a given topic if the first two arguments 86 | # are "help" and the topic. 87 | # 88 | # If not using one of the help methods, the given command will be executed 89 | # as if it were run directly. 90 | # 91 | # Usage: 92 | # doc_execute SCRIPT 93 | # doc_execute SCRIPT help [TOPIC] 94 | # doc_execute SCRIPT [TOPIC/COMMAND] [OPTIONS] [ARGUMENTS] 95 | # 96 | # 97 | 98 | local src=$(type -p "$1"); shift 99 | 100 | if [[ ! $1 || $1 == help ]]; then 101 | shift 102 | doc_help "$src" "$1" 103 | else 104 | source "$src"; "$@" 105 | fi 106 | } 107 | 108 | doc_topics() 109 | { 110 | # 111 | # 112 | # Show all doc tags in given files. 113 | # 114 | # Usage: doc_topics [FILE...] 115 | # 116 | # 117 | 118 | local src=$(type -p "$1") 119 | local t="doc" 120 | local c='^[[:space:]]*#[[:space:]]*' 121 | local n=']*\)>' 122 | 123 | sed -n "/$c$n/p" "$src" | 124 | sed "s/$c$n.*$/\1/" | sort -u | 125 | grep -v "^$(basename "$src")$" 126 | } 127 | 128 | BASHFUL_DOC_LOADED=1 129 | -------------------------------------------------------------------------------- /bin/bashful-execute: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Filename: bashful-execute 4 | # Description: Functions for building commands. 5 | # Maintainer: Jeremy Cantrell 6 | # Last Modified: Wed 2012-03-28 22:33:16 (-0400) 7 | 8 | # 9 | # 10 | # The execute library provides functions for building and executing commands. 11 | # 12 | # 13 | 14 | if (( ${BASH_LINENO:-0} == 0 )); then 15 | source bashful-doc 16 | doc_execute "$0" "$@" 17 | exit 18 | fi 19 | 20 | [[ $BASHFUL_EXECUTE_LOADED ]] && return 21 | 22 | execute() 23 | { 24 | # 25 | # 26 | # Execute a given command or stored command. 27 | # 28 | # Usage: execute [ARGUMENT...] 29 | # 30 | # 31 | 32 | "${EXECUTE_CMD[@]}" "$@" 33 | } 34 | 35 | execute_in() 36 | { 37 | # 38 | # 39 | # Execute a command in a given directory. 40 | # 41 | # Usage: execute_in DIRECTORY [COMMAND...] 42 | # 43 | # 44 | 45 | local OPWD=$PWD; cd "$1"; shift 46 | execute "$@"; error=$? 47 | cd "$OPWD" 48 | return $error 49 | } 50 | 51 | execute_push() 52 | { 53 | # 54 | # 55 | # Add an argument to the stored command. 56 | # 57 | # Usage: execute_push [ARGUMENT...] 58 | # 59 | # 60 | 61 | EXECUTE_CMD+=("$@") 62 | } 63 | 64 | execute_pop() 65 | { 66 | # 67 | # 68 | # Remove the last (or given number) argument for the stored command. 69 | # 70 | # Usage: execute_pop [NUM_OF_ARGS] 71 | # 72 | # 73 | 74 | local i 75 | for (( i=0; i<${1:-1}; i++ )); do 76 | unset EXECUTE_CMD[${#EXECUTE_CMD[@]}-1] 77 | done 78 | } 79 | 80 | execute_clear() 81 | { 82 | # 83 | # 84 | # Clear stored commands. 85 | # 86 | # Usage: execute_clear 87 | # 88 | # 89 | 90 | unset EXECUTE_CMD 91 | } 92 | 93 | BASHFUL_EXECUTE_LOADED=1 94 | -------------------------------------------------------------------------------- /bin/bashful-files: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Filename: bashful-files 4 | # Description: Miscellaneous utility functions for dealing with files. 5 | # Maintainer: Jeremy Cantrell 6 | # Last Modified: Fri 2012-07-06 21:47:54 (-0400) 7 | 8 | # 9 | # 10 | # The files library provides functions for working with files/directories. 11 | # 12 | # 13 | 14 | if (( ${BASH_LINENO:-0} == 0 )); then 15 | source bashful-doc 16 | doc_execute "$0" "$@" 17 | exit 18 | fi 19 | 20 | [[ $BASHFUL_FILES_LOADED ]] && return 21 | 22 | source bashful-messages 23 | source bashful-modes 24 | source bashful-utils 25 | 26 | commonpath() 27 | { 28 | # 29 | # 30 | # Gets the common path of the paths passed on stdin. 31 | # Alternatively, paths can be passed as arguments. 32 | # 33 | # Usage: commonpath [PATH...] 34 | # 35 | # 36 | 37 | local path 38 | 39 | # Make sure command line args go to stdin 40 | if (( $# > 0 )); then 41 | for path in "$@"; do 42 | echo "$path" 43 | done | commonpath 44 | return 45 | fi 46 | 47 | local prefix=$( 48 | while read -r; do 49 | echo "$(abspath "$REPLY")/" 50 | done | commonprefix 51 | ) 52 | 53 | # We only want to break at path separators 54 | if [[ $prefix != */ ]]; then 55 | prefix=${prefix%/*}/ 56 | fi 57 | 58 | # Only strip the trailing slash if it's not root (/) 59 | if [[ $prefix != / ]]; then 60 | prefix=${prefix%/} 61 | fi 62 | 63 | echo "$prefix" 64 | } 65 | 66 | commontail() 67 | { 68 | # 69 | # 70 | # Gets the common tails of the paths passed on stdin. 71 | # Alternatively, paths can be passed as arguments. 72 | # 73 | # Usage: commontail [PATH...] 74 | # 75 | # Usage examples: 76 | # commontail /foo/bar /boo/bar #==> bar 77 | # commontail /foo/bar /boo/far #==> 78 | # 79 | # 80 | 81 | local path 82 | 83 | # Make sure command line args go to stdin 84 | if (( $# > 0 )); then 85 | for path in "$@"; do 86 | echo "$path" 87 | done | commontail 88 | return 89 | fi 90 | 91 | local suffix=$( 92 | while read -r; do 93 | echo "$(abspath "$REPLY")" 94 | done | commonsuffix 95 | ) 96 | 97 | echo "${suffix#*/}" 98 | } 99 | 100 | extname() 101 | { 102 | # 103 | # 104 | # Get the extension of the given filename. 105 | # 106 | # Usage: extname [-n LEVELS] FILENAME 107 | # 108 | # Usage examples: 109 | # extname foo.txt #==> .txt 110 | # extname -n2 foo.tar.gz #==> .tar.gz 111 | # extname foo.tar.gz #==> .tar.gz 112 | # extname -n1 foo.tar.gz #==> .gz 113 | # 114 | # 115 | 116 | local levels 117 | 118 | unset OPTIND 119 | while getopts ":n:" option; do 120 | case $option in 121 | n) levels=$OPTARG ;; 122 | esac 123 | done && shift $(($OPTIND - 1)) 124 | 125 | local filename=${1##*/} 126 | 127 | [[ $filename == *.* ]] || return 128 | 129 | local fn=$filename 130 | local exts ext 131 | 132 | # Detect some common multi-extensions 133 | if [[ ! $levels ]]; then 134 | case $(lower <<<$filename) in 135 | *.tar.gz|*.tar.bz2) levels=2 ;; 136 | esac 137 | fi 138 | 139 | levels=${levels:-1} 140 | 141 | for (( i=0; i<$levels; i++ )); do 142 | ext=.${fn##*.} 143 | exts=$ext$exts 144 | fn=${fn%$ext} 145 | [[ $exts == $filename ]] && return 146 | done 147 | 148 | echo "$exts" 149 | } 150 | 151 | filename() 152 | { 153 | # 154 | # 155 | # Gets the filename of the given path. 156 | # 157 | # Usage: filename [-n LEVELS] FILENAME 158 | # 159 | # Usage examples: 160 | # filename /path/to/file.txt #==> file 161 | # filename -n2 /path/to/file.tar.gz #==> file 162 | # filename /path/to/file.tar.gz #==> file 163 | # filename -n1 /path/to/file.tar.gz #==> file.tar 164 | # 165 | # 166 | 167 | basename "$1" $(extname "$@") 168 | } 169 | 170 | increment() 171 | { 172 | # 173 | # 174 | # Get the next filename in line for the given file. 175 | # 176 | # Usage: increment FILENAME 177 | # 178 | # Usage examples: 179 | # increment does_not_exist #==> does_not_exist 180 | # increment does_exist #==> does_exist (1) 181 | # increment does_exist #==> does_exist (2) 182 | # 183 | # 184 | 185 | local file=$1 186 | local count=1 187 | local pattern=${2:- (\{num\})} 188 | 189 | while [[ -e $file ]]; do 190 | file="${1}${pattern//\{num\}/$((count++))}" 191 | done 192 | 193 | echo "$file" 194 | } 195 | 196 | listdir() 197 | { 198 | # 199 | # 200 | # List the files in the given directory (1 level deep). 201 | # Accepts the same options as the find command. 202 | # 203 | # Usage: listdir DIR [OPTIONS] 204 | # 205 | # 206 | 207 | local dir=$1; shift 208 | find "$dir" -maxdepth 1 -mindepth 1 "$@" 209 | } 210 | 211 | files() 212 | { 213 | # 214 | # 215 | # List all the files in the given directory (recursively). 216 | # Will not display hidden files. 217 | # Accepts the same options as the find command. 218 | # 219 | # Usage: files DIR [OPTIONS] 220 | # 221 | # 222 | 223 | local dir=$1; shift 224 | find "$dir" \( -type f -o -type l \) \! -wholename "*/.*" "$@" 225 | } 226 | 227 | abspath() 228 | { 229 | # 230 | # 231 | # Gets the absolute path of the given path. 232 | # Will resolve paths that contain '.' and '..'. 233 | # Think readlink without the symlink resolution. 234 | # 235 | # Usage: abspath [PATH] 236 | # 237 | # 238 | 239 | local path=${1:-$PWD} 240 | 241 | # Path looks like: ~user/... 242 | # Gods of bash, forgive me for using eval 243 | if [[ $path =~ ~[a-zA-Z] ]]; then 244 | if [[ ${path%%/*} =~ ^~[[:alpha:]_][[:alnum:]_]*$ ]]; then 245 | path=$(eval echo $path) 246 | fi 247 | fi 248 | 249 | # Path looks like: ~/... 250 | [[ $path == ~* ]] && path=${path/\~/$HOME} 251 | 252 | # Path is not absolute 253 | [[ $path != /* ]] && path=$PWD/$path 254 | 255 | path=$(squeeze "/" <<<"$path") 256 | 257 | local elms=() 258 | local elm 259 | local OIFS=$IFS; IFS="/" 260 | for elm in $path; do 261 | IFS=$OIFS 262 | [[ $elm == . ]] && continue 263 | if [[ $elm == .. ]]; then 264 | elms=("${elms[@]:0:$((${#elms[@]}-1))}") 265 | else 266 | elms=("${elms[@]}" "$elm") 267 | fi 268 | done 269 | IFS="/" 270 | echo "/${elms[*]}" 271 | IFS=$OIFS 272 | } 273 | 274 | relpath() 275 | { 276 | # 277 | # 278 | # Gets the relative path from SOURCE to DESTINATION. 279 | # Output should mirror the python function os.path.relpath(). 280 | # All arguments default to the current directory. 281 | # 282 | # Usage: relpath [DESTINATION] [SOURCE] 283 | # 284 | # Usage examples: 285 | # relpath /home/user /home/user/bin #==> bin 286 | # relpath /home/user/bin /home/user #==> .. 287 | # relpath /foo/bar/baz / #==> ../../.. 288 | # relpath /foo/bar /baz #==> ../../baz 289 | # relpath /home/user /home/user #==> . 290 | # relpath #==> . 291 | # 292 | # 293 | 294 | local dst=$(abspath "$1") 295 | local src=$(abspath "$2") 296 | 297 | local common=$(commonpath "$dst" "$src") 298 | 299 | dst=${dst#$common}; dst=${dst#/} 300 | src=${src#$common}; src=${src#/} 301 | 302 | local OIFS=$IFS; local IFS=/ 303 | src=($src) 304 | IFS=$OIFS 305 | 306 | local rel= 307 | for i in "${!src[@]}"; do 308 | rel+=../ 309 | done 310 | 311 | rel=${rel}${dst} 312 | 313 | # Handle some corner cases. 314 | # Arguments were the same path. 315 | [[ $rel ]] || rel=. 316 | # Make sure there are no trailing slashes. 317 | # ...except for root. 318 | [[ $rel == / ]] || rel=${rel%%/} 319 | 320 | echo "$rel" 321 | } 322 | 323 | link() 324 | { 325 | # 326 | # 327 | # Version of ln that respects the interactive/verbose settings. 328 | # 329 | # Usage: link SOURCE [DESTINATION] 330 | # 331 | # 332 | 333 | interactive ${INTERACTIVE:-1} 334 | verbose ${VERBOSE:-1} 335 | 336 | $SUDO ln -snT $(interactive_option) $(verbose_echo -v) "$@" 337 | } 338 | 339 | linkrel() 340 | { 341 | # 342 | # 343 | # Like link, but uses relpath to make the paths relative. 344 | # 345 | # Usage: linkrel SOURCE [DESTINATION] 346 | # 347 | # 348 | 349 | local dir=$(relpath "${@%/*}")/ 350 | dir=${dir##./} 351 | link "${dir}${1##*/}" "$2" 352 | } 353 | 354 | move() 355 | { 356 | # 357 | # 358 | # Version of mv that respects the interactive/verbose settings. 359 | # Accepts the same options/arguments as mv. 360 | # 361 | # 362 | 363 | interactive ${INTERACTIVE:-1} 364 | verbose ${VERBOSE:-1} 365 | 366 | $SUDO mv -T $(interactive_option) $(verbose_echo -v) "$@" 367 | } 368 | 369 | copy() 370 | { 371 | # 372 | # 373 | # Version of cp that respects the interactive/verbose settings. 374 | # Accepts the same options/arguments as cp. 375 | # 376 | # 377 | 378 | interactive ${INTERACTIVE:-1} 379 | verbose ${VERBOSE:-1} 380 | 381 | $SUDO cp -Tr $(interactive_option) $(verbose_echo -v) "$@" 382 | } 383 | 384 | stow() 385 | { 386 | # 387 | # 388 | # Replicate a directory tree and link regular files. 389 | # 390 | # 391 | 392 | local src=$1 393 | local dst=${2:-$PWD} 394 | 395 | local OIFS=$IFS; IFS=$'\n' 396 | for f in $(files "$src"); do 397 | IFS=$OIFS 398 | local nf=$dst/${f#$src/} 399 | mkdir -p "$(dirname "$nf")" 400 | linkrel "$f" "$nf" 401 | done 402 | } 403 | 404 | remove() 405 | { 406 | # 407 | # 408 | # Version of rm that respects the interactive/verbose settings. 409 | # Accepts the same options/arguments as rm. 410 | # 411 | # 412 | 413 | interactive ${INTERACTIVE:-1} 414 | verbose ${VERBOSE:-1} 415 | 416 | $SUDO rm -r $(interactive_option) $(verbose_echo -v) "$@" 417 | } 418 | 419 | cleanup() 420 | { 421 | # 422 | # 423 | # Cleans up any temp files lying around. 424 | # Intended to be used alongside tempfile() and not to be called directly. 425 | # 426 | # 427 | 428 | for file in "${CLEANUP_FILES[@]}"; do 429 | $SUDO rm -rf "$file" 430 | done 431 | } 432 | 433 | tempfile() 434 | { 435 | # 436 | # 437 | # Creates and keeps track of temp files. 438 | # 439 | # Usage examples: 440 | # tempfile # $TEMPFILE is now a regular file 441 | # 442 | # 443 | 444 | TEMPFILE=$(mktemp "$@") 445 | if [[ ! $TEMPFILE ]]; then 446 | error "Could not create temporary file." 447 | return 1 448 | fi 449 | CLEANUP_FILES=("${CLEANUP_FILES[@]}" "$TEMPFILE") 450 | trap cleanup INT TERM EXIT 451 | } 452 | 453 | tempdir() 454 | { 455 | # 456 | # 457 | # Creates and keeps track of temp directories. 458 | # 459 | # Usage examples: 460 | # tempdir # $TEMPDIR is now a directory 461 | # 462 | # 463 | 464 | tempfile -d -t "$(basename "$0").XXXXXX" 465 | TEMPDIR=$TEMPFILE 466 | } 467 | 468 | truncate() 469 | { 470 | # 471 | # 472 | # Removes all similar unused files. 473 | # The only assumption is that the prefix is separated from the identifier 474 | # by a single hyphen (-). 475 | # 476 | # Usage: truncate PREFIX SUFFIX [EXCLUDED_PREFIX...] 477 | # 478 | # Usage examples: 479 | # 480 | # Given the following files: 481 | # 482 | # file.txt -> file-c.txt 483 | # file-a.txt 484 | # file-b.txt 485 | # file-c.txt 486 | # 487 | # The following command: 488 | # 489 | # truncate file .txt 490 | # 491 | # Will leave only the following files: 492 | # 493 | # file.txt -> file-c.txt 494 | # file-c.txt 495 | # 496 | # If you have other files with similar prefixes they will be removed as 497 | # well. For example, if we also had the following files: 498 | # 499 | # file-foo-a.txt 500 | # file-foo-b.txt 501 | # file-bar-a.txt 502 | # file-bar-b.txt 503 | # 504 | # If you want to keep these files, you will have to pass exclusions like: 505 | # 506 | # truncate file .txt file-foo file-bar 507 | # 508 | # 509 | 510 | local prefix=$1; shift 511 | local suffix=$1; shift 512 | local filename=$prefix$suffix 513 | 514 | # There is no symlink to follow 515 | if [[ ! -L $filename ]]; then 516 | error "Name not provided or does not exist as a symlink." 517 | return 1 518 | fi 519 | 520 | # Get the file to NOT remove 521 | local target=$(readlink -f "$filename") 522 | 523 | if [[ ! -e $target ]]; then 524 | error "Target file does not exist." 525 | return 1 526 | fi 527 | 528 | local dir=$(dirname "$target") 529 | local file fn exclude 530 | 531 | for file in "$dir"/$(basename "$prefix")-*$suffix; do 532 | [[ -f $file ]] || continue 533 | fn=${file##*/} 534 | # Make sure file doesn't match an exclusion 535 | for exclude in "$@"; do 536 | [[ $fn == $exclude* ]] && continue 537 | done 538 | if [[ $file != $target ]]; then 539 | remove "$file" 540 | fi 541 | done 542 | } 543 | 544 | backup() 545 | { 546 | # 547 | # 548 | # Backup a file/directory with a timestamp. 549 | # 550 | # Usage: backup PATH [DIRECTORY] 551 | # 552 | # 553 | 554 | local f=$1; [[ -z $f || ! -f $f ]] && return 1 555 | local d=$2; [[ -z $d ]] && d=$(dirname "$f") 556 | mkdir -p "$d" 557 | copy "$f" "$d/$TIMESTAMP/$(basename "$f")" 558 | } 559 | 560 | BASHFUL_FILES_LOADED=1 561 | -------------------------------------------------------------------------------- /bin/bashful-input: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Filename: bashful-input 4 | # Description: A set of functions for interacting with the user. 5 | # Maintainer: Jeremy Cantrell 6 | # Last Modified: Tue 2011-07-05 19:38:57 (-0400) 7 | 8 | # 9 | # 10 | # The input library provides functions for taking user input. 11 | # 12 | # All functions are sensitive to the following variables: 13 | # 14 | # INTERACTIVE # If unset/false, the user will not be prompted. 15 | # 16 | # The INTERACTIVE variable only matters if the function is used with the 17 | # interactive mode check option (-c). 18 | # 19 | # 20 | 21 | if (( ${BASH_LINENO:-0} == 0 )); then 22 | source bashful-doc 23 | doc_execute "$0" "$@" 24 | exit 25 | fi 26 | 27 | [[ $BASHFUL_INPUT_LOADED ]] && return 28 | 29 | source bashful-messages 30 | source bashful-modes 31 | source bashful-utils 32 | 33 | input() 34 | { 35 | # 36 | # 37 | # Prompts the user to input text. 38 | # 39 | # Usage: input [-cs] [-p PROMPT] [-d DEFAULT] 40 | # 41 | # Usage examples: 42 | # 43 | # If user presses enter with no response, foo is taken as the input: 44 | # input -p "Enter value" -d foo 45 | # 46 | # User will only be prompted if interactive mode is enabled: 47 | # input -c -p "Enter value" 48 | # 49 | # Input will be hidden: 50 | # input -s -p "Enter password" 51 | # 52 | # 53 | 54 | local p="Enter value" 55 | local d c s reply 56 | 57 | unset OPTIND 58 | while getopts ":p:d:cs" option; do 59 | case $option in 60 | p) p=$OPTARG ;; 61 | d) d=$OPTARG ;; 62 | c) c=1 ;; 63 | s) s=-s ;; 64 | esac 65 | done && shift $(($OPTIND - 1)) 66 | 67 | [[ $s ]] && unset d 68 | 69 | if truth $c && ! interactive; then 70 | echo "$d" 71 | return 72 | fi 73 | 74 | # Shorten home paths, if they exist. 75 | p=${p//$HOME/~} 76 | 77 | [[ $s ]] || p="${p}${d:+ [$d]}" 78 | read $s -ep "$p: " reply || return 1 79 | [[ $s ]] && echo >&2 80 | 81 | echo "${reply:-$d}" 82 | } 83 | 84 | input_lines() 85 | { 86 | # 87 | # 88 | # Prompts the user to input text lists. 89 | # 90 | # Usage: input_lines [-c] [-p PROMPT] 91 | # 92 | # 93 | 94 | local p="Enter values" 95 | local c reply 96 | 97 | unset OPTIND 98 | while getopts ":p:c" option; do 99 | case $option in 100 | p) p=$OPTARG ;; 101 | c) c=1 ;; 102 | esac 103 | done && shift $(($OPTIND - 1)) 104 | 105 | if truth $c && ! interactive; then 106 | return 107 | fi 108 | 109 | p+=" (one per line)" 110 | 111 | # Shorten home paths, if they exist. 112 | p=${p//$HOME/~} 113 | 114 | echo "$p:" >&2 115 | cat # Accept input until EOF (ctrl-d) 116 | } 117 | 118 | question() 119 | { 120 | # 121 | # 122 | # Prompts the user with a yes/no question. 123 | # The default value can be anything that evaluates to true/false. 124 | # See documentation for the "truth" command for more info. 125 | # 126 | # Usage: question [-c] [-p PROMPT] [-d DEFAULT] 127 | # 128 | # Usage examples: 129 | # 130 | # If user presses enter with no response, answer will be "yes": 131 | # question -p "Continue?" -d1 132 | # 133 | # User will only be prompted if interactive mode is enabled: 134 | # question -c -p "Continue?" 135 | # 136 | # 137 | 138 | local p="Are you sure you want to proceed?" 139 | local d c reply choice 140 | 141 | unset OPTIND 142 | while getopts ":p:d:c" option; do 143 | case $option in 144 | p) p=$OPTARG ;; 145 | d) d=$OPTARG ;; 146 | c) c=1 ;; 147 | esac 148 | done && shift $(($OPTIND - 1)) 149 | 150 | if truth $c && ! interactive; then 151 | return 0 152 | fi 153 | 154 | truth "$d" && d=y || d=n 155 | 156 | # Shorten home paths, if they exist. 157 | p=${p//$HOME/~} 158 | 159 | truth $(input -d $d -p "$p") 160 | } 161 | 162 | choice() 163 | { 164 | # 165 | # 166 | # Prompts the user to choose from a set of choices. 167 | # If there is only one choice, it will be returned immediately. 168 | # 169 | # Usage: choice [-c] [-p PROMPT] [CHOICE...] 170 | # 171 | # Usage examples: 172 | # choice -p "Choose your favorite color" red green blue 173 | # 174 | # 175 | 176 | local p="Select from these choices" 177 | local c 178 | 179 | unset OPTIND 180 | while getopts ":p:c" option; do 181 | case $option in 182 | p) p=$OPTARG ;; 183 | c) c=1 ;; 184 | esac 185 | done && shift $(($OPTIND - 1)) 186 | 187 | if truth $c && ! interactive; then 188 | return 189 | fi 190 | 191 | if (( $# <= 1 )); then 192 | echo "$1" 193 | exit 194 | fi 195 | 196 | # Shorten home paths, if they exist. 197 | p=${p//$HOME/~} 198 | 199 | echo "$p:" >&2 200 | select choice in "$@"; do 201 | if [[ $choice ]]; then 202 | echo "$choice" 203 | break 204 | fi 205 | done 206 | } 207 | 208 | pause() 209 | { 210 | # 211 | # 212 | # Pause and wait for user interaction. 213 | # 214 | # Usage: pause [OPTIONS] 215 | # 216 | # 217 | 218 | local p="Press any key to continue..." 219 | local c 220 | 221 | unset OPTIND 222 | while getopts ":p:c" option; do 223 | case $option in 224 | p) p=$OPTARG ;; 225 | c) c=1 ;; 226 | esac 227 | done && shift $(($OPTIND - 1)) 228 | 229 | if truth $c && ! interactive; then 230 | return 231 | fi 232 | 233 | read -s -p "$p" -n1 && echo >&2 234 | } 235 | 236 | BASHFUL_INPUT_LOADED=1 237 | -------------------------------------------------------------------------------- /bin/bashful-messages: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Filename: bashful-messages 4 | # Description: A set of functions for giving the user information. 5 | # Maintainer: Jeremy Cantrell 6 | # Last Modified: Tue 2011-07-05 19:38:57 (-0400) 7 | 8 | # 9 | # 10 | # The messages library provides functions for notifying the user. 11 | # 12 | # All functions are sensitive to the following variables: 13 | # 14 | # VERBOSE # If unset/false, the user will not see notifications. 15 | # 16 | # The VERBOSE variable only matters if the function is used with the verbose 17 | # mode check option (-c). 18 | # 19 | # 20 | 21 | if (( ${BASH_LINENO:-0} == 0 )); then 22 | source bashful-doc 23 | doc_execute "$0" "$@" 24 | exit 25 | fi 26 | 27 | [[ $BASHFUL_MESSAGES_LOADED ]] && return 28 | 29 | source bashful-modes 30 | source bashful-terminfo 31 | source bashful-utils 32 | 33 | usage() 34 | { 35 | # 36 | # 37 | # Display usage information and exit with the given error code. 38 | # Will automatically populate certain sections if things like verbose or 39 | # interactive modes are set (either on or off). 40 | # 41 | # Usage: usage [ERROR] 42 | # 43 | # Required variables: 44 | # 45 | # SCRIPT_NAME 46 | # 47 | # Optional variables: 48 | # 49 | # SCRIPT_ARGUMENTS 50 | # SCRIPT_DESCRIPTION 51 | # SCRIPT_EXAMPLES 52 | # SCRIPT_OPTIONS 53 | # SCRIPT_USAGE 54 | # 55 | # 56 | 57 | if [[ $SCRIPT_NAME ]]; then 58 | local p=" " 59 | { 60 | echo "Usage: $SCRIPT_NAME [OPTIONS] $SCRIPT_ARGUMENTS" 61 | [[ $SCRIPT_USAGE ]] && echo "$SCRIPT_USAGE" 62 | 63 | if [[ $SCRIPT_DESCRIPTION ]]; then 64 | echo 65 | echo -e "$SCRIPT_DESCRIPTION" 66 | fi 67 | 68 | if [[ $SCRIPT_EXAMPLES ]]; then 69 | echo 70 | echo "EXAMPLES" 71 | echo 72 | echo -e "$SCRIPT_EXAMPLES" 73 | fi 74 | 75 | echo 76 | echo "GENERAL OPTIONS" 77 | echo 78 | echo "${p}-h Display this help message." 79 | 80 | if [[ $INTERACTIVE ]]; then 81 | echo 82 | echo "${p}-i Interactive. Prompt for certain actions." 83 | echo "${p}-f Don't prompt." 84 | fi 85 | 86 | if [[ $VERBOSE ]]; then 87 | echo 88 | echo "${p}-v Be verbose." 89 | echo "${p}-q Be quiet." 90 | fi 91 | 92 | if [[ $SCRIPT_OPTIONS ]]; then 93 | echo 94 | echo "APPLICATION OPTIONS" 95 | echo 96 | echo -e "$SCRIPT_OPTIONS" | sed "s/^/${p}/" 97 | fi 98 | } | squeeze_lines >&2 99 | fi 100 | 101 | exit ${1:-0} 102 | } 103 | 104 | die() 105 | { 106 | # 107 | # 108 | # Displays an error message and exits with the given error code. 109 | # 110 | # Usage: die [MESSAGE] [ERROR] 111 | # 112 | # 113 | 114 | error "$1"; exit ${2:-1} 115 | } 116 | 117 | info() 118 | { 119 | # 120 | # 121 | # Displays a colorized (if available) informational message. 122 | # 123 | # Usage: info [-c] [MESSAGE] 124 | # 125 | # 126 | 127 | local c 128 | 129 | unset OPTIND 130 | while getopts ":c" option; do 131 | case $option in 132 | c) c=1 ;; 133 | esac 134 | done && shift $(($OPTIND - 1)) 135 | 136 | if truth $c && ! verbose; then 137 | return 138 | fi 139 | 140 | local msg=${1:-All updates are complete.} 141 | 142 | # Shorten home paths, if they exist. 143 | msg=${msg//$HOME/\~} 144 | 145 | echo -e "${term_bold}${term_fg_blue}==> ${term_fg_white}${msg}${term_reset}" >&2 146 | } 147 | 148 | warn() 149 | { 150 | # 151 | # 152 | # Displays a colorized (if available) warning message. 153 | # 154 | # Usage: warn [-c] [MESSAGE] 155 | # 156 | # 157 | 158 | local c 159 | 160 | unset OPTIND 161 | while getopts ":c" option; do 162 | case $option in 163 | c) c=1 ;; 164 | esac 165 | done && shift $(($OPTIND - 1)) 166 | 167 | if truth $c && ! verbose; then 168 | return 169 | fi 170 | 171 | local msg=${1:-A warning has occurred.} 172 | 173 | # Shorten home paths, if they exist. 174 | msg=${msg//$HOME/\~} 175 | 176 | echo -e "${term_bold}${term_fg_yellow}WARNING: ${term_fg_white}${msg}${term_reset}" >&2 177 | } 178 | 179 | error() 180 | { 181 | # 182 | # 183 | # Displays a colorized (if available) error message. 184 | # 185 | # Usage: error [-c] [MESSAGE] 186 | # 187 | # 188 | 189 | local c 190 | 191 | unset OPTIND 192 | while getopts ":c" option; do 193 | case $option in 194 | c) c=1 ;; 195 | esac 196 | done && shift $(($OPTIND - 1)) 197 | 198 | if truth $c && ! verbose; then 199 | return 200 | fi 201 | 202 | local msg=${1:-An error has occurred.} 203 | 204 | # Shorten home paths, if they exist. 205 | msg=${msg//$HOME/\~} 206 | 207 | echo -e "${term_bold}${term_fg_red}ERROR: ${term_fg_white}${msg}${term_reset}" >&2 208 | } 209 | 210 | BASHFUL_MESSAGES_LOADED=1 211 | -------------------------------------------------------------------------------- /bin/bashful-modes: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Filename: bashful-modes 4 | # Description: Set of functions to interact with different script modes. 5 | # Maintainer: Jeremy Cantrell 6 | # Last Modified: Tue 2011-07-05 19:38:58 (-0400) 7 | 8 | # 9 | # 10 | # The modes library provides functions for using getting/setting mode values. 11 | # 12 | # The modes are controlled by the following variables: 13 | # 14 | # INTERACTIVE # If unset/false, the user will not be prompted. 15 | # VERBOSE # If unset/false, the user will not see notifications. 16 | # 17 | # The following commands get/set these variables: 18 | # 19 | # interactive 20 | # verbose 21 | # 22 | # If called with no argument, it returns the state of the mode in question. 23 | # If called with an argument, The mode is set to the value of the argument. 24 | # 25 | # The most common way that a mode would be set: 26 | # 27 | # verbose ${VERBOSE:-1} 28 | # 29 | # This will set verbose mode to true if it is not already set. 30 | # 31 | # It can be used in the following way: 32 | # 33 | # verbose && echo "Verbose mode is set!" 34 | # 35 | # 36 | 37 | if (( ${BASH_LINENO:-0} == 0 )); then 38 | source bashful-doc 39 | doc_execute "$0" "$@" 40 | exit 41 | fi 42 | 43 | [[ $BASHFUL_MODES_LOADED ]] && return 44 | 45 | source bashful-execute 46 | source bashful-utils 47 | 48 | interactive() 49 | { 50 | # 51 | # 52 | # With no arguments, test if interactive mode is enabled. 53 | # With one argument, set interactive mode to given value. 54 | # 55 | # Usage: interactive [VALUE] 56 | # 57 | # 58 | 59 | if (( $# == 0 )); then 60 | truth $INTERACTIVE && return 0 61 | return 1 62 | fi 63 | 64 | export INTERACTIVE=$(truth_value $1) 65 | } 66 | 67 | interactive_echo() 68 | { 69 | # 70 | # 71 | # Will only echo the first argument if interactive mode is enabled. 72 | # Otherwise, echo the second argument. 73 | # 74 | # Usage: interactive_echo [TRUE_VALUE] [FALSE_VALUE] 75 | # 76 | # 77 | 78 | truth_echo "$INTERACTIVE" "$1" "$2" 79 | } 80 | 81 | interactive_option() 82 | { 83 | # 84 | # 85 | # Echo the appropriate flag depending on the state of interactive mode. 86 | # 87 | # 88 | 89 | interactive_echo "-i" "-f" 90 | } 91 | 92 | verbose() 93 | { 94 | # 95 | # 96 | # With no arguments, test if verbose mode is enabled. 97 | # With one argument, set verbose mode to given value. 98 | # 99 | # Usage: verbose [VALUE] 100 | # 101 | # 102 | 103 | if (( $# == 0 )); then 104 | truth $VERBOSE && return 0 105 | return 1 106 | fi 107 | 108 | export VERBOSE=$(truth_value $1) 109 | } 110 | 111 | verbose_echo() 112 | { 113 | # 114 | # 115 | # Will only echo the first argument if verbose mode is enabled. 116 | # Otherwise, echo the second argument. 117 | # 118 | # Usage: verbose_echo [TRUE_VALUE] [FALSE_VALUE] 119 | # 120 | # 121 | 122 | truth_echo "$VERBOSE" "$1" "$2" 123 | } 124 | 125 | verbose_option() 126 | { 127 | # 128 | # 129 | # Echo the appropriate flag depending on the state of verbose mode. 130 | # 131 | # 132 | 133 | verbose_echo "-v" "-q" 134 | } 135 | 136 | verbose_execute() 137 | { 138 | # 139 | # 140 | # Will execute the given command and only display the output if verbose 141 | # mode is enabled. 142 | # 143 | # Usage: verbose_execute [COMMAND] 144 | # 145 | # 146 | 147 | if verbose; then 148 | execute "$@" 149 | else 150 | execute "$@" &>/dev/null 151 | fi 152 | } 153 | 154 | verbose_execute_in() 155 | { 156 | # 157 | # 158 | # Will execute the given command in the given directory and only display 159 | # the output if verbose mode is enabled. 160 | # 161 | # Usage: verbose_execute_in DIRECTORY [COMMAND] 162 | # 163 | # 164 | 165 | if verbose; then 166 | execute_in "$@" 167 | else 168 | execute_in "$@" &>/dev/null 169 | fi 170 | } 171 | 172 | elevated() 173 | { 174 | # 175 | # 176 | # With no arguments, test if elevated mode is enabled. 177 | # With one argument, set elevated mode to given value. 178 | # 179 | # Usage: elevated [VALUE] 180 | # 181 | # 182 | 183 | if (( $# == 0 )); then 184 | truth $ELEVATED && return 0 185 | return 1 186 | fi 187 | 188 | export ELEVATED=$(truth_value $1) 189 | export SUDO=$(truth_echo "$ELEVATED" sudo) 190 | } 191 | 192 | BASHFUL_MODES_LOADED=1 193 | -------------------------------------------------------------------------------- /bin/bashful-profile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Filename: bashful-profile 4 | # Description: Utilities for using script profiles. 5 | # Maintainer: Jeremy Cantrell 6 | # Last Modified: Thu 2012-06-07 20:59:27 (-0400) 7 | 8 | # 9 | # 10 | # The profile library provides functions for using profiles in scripts. 11 | # 12 | # There are some pieces of info needed for profile to work well. The most 13 | # important being the name of the app using it. This is provided by setting 14 | # either PROFILE_NAME or SCRIPT_NAME. If PROFILE_NAME is set, it will be 15 | # preferred over SCRIPT_NAME. Technically this is the only variable that is 16 | # required to function. 17 | # 18 | # It's recommended that you have also set verbose and interactive mode. 19 | # 20 | # If you intend to write new profiles with your script, a default profile 21 | # should be provided with the PROFILE_DEFAULT variable like this: 22 | # 23 | # PROFILE_DEFAULT=" 24 | # do_some_action=1 25 | # my_name='Jim Generic' 26 | # some_array=(this is an array) 27 | # # some_optional_var=foo 28 | # " 29 | # 30 | # Any new profile will be given these values for you to change, and any 31 | # profile that does not specify a value for a particular variable will inherit 32 | # the value from the default profile. Uncommented variables are considered to 33 | # be required and is considered an error if any profile does not provide it. 34 | # Any commented variables are considered optional and will not be subject to 35 | # this check. 36 | # 37 | # You can also use placeholders within PROFILE_DEFAULT that will be replaced 38 | # when the profile is created. Currently, the only placeholders are: 39 | # 40 | # PROFILE 41 | # PROFILE_NAME 42 | # 43 | # Within PROFILE_DEFAULT, these placeholders are given as: 44 | # 45 | # {{PROFILE}} 46 | # {{PROFILE_NAME}} 47 | # 48 | # You add more placeholders by creating an array called 49 | # PROFILE_DEFAULT_VARIABLES. On profile creation, variables with these names 50 | # will be substituted. 51 | # 52 | # You can override the default config directory by setting CONFIG_DIR. 53 | # Otherwise, this value will be set to one of the following: 54 | # 55 | # Run as user: ~/.$SCRIPT_NAME 56 | # Run as root: /usr/local/etc/$SCRIPT_NAME 57 | # PREFIX is set: $PREFIX/$SCRIPT_NAME 58 | # 59 | # Certain actions (currently: create, edit, delete) allow you to add commands 60 | # that will be run before (pre) and/or after (post) the action. The format of 61 | # these hook functions are: 62 | # 63 | # profile__() 64 | # { 65 | # # CODE GOES HERE 66 | # } 67 | # 68 | # For example, the following function would take place after any profile is 69 | # created: 70 | # 71 | # profile_create_post() 72 | # { 73 | # echo "Profile '$PROFILE' has been created." 74 | # } 75 | # 76 | # In summary, before any functionality can be used, you must do the following: 77 | # 78 | # source bashful-profile 79 | # 80 | # PROFILE_NAME=myapp 81 | # PROFILE_DEFAULT="..." 82 | # 83 | # profile_init # This will return non-zero exit code on error. 84 | # 85 | # 86 | 87 | if (( ${BASH_LINENO:-0} == 0 )); then 88 | source bashful-doc 89 | doc_execute "$0" "$@" 90 | exit 91 | fi 92 | 93 | [[ $BASHFUL_PROFILE_LOADED ]] && return 94 | 95 | source bashful-files 96 | source bashful-input 97 | source bashful-messages 98 | source bashful-modes 99 | source bashful-utils 100 | 101 | profile_hook() 102 | { 103 | # 104 | # 105 | # Execute a specified profile hook. 106 | # 107 | # Usage: profile_hook ACTION HOOK 108 | # 109 | # 110 | 111 | local command=profile_${1}_${2} 112 | if type $command &>/dev/null; then 113 | $command 114 | fi 115 | } 116 | 117 | profile_actions() 118 | { 119 | # 120 | # 121 | # Lists all available profile actions. 122 | # 123 | # 124 | 125 | actions "profile" 126 | } 127 | 128 | profile_choose() 129 | { 130 | # 131 | # 132 | # Prompt user for a profile. 133 | # 134 | # If interactive mode is not enabled, you better have already set the 135 | # profile or it's errors for everyone. 136 | # 137 | # 138 | 139 | if (( $(profile_list | wc -l) == 0 )); then 140 | error "No profiles available." 141 | return 0 142 | fi 143 | 144 | local OIFS=$IFS; local IFS=$'\n' 145 | local profiles=($(profile_list)) 146 | IFS=$OIFS 147 | 148 | local choice=$(choice -c -p "Choose profile" "${profiles[@]}") 149 | 150 | if [[ $choice ]]; then 151 | echo "$choice" 152 | else 153 | error "Profile not provided." 154 | return 1 155 | fi 156 | } 157 | 158 | profile_clear() 159 | { 160 | # 161 | # 162 | # Unsets all variables from a profile. 163 | # 164 | # 165 | 166 | local var 167 | for var in $(profile_variables); do 168 | unset $var 169 | done 170 | if [[ $PROFILE_VARIABLES ]]; then 171 | for var in "${PROFILE_VARIABLES[@]}"; do 172 | unset $var 173 | done 174 | fi 175 | } 176 | 177 | profile_create() 178 | { 179 | # 180 | # 181 | # Creates a new profile. 182 | # 183 | # 184 | 185 | [[ $PROFILE ]] || PROFILE=$(input -c -p "Enter profile" -d "${PWD##*/}") 186 | 187 | profile_file || return 1 188 | 189 | mkdir -p "$PROFILE_DIR" 190 | 191 | if [[ -f $PROFILE_FILE ]]; then 192 | error "Profile '$PROFILE' already exists." 193 | return 1 194 | fi 195 | 196 | info -c "Creating new profile '$PROFILE'..." 197 | 198 | profile_hook create pre 199 | 200 | local default=$PROFILE_DEFAULT 201 | 202 | local variables=( 203 | 'PROFILE' 204 | 'PROFILE_NAME' 205 | "${PROFILE_DEFAULT_VARIABLES[@]}" 206 | ) 207 | 208 | local default=$(flatten "$PROFILE_DEFAULT" "${variables[@]}") 209 | 210 | squeeze_lines <<<"$default" >$PROFILE_FILE 211 | 212 | if interactive; then 213 | editor "$PROFILE_FILE" 214 | else 215 | warn -c "Profile '$PROFILE_FILE' may need to be modified." 216 | fi 217 | 218 | profile_hook create post 219 | } 220 | 221 | profile_delete() 222 | { 223 | # 224 | # 225 | # Deletes an existing profile. 226 | # 227 | # 228 | 229 | profile_verify || return 1 230 | question -c -p "Are you sure you want to delete '$PROFILE'?" || return 1 231 | info -c "Deleting profile '$PROFILE'..." 232 | profile_hook delete pre 233 | rm -f "$PROFILE_FILE" 234 | profile_hook delete post 235 | } 236 | 237 | profile_edit() 238 | { 239 | # 240 | # 241 | # Edits an existing profile. 242 | # 243 | # 244 | 245 | profile_verify || return 1 246 | profile_hook edit pre 247 | editor "$PROFILE_FILE" 248 | profile_hook edit post 249 | } 250 | 251 | profile_file() 252 | { 253 | # 254 | # 255 | # Make sure the profile file is set before continuing. 256 | # 257 | # 258 | 259 | if [[ ! $PROFILE ]]; then 260 | PROFILE=$(profile_choose) || return 1 261 | fi 262 | 263 | if [[ ! $PROFILE ]]; then 264 | error "Profile not provided." 265 | return 1 266 | fi 267 | 268 | PROFILE_FILE=$PROFILE_DIR/$PROFILE 269 | } 270 | 271 | profile_init() 272 | { 273 | # 274 | # 275 | # Initialize the profile environment. 276 | # 277 | # This function should be called in the script before any other 278 | # functionality is used. If prefix was set, use that. 279 | # Otherwise use a prefix appropriate for the user's permissions. 280 | # 281 | # 282 | 283 | if (( EUID == 0 )); then 284 | PREFIX=${PREFIX:-/usr/local} 285 | else 286 | PREFIX=${PREFIX:-$HOME} 287 | fi 288 | 289 | # If profile name isn't set, default to script name. 290 | PROFILE_NAME=${PROFILE_NAME:-$SCRIPT_NAME} 291 | 292 | # If neither was set, you're doing it wrong. 293 | if [[ ! $PROFILE_NAME ]]; then 294 | error "PROFILE_NAME/SCRIPT_NAME not set." 295 | return 1 296 | fi 297 | 298 | # Depending on whether or not we have root privileges, 299 | # the config directory will be in $HOME or /usr/local/etc 300 | if [[ ! $CONFIG_DIR ]]; then 301 | if [[ ${PREFIX%%/} == $HOME ]]; then 302 | CONFIG_DIR=$PREFIX/.$PROFILE_NAME 303 | else 304 | CONFIG_DIR=$PREFIX/etc/$PROFILE_NAME 305 | fi 306 | fi 307 | 308 | PROFILE_DIR=$CONFIG_DIR/profiles 309 | } 310 | 311 | profile_list() 312 | { 313 | # 314 | # 315 | # List profiles. 316 | # 317 | # If called with no argument and PROFILE is not set, then all profiles 318 | # will be listed. If PROFILE is set, then it will be used as 319 | # a pattern to filter the list. If a pattern is passed, it will override 320 | # anything that PROFILE is set to. 321 | # 322 | # Usage: profile_list [PATTERN] 323 | # 324 | # Usage examples: 325 | # profile_list bash-.* # List all profiles starting with "bash-" 326 | # profile_list .* # Equivalent to: profile_list 327 | # 328 | # 329 | 330 | local profile=${1:-$PROFILE} 331 | 332 | listdir "$PROFILE_DIR" -type f | 333 | awk -F'/' '{print $NF}' | 334 | grep -v '^\.' | 335 | grep "${profile:+^$profile$}" | 336 | sort 337 | } 338 | 339 | profile_load() 340 | { 341 | # 342 | # 343 | # Load a profile. 344 | # 345 | # If a profile default is set, any missing settings will fallback to it. 346 | # Any previous profile settings will be cleared first based on what is 347 | # defined in the default profile. This is to be sure that you're not using 348 | # settings from different profiles. 349 | # 350 | # The uncommented default profile settings are considered required, and 351 | # uncommented settings are considered optional. 352 | # 353 | # 354 | 355 | profile_verify || return 1 356 | 357 | profile_hook load pre 358 | 359 | profile_clear 360 | 361 | eval "$PROFILE_DEFAULT"; source $PROFILE_FILE 362 | 363 | # If any required setting is unset, you're getting an error. 364 | local var 365 | for var in $(profile_variables_required); do 366 | if [[ ! ${!var} ]]; then 367 | error "Variable '$var' is not set for profile '$profile'." 368 | return 1 369 | fi 370 | done 371 | 372 | export PROFILE 373 | for var in $(profile_variables); do 374 | export $var 375 | done 376 | 377 | profile_hook load post 378 | } 379 | 380 | profile_variables() 381 | { 382 | # 383 | # 384 | # List all variables available to a profile (set or not). 385 | # 386 | # 387 | 388 | variables <<<"$PROFILE_DEFAULT" 389 | } 390 | 391 | profile_variables_required() 392 | { 393 | # 394 | # 395 | # List all required variables available to a profile (set or not). 396 | # 397 | # 398 | 399 | grep -v '^[[:space:]]*#' <<<"$PROFILE_DEFAULT" | variables 400 | } 401 | 402 | profile_verify() 403 | { 404 | # 405 | # 406 | # Make sure the profile file exists before continuing. 407 | # 408 | # 409 | 410 | profile_file || return 1 411 | 412 | if [[ ! -f $PROFILE_FILE ]]; then 413 | error "Profile '$PROFILE' does not exist or not a regular file." 414 | return 1 415 | fi 416 | } 417 | 418 | BASHFUL_PROFILE_LOADED=1 419 | -------------------------------------------------------------------------------- /bin/bashful-terminfo: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Filename: bashful-terminfo 4 | # Description: Sets terminal strings for things like color, bold, etc. 5 | # Maintainer: Jeremy Cantrell 6 | # Last Modified: Tue 2011-07-05 00:58:02 (-0400) 7 | 8 | # 9 | # 10 | # The terminfo library provides variables for altering the appearance of 11 | # terminal output (bold, standout, underline, colors, etc). 12 | # 13 | # If available to the terminal, the 8 standard color names are loaded 14 | # initially, along with things like bold, underline, etc. 15 | # 16 | # It's recommended that any access to colors/formatting be used through these 17 | # convenience functions: 18 | # 19 | # These functions will print text in a given color. One handles foreground, 20 | # the other handles background. Any colors beyond the standard 8 are loaded on 21 | # first access and cached for later use. 22 | # 23 | # Usage examples: 24 | # FG green "This foreground is green" 25 | # BG black "This background is black" 26 | # FG green "$(BG black "This text is green and black")" 27 | # 28 | # Although, if you don't care about any colors beyond the first 8 named ones, 29 | # you could just as easily do: 30 | # 31 | # echo "${term_fg_green}${term_bg_black}TEXT${term_reset}" 32 | # 33 | # This is also true for the formatting strings below (reset, bold, etc). 34 | # 35 | # You can also use numbered colors from 1 to the maximum number your terminal 36 | # supports. 37 | # 38 | # FG 255 "This foreground is whatever 255 is" 39 | # 40 | # In addition to the FG and BG functions, there's also a function that will 41 | # apply formatting options like bold, underline, etc. 42 | # 43 | # FX FORMAT TEXT 44 | # 45 | # Where FORMAT is one of: 46 | # 47 | # reset 48 | # bold 49 | # dim 50 | # standout 51 | # italic 52 | # underline 53 | # blink 54 | # reverse 55 | # 56 | # Don't be surprised if a lot of the formatting strings don't work. 99% of the 57 | # time, it's because your terminal doesn't support it, or you don't have the 58 | # right TERM setting. My best advice is to use xterm and set TERM to 59 | # xterm-256color. 60 | # 61 | # 62 | 63 | if (( ${BASH_LINENO:-0} == 0 )); then 64 | source bashful-doc 65 | doc_execute "$0" "$@" 66 | exit 67 | fi 68 | 69 | [[ $BASHFUL_TERMINFO_LOADED ]] && return 70 | 71 | if [[ ! $TERM ]]; then 72 | tput() { return; } 73 | fi 74 | 75 | term_colors=$(tput colors) 76 | 77 | P() { echo "${1}${2}${term_reset}"; } 78 | 79 | C() 80 | { 81 | local name="term_${1}g_$(printf "%03d" "$((10#$2))")" 82 | if [[ ! ${!name} ]] && (( $term_colors >= 8 )); then 83 | eval "$name=\"$(tput seta$1 $2)\"" 84 | fi 85 | P "${!name}" "$3" 86 | } 87 | 88 | FG() { C f "$1" "$2"; } 89 | BG() { C b "$1" "$2"; } 90 | 91 | FX() { P "$(named "term_$1")" "$2"; } 92 | 93 | term_reset=$(tput sgr0) 94 | term_bold=$(tput bold) 95 | term_dim=$(tput dim) 96 | term_standout=$(tput smso) 97 | term_italic=$(tput sitm) 98 | term_underline=$(tput smul) 99 | term_blink=$(tput blink) 100 | term_reverse=$(tput rev) 101 | 102 | if (( $term_colors >= 8 )); then 103 | # Foreground colors 104 | term_fg_black=$(tput setaf 0) 105 | term_fg_red=$(tput setaf 1) 106 | term_fg_green=$(tput setaf 2) 107 | term_fg_yellow=$(tput setaf 3) 108 | term_fg_blue=$(tput setaf 4) 109 | term_fg_magenta=$(tput setaf 5) 110 | term_fg_cyan=$(tput setaf 6) 111 | term_fg_white=$(tput setaf 7) 112 | # Background colors 113 | term_bg_black=$(tput setab 0) 114 | term_bg_red=$(tput setab 1) 115 | term_bg_green=$(tput setab 2) 116 | term_bg_yellow=$(tput setab 3) 117 | term_bg_blue=$(tput setab 4) 118 | term_bg_magenta=$(tput setab 5) 119 | term_bg_cyan=$(tput setab 6) 120 | term_bg_white=$(tput setab 7) 121 | fi 122 | 123 | BASHFUL_TERMINFO_LOADED=1 124 | -------------------------------------------------------------------------------- /bin/bashful-utils: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Filename: bashful-utils 4 | # Description: Miscellaneous utility functions for use in other scripts. 5 | # Maintainer: Jeremy Cantrell 6 | # Last Modified: Sun 2012-07-22 13:21:08 (-0400) 7 | 8 | # 9 | # 10 | # The utils library provides miscellaneous functions. 11 | # 12 | # 13 | 14 | if (( ${BASH_LINENO:-0} == 0 )); then 15 | source bashful-doc 16 | doc_execute "$0" "$@" 17 | exit 18 | fi 19 | 20 | [[ $BASHFUL_UTILS_LOADED ]] && return 21 | 22 | lower() 23 | { 24 | # 25 | # 26 | # Convert stdin to lowercase. 27 | # 28 | # 29 | 30 | tr '[:upper:]' '[:lower:]' 31 | } 32 | 33 | upper() 34 | { 35 | # 36 | # 37 | # Convert stdin to uppercase. 38 | # 39 | # 40 | 41 | tr '[:lower:]' '[:upper:]' 42 | } 43 | 44 | title() 45 | { 46 | # 47 | # 48 | # Convert stdin to titlecase. 49 | # 50 | # 51 | 52 | lower | sed 's/\<./\u&/g' | 53 | sed "s/'[[:upper:]]/\L&\l/g" 54 | } 55 | 56 | detox() 57 | { 58 | # 59 | # 60 | # Make text from stdin slightly less insane. 61 | # 62 | # 63 | 64 | sed 's/[^A-Za-z0-9 ]/ /g' | 65 | squeeze | sed 's/ /_/g' | lower 66 | } 67 | 68 | camel() 69 | { 70 | # 71 | # 72 | # Make text from stdin camel case. 73 | # 74 | # 75 | 76 | sed 's/_/ /g' | 77 | sed 's/\<\(.\)/\U\1/g' | 78 | sed 's/ //g' 79 | } 80 | 81 | snake() 82 | { 83 | # 84 | # 85 | # Make text from stdin snake case. 86 | # 87 | # 88 | 89 | sed 's/\([[:upper:]]\)/ \1/g' | detox 90 | } 91 | 92 | 93 | first() 94 | { 95 | # 96 | # 97 | # Get the first value that is non-empty. 98 | # 99 | # Usage examples: 100 | # EDITOR=$(first "$VISUAL" "$EDITOR" vi) 101 | # 102 | # 103 | 104 | local value 105 | for value in "$@"; do 106 | if [[ $value ]]; then 107 | echo "$value" 108 | return 0 109 | fi 110 | done 111 | return 1 112 | } 113 | 114 | named() 115 | { 116 | # 117 | # 118 | # Get the value of the variable named the passed argument. 119 | # 120 | # The only reason why this function exists is because I can't do: 121 | # 122 | # echo ${!"some_var"} 123 | # 124 | # Instead, I have to do: 125 | # 126 | # some_var="The value I really want" 127 | # name="some_var" 128 | # echo ${!name} #==> The value I really want 129 | # 130 | # With named(), I can now do: 131 | # 132 | # some_var="The value I really want" 133 | # named "some_var" #==> The value I really want 134 | # 135 | # Which eliminates the need for an intermediate variable. 136 | # 137 | # 138 | 139 | echo "${!1}" 140 | } 141 | 142 | truth() 143 | { 144 | # 145 | # 146 | # Determine the "truthiness" of the given value. 147 | # 148 | # Usage examples: 149 | # truth True #==> true 150 | # truth #==> false 151 | # truth 1 #==> true 152 | # truth false #==> false 153 | # truth on #==> true 154 | # truth spam #==> false 155 | # 156 | # 157 | 158 | case $(lower <<<"$1") in 159 | yes|y|true|t|on|1) return 0 ;; 160 | esac 161 | return 1 162 | } 163 | 164 | truth_echo() 165 | { 166 | # 167 | # 168 | # Depending on the "truthiness" of the given value, echo the first (true) 169 | # or second (false) value. 170 | # 171 | # 172 | 173 | if truth "$1"; then 174 | [[ $2 ]] && echo "$2" 175 | else 176 | [[ $3 ]] && echo "$3" 177 | fi 178 | } 179 | 180 | truth_value() 181 | { 182 | # 183 | # 184 | # Gets a value that represents the "truthiness" of the given value. 185 | # 186 | # 187 | 188 | truth_echo "$1" 1 0 189 | } 190 | 191 | squeeze() 192 | { 193 | # 194 | # 195 | # Removes leading/trailing whitespace and condenses all other consecutive 196 | # whitespace into a single space. 197 | # 198 | # Usage examples: 199 | # echo " foo bar baz " | squeeze #==> "foo bar baz" 200 | # 201 | # 202 | 203 | local char=${1:-[[:space:]]} 204 | sed "s%\(${char//%/\\%}\)\+%\1%g" | trim "$char" 205 | } 206 | 207 | squeeze_lines() 208 | { 209 | # 210 | # 211 | # Removes all leading/trailing blank lines and condenses all other 212 | # consecutive blank lines into a single blank line. 213 | # 214 | # 215 | 216 | sed '/^[[:space:]]\+$/s/.*//g' | cat -s | trim_lines 217 | } 218 | 219 | trim() 220 | { 221 | # 222 | # 223 | # Removes all leading/trailing whitespace 224 | # 225 | # Usage examples: 226 | # echo " foo bar baz " | trim #==> "foo bar baz" 227 | # 228 | # 229 | 230 | ltrim "$1" | rtrim "$1" 231 | } 232 | 233 | ltrim() 234 | { 235 | # 236 | # 237 | # Removes all leading whitespace (from the left). 238 | # 239 | # 240 | 241 | local char=${1:-[:space:]} 242 | sed "s%^[${char//%/\\%}]*%%" 243 | } 244 | 245 | rtrim() 246 | { 247 | # 248 | # 249 | # Removes all trailing whitespace (from the right). 250 | # 251 | # 252 | 253 | local char=${1:-[:space:]} 254 | sed "s%[${char//%/\\%}]*$%%" 255 | } 256 | 257 | trim_lines() 258 | { 259 | # 260 | # 261 | # Removes all leading/trailing blank lines. 262 | # 263 | # Explanation of sed command: 264 | # 265 | # :a # Set label a 266 | # $!{ # For every line except the last... 267 | # N # Add to pattern space with a newline 268 | # ba # Go back to label a 269 | # } 270 | # 271 | # The pattern space now consists of a single string containing newlines. 272 | # 273 | # s/^[[:space:]]*\n// # Remove all leading whitespace (blank lines). 274 | # s/\n[[:space:]]*$// # Remove all trailing whitespace (blank lines). 275 | # 276 | # 277 | 278 | sed ':a;$!{N;ba;};s/^[[:space:]]*\n//;s/\n[[:space:]]*$//' 279 | } 280 | 281 | sleep_until() 282 | { 283 | # 284 | # 285 | # Causes the running process to wait until the given date. 286 | # If the date is in the past, it immediately returns. 287 | # 288 | # 289 | 290 | local secs=$(($(date -d "$1" +%s) - $(date +%s))) 291 | (( secs > 0 )) && sleep $secs 292 | } 293 | 294 | variables() 295 | { 296 | # 297 | # 298 | # Pulls all variable names from stdin. 299 | # 300 | # 301 | 302 | sed 's/[[:space:];]/\n/g' "$@" | 303 | egrep '^[a-zA-Z0-9_]+=' | 304 | sed 's/=.*$//' | sort -u 305 | } 306 | 307 | functions() 308 | { 309 | # 310 | # 311 | # Pulls all function names from stdin. 312 | # 313 | # 314 | 315 | sed 's/[[:space:];]/\n/g' "$@" | 316 | grep -E '^[a-zA-Z0-9_-]+\(\)' | 317 | sed 's/().*$//' | sort -u 318 | } 319 | 320 | editor() 321 | { 322 | # 323 | # 324 | # Execute the preferred editor. 325 | # 326 | # 327 | 328 | $(first "$VISUAL" "$EDITOR" "vi") "$@" 329 | } 330 | 331 | pager() 332 | { 333 | # 334 | # 335 | # Execute the preferred pager. 336 | # 337 | # 338 | 339 | $(first "$PAGER" "less") 340 | } 341 | 342 | repeat() 343 | { 344 | # 345 | # 346 | # Repeat a command a given number of times. 347 | # 348 | # 349 | 350 | local count=$1; shift 351 | local i 352 | for i in $(seq 1 $count); do 353 | "$@" 354 | done 355 | } 356 | 357 | lines() 358 | { 359 | # 360 | # 361 | # Get all lines except for comments and blank lines. 362 | # 363 | # Usage: lines [FILE...] 364 | # 365 | # 366 | 367 | grep -E -v '^[[:space:]]*#|^[[:space:]]*$' "$@" 368 | } 369 | 370 | eval_embedded() 371 | { 372 | # 373 | # 374 | # Eval embedded code contained in a given tag. 375 | # 376 | # Usage: eval_embedded NAME [FILE...] 377 | # 378 | # 379 | 380 | local name=$1; shift 381 | eval "$(embedded_tag "eval:$name" "$@")" 382 | } 383 | 384 | embedded() 385 | { 386 | local c="#$1"; c="^[[:space:]]*$c[[:space:]]*" 387 | sed -n "/$c/p" "$@" | sed "s/$c//" | squeeze_lines 388 | } 389 | 390 | embedded_range() 391 | { 392 | # 393 | # 394 | # Get embedded text between a given range. 395 | # 396 | # Usage: embedded_range START END [FILE...] 397 | # 398 | # 399 | 400 | local s=${1//\//\\/}; shift 401 | local e=${1//\//\\/}; shift 402 | local c="^[[:space:]]*#[[:space:]]*" 403 | 404 | sed -n "/$c$s/,/$c$e/p" "$@" | sed '1d;$d' | sed "s/$c//" | squeeze_lines 405 | } 406 | 407 | embedded_tag() 408 | { 409 | # 410 | # 411 | # Get the embedded text between a start and end XML-like tag. 412 | # 413 | # Usage: embedded_tag NAME 414 | # 415 | # 416 | 417 | local name=$1; shift 418 | embedded_range "<$name>" "" "$@" 419 | } 420 | 421 | commonprefix() 422 | { 423 | # 424 | # 425 | # Gets the common prefix of the strings passed on stdin. 426 | # 427 | # Usage examples: 428 | # echo -e "spam\nspace" | commonprefix #==> spa 429 | # echo -e "foo\nbar\nbaz" | commonprefix #==> 430 | # 431 | # 432 | 433 | local i compare prefix 434 | 435 | if (( $# > 0 )); then 436 | local str 437 | for str in "$@"; do 438 | echo "$str" 439 | done | commonprefix 440 | return 441 | fi 442 | 443 | while read -r; do 444 | [[ $prefix ]] || prefix=$REPLY 445 | i=0 446 | unset compare 447 | while true; do 448 | [[ ${REPLY:$i:1} || ${prefix:$i:1} ]] || break 449 | [[ ${REPLY:$i:1} != ${prefix:$i:1} ]] && break 450 | compare+=${REPLY:$((i++)):1} 451 | done 452 | prefix=$compare 453 | echo "$prefix" 454 | done | tail -n1 455 | } 456 | 457 | commonsuffix() 458 | { 459 | # 460 | # 461 | # Gets the common suffix of the strings passed on stdin. 462 | # 463 | # Usage examples: 464 | # echo -e "foobar\nbabar" | commonsuffix #==> bar 465 | # echo -e "broom\ngroom" | commonsuffix #==> room 466 | # 467 | # 468 | 469 | if (( $# > 0 )); then 470 | local str 471 | for str in "$@"; do 472 | echo "$str" 473 | done | commonsuffix 474 | return 475 | fi 476 | 477 | rev | commonprefix | rev 478 | } 479 | 480 | sort_list() 481 | { 482 | # 483 | # 484 | # Sorts a list from stdin. 485 | # 486 | # Usage: sort_list [-ur] [DELIMITER] 487 | # 488 | # Usage examples: 489 | # echo "c b a" | sort_list #==> a b c 490 | # echo "c, b, a" | sort_list ", " #==> a, b, c 491 | # echo "c b b b a" | sort_list -u #==> a b c 492 | # echo "c b a" | sort_list -r #==> c b a 493 | # 494 | # 495 | 496 | local r u 497 | 498 | unset OPTIND 499 | while getopts ":ur" option; do 500 | case $option in 501 | u) u=-u ;; 502 | r) r=-r ;; 503 | esac 504 | done && shift $(($OPTIND - 1)) 505 | 506 | local delim=${1:- } 507 | local item list 508 | 509 | OIFS=$IFS; IFS=$'\n' 510 | for item in $(sed "s%${delim//%/\%}%\n%g" | sort $r $u); do 511 | IFS=$OIFS 512 | list+="$(trim <<<"$item")$delim" 513 | done 514 | 515 | echo "${list%%$delim}" 516 | } 517 | 518 | split_string() 519 | { 520 | # 521 | # 522 | # Split text from stdin into a list. 523 | # 524 | # DELIMITER defaults to ",". 525 | # 526 | # Usage: split_string [DELIMITER] 527 | # 528 | # Usage examples: 529 | # echo "foo, bar, baz" | split_string #==> foo\nbar\nbaz 530 | # echo "foo|bar|baz" | split_string "|" #==> foo\nbar\nbaz 531 | # 532 | # 533 | 534 | local delim=${1:-,} 535 | local line str 536 | 537 | while read -r; do 538 | OIFS=$IFS; IFS=$delim 539 | for str in $REPLY; do 540 | IFS=$OIFS 541 | trim <<<"$str" 542 | done 543 | done 544 | } 545 | 546 | join_lines() 547 | { 548 | # 549 | # 550 | # Joins lines from stdin into a string. 551 | # 552 | # DELIMITER defaults to ", ". 553 | # 554 | # Usage: join_lines [DELIMITER] 555 | # 556 | # Usage examples: 557 | # echo -e "foo\nbar\nbaz" | join_lines #==> foo, bar, baz 558 | # echo -e "foo\nbar\nbaz" | join_lines "|" #==> foo|bar|baz 559 | # 560 | # 561 | 562 | local delim=${1:-, } 563 | 564 | while read -r; do 565 | echo -ne "${REPLY}${delim}" 566 | done | sed "s/$delim$//" 567 | echo 568 | } 569 | 570 | flatten() 571 | { 572 | # 573 | # 574 | # Substitute variable names with variables. 575 | # 576 | # The default is to try to substitute all environment variables, but if 577 | # any names are given, it will be limited to just those. 578 | # 579 | # The placeholder syntax can be changed by setting the following variables: 580 | # 581 | # FLATTEN_L # Default: {{ 582 | # FLATTEN_R # Default: }} 583 | # 584 | # Usage: flatten TEXT [VAR...] 585 | # 586 | # 587 | 588 | local t=$1; shift 589 | local n 590 | 591 | local fl=${FLATTEN_L:-\{\{} 592 | local fr=${FLATTEN_R:-\}\}} 593 | 594 | if (( $# == 0 )); then 595 | IFS=$'\n' set -- $(set | variables) 596 | fi 597 | 598 | for n in "$@"; do 599 | t=${t//${fl}${n}${fr}/${!n}} 600 | done 601 | 602 | echo "$t" 603 | } 604 | 605 | flatten_file() 606 | { 607 | # 608 | # 609 | # Substitute variable names with variables in a file. 610 | # 611 | # The default is to try to substitute all environment variables, but if 612 | # any names are given, it will be limited to just those. 613 | # 614 | # The placeholder syntax can be changed by setting the following variables: 615 | # 616 | # FLATTEN_L # Default: {{ 617 | # FLATTEN_R # Default: }} 618 | # 619 | # Usage: flatten_file FILE [VAR...] 620 | # 621 | # 622 | 623 | local fn=$1; shift 624 | 625 | [[ -f $fn ]] || return 1 626 | 627 | local n 628 | 629 | local fl=${FLATTEN_L:-\{\{} 630 | local fr=${FLATTEN_R:-\}\}} 631 | 632 | if (( $# == 0 )); then 633 | IFS=$'\n' set -- $(set | variables) 634 | fi 635 | 636 | for n in "$@"; do 637 | sed -i "s%${fl}${n//%/\%}${fr}%${!n}%g" "$fn" 638 | done 639 | } 640 | 641 | timestamp() 642 | { 643 | # 644 | # 645 | # Nothing special, really. Just a frequently used date format. 646 | # 647 | # 648 | 649 | date +%Y%m%d%H%M%S 650 | } 651 | 652 | actions() 653 | { 654 | # 655 | # 656 | # Show all functions that are prefixed with SCRIPT_NAME (stripped off). 657 | # 658 | # Usage: actions [NAME] 659 | # 660 | # 661 | 662 | local name=${1:-$SCRIPT_NAME} 663 | declare -F | 664 | awk '{print $NF}' | 665 | grep "^${name}_" | 666 | sed "s/^${name}_//" | 667 | sort -u 668 | } 669 | 670 | find_above() 671 | { 672 | # 673 | # 674 | # Find the nearest file/directory above the current directory. 675 | # Takes the same options as find. 676 | # 677 | # Usage: find_above [OPTIONS] 678 | # 679 | # 680 | 681 | local OPWD=$PWD 682 | local result 683 | while true; do 684 | find "$PWD" -maxdepth 1 -mindepth 1 "$@" 685 | [[ $PWD == / ]] && break 686 | cd .. 687 | done 688 | cd "$OPWD" 689 | } 690 | 691 | in_array() 692 | { 693 | # 694 | # 695 | # Determine if a value is in an array. 696 | # 697 | # Usage: in_array [VALUE] [ARRAY] 698 | # 699 | # 700 | 701 | local value=$1; shift 702 | for item in "$@"; do 703 | [[ $item == $value ]] && return 0 704 | done 705 | return 1 706 | } 707 | 708 | # Convenience (indicates when script was executed) 709 | TIMESTAMP=$(timestamp) 710 | 711 | BASHFUL_UTILS_LOADED=1 712 | -------------------------------------------------------------------------------- /bin/shdoc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Filename: shdoc 4 | # Description: Extracts documentation from shell scripts. 5 | # Maintainer: Jeremy Cantrell 6 | # Last Modified: Tue 2011-07-05 19:38:58 (-0400) 7 | 8 | # 9 | # 10 | # Extracts documentation from shell scripts. 11 | # 12 | # Usage: shdoc [OPTIONS] COMMAND [SECTION] 13 | # 14 | # 15 | 16 | source bashful-doc 17 | source bashful-messages 18 | source bashful-utils 19 | 20 | SCRIPT_NAME=$(basename "$0" .sh) 21 | SCRIPT_USAGE="Extracts documentation from shell scripts." 22 | SCRIPT_ARGUMENTS="COMMAND [SECTION]" 23 | 24 | ACTION=doc_help 25 | 26 | unset OPTIND 27 | while getopts ":hL" option; do 28 | case $option in 29 | L) ACTION=doc_topics ;; 30 | h) usage 0 ;; 31 | *) usage 1 ;; 32 | esac 33 | done && shift $(($OPTIND - 1)) 34 | 35 | if (( $# == 0 )) || [[ $1 == help ]]; then 36 | set - "$0" 37 | fi 38 | 39 | $ACTION "$@" 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bashful", 3 | "version": "0.0.1", 4 | "description": "A collection of libraries to simplify writing bash scripts", 5 | "global": "true", 6 | "install": "make install" 7 | } 8 | 9 | -------------------------------------------------------------------------------- /share/man/man1/bashful.1: -------------------------------------------------------------------------------- 1 | .TH bashful 1 "September 2010" bashful 2 | .SH NAME 3 | Bashful - A collection of libraries to simplify writing bash scripts 4 | .SH SYNOPSIS 5 | source 6 | .B bashful 7 | .br 8 | source 9 | .B bashful-execute 10 | .br 11 | source 12 | .B bashful-files 13 | .br 14 | source 15 | .B bashful-input 16 | .br 17 | source 18 | .B bashful-messages 19 | .br 20 | source 21 | .B bashful-modes 22 | .br 23 | source 24 | .B bashful-profile 25 | .br 26 | source 27 | .B bashful-terminfo 28 | .br 29 | source 30 | .B bashful-utils 31 | .br 32 | .SH DESCRIPTION 33 | .B Bashful 34 | is a collection of bash libraries to simplify writing other bash scripts. 35 | .P 36 | To get complete documentation for each library, just invoke it at the command line with 37 | .B help 38 | as the first argument. For example, to get documentation for the 39 | .B input 40 | library: 41 | .P 42 | .RS 43 | .B $ 44 | bashful-input help 45 | .RE 46 | .P 47 | .B bashful-execute 48 | .RS 49 | This library provides functions for building and executing commands. 50 | .RE 51 | .P 52 | .B bashful-files 53 | .RS 54 | This library provides functions for working with files/directories. 55 | .RE 56 | .P 57 | .B bashful-input 58 | .RS 59 | This library provides functions for getting user input. 60 | .RE 61 | .P 62 | .B bashful-messages 63 | .RS 64 | This library provides functions for notifying the user. 65 | .RE 66 | .P 67 | .B bashful-modes 68 | .RS 69 | This library provides functions for getting and setting mode values (verbose, interactive). 70 | .RE 71 | .P 72 | .B bashful-profile 73 | .RS 74 | This library provides functions for using configuration profiles. 75 | .RE 76 | .P 77 | .B bashful-terminfo 78 | .RS 79 | This library provides functions for altering the appearance of terminal output (bold, underline, color). 80 | .RE 81 | .P 82 | .B bashful-utils 83 | .RS 84 | This library provides miscellaneous functions. 85 | .RE 86 | -------------------------------------------------------------------------------- /share/man/man1/shdoc.1: -------------------------------------------------------------------------------- 1 | .TH shdoc 1 "September 2010" shdoc 2 | .SH NAME 3 | shdoc - Extracts documentation from shell scripts. 4 | .SH SYNOPSIS 5 | .B shdoc 6 | [\fIOPTIONS\fR] [\fICOMMAND\fR] [\fISECTION\fR] 7 | .SH OPTIONS 8 | .TP 9 | .B \-h 10 | Display a short help message and exit. 11 | -------------------------------------------------------------------------------- /share/zsh/functions/_shdoc: -------------------------------------------------------------------------------- 1 | #compdef shdoc 2 | 3 | local context state line config_dir 4 | typeset -A opt_args 5 | 6 | _arguments -s -S \ 7 | "-h[Display help message]" \ 8 | "1::command:->command" \ 9 | "2::topic:->topic" \ 10 | && return 0 11 | 12 | case $state in 13 | 'command') _command ;; 14 | 'topic') _values 'topics' $(shdoc -L $words[2]) ;; 15 | esac 16 | --------------------------------------------------------------------------------