├── .shellspec ├── LICENSE ├── README.md ├── altshfmt ├── altshfmt.bat └── spec ├── altshfmt_spec.sh └── spec_helper.sh /.shellspec: -------------------------------------------------------------------------------- 1 | --require spec_helper 2 | 3 | ## Default kcov (coverage) options 4 | # --kcov-options "--include-path=. --path-strip-level=1" 5 | # --kcov-options "--include-pattern=.sh" 6 | # --kcov-options "--exclude-pattern=/.shellspec,/spec/,/coverage/,/report/" 7 | 8 | ## Example: Include script "myprog" with no extension 9 | # --kcov-options "--include-pattern=.sh,myprog" 10 | 11 | ## Example: Only specified files/directories 12 | # --kcov-options "--include-pattern=myprog,/lib/" 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 ShellSpec 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AltSH formatter (experimental) 2 | 3 | The **`altshfmt`** is an experimental tool for formatting AltSH (alternative shell script) with extended syntax that cannot be formatted correctly by [shfmt][shfmt]. This is implemented as a wrapper for `shfmt`. 4 | 5 | **Currently supported syntax**: [ShellSpec][shellspec], [shpec][shpec] 6 | 7 | [altshfmt-releases]: https://github.com/shellspec/altshfmt/releases 8 | [shfmt]: https://github.com/mvdan/sh#shfmt 9 | [shfmt-releases]: https://github.com/mvdan/sh/releases 10 | [busybox-w32]: https://frippery.org/busybox 11 | [shellspec]: https://github.com/shellspec/shellspec 12 | [shpec]: https://github.com/rylnd/shpec 13 | [shell-format]: https://marketplace.visualstudio.com/items?itemName=foxundermoon.shell-format 14 | 15 | ## **Use it at your own risk** 16 | 17 | * It has not been enough tested. Please make sure to save and version control your files properly so that they can be restored even if they are corrupted. 18 | * It depends on the behavior of `shfmt`, so it may not work depending on the combination of versions. 19 | * Incompatible changes may be made. 20 | 21 | ## Requirements 22 | 23 | * [shfmt][shfmt-releases] v3.2.1 24 | * Basic commands (`awk`, `cat`, `cmp`, `cp`, `diff`, `grep`, `ls`, `mktemp`, `rm`, `sed`, `which`) 25 | 26 | ## How to use 27 | 28 | Since `altshfmt` is implemented as compatible as possible with `shfmt`, You can use it instead of the `shfmt` command. 29 | 30 | ### Linux or macOS 31 | 32 | * Download `altshfmt` archive from [here][altshfmt-releases] and extract it to a directory, or use `git clone`. 33 | * Download `shfmt` binary from [here][shfmt-releases] and save it in the directory where `altshfmt` is located. 34 | 35 | ### Windows 36 | 37 | WSL (Windows 10, version 1803 and later) or [busybox-w32][busybox-w32] is required. To run it from Windows (instead of from WSL), run `altshfmt.bat` instead of `altshfmt`. 38 | 39 | #### WSL 40 | 41 | * Download `altshfmt` archive from [here][altshfmt-releases] and extract it to a directory, or use `git clone`. 42 | * Download `shfmt` binary from [here][shfmt-releases] and save it in the directory where `altshfmt` is located. 43 | * The `shfmt` binary can be used for Windows or Linux. 44 | 45 | #### busybox-w32 46 | 47 | * Download `altshfmt` archive from [here][altshfmt-releases] and extract it to a directory, or use `git clone`. 48 | * Download `shfmt` binary from [here][shfmt-releases] and save it in the directory where `altshfmt` is located. 49 | * Download `busybox` binary from [here][busybox-w32], rename it to `bash.exe` and save it in the directory where `altshfmt` is located. 50 | 51 | ### Using with VSCode 52 | 53 | Use the [shell-format][shell-format] ([github](https://github.com/foxundermoon/vs-shell-format)) extension. 54 | 55 | * Install the shell-format extension and change `shellformat.path` in `settings.json` to the path to the `altshfmt` (or `altshfmt.bat` for windows). 56 | 57 | ## About syntax detection 58 | 59 | The syntax is **automatically determined** from the beginning and ending pairs of the block of DSL used in the shell script. 60 | 61 | ### shell directive 62 | 63 | If you have problems with the automatic detection, you can also use the shell directive. The shell directive is a comment that begins with "`shell:`". 64 | 65 | Example 66 | 67 | ```sh 68 | #!/bin/sh 69 | # shell: sh altsh=shellspec 70 | 71 | Describe 72 | ... 73 | End 74 | ``` 75 | 76 | Usage: `shell: [] [altsh=]` 77 | 78 | * `shell`: `sh`, `bash`, `mksh` 79 | * Unspecified: same as `auto` 80 | * It is treated as the value of the `-ln` flag of the `shfmt` command 81 | * `auto`: do not specify the `-ln` flag 82 | * `sh`: implies `-ln posix` 83 | * `bash`: implies `-ln bash` 84 | * `mksh`: implies `-ln mksh` 85 | * Others: implies `-ln bash` 86 | * `syntax`: `shellspec`, `shpec` 87 | * Unspecified: Treat it as a pure shell script 88 | * Others: Treat it as a pure shell script 89 | 90 | ## Limitation 91 | 92 | `altshfmt` is several times slower than `shfmt`. 93 | -------------------------------------------------------------------------------- /altshfmt: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -eu 4 | 5 | VERSION=0.2.0 URL="https://github.com/shellspec/altshfmt" 6 | TMPBASE='' COLOR='' SHFMT='' 7 | list='' diff='' write='' find='' tojson='' filename='' help='' 8 | hasflags='' haspaths='' 9 | 10 | SHELLSPEC_DSL_EXAMPLE_GROUP='([fx]?(ExampleGroup|Describe|Context))' 11 | SHELLSPEC_DSL_EXAMPLE='([fx]?(Example|Specify|It))' 12 | SHELLSPEC_DSL_DATA='(Data(:raw|:expand)?)' 13 | SHELLSPEC_DSL_ONLINE_DATA='(Data[[:space:]]+[[:alnum:]_<'\''"])' 14 | SHELLSPEC_DSL_PARAMETERS='(Parameters(:block|:dynamic|:matrix|:value)?)' 15 | SHELLSPEC_DSL_MOCK='(Mock)' 16 | SHELLSPEC_DSL_BEGIN="$SHELLSPEC_DSL_EXAMPLE_GROUP|$SHELLSPEC_DSL_EXAMPLE|$SHELLSPEC_DSL_DATA|$SHELLSPEC_DSL_PARAMETERS|$SHELLSPEC_DSL_MOCK" 17 | SHELLSPEC_DSL_END='(End)' 18 | 19 | SHPEC_DSL_BEGIN='(describe|it)' 20 | SHPEC_DSL_END='(end)' 21 | 22 | readlinkf() { 23 | [ "${1:-}" ] || return 1 24 | max_symlinks=40 25 | CDPATH='' # to avoid changing to an unexpected directory 26 | 27 | target=$1 28 | [ -e "${target%/}" ] || target=${1%"${1##*[!/]}"} # trim trailing slashes 29 | [ -d "${target:-/}" ] && target="$target/" 30 | 31 | cd -P . 2>/dev/null || return 1 32 | while [ "$max_symlinks" -ge 0 ] && max_symlinks=$((max_symlinks - 1)); do 33 | if [ ! "$target" = "${target%/*}" ]; then 34 | case $target in 35 | /*) cd -P "${target%/*}/" 2>/dev/null || break ;; 36 | *) cd -P "./${target%/*}" 2>/dev/null || break ;; 37 | esac 38 | target=${target##*/} 39 | fi 40 | 41 | if [ ! -L "$target" ]; then 42 | target="${PWD%/}${target:+/}${target}" 43 | printf '%s\n' "${target:-/}" 44 | return 0 45 | fi 46 | 47 | # `ls -dl` format: "%s %u %s %s %u %s %s -> %s\n", 48 | # , , , , 49 | # , , , 50 | # https://pubs.opengroup.org/onlinepubs/9699919799/utilities/ls.html 51 | link=$(ls -dl -- "$target" 2>/dev/null) || break 52 | target=${link#*" $target -> "} 53 | done 54 | return 1 55 | } 56 | 57 | self=$(readlinkf "$0") 58 | basedir=${self%/*} 59 | if [ "${ALTSHFMT_WINCWD:-}" ]; then 60 | cd -- "${ALTSHFMT_WINCWD:-}" 61 | fi 62 | 63 | is_wsl() { 64 | IFS= read -r osrelease /dev/null; then 73 | if [ -e "${basedir%/}/shfmt.exe" ]; then 74 | SHFMT="${basedir%/}/shfmt.exe" 75 | fi 76 | fi 77 | [ "$SHFMT" ] && return 0 78 | SHFMT=$(which shfmt) && return 0 79 | echo "shfmt: command not found" >&2 80 | exit 1 81 | } 82 | detect_shfmt 83 | 84 | if [ "${FORCE_COLOR:-}" = "true" ]; then 85 | COLOR=1 86 | elif [ "${TERM:-}" = "dumb" ]; then 87 | : 88 | elif [ -t 1 ]; then 89 | COLOR=1 90 | fi 91 | 92 | shfmt() { 93 | "$SHFMT" "$@" 94 | } 95 | 96 | shell_directive() { 97 | case $1 in (*[\#\$\&\(\)\*\;\<\>\?\[\\\]\`\{\|\}]*) 98 | echo 'The following characters are not allowed' \ 99 | 'in the shell directive: #$&()*;<>?[\]`{|}.' >&2 100 | exit 1 101 | esac 102 | if ! ( eval "set -- $1" ) 2>/dev/null; then 103 | printf '%s' "Invalid the shell directive: " >&2 104 | fi 105 | eval "set -- $1" 106 | shell=$1 altsh='' 107 | shift 108 | while [ $# -gt 0 ]; do 109 | case $1 in 110 | altsh=*) altsh=${1#*=} ;; 111 | *) ;; # Unknown attributes MUST be ignored 112 | esac 113 | shift 114 | done 115 | } 116 | 117 | detect_syntax() { 118 | awk ' 119 | BEGIN { buf = ""; syntax = "auto"; directive = ""; altsh = ""; detect = "" } 120 | NR == 1 && /^#![[:space:]]*\// { 121 | line=$0 122 | sub(/#![[:space:]]+/, "") 123 | syntax = match($1, /.*\/env$/) ? $2 : $1 124 | sub(/.*\//, "", syntax) 125 | $0=line 126 | } 127 | { buf = buf $0 "\n" } 128 | /^#[[:space:]]*shell:.*/ { 129 | sub(/.*shell:[[:space:]]*/, "") 130 | directive = (match($1, /.*=.*/) ? syntax " " : "") $0 131 | exit 132 | } 133 | /^[[:space:]]*('"$SHELLSPEC_DSL_BEGIN"')([[:space:]].*|$)/ { 134 | detect = "shellspec" 135 | } 136 | /^[[:space:]]*('"$SHELLSPEC_DSL_END"')([[:space:]].*|$)/ { 137 | if (detect == "shellspec") { altsh = detect; exit } 138 | } 139 | /^[[:space:]]*('"$SHPEC_DSL_BEGIN"')([[:space:]].*|$)/ { 140 | detect = "shpec" 141 | } 142 | /^[[:space:]]*('"$SHPEC_DSL_END"')([[:space:]].*|$)/ { 143 | if (detect == "shpec") { altsh = detect; exit } 144 | } 145 | END { 146 | if (!directive) directive = syntax 147 | if (altsh) directive = directive " altsh=" altsh 148 | # print directive > "/dev/tty" 149 | print directive 150 | printf "%s", buf 151 | while(getline > 0) print $0 152 | } 153 | ' 154 | } 155 | 156 | altshfmt() { 157 | detect_syntax | { 158 | read -r line 159 | shell_directive "$line" 160 | case $shell in 161 | auto) ;; 162 | sh) set -- -ln posix "$@" ;; 163 | bash | mksh) set -- -ln "$shell" "$@" ;; 164 | *) set -- -ln bash "$@" ;; 165 | esac 166 | # shellcheck disable=SC2016 167 | case $altsh in 168 | shellspec) 169 | code=' 170 | /^[[:space:]]*('"$SHELLSPEC_DSL_END"')([[:space:]].*|$)/ { 171 | print "} # @@altsh@@end" 172 | } 173 | { print } 174 | /^[[:space:]]*('"$SHELLSPEC_DSL_ONLINE_DATA"').*/ { 175 | next 176 | } 177 | /^[[:space:]]*('"$SHELLSPEC_DSL_BEGIN"')([[:space:]].*|$)/ { 178 | while(match($0, /.*\\$/)) { getline; print } 179 | print "{ # @@altsh@@begin" 180 | } 181 | ' 182 | ;; 183 | shpec) 184 | code=' 185 | /^[[:space:]]*('"$SHPEC_DSL_END"')([[:space:]].*|$)/ { 186 | print "} # @@altsh@@end" 187 | } 188 | { print } 189 | /^[[:space:]]*('"$SHPEC_DSL_BEGIN"')([[:space:]].*|$)/ { 190 | while(match($0, /.*\\$/)) { getline; print } 191 | print "{ # @@altsh@@begin" 192 | } 193 | ' 194 | ;; 195 | *) code="" ;; 196 | esac 197 | if [ "$code" ]; then 198 | awk "$code" | shfmt "$@" | grep -v "# @@altsh@@" 199 | else 200 | shfmt "$@" 201 | fi 202 | } 203 | } 204 | 205 | optparse() { 206 | OPTARG='' OPTIND=1 i=$(($# + 1)) 207 | while [ $# -gt 0 ]; do 208 | n=1 209 | case $1 in (-*) hasflags=1; esac 210 | case $1 in 211 | -[!-]* | --[!-]* | --) 212 | case ${1#"${1%%[!-]*}"} in 213 | l) list=1 && shift && n=0 ;; 214 | w) write=1 && shift && n=0 ;; 215 | d) diff=1 && shift && n=0 ;; 216 | filename) [ $# -gt 1 ] && filename=$2 && n=2 ;; 217 | filename=*) filename=${1#*=} ;; 218 | i | ln) [ $# -gt 1 ] && n=2 ;; 219 | f) find=1 ;; 220 | tojson) tojson=1 ;; 221 | h | help) help=1 ;; 222 | esac 223 | ;; 224 | *) 225 | [ "$1" = "-" ] && [ $# -eq 1 ] && return 0 226 | haspaths=1 227 | break 228 | ;; 229 | esac 230 | while [ "$n" -gt 0 ] && n=$((n - 1)); do 231 | OPTARG="$OPTARG \"\${$((i - $#))}\"" 232 | OPTIND=$((OPTIND + 1)) 233 | shift 234 | done 235 | done 236 | while [ $# -gt 0 ]; do 237 | OPTARG="$OPTARG \"\${$((i - $#))}\"" 238 | shift 239 | done 240 | } 241 | 242 | pretty() { 243 | IFS= read -r line 244 | IFS= read -r line 245 | printf "%s\n" "--- ${1}.orig" 246 | printf "%s\n" "+++ $1" 247 | if [ "$COLOR" ]; then 248 | range="\033[36m" deleted="\033[31m" added="\033[32m" reset="\033[m" 249 | else 250 | range="" deleted="" added="" reset="" 251 | fi 252 | while IFS= read -r line; do 253 | case $line in 254 | @@*) printf "${range}%s${reset}\n" "$line" ;; 255 | -*) printf "${deleted}%s${reset}\n" "$line" ;; 256 | +*) printf "${added}%s${reset}\n" "$line" ;; 257 | *) printf "%s\n" "$line" ;; 258 | esac 259 | done 260 | echo 261 | } 262 | 263 | altshfmt_find() { 264 | if [ -d "$1" ]; then 265 | case $SHFMT in 266 | *.exe) shfmt -f "$1" | sed 's|\\|/|g' ;; 267 | *) shfmt -f "$1" ;; 268 | esac 269 | else 270 | printf '%s\n' "$1" 271 | fi 272 | } 273 | 274 | check_syntax() { 275 | shfmt < "$1" >/dev/null 276 | } 277 | 278 | altshfmt_file() { 279 | origfile=$1 && filename=$2 && shift 2 280 | newfile="$TMPBASE/new" changed='' 281 | if [ -f "$origfile" ]; then 282 | # The reason for doing the syntax check first is to output the correct line number. 283 | check_syntax "$origfile" 284 | altshfmt "$@" < "$origfile" > "$newfile" || return $? 285 | else 286 | altshfmt "$@" "$origfile" < /dev/null > "$newfile" || return $? 287 | fi 288 | 289 | if [ "$list" ] || [ "$diff" ]; then 290 | cmp -s "$origfile" "$newfile" || changed=1 291 | fi 292 | 293 | if [ "$list" ] && [ "$changed" ]; then 294 | printf '%s\n' "$filename" 295 | fi 296 | 297 | if [ "$diff" ] && [ "$changed" ]; then 298 | diff -u "$origfile" "$newfile" | pretty "$filename" 299 | fi 300 | 301 | if [ "$write" ]; then 302 | if [ -s "$origfile" ] && [ ! -s "$newfile" ]; then 303 | echo "Something's wrong (the output is empty)." >&2 304 | exit 1 305 | fi 306 | cp "$newfile" "$origfile" 307 | fi 308 | 309 | if [ "$diff" ] && [ "$changed" ]; then 310 | return 1 311 | fi 312 | 313 | if [ ! "${list}${diff}${write}" ]; then 314 | cat "$newfile" 315 | fi 316 | } 317 | 318 | altshfmt_files() { 319 | cmd="" i=1 320 | while [ $i -lt "$1" ] && i=$((i + 1)); do 321 | cmd="$cmd \"\${$i}\"" 322 | done 323 | ret=0 324 | while [ $i -lt $# ] && i=$((i + 1)); do 325 | eval "origfile=\"\${$i}\"" 326 | if [ "$origfile" = "-" ] && [ ! -e "$origfile" ]; then 327 | shfmt - - 328 | exit 1 329 | fi 330 | altshfmt_find "$origfile" | ( 331 | ret=0 332 | while IFS= read -r origfile; do 333 | altshfmt_files_ "$origfile" "$@" || ret=$? 334 | done 335 | exit "$ret" 336 | ) || ret=$? 337 | done 338 | return "$ret" 339 | } 340 | altshfmt_files_() { 341 | origfile=$1 342 | shift 343 | eval "set -- $cmd" 344 | altshfmt_file "$origfile" "$origfile" "$@" 345 | } 346 | 347 | cleanup() { 348 | if [ "$TMPBASE" ] && [ -d "$TMPBASE" ]; then 349 | rm -rf "$TMPBASE" 350 | fi 351 | } 352 | 353 | ${__SOURCED__:+return} 354 | 355 | trap cleanup INT TERM EXIT 356 | TMPBASE=$(mktemp -d) 357 | 358 | optparse "$@" 359 | eval "set -- $OPTARG" 360 | 361 | if [ "$find" ] && [ "$haspaths" ]; then 362 | shfmt "$@" 363 | elif [ "$tojson" ]; then 364 | shfmt "$@" 365 | elif [ "$OPTIND" -le $# ]; then 366 | altshfmt_files "$OPTIND" "$@" 367 | elif [ -t 0 ] && [ "$hasflags" ]; then 368 | shfmt "$@" 369 | if [ "$help" ]; then 370 | echo "" 371 | echo "Integrated with altshfmt v$VERSION." 372 | echo "For more information, see $URL." 373 | fi 374 | else 375 | origfile="$TMPBASE/orig" 376 | cat > "$origfile" 377 | altshfmt_file "$origfile" "${filename:-""}" "$@" 378 | fi 379 | -------------------------------------------------------------------------------- /altshfmt.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal 3 | set ALTSHFMT_WINCWD=%CD% 4 | set WSLENV=ALTSHFMT_WINCWD/pu:%WSLENV% 5 | cd "%~dp0" 6 | bash altshfmt %* 7 | -------------------------------------------------------------------------------- /spec/altshfmt_spec.sh: -------------------------------------------------------------------------------- 1 | Describe "altshfmt" 2 | Include ./altshfmt 3 | 4 | Describe "detect_syntax()" 5 | Context "when shell directive defined" 6 | Context "and not specified shebang" 7 | Data 8 | #|# shell: altsh=shellspec 9 | #|script 10 | End 11 | 12 | Specify "The default shell is auto" 13 | When call detect_syntax 14 | The line 1 should eq "auto altsh=shellspec" 15 | The lines of output should eq 3 16 | End 17 | End 18 | 19 | Context "and specified shebang and shell" 20 | Data 21 | #|#!/bin/sh 22 | #|# shell: bash altsh=shellspec 23 | #|script 24 | End 25 | 26 | Specify "The directive take precedence over shebang" 27 | When call detect_syntax 28 | The line 1 should eq "bash altsh=shellspec" 29 | The lines of output should eq 4 30 | End 31 | End 32 | 33 | Context "and specified shebang only" 34 | Data 35 | #|#!/usr/bin/bash aa 36 | #|# shell: altsh=shellspec 37 | #|script 38 | End 39 | 40 | Specify "Use shebang as the default shell" 41 | When call detect_syntax 42 | The line 1 should eq "bash altsh=shellspec" 43 | The lines of output should eq 4 44 | End 45 | 46 | Describe "using env" 47 | Data 48 | #|#!/usr/bin/env bash arg 49 | #|# shell: altsh=shellspec 50 | #|script 51 | End 52 | 53 | Specify "Use shebang as the default shell" 54 | When call detect_syntax 55 | The line 1 should eq "bash altsh=shellspec" 56 | The lines of output should eq 4 57 | End 58 | End 59 | End 60 | End 61 | 62 | Context "when shell directive not defined" 63 | Context "and not specified shebang" 64 | Data 65 | #|script 66 | End 67 | 68 | Specify "The default shell is auto" 69 | When call detect_syntax 70 | The line 1 should eq "auto" 71 | The lines of output should eq 2 72 | End 73 | End 74 | 75 | Context "and specified shebang" 76 | Data 77 | #|#!/usr/bin/env bash arg 78 | #|script 79 | End 80 | 81 | Specify "Use shebang as the default shell" 82 | When call detect_syntax 83 | The line 1 should eq "bash" 84 | The lines of output should eq 3 85 | End 86 | 87 | Data 88 | #|#! /bin/bash arg 89 | #|script 90 | End 91 | 92 | Specify "Allow spaces in shebang" 93 | When call detect_syntax 94 | The line 1 should eq "bash" 95 | The lines of output should eq 3 96 | End 97 | 98 | Data 99 | #|#! /usr/bin/env /bin/bash arg 100 | #|script 101 | End 102 | 103 | Specify "Allow spaces in shebang with env command" 104 | When call detect_syntax 105 | The line 1 should eq "bash" 106 | The lines of output should eq 3 107 | End 108 | End 109 | 110 | Context "if shellspec DSL is found" 111 | Data 112 | #|#!/usr/bin/env bash arg 113 | #|script 114 | #|Describe 115 | #|End 116 | End 117 | 118 | Specify "The altsh is shellspec" 119 | When call detect_syntax 120 | The line 1 should eq "bash altsh=shellspec" 121 | The lines of output should eq 5 122 | End 123 | End 124 | End 125 | End 126 | 127 | Describe "optparse()" 128 | _optparse() { 129 | optparse "$@" 130 | eval "set -- $OPTARG" 131 | echo "$OPTIND:" "$@" 132 | } 133 | 134 | It "supports the -l flag" 135 | When call _optparse -l -x arg1 arg2 136 | The variable list should eq 1 137 | The output should eq "2: -x arg1 arg2" 138 | End 139 | 140 | It "supports the -w flag" 141 | When call _optparse -w -x arg1 arg2 142 | The variable write should eq 1 143 | The output should eq "2: -x arg1 arg2" 144 | End 145 | 146 | It "supports the -d flag" 147 | When call _optparse -d -x arg1 arg2 148 | The variable diff should eq 1 149 | The output should eq "2: -x arg1 arg2" 150 | End 151 | 152 | It "supports the -filename flag" 153 | When call _optparse -filename "path" -x arg1 arg2 154 | The variable filename should eq "path" 155 | The output should eq "4: -filename path -x arg1 arg2" 156 | End 157 | 158 | It "supports the -i flag" 159 | When call _optparse -i 2 -x arg1 arg2 160 | The output should eq "4: -i 2 -x arg1 arg2" 161 | End 162 | 163 | It "supports the -ln flag" 164 | When call _optparse -ln posix -x arg1 arg2 165 | The output should eq "4: -ln posix -x arg1 arg2" 166 | End 167 | 168 | It "supports the -f flag" 169 | When call _optparse -f -x arg1 arg2 170 | The variable find should eq 1 171 | The output should eq "3: -f -x arg1 arg2" 172 | End 173 | 174 | It "supports the -tojson flag" 175 | When call _optparse -tojson -x arg1 arg2 176 | The variable tojson should eq 1 177 | The output should eq "3: -tojson -x arg1 arg2" 178 | End 179 | 180 | It "supports unknown flags" 181 | When call _optparse -unknown -x arg1 arg2 182 | The status should be success 183 | The output should eq "3: -unknown -x arg1 arg2" 184 | End 185 | 186 | It "determines that there are paths" 187 | When call _optparse -p file -x arg1 arg2 188 | The status should be success 189 | The output should eq "2: -p file -x arg1 arg2" 190 | End 191 | 192 | It "can handle flags starting with --" 193 | When call _optparse --l -x arg1 arg2 194 | The variable list should eq 1 195 | The output should eq "2: -x arg1 arg2" 196 | End 197 | 198 | It "does not treat after PATH as a flag." 199 | When call _optparse -l -l arg1 arg2 -l 200 | The variable list should eq 1 201 | The output should eq "1: arg1 arg2 -l" 202 | End 203 | End 204 | End 205 | -------------------------------------------------------------------------------- /spec/spec_helper.sh: -------------------------------------------------------------------------------- 1 | # shellcheck shell=sh 2 | 3 | # Defining variables and functions here will affect all specfiles. 4 | # Change shell options inside a function may cause different behavior, 5 | # so it is better to set them here. 6 | # set -eu 7 | 8 | # This callback function will be invoked only once before loading specfiles. 9 | spec_helper_precheck() { 10 | # Available functions: info, warn, error, abort, setenv, unsetenv 11 | # Available variables: VERSION, SHELL_TYPE, SHELL_VERSION 12 | : minimum_version "0.28.1" 13 | } 14 | 15 | # This callback function will be invoked after a specfile has been loaded. 16 | spec_helper_loaded() { 17 | : 18 | } 19 | 20 | # This callback function will be invoked after core modules has been loaded. 21 | spec_helper_configure() { 22 | # Available functions: import, before_each, after_each, before_all, after_all 23 | : import 'support/custom_matcher' 24 | } 25 | --------------------------------------------------------------------------------