├── .travis.yml ├── Changes ├── License ├── Makefile ├── Meta ├── ReadMe.pod ├── bin └── bash+ ├── doc └── bash+.swim ├── lib └── bash+.bash ├── man ├── man1 │ └── bash+.1 └── man3 │ └── bash+.3 └── test ├── base.t ├── die.t ├── fcopy.t ├── lib └── foo │ ├── bar.bash │ └── foo.bash ├── setup ├── shellcheck.t ├── source-bash+-std.t ├── source-bash+.t ├── use.t └── version-check.t /.travis.yml: -------------------------------------------------------------------------------- 1 | # C language gives closest shell env. 2 | language: c 3 | 4 | script: 5 | - git submodule update --init --recursive 6 | - PROVEOPT=-v make test 7 | -------------------------------------------------------------------------------- /Changes: -------------------------------------------------------------------------------- 1 | --- 2 | version: 0.1.0 3 | date: Sat 14 Nov 2020 10:14:14 AM EST 4 | changes: 5 | - Add tests for version-check 6 | - Improve version-check 7 | - Move PATH assignment into test/setup 8 | - Meta bashplus supports Bash 3.2 9 | --- 10 | version: 0.0.9 11 | date: Wed 11 Nov 2020 02:19:32 PM EST 12 | changes: 13 | - Apply shellcheck fixes 14 | - Modernize bash code 15 | --- 16 | version: 0.0.8 17 | date: Fri Aug 21 08:00:45 PDT 2020 18 | changes: 19 | - Support paths with spaces @admorgan++ 20 | --- 21 | version: 0.0.7 22 | date: Sat Jan 23 16:28:59 PST 2016 23 | changes: 24 | - Update tooling, and copyright 25 | --- 26 | version: 0.0.6 27 | date: Fri Jan 23 21:05:15 PST 2015 28 | changes: 29 | - Update tooling, and copyright 30 | --- 31 | version: 0.0.1 32 | date: Sun Oct 27 19:07:51 PDT 2013 33 | changes: 34 | - First release. 35 | -------------------------------------------------------------------------------- /License: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright © 2013-2020 Ingy döt Net 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the ‘Software’), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ifeq ($(MAKECMDGOALS),install) 2 | ifeq "$(shell bpan version 2>/dev/null)" "" 3 | $(error 'BPAN not installed. See http://bpan.org') 4 | endif 5 | endif 6 | 7 | NAME := bash+ 8 | LIB := lib/$(NAME).bash 9 | DOC := doc/$(NAME).swim 10 | MAN1 := man/man1 11 | MAN3 := man/man3 12 | 13 | INSTALL_LIB ?= $(shell bpan env BPAN_LIB) 14 | INSTALL_DIR ?= test 15 | INSTALL_MAN1 ?= $(shell bpan env BPAN_MAN1) 16 | INSTALL_MAN3 ?= $(shell bpan env BPAN_MAN3) 17 | 18 | DOCKER_IMAGE := ingy/bash-testing:0.0.1 19 | DOCKER_TESTS := 5.1 5.0 4.4 4.3 4.2 4.1 4.0 3.2 20 | DOCKER_TESTS := $(DOCKER_TESTS:%=docker-test-%) 21 | 22 | default: help 23 | 24 | help: 25 | @echo 'Rules: test, install, doc' 26 | 27 | .PHONY: test 28 | test: 29 | prove $(PROVEOPT:%=% )test/ 30 | 31 | test-all: test docker-test 32 | 33 | docker-test: $(DOCKER_TESTS) 34 | 35 | $(DOCKER_TESTS): 36 | $(call docker-make-test,$(@:docker-test-%=%)) 37 | 38 | install: 39 | install -C -d -m 0755 $(INSTALL_LIB)/$(INSTALL_DIR)/ 40 | install -C -m 0755 $(LIB) $(INSTALL_LIB)/$(INSTALL_DIR)/ 41 | install -C -d -m 0755 $(INSTALL_MAN1)/ 42 | install -C -d -m 0755 $(INSTALL_MAN3)/ 43 | install -C -m 0644 $(MAN1)/$(NAME).1 $(INSTALL_MAN1)/ 44 | install -C -m 0644 $(MAN3)/$(NAME).3 $(INSTALL_MAN3)/ 45 | 46 | .PHONY: doc 47 | doc: ReadMe.pod $(MAN1)/$(NAME).1 $(MAN3)/$(NAME).3 48 | 49 | ReadMe.pod: $(DOC) 50 | swim --to=pod --complete --wrap $< > $@ 51 | 52 | $(MAN1)/%.1: doc/%.swim 53 | swim --to=man $< > $@ 54 | 55 | $(MAN3)/%.3: doc/%.swim 56 | swim --to=man $< > $@ 57 | 58 | define docker-make-test 59 | docker run -i -t --rm \ 60 | -v $(PWD):/git-subrepo \ 61 | -w /git-subrepo \ 62 | $(DOCKER_IMAGE) \ 63 | /bin/bash -c ' \ 64 | set -x && \ 65 | [[ -d /bash-$(1) ]] && \ 66 | export PATH=/bash-$(1)/bin:$$PATH && \ 67 | bash --version && \ 68 | make test \ 69 | ' 70 | endef 71 | -------------------------------------------------------------------------------- /Meta: -------------------------------------------------------------------------------- 1 | =meta: 0.0.2 2 | 3 | name: bashplus 4 | version: 0.1.0 5 | abstract: Modern Bash Programming 6 | homepage: https://github.com/ingydotnet/bashplus 7 | 8 | license: MIT 9 | copyright: 2013-2020 10 | author: 11 | name: Ingy döt Net 12 | email: ingy@ingy.net 13 | github: ingydotnet 14 | twitter: ingydotnet 15 | freenode: ingy 16 | homepage: http://ingy.net 17 | 18 | requires: 19 | bash: 3.2 20 | test: 21 | cmd: make test 22 | install: 23 | cmd: make install 24 | 25 | devel: 26 | git: git@github.org/ingydotnet/bashplus 27 | irc: irc.freenode.net/bpan 28 | bug: https://github.com/ingydotnet/bashplus/issues/ 29 | -------------------------------------------------------------------------------- /ReadMe.pod: -------------------------------------------------------------------------------- 1 | =pod 2 | 3 | =for comment 4 | DO NOT EDIT. This Pod was generated by Swim v0.1.48. 5 | See http://github.com/ingydotnet/swim-pm#readme 6 | 7 | =encoding utf8 8 | 9 | =head1 Name 10 | 11 | Bash+(1) - Modern Bash Programming 12 | 13 | =for html 14 | bashplus 15 | 16 | =head1 Synopsis 17 | 18 | source bash+ :std :array 19 | 20 | use Foo::Bar this that 21 | 22 | Array.new args "$@" 23 | 24 | if args.empty?; then 25 | die "I need args!" 26 | fi 27 | 28 | Foo::Bar.new foo args 29 | 30 | this is awesome # <= this is a real command! (You just imported it) 31 | 32 | =head1 Description 33 | 34 | Bash+ is just Bash... B some libraries that can make Bash programming a 35 | lot nicer. 36 | 37 | =for comment # Installation 38 | 39 | Get the source code from GitHub: 40 | 41 | git clone git@github.com:ingydotnet/bashplus 42 | 43 | Then run: 44 | 45 | make test 46 | make install # Possibly with 'sudo' 47 | 48 | =head1 Usage 49 | 50 | For now look at some libraries the use Bash+: 51 | 52 | =over 53 | 54 | =item * L 55 | 56 | =item * L 57 | 58 | =item * L 59 | 60 | =back 61 | 62 | =head1 Status 63 | 64 | If you are interested in chatting about this, C on 65 | irc.freenode.net. 66 | 67 | =head1 Author 68 | 69 | Written by Ingy döt Net 70 | 71 | =head1 Copyright & License 72 | 73 | Copyright 2013-2020. Ingy döt Net. 74 | 75 | The MIT License (MIT). 76 | 77 | =cut 78 | -------------------------------------------------------------------------------- /bin/bash+: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | #------------------------------------------------------------------------------ 3 | # Bash+ - Modern Bash Programming 4 | # 5 | # Copyright (c) 2013-2020 Ingy döt Net 6 | #------------------------------------------------------------------------------ 7 | 8 | set -e 9 | shopt -s compat31 &>/dev/null || true 10 | 11 | #------------------------------------------------------------------------------ 12 | # Determine how `bash+` was called, and do the right thing: 13 | #------------------------------------------------------------------------------ 14 | if [[ ${BASH_SOURCE[0]} != "$0" ]]; then 15 | # 'bash+' is being sourced: 16 | [[ ${BASH_SOURCE[0]} =~ /bin/bash\\+$ ]] || { 17 | echo "Invalid Bash+ path '${BASH_SOURCE[0]}'" 2> /dev/null 18 | exit 1 19 | } 20 | source "${BASH_SOURCE[0]%/bin/*}"/lib/bash+.bash || return $? 21 | bash+:import "$@" 22 | return $? 23 | 24 | else 25 | if [[ $# -eq 1 ]] && [[ $1 == --version ]]; then 26 | echo 'bash+ version 0.0.9' 27 | else 28 | cat <<'...' 29 | 30 | Greetings modern Bash programmer. Welcome to Bash+! 31 | 32 | Bash+ is framework that makes Bash programming more like Ruby and Perl. 33 | 34 | See: https://github.com/bpan-org/bashplus 35 | 36 | If you got here trying to use bash+ in a program, you need to source it: 37 | 38 | source bash+ 39 | 40 | Happy Bash Hacking! 41 | 42 | ... 43 | fi 44 | fi 45 | -------------------------------------------------------------------------------- /doc/bash+.swim: -------------------------------------------------------------------------------- 1 | Bash+(1) 2 | ======== 3 | 4 | Modern Bash Programming 5 | 6 | 7 | 8 | = Synopsis 9 | 10 | source bash+ :std :array 11 | 12 | use Foo::Bar this that 13 | 14 | Array.new args "$@" 15 | 16 | if args.empty?; then 17 | die "I need args!" 18 | fi 19 | 20 | Foo::Bar.new foo args 21 | 22 | this is awesome # <= this is a real command! (You just imported it) 23 | 24 | = Description 25 | 26 | Bash+ is just Bash... *plus* some libraries that can make Bash programming a 27 | lot nicer. 28 | 29 | ## Installation 30 | 31 | Get the source code from GitHub: 32 | 33 | git clone git@github.com:ingydotnet/bashplus 34 | 35 | Then run: 36 | 37 | make test 38 | make install # Possibly with 'sudo' 39 | 40 | = Usage 41 | 42 | For now look at some libraries the use Bash+: 43 | 44 | * https://github.com/ingydotnet/git-hub 45 | * https://github.com/ingydotnet/json-bash 46 | * https://github.com/ingydotnet/test-more-bash 47 | 48 | = Status 49 | 50 | If you are interested in chatting about this, `/join #bpan` on 51 | irc.freenode.net. 52 | 53 | = Author 54 | 55 | Written by Ingy döt Net 56 | 57 | = Copyright & License 58 | 59 | Copyright 2013-2020. Ingy döt Net. 60 | 61 | The MIT License (MIT). 62 | -------------------------------------------------------------------------------- /lib/bash+.bash: -------------------------------------------------------------------------------- 1 | # bash+ - Modern Bash Programming 2 | # 3 | # Copyright (c) 2013-2020 Ingy döt Net 4 | 5 | set -e 6 | 7 | [[ ${BASHPLUS_VERSION-} ]] && return 0 8 | 9 | BASHPLUS_VERSION=0.1.0 10 | 11 | bash+:version-check() { 12 | local cmd want got out 13 | 14 | IFS=' ' read -r -a cmd <<< "${1:?}" 15 | IFS=. read -r -a want <<< "${2:?}" 16 | : "${want[0]:=0}" 17 | : "${want[1]:=0}" 18 | : "${want[2]:=0}" 19 | 20 | if [[ ${cmd[*]} == bash ]]; then 21 | got=("${BASH_VERSINFO[@]}") 22 | BASHPLUS_VERSION_CHECK=${BASH_VERSION-} 23 | else 24 | [[ ${#cmd[*]} -gt 1 ]] || cmd+=(--version) 25 | out=$("${cmd[@]}") || 26 | { echo "Failed to run '${cmd[*]}'" >&2; exit 1; } 27 | [[ $out =~ ([0-9]+\.[0-9]+(\.[0-9]+)?) ]] || 28 | { echo "Can't determine version number from '${cmd[*]}'" >&2; exit 1; } 29 | BASHPLUS_VERSION_CHECK=${BASH_REMATCH[1]} 30 | IFS=. read -r -a got <<< "$BASHPLUS_VERSION_CHECK" 31 | fi 32 | : "${got[2]:=0}" 33 | 34 | (( got[0] > want[0] || (( 35 | got[0] == want[0] && (( 36 | got[1] > want[1] || (( 37 | got[1] == want[1] && got[2] >= want[2] 38 | )) )) )) )) 39 | } 40 | 41 | bash+:version-check bash 3.2 || 42 | { echo "The 'bashplus' library requires 'Bash 3.2+'." >&2; exit 1; } 43 | 44 | @() (echo "$@") # XXX do we want to keep this? 45 | 46 | bash+:export:std() { 47 | set -o pipefail 48 | 49 | if bash+:version-check bash 4.4; then 50 | set -o nounset 51 | shopt -s inherit_errexit 52 | fi 53 | 54 | echo use die warn 55 | } 56 | 57 | # Source a bash library call import on it: 58 | bash+:use() { 59 | local library_name=${1:?bash+:use requires library name}; shift 60 | local library_path=; library_path=$(bash+:findlib "$library_name") || true 61 | [[ $library_path ]] || 62 | bash+:die "Can't find library '$library_name'." 1 63 | 64 | source "$library_path" 65 | if bash+:can "$library_name:import"; then 66 | "$library_name:import" "$@" 67 | else 68 | bash+:import "$@" 69 | fi 70 | } 71 | 72 | # Copy bash+: functions to unprefixed functions 73 | bash+:import() { 74 | local arg= 75 | for arg; do 76 | if [[ $arg =~ ^: ]]; then 77 | # Word splitting required here 78 | # shellcheck disable=2046 79 | bash+:import $(bash+:export"$arg") 80 | else 81 | bash+:fcopy "bash+:$arg" "$arg" 82 | fi 83 | done 84 | } 85 | 86 | # Function copy 87 | bash+:fcopy() { 88 | bash+:can "${1:?bash+:fcopy requires an input function name}" || 89 | bash+:die "'$1' is not a function" 2 90 | local func 91 | func=$(type "$1" 3>/dev/null | tail -n+3) 92 | [[ ${3-} ]] && "$3" 93 | eval "${2:?bash+:fcopy requires an output function name}() $func" 94 | } 95 | 96 | # Find the path of a library 97 | bash+:findlib() { 98 | local library_name 99 | library_name=$(tr '[:upper:]' '[:lower:]' <<< "${1//:://}").bash 100 | local lib=${BASHPLUSLIB:-${BASHLIB:-$PATH}} 101 | library_name=${library_name//+/\\+} 102 | IFS=':' read -r -a libs <<< "$lib" 103 | find "${libs[@]}" -name "${library_name##*/}" 2>/dev/null | 104 | grep -E "$library_name\$" | 105 | head -n1 106 | } 107 | 108 | bash+:die() { 109 | local msg=${1:-Died} 110 | msg=${msg//\\n/$'\n'} 111 | 112 | printf "%s" "$msg" >&2 113 | if [[ $msg == *$'\n' ]]; then 114 | exit 1 115 | else 116 | printf "\n" 117 | fi 118 | 119 | local c 120 | IFS=' ' read -r -a c <<< "$(caller "${DIE_STACK_LEVEL:-${2:-0}}")" 121 | if (( ${#c[@]} == 2 )); then 122 | msg=" at line %d of %s" 123 | else 124 | msg=" at line %d in %s of %s" 125 | fi 126 | 127 | # shellcheck disable=2059 128 | printf "$msg\n" "${c[@]}" >&2 129 | exit 1 130 | } 131 | 132 | bash+:warn() { 133 | local msg=${1:-Warning} 134 | printf "%s" "${msg//\\n/$'\n'}\n" >&2 135 | } 136 | 137 | bash+:can() { 138 | [[ $(type -t "${1:?bash+:can requires a function name}") == function ]] 139 | } 140 | -------------------------------------------------------------------------------- /man/man1/bash+.1: -------------------------------------------------------------------------------- 1 | .\" Automatically generated by Pod::Man 4.10 (Pod::Simple 3.35) 2 | .\" 3 | .\" Standard preamble: 4 | .\" ======================================================================== 5 | .de Sp \" Vertical space (when we can't use .PP) 6 | .if t .sp .5v 7 | .if n .sp 8 | .. 9 | .de Vb \" Begin verbatim text 10 | .ft CW 11 | .nf 12 | .ne \\$1 13 | .. 14 | .de Ve \" End verbatim text 15 | .ft R 16 | .fi 17 | .. 18 | .\" Set up some character translations and predefined strings. \*(-- will 19 | .\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left 20 | .\" double quote, and \*(R" will give a right double quote. \*(C+ will 21 | .\" give a nicer C++. Capital omega is used to do unbreakable dashes and 22 | .\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff, 23 | .\" nothing in troff, for use with C<>. 24 | .tr \(*W- 25 | .ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p' 26 | .ie n \{\ 27 | . ds -- \(*W- 28 | . ds PI pi 29 | . if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch 30 | . if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch 31 | . ds L" "" 32 | . ds R" "" 33 | . ds C` "" 34 | . ds C' "" 35 | 'br\} 36 | .el\{\ 37 | . ds -- \|\(em\| 38 | . ds PI \(*p 39 | . ds L" `` 40 | . ds R" '' 41 | . ds C` 42 | . ds C' 43 | 'br\} 44 | .\" 45 | .\" Escape single quotes in literal strings from groff's Unicode transform. 46 | .ie \n(.g .ds Aq \(aq 47 | .el .ds Aq ' 48 | .\" 49 | .\" If the F register is >0, we'll generate index entries on stderr for 50 | .\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index 51 | .\" entries marked with X<> in POD. Of course, you'll have to process the 52 | .\" output yourself in some meaningful fashion. 53 | .\" 54 | .\" Avoid warning from groff about undefined register 'F'. 55 | .de IX 56 | .. 57 | .nr rF 0 58 | .if \n(.g .if rF .nr rF 1 59 | .if (\n(rF:(\n(.g==0)) \{\ 60 | . if \nF \{\ 61 | . de IX 62 | . tm Index:\\$1\t\\n%\t"\\$2" 63 | .. 64 | . if !\nF==2 \{\ 65 | . nr % 0 66 | . nr F 2 67 | . \} 68 | . \} 69 | .\} 70 | .rr rF 71 | .\" ======================================================================== 72 | .\" 73 | .IX Title "STDIN 1" 74 | .TH STDIN 1 "November 2020" "Generated by Swim v0.1.48" "Modern Bash Programming" 75 | .\" For nroff, turn off justification. Always turn off hyphenation; it makes 76 | .\" way too many mistakes in technical documents. 77 | .if n .ad l 78 | .nh 79 | .SH "Name" 80 | .IX Header "Name" 81 | Bash+(1) \- Modern Bash Programming 82 | .SH "Synopsis" 83 | .IX Header "Synopsis" 84 | .Vb 1 85 | \& source bash+ :std :array 86 | \& 87 | \& use Foo::Bar this that 88 | \& 89 | \& Array.new args "$@" 90 | \& 91 | \& if args.empty?; then 92 | \& die "I need args!" 93 | \& fi 94 | \& 95 | \& Foo::Bar.new foo args 96 | \& 97 | \& this is awesome # <= this is a real command! (You just imported it) 98 | .Ve 99 | .SH "Description" 100 | .IX Header "Description" 101 | Bash+ is just Bash... \fBplus\fR some libraries that can make Bash programming a lot nicer. 102 | .PP 103 | Get the source code from GitHub: 104 | .PP 105 | .Vb 1 106 | \& git clone git@github.com:ingydotnet/bashplus 107 | .Ve 108 | .PP 109 | Then run: 110 | .PP 111 | .Vb 2 112 | \& make test 113 | \& make install # Possibly with \*(Aqsudo\*(Aq 114 | .Ve 115 | .SH "Usage" 116 | .IX Header "Usage" 117 | For now look at some libraries the use Bash+: 118 | .IP "\(bu" 4 119 | 120 | .IP "\(bu" 4 121 | 122 | .IP "\(bu" 4 123 | 124 | .SH "Status" 125 | .IX Header "Status" 126 | If you are interested in chatting about this, \f(CW\*(C`/join #bpan\*(C'\fR on irc.freenode.net. 127 | .SH "Author" 128 | .IX Header "Author" 129 | Written by Ingy döt Net 130 | .SH "Copyright & License" 131 | .IX Header "Copyright & License" 132 | Copyright 2013\-2020. Ingy döt Net. 133 | .PP 134 | The \s-1MIT\s0 License (\s-1MIT\s0). 135 | -------------------------------------------------------------------------------- /man/man3/bash+.3: -------------------------------------------------------------------------------- 1 | .\" Automatically generated by Pod::Man 4.10 (Pod::Simple 3.35) 2 | .\" 3 | .\" Standard preamble: 4 | .\" ======================================================================== 5 | .de Sp \" Vertical space (when we can't use .PP) 6 | .if t .sp .5v 7 | .if n .sp 8 | .. 9 | .de Vb \" Begin verbatim text 10 | .ft CW 11 | .nf 12 | .ne \\$1 13 | .. 14 | .de Ve \" End verbatim text 15 | .ft R 16 | .fi 17 | .. 18 | .\" Set up some character translations and predefined strings. \*(-- will 19 | .\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left 20 | .\" double quote, and \*(R" will give a right double quote. \*(C+ will 21 | .\" give a nicer C++. Capital omega is used to do unbreakable dashes and 22 | .\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff, 23 | .\" nothing in troff, for use with C<>. 24 | .tr \(*W- 25 | .ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p' 26 | .ie n \{\ 27 | . ds -- \(*W- 28 | . ds PI pi 29 | . if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch 30 | . if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch 31 | . ds L" "" 32 | . ds R" "" 33 | . ds C` "" 34 | . ds C' "" 35 | 'br\} 36 | .el\{\ 37 | . ds -- \|\(em\| 38 | . ds PI \(*p 39 | . ds L" `` 40 | . ds R" '' 41 | . ds C` 42 | . ds C' 43 | 'br\} 44 | .\" 45 | .\" Escape single quotes in literal strings from groff's Unicode transform. 46 | .ie \n(.g .ds Aq \(aq 47 | .el .ds Aq ' 48 | .\" 49 | .\" If the F register is >0, we'll generate index entries on stderr for 50 | .\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index 51 | .\" entries marked with X<> in POD. Of course, you'll have to process the 52 | .\" output yourself in some meaningful fashion. 53 | .\" 54 | .\" Avoid warning from groff about undefined register 'F'. 55 | .de IX 56 | .. 57 | .nr rF 0 58 | .if \n(.g .if rF .nr rF 1 59 | .if (\n(rF:(\n(.g==0)) \{\ 60 | . if \nF \{\ 61 | . de IX 62 | . tm Index:\\$1\t\\n%\t"\\$2" 63 | .. 64 | . if !\nF==2 \{\ 65 | . nr % 0 66 | . nr F 2 67 | . \} 68 | . \} 69 | .\} 70 | .rr rF 71 | .\" ======================================================================== 72 | .\" 73 | .IX Title "STDIN 1" 74 | .TH STDIN 1 "November 2020" "Generated by Swim v0.1.48" "Modern Bash Programming" 75 | .\" For nroff, turn off justification. Always turn off hyphenation; it makes 76 | .\" way too many mistakes in technical documents. 77 | .if n .ad l 78 | .nh 79 | .SH "Name" 80 | .IX Header "Name" 81 | Bash+(1) \- Modern Bash Programming 82 | .SH "Synopsis" 83 | .IX Header "Synopsis" 84 | .Vb 1 85 | \& source bash+ :std :array 86 | \& 87 | \& use Foo::Bar this that 88 | \& 89 | \& Array.new args "$@" 90 | \& 91 | \& if args.empty?; then 92 | \& die "I need args!" 93 | \& fi 94 | \& 95 | \& Foo::Bar.new foo args 96 | \& 97 | \& this is awesome # <= this is a real command! (You just imported it) 98 | .Ve 99 | .SH "Description" 100 | .IX Header "Description" 101 | Bash+ is just Bash... \fBplus\fR some libraries that can make Bash programming a lot nicer. 102 | .PP 103 | Get the source code from GitHub: 104 | .PP 105 | .Vb 1 106 | \& git clone git@github.com:ingydotnet/bashplus 107 | .Ve 108 | .PP 109 | Then run: 110 | .PP 111 | .Vb 2 112 | \& make test 113 | \& make install # Possibly with \*(Aqsudo\*(Aq 114 | .Ve 115 | .SH "Usage" 116 | .IX Header "Usage" 117 | For now look at some libraries the use Bash+: 118 | .IP "\(bu" 4 119 | 120 | .IP "\(bu" 4 121 | 122 | .IP "\(bu" 4 123 | 124 | .SH "Status" 125 | .IX Header "Status" 126 | If you are interested in chatting about this, \f(CW\*(C`/join #bpan\*(C'\fR on irc.freenode.net. 127 | .SH "Author" 128 | .IX Header "Author" 129 | Written by Ingy döt Net 130 | .SH "Copyright & License" 131 | .IX Header "Copyright & License" 132 | Copyright 2013\-2020. Ingy döt Net. 133 | .PP 134 | The \s-1MIT\s0 License (\s-1MIT\s0). 135 | -------------------------------------------------------------------------------- /test/base.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source test/setup 4 | 5 | source bash+ :std 6 | 7 | ok $? "'source bash+' works" 8 | 9 | is "$BASHPLUS_VERSION" '0.1.0' 'BASHPLUS_VERSION is 0.1.0' 10 | 11 | done_testing 2 12 | -------------------------------------------------------------------------------- /test/die.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source test/setup 4 | 5 | source bash+ :std 6 | 7 | got=$(die "Nope" 2>&1) || true 8 | want="Nope 9 | at line 7 in main of test/die.t" 10 | is "$got" "$want" "die() msg ok" 11 | 12 | got=$(die "Nope\n" 2>&1) || true 13 | want="Nope" 14 | is "$got" "$want" "die() msg ok" 15 | 16 | done_testing 2 17 | -------------------------------------------------------------------------------- /test/fcopy.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source test/setup 4 | 5 | source bash+ 6 | 7 | foo() { 8 | echo O HAI 9 | } 10 | 11 | like "$(type bar 2>&1)" 'bar: not found' \ 12 | 'bar is not yet a function' 13 | 14 | bash+:fcopy foo bar 15 | 16 | type -t bar &>/dev/null 17 | ok $? 'bar is now a function' 18 | is "$(type foo | tail -n+3)" "$(type bar | tail -n+3)" \ 19 | 'Copy matches original' 20 | 21 | done_testing 3 22 | -------------------------------------------------------------------------------- /test/lib/foo/bar.bash: -------------------------------------------------------------------------------- 1 | Foo__Bar_VERSION='1.2.3' 2 | 3 | Foo::Bar:baz() { :;} 4 | -------------------------------------------------------------------------------- /test/lib/foo/foo.bash: -------------------------------------------------------------------------------- 1 | Foo::Foo:import() { 2 | echo $1---$2 3 | } 4 | -------------------------------------------------------------------------------- /test/setup: -------------------------------------------------------------------------------- 1 | # shellcheck shell=bash 2 | 3 | #------------------------------------------------------------------------------ 4 | # This is a tiny version of test-more-bash that I use here. test-more-bash uses 5 | # bash+, so I want to avoid the circular dependency. This little guy does 6 | # 80-90% what test-more-bash does, with minimal code. It's a good example of 7 | # how nice Bash can be. 8 | #------------------------------------------------------------------------------ 9 | 10 | set -e -o pipefail 11 | 12 | PATH=$PWD/bin:$PATH 13 | 14 | run=0 15 | 16 | plan() { 17 | echo "1..$1" 18 | } 19 | 20 | pass() { 21 | (( ++run )) 22 | echo "ok $run${1:+ - $1}" 23 | } 24 | 25 | fail() { 26 | (( ++run )) 27 | echo "not ok $run${1:+ - $1}" 28 | } 29 | 30 | is() { 31 | if [[ $1 == "$2" ]]; then 32 | pass "$3" 33 | else 34 | fail "$3" 35 | diag "Got: $1" 36 | diag "Want: $2" 37 | fi 38 | } 39 | 40 | ok() { 41 | if (exit "${1:-$?}"); then 42 | pass "$2" 43 | else 44 | fail "$2" 45 | fi 46 | } 47 | 48 | like() { 49 | if [[ $1 =~ $2 ]]; then 50 | pass "$3" 51 | else 52 | fail "$3" 53 | diag "Got: $1" 54 | diag "Like: $2" 55 | fi 56 | } 57 | 58 | unlike() { 59 | if [[ ! $1 =~ $2 ]]; then 60 | pass "$3" 61 | else 62 | fail "$3" 63 | diag "Got: $1" 64 | diag "Dont: $2" 65 | fi 66 | } 67 | 68 | done_testing() { 69 | echo "1..${1:-$run}" 70 | } 71 | 72 | diag() { 73 | echo "# ${1//$'\n'/$'\n'# }" >&2 74 | } 75 | 76 | note() { 77 | echo "# ${1//$'\n'/$'\n'# }" 78 | } 79 | 80 | #! vim: ft=sh sw=2: 81 | -------------------------------------------------------------------------------- /test/shellcheck.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source test/setup 4 | 5 | source bash+ 6 | 7 | if ! command -v shellcheck >/dev/null; then 8 | plan skip_all "The 'shellcheck' utility is not installed" 9 | fi 10 | if [[ ! $(shellcheck --version) =~ 0\.7\.1 ]]; then 11 | plan skip_all "This test wants shellcheck version 0.7.1" 12 | fi 13 | 14 | IFS=$'\n' read -d '' -r -a shell_files <<< "$( 15 | find bin -type f 16 | find lib -type f 17 | echo test/setup 18 | find test -name '*.t' 19 | )" || true 20 | 21 | skips=( 22 | # We want to keep these 2 here always: 23 | SC1090 # Can't follow non-constant source. Use a directive to specify location. 24 | SC1091 # Not following: bash+ was not specified as input (see shellcheck -x). 25 | ) 26 | 27 | skip=$(IFS=,; echo "${skips[*]}") 28 | 29 | for file in "${shell_files[@]}"; do 30 | [[ $file == *swp ]] && continue 31 | is "$(shellcheck -e "$skip" "$file")" "" \ 32 | "The shell file '$file' passes shellcheck" 33 | done 34 | 35 | done_testing 36 | 37 | # vim: set ft=sh: 38 | -------------------------------------------------------------------------------- /test/source-bash+-std.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source test/setup 4 | 5 | source bash+ :std 6 | 7 | ok "$(bash+:can use)" 'use is imported' 8 | ok "$(bash+:can die)" 'die is imported' 9 | ok "$(bash+:can warn)" 'warn is imported' 10 | 11 | ok "$(! bash+:can import)" 'import is not imported' 12 | ok "$(! bash+:can main)" 'main is not imported' 13 | ok "$(! bash+:can fcopy)" 'fcopy is not imported' 14 | ok "$(! bash+:can findlib)" 'findlib is not imported' 15 | ok "$(! bash+:can can)" 'can is not imported' 16 | 17 | done_testing 8 18 | -------------------------------------------------------------------------------- /test/source-bash+.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source test/setup 4 | 5 | source bash+ 6 | 7 | functions=( 8 | use 9 | import 10 | fcopy 11 | findlib 12 | die 13 | warn 14 | can 15 | ) 16 | 17 | for f in "${functions[@]}"; do 18 | is "$(type -t "bash+:$f")" function \ 19 | "bash+:$f is a function" 20 | done 21 | 22 | done_testing 7 23 | -------------------------------------------------------------------------------- /test/use.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source test/setup 4 | 5 | source bash+ :std can 6 | 7 | # shellcheck disable=2034 8 | BASHLIB=test/lib 9 | 10 | use Foo::Bar 11 | 12 | ok $? 'use Foo::Bar - works' 13 | ok "$(can Foo::Bar:baz)" 'Function Foo::Bar:baz exists' 14 | 15 | # shellcheck disable=2016,2154 16 | is "$Foo__Bar_VERSION" 1.2.3 '$Foo__Bar_VERSION == 1.2.3' 17 | 18 | output=$(use Foo::Foo Boo Booo) 19 | ok $? 'use Foo::Foo Boo Booo - works' 20 | is "$output" Boo---Booo 'Correct import called' 21 | 22 | done_testing 5 23 | -------------------------------------------------------------------------------- /test/version-check.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source test/setup 4 | 5 | PATH=$PWD/bin:$PATH 6 | source bash+ version-check 7 | 8 | t1() (echo 0.1.2) 9 | t2() (echo 0.1) 10 | 11 | ok "$(version-check t1 0)" "0.1.2 >= 0" 12 | ok "$(version-check t1 0.1)" "0.1.2 >= 0.1" 13 | ok "$(version-check t1 0.1.1)" "0.1.2 >= 0.1.1" 14 | ok "$(version-check t1 0.1.2)" "0.1.2 >= 0.1.2" 15 | ok "$(! version-check t1 0.2)" "0.1.2 >= 0.2 fails" 16 | ok "$(! version-check t1 0.1.3)" "0.1.2 >= 0.1.3 fails" 17 | 18 | ok "$(version-check t2 0)" "0.1 >= 0" 19 | ok "$(version-check t2 0.1)" "0.1 >= 0.1" 20 | ok "$(! version-check t2 0.2)" "0.1 >= 0.2 fails" 21 | ok "$(! version-check t2 0.1.1)" "0.1 >= 0.1.1" 22 | 23 | done_testing 10 24 | --------------------------------------------------------------------------------