diff --git a/.shellspec b/.shellspec new file mode 100644 index 0000000..5e46908 --- /dev/null +++ b/.shellspec @@ -0,0 +1,11 @@ +--require spec_helper +## Default kcov (coverage) options +# --kcov-options "--include-path=. --path-strip-level=1" +# --kcov-options "--include-pattern=.sh" +# --kcov-options "--exclude-pattern=/.shellspec,/spec/,/coverage/,/report/" + +## Example: Include script "myprog" with no extension +# --kcov-options "--include-pattern=.sh,myprog" + +## Example: Only specified files/directories +# --kcov-options "--include-pattern=myprog,/lib/" diff --git a/backlog.adoc b/backlog.adoc index b19f055..9a16c6b 100644 --- a/backlog.adoc +++ b/backlog.adoc @@ -1,4 +1,5 @@ Todo ---- * Quote function - +* Create tests, hopefully with a 'framework' that doesn't suck +* create str_icat - string idempotent concatenation, e.g. PATH=$PATH:foo will have foo once even if previously set. diff --git a/cmd.sh b/cmd.sh index 1033184..bf4641a 100644 --- a/cmd.sh +++ b/cmd.sh @@ -1,10 +1,5 @@ -if [ -z "$CMD_SH" ]; then -CMD_SH=1 +#!/bin/sh cmd_exists() { - cmd=$1 - - type "$cmd" >/dev/null 2>&1; + type "$1" > /dev/null 2>&1 } - -fi diff --git a/compiler.sh b/compiler.sh index 8540973..4f0f9dd 100644 --- a/compiler.sh +++ b/compiler.sh @@ -1,31 +1,33 @@ -if [ -z "$COMPILER_SH" ]; then -COMPILER_SH=1 +#!/bin/sh -cc() { - cc=$1 file=$2; args=$3 +cc_is_pp() { + pp=$1; file=$2; cc=${3:-${CC:-cc}} - $cc $args -o /dev/null "$file" > /dev/null 2>&1; -} + ! type "$cc" > /dev/null 2>&1 && printf "%s\n" "\$1 with value $cc not found" >&2 && return 2 + [ ! -f "$file" ] && printf "%s\n" "\$2 with value $file doesn't exist" >&2 && return 3 -cc_is_pp() { - cc=$1; file=$2; pp_sym=$3 + printf "typedef int x;\n#if !%s\n#error yes\n#endif\n" "$pp" > "$file" + $cc -c -o /dev/null "$file" > /dev/null 2>&1 || return 1 - printf "typedef int x;\n#if \"%s\"\n#error\n#endif" "$pp_sym" > "$file" - $cc "$file" "-c" + return 0 } -cc_is_lflag() { - cc=$1; file=$2; lflag=$3 +cc_is_ldflag() { + flag=$1; file=$2; cc=${3:-${CC:-cc}} + + ! type "$cc" > /dev/null 2>&1 && printf "%s\n" "\$1 with value $cc not found" >&2 && exit 1 + [ ! -f "$file" ] && printf "%s\n" "\$2 with value $file doesn't exist" >&2 && exit 1 printf "typedef int x;\n" > "$file" - $cc "$file" "-nostdlib -shared $lflag" + $cc "$file" -c -o /dev/null "-nostdlib -shared $flag" > /dev/null 2>&1 } cc_is_flag() { - cc=$1; file=$2; flag=$3 + flag=$1; file=$2; cc=${3:-${CC:-cc}} + + ! type "$cc" > /dev/null 2>&1 && printf "%s\n" "\$1 with value $cc not found" >&2 && exit 1 + [ ! -f "$file" ] && printf "%s\n" "\$2 with value $file doesn't exist" >&2 && exit 1 printf "typedef int x;\n" > "$file" - $cc "$file" "-c $flag" + $cc "$file" -c -o /dev/null "$flag" > /dev/null 2>&1 } - -fi diff --git a/file.sh b/file.sh index 1a05a02..82917ae 100644 --- a/file.sh +++ b/file.sh @@ -1,24 +1,48 @@ -if [ -z "$FILE_SH" ]; then -FILE_SH=1 +#!/bin/sh -file_tmp() { - src=$1; dir=$2; ext=$3 +fl_tmp() { + suffix=; quiet=0 + file=; dir="/tmp"; tries=100 - set -C - i=0 + usage=$(cat <<'EOF' +Usage: $0 [OPTION]... [DIR] + Create a temporaray file in [DIR:-/tmp] + -q, supress stderr output + -s SUFFUX, append suffix SUFFIX +EOF +) - while [ "$i" -le 50 ]; do i=$((i + 1)) - if eval "$src=\"\${dir}/tmp-\$\$-\$PPID-\$i.\$ext\" 2>|\ - /dev/null > \"\${dir}/tmp-\$\$-\$PPID-\$i.\$ext\""; then - i=0 - break - fi + while [ $# -gt 0 ]; do + case "$1" in + -s) [ "$2" ] && suffix=$2 && shift || echo "$usage";; + -q) quiet=1;; + -h) printf "%s\n" "$usage" && exit 0;; + --) shift && break;; + *) break;; + esac + shift done + [ "$1" ] && dir="$1" + + if [ "$suffix" ]; then + printf "%s" "$suffix" | grep -q '^\.' || suffix=".$suffix" + fi + + if [ ! -d "$dir" ]; then + [ "$quiet" -ne 1 ] && printf "%s\n" "\$1 with value $dir isn't a valid directory" 1>&2 + return 2 + fi + + set -C + i=0 + while [ $((i = i + 1)) -le "$tries" ]; do + file="${dir}/tmp-$$-$PPID-$i${suffix}" + [ ! -f "$file" ] && touch "$file" > /dev/null 1>&2 && break + done set +C - trap 'rm "$src"' EXIT INT QUIT TERM HUP + [ "$i" -gt "$tries" ] && return 1 - test "$i" -eq 0 && return 0 || return 1 + printf "%s\n" "$file" + return 0 } - -fi diff --git a/io.sh b/io.sh deleted file mode 100644 index 159ecf9..0000000 --- a/io.sh +++ /dev/null @@ -1,10 +0,0 @@ -if [ -z "$IO_SH" ]; then -IO_SH=1 - -io_puts() { - args="$*" - - printf "%s\n" "$args"; -} - -fi diff --git a/path.sh b/path.sh index b43abac..c13d398 100644 --- a/path.sh +++ b/path.sh @@ -1,20 +1,17 @@ -if [ -z "$PATH_SH" ]; then -PATH_SH=1 +#!/bin/sh -path_resolve() { - dst="$1"; src="$2" - - eval "$dst=\"\$(ls -l \$src | awk '{ printf \$NF }')\"" && return 0 - - return 1 +path_absolute() { + printf "%s" "$(cd "$(dirname "$1")" && pwd)/$(basename "$1")" } -path_real() { - src="$1" +path_relative() { + dst="$(cd $(dirname $1); pwd -P)"; src="$(cd $(dirname $1); pwd -P)" + up= - cd $(dirname \"$src\") - printf "%s" "$(pwd)/$(basename \"$src\")" - cd - -} + while [ "${dst#$src/}" = "$dst" ]; do + src=$(dirname "$src") + up="../$up" + done -fi + printf "%s" "$up${dst#$src}/" +} diff --git a/process.sh b/process.sh index 960633f..5cb7ea2 100644 --- a/process.sh +++ b/process.sh @@ -1,11 +1,45 @@ -if [ "$PROC_SH" ]; then -PROC_SH=1 +#!/bin/sh -proc_abort() { - args="$*" +pr_dosh() { + file=; retries=3; quiet=0 + n=0 - printf "%s\n" "$args" - exit 1 -} + usage=$(cat <<'EOF' +Usage: $0 [OPTION]... [FILES] + Run shell scripts in [FILES] + -q, supress stderr output + -r RETRIES, number of retries per file to run (default $retries) +EOF +) + + while [ $# -gt 0 ]; do + case "$1" in + -r) + if [ -n "$2" ]; then + retries="$2" + shift + else + echo "$usage" + fi + ;; + -q) quiet=1;; + -h) printf "%s\n" "$usage" && exit 0;; + --) shift && break;; + *) break;; + esac + shift + done -fi + for file in "$@"; do + printf "Running %s\n" "$file" + [ ! -f "$file" ] && [ "$quiet" -ne 0 ] && printf "Argument %s isn't a file\n" "$file" 2>&1 + while ! ( trap - INT QUIT TSTP; . $file ) && [ $((n = n + 1)) -le "$retries" ]; do + if [ "$quiet" -eq 0 ]; then + printf "Failed to run %s: %s/%s times\n" "$file" "$n" "$retries" 2>&1 + fi + done + [ "$n" -gt "$retries" ] && printf "Failed to %s\n" "$file" && return 2 + done + + return 0 +} diff --git a/regex.sh b/regex.sh index 564c1de..bba9433 100644 --- a/regex.sh +++ b/regex.sh @@ -1,10 +1,7 @@ -if [ -z "$REGEX_SH" ]; then -REGEX_SH=1 +#!/bin/sh rx_match() { needle="$1"; haystack="$2" eval "case \"\$haystack\" in $needle) return 0;; *) return 1;; esac"; } - -fi diff --git a/spec/cmd_spec.sh b/spec/cmd_spec.sh new file mode 100644 index 0000000..23b2db2 --- /dev/null +++ b/spec/cmd_spec.sh @@ -0,0 +1,16 @@ +% FIXTURES: "$SHELLSPEC_HELPERDIR/fixtures" + +Describe 'cmd.sh' + Before SHELLSPEC_LOAD_PATH="$FIXTURES" + Include ./cmd.sh + Describe 'cmd_exists' + It 'succeeds on a valid command' + When call cmd_exists "[" + The status should be success + End + It 'fails on an invalid command' + # When call cmd_exists "/dev/null" + # The status should be failure + End + End +End diff --git a/spec/compiler_spec.sh b/spec/compiler_spec.sh new file mode 100644 index 0000000..5050057 --- /dev/null +++ b/spec/compiler_spec.sh @@ -0,0 +1,35 @@ +% FIXTURES: "$SHELLSPEC_HELPERDIR/fixtures" + +Describe 'compiler.sh' + before() { + . $SHELLSPEC_PROJECT_ROOT/file.sh + export SHELLSPEC_LOAD_PATH="$FIXTURES" + tmp=$(fl_tmp -s .c) + } + after() { + rm -rf $tmp + } + BeforeAll 'before' + AfterAll 'after' + Include ./compiler.sh + Describe 'cc_is_pp' + It 'succeeds when given a valid preprocessor symbol' + When run cc_is_pp '__GNUC__' "$tmp" + The status should be success + End + It 'fails when given an invalid preprocessor symbol' + When run cc_is_pp 'FOOBAR' "$tmp" + The status should be failure + End + End + Describe 'cc_is_flag' + It 'succeeds when given a valid flag' + When run cc_is_flag '-Wall' "$tmp" + The status should be success + End + It 'fails when given an invalid flag' + When run cc_is_flag '-null' "$tmp" + The status should be failure + End + End +End diff --git a/spec/file_spec.sh b/spec/file_spec.sh new file mode 100644 index 0000000..2b931a5 --- /dev/null +++ b/spec/file_spec.sh @@ -0,0 +1,32 @@ +% FIXTURES: "$SHELLSPEC_HELPERDIR/fixtures" + +Describe 'file.sh' + Before SHELLSPEC_LOAD_PATH="$FIXTURES" + Include ./file.sh + Describe 'fl_tmp' + It 'supresses stderr with -q switch' + When run fl_tmp -q /dev/null + The status should be failure + End + It 'adds a suffix with -s switch' + When run fl_tmp -s '.c' + The stdout should end with ".c" + The status should be success + End + It 'outputs help info with -h switch' + When run fl_tmp -h + The stdout should start with "Usage" + The status should be success + End + It 'fails if given an invalid directory' + When run fl_tmp /dev/null + The stderr should equal "\$1 with value /dev/null isn't a valid directory" + The status should be failure + End + It 'creates a temparary file and succeeds with no args' + When run fl_tmp + The stdout should start with "/tmp/" + The status should be success + End + End +End diff --git a/spec/fixtures/failure.sh b/spec/fixtures/failure.sh new file mode 100644 index 0000000..ecdbef9 --- /dev/null +++ b/spec/fixtures/failure.sh @@ -0,0 +1,2 @@ +#!/bin/sh +exit 1 diff --git a/spec/fixtures/success.sh b/spec/fixtures/success.sh new file mode 100644 index 0000000..43a4d1e --- /dev/null +++ b/spec/fixtures/success.sh @@ -0,0 +1,2 @@ +#!/bin/sh +echo hello world diff --git a/spec/process_spec.sh b/spec/process_spec.sh new file mode 100644 index 0000000..fc09534 --- /dev/null +++ b/spec/process_spec.sh @@ -0,0 +1,13 @@ +% FIXTURES: "$SHELLSPEC_HELPERDIR/fixtures" + +Describe 'process.sh' + Before SHELLSPEC_LOAD_PATH="$FIXTURES" + Include ./process.sh + Describe 'pr_dosh' + It 'retries 6 times given arg' + When call pr_dosh -r 6 "$FIXTURES/failure.sh" + The stdout should match pattern "*failure.sh" + The status should equal 2 + End + End +End diff --git a/spec/spec_helper.sh b/spec/spec_helper.sh new file mode 100644 index 0000000..93f1908 --- /dev/null +++ b/spec/spec_helper.sh @@ -0,0 +1,24 @@ +# shellcheck shell=sh + +# Defining variables and functions here will affect all specfiles. +# Change shell options inside a function may cause different behavior, +# so it is better to set them here. +# set -eu + +# This callback function will be invoked only once before loading specfiles. +spec_helper_precheck() { + # Available functions: info, warn, error, abort, setenv, unsetenv + # Available variables: VERSION, SHELL_TYPE, SHELL_VERSION + : minimum_version "0.28.1" +} + +# This callback function will be invoked after a specfile has been loaded. +spec_helper_loaded() { + : +} + +# This callback function will be invoked after core modules has been loaded. +spec_helper_configure() { + # Available functions: import, before_each, after_each, before_all, after_all + : import 'support/custom_matcher' +} diff --git a/string.sh b/string.sh index d594c57..d22af2f 100644 --- a/string.sh +++ b/string.sh @@ -1,5 +1,4 @@ -if [ -z "$STRING_SH" ]; then -STRING_SH=1 +#!/bin/sh str_to_sha1() { dst="$1"; src="$2" @@ -8,19 +7,3 @@ str_to_sha1() { return $? } - -str_ipush() { - dst=$1; src=$2; sep=$3 - - eval "test -z \$$dst" && sep= - - if eval "rx_match \"${sep}${src}|*${sep}${src}${sep}*|$src\" \"\${$dst}\""; then - return 1 - fi - - eval "$dst=\"\$$dst\$sep\$src\"" - - return 0 -} - -fi