wip
This commit is contained in:
224
helpers
224
helpers
@@ -1,224 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
installed() {
|
||||
command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
installed2() {
|
||||
dpkg --list | grep -qw "ii $1"
|
||||
}
|
||||
|
||||
apt_install() {
|
||||
sudo apt install -y --autoremove $*
|
||||
}
|
||||
|
||||
require() {
|
||||
sw=()
|
||||
for package in "$@"; do
|
||||
if ! installed "$package" && ! installed2 "$package"; then
|
||||
sw+=("$package")
|
||||
fi
|
||||
done
|
||||
if [ ${#sw[@]} -gt 0 ]; then
|
||||
info "This packages will be installed in your system:\n${sw[*]}"
|
||||
apt_install ${sw[*]}
|
||||
[ $? -gt 0 ] && die "installation cancelled" 201
|
||||
fi
|
||||
}
|
||||
|
||||
require2() {
|
||||
sw=()
|
||||
for package in "$@"; do
|
||||
if ! installed "$package" && ! installed2 "$package"; then
|
||||
sw+=("$package")
|
||||
fi
|
||||
done
|
||||
if [ ${#sw[@]} -gt 0 ]; then
|
||||
die "This packages must be installed in your system:\n${sw[*]}" 200
|
||||
fi
|
||||
}
|
||||
|
||||
title() {
|
||||
[ "$1" ] && title="$1" || title="$(grep -m 1 -oP "(?<=^##makedesc:\s).*$" ${BASH_SOURCE[1]})"
|
||||
info
|
||||
info "==============================================="
|
||||
info "$title"
|
||||
info "==============================================="
|
||||
info
|
||||
}
|
||||
|
||||
unpak_targz() {
|
||||
require tar
|
||||
tar -xzf "$1" -C "$2"
|
||||
}
|
||||
|
||||
symlink() {
|
||||
ln -sf "$1" "$2"
|
||||
}
|
||||
|
||||
download() {
|
||||
require wget
|
||||
wget "$1" -O "$2"
|
||||
}
|
||||
|
||||
clone() {
|
||||
require git
|
||||
git clone $*
|
||||
}
|
||||
|
||||
clone_quick() {
|
||||
require git
|
||||
git clone $* --depth=1 --single-branch
|
||||
}
|
||||
|
||||
abspath() {
|
||||
echo $(realpath -q "${1/#\~/$HOME}")
|
||||
}
|
||||
|
||||
is_writable() {
|
||||
[ -w "$(abspath $1)" ]
|
||||
}
|
||||
|
||||
is_dir() {
|
||||
[ -d "$(abspath $1)" ]
|
||||
}
|
||||
|
||||
is_file() {
|
||||
[ -f "$(abspath $1)" ]
|
||||
}
|
||||
|
||||
is_function() {
|
||||
declare -F "$1" > /dev/null
|
||||
}
|
||||
|
||||
regex_match() {
|
||||
printf "%s" "$1" | grep -qP "$2"
|
||||
}
|
||||
|
||||
in_array() {
|
||||
local find=$1
|
||||
shift
|
||||
for e in "$@"; do
|
||||
[[ "$e" == "$find" ]] && return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
implode() {
|
||||
local d=${1-}
|
||||
local f=${2-}
|
||||
if shift 2; then
|
||||
printf %s "$f" "${@/#/$d}"
|
||||
fi
|
||||
}
|
||||
|
||||
IINFO="( i )"
|
||||
INOTE="( * )"
|
||||
IWARN="( # )"
|
||||
IERROR="( ! )"
|
||||
IFATAL="( @ )"
|
||||
ISUCCESS="( ! )"
|
||||
IASK="( ? )"
|
||||
IDEBUG="(DBG)"
|
||||
IVRB="( + )"
|
||||
|
||||
BOLD="\e[1m"
|
||||
DIM="\e[2m"
|
||||
NOTBOLD="\e[22m" # sometimes \e[21m
|
||||
NOTDIM="\e[22m"
|
||||
NORMAL="\e[20m"
|
||||
RESET="\e[0m"
|
||||
|
||||
FRESET="\e[39m"
|
||||
FBLACK="\e[30m"
|
||||
FWHITE="\e[97m"
|
||||
FRED="\e[31m"
|
||||
FGREEN="\e[32m"
|
||||
FYELLOW="\e[33m"
|
||||
FBLUE="\e[34m"
|
||||
FLRED="\e[91m"
|
||||
FLGREEN="\e[92m"
|
||||
FLYELLOW="\e[93m"
|
||||
FLBLUE="\e[94m"
|
||||
|
||||
BRESET="\e[49m"
|
||||
BBLACK="\e[40m"
|
||||
BWHITE="\e[107m"
|
||||
BRED="\e[41m"
|
||||
BGREEN="\e[42m"
|
||||
BYELLOW="\e[43m"
|
||||
BBLUE="\e[44m"
|
||||
BLRED="\e[101m"
|
||||
BLGREEN="\e[102m"
|
||||
BLYELLOW="\e[103m"
|
||||
BLBLUE="\e[104m"
|
||||
|
||||
dt() {
|
||||
echo "[$(date +'%H:%M:%S')] "
|
||||
}
|
||||
|
||||
ask() {
|
||||
IFS= read -rp "$(print ${BOLD}${BBLUE}${FWHITE}${IASK}${BRESET}\ ${BOLD}$1 ): " $2
|
||||
}
|
||||
|
||||
print() {
|
||||
echo -e "$*${RESET}"
|
||||
}
|
||||
|
||||
debug() {
|
||||
if [ "$2" ]; then
|
||||
print "${DIM}${BOLD}${RESET}${DIM}${FUNCNAME[1]:-?}():${BASH_LINENO:-?}\t$1 "
|
||||
else
|
||||
print "${DIM}${BOLD}${RESET}${DIM}$1 "
|
||||
fi
|
||||
}
|
||||
|
||||
verbose() {
|
||||
print "${BOLD}${IVRB}${RESET}${FYELLOW} $1 "
|
||||
}
|
||||
|
||||
info() {
|
||||
print "${BOLD}${FWHITE}${BLBLUE}${IINFO}${RESET}${FWHITE} $1 "
|
||||
}
|
||||
|
||||
note() {
|
||||
print "${BOLD}${DIM}${FWHITE}${INOTE}${RESET} $1 "
|
||||
}
|
||||
|
||||
success() {
|
||||
print "${BOLD}${BGREEN}${FWHITE}${ISUCCESS}${BRESET}$FGREEN $1 "
|
||||
}
|
||||
|
||||
warn() {
|
||||
print "${BOLD}${BYELLOW}${FBLACK}${IWARN}${BRESET}${FYELLOW} Warning:${RESET} $1 "
|
||||
}
|
||||
|
||||
error() {
|
||||
print "${BOLD}${BLRED}${FWHITE}${IERROR} Error: ${BRESET}${FLRED} $1 " >&2
|
||||
}
|
||||
|
||||
fatal() {
|
||||
print "${BOLD}${BRED}${FWHITE}${IFATAL} FATAL: $1 " >&2
|
||||
print_stacktrace
|
||||
}
|
||||
|
||||
die() {
|
||||
error "${1:-halted}"
|
||||
exit ${2:-100}
|
||||
}
|
||||
|
||||
print_stacktrace() {
|
||||
STACK=""
|
||||
local i
|
||||
local stack_size=${#FUNCNAME[@]}
|
||||
debug "Callstack:"
|
||||
# for (( i=$stack_size-1; i>=1; i-- )); do
|
||||
for (( i=1; i<$stack_size; i++ )); do
|
||||
local func="${FUNCNAME[$i]}"
|
||||
[ x$func = x ] && func=MAIN
|
||||
local linen="${BASH_LINENO[$(( i - 1 ))]}"
|
||||
local src="${BASH_SOURCE[$i]}"
|
||||
[ x"$src" = x ] && src=non_file_source
|
||||
debug " at $func $src:$linen"
|
||||
done
|
||||
}
|
||||
29
helpers/arg-parser/README.md
Normal file
29
helpers/arg-parser/README.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Argument parser for bash scripts
|
||||
|
||||
More info:
|
||||
* 🇷🇺 [axenov.dev/bash-args](https://axenov.dev/bash-args/)
|
||||
* 🇺🇸 [axenov.dev/en/bash-processing-arguments-in-a-script-when-called-from-the-shell/](https://axenov.dev/en/bash-processing-arguments-in-a-script-when-called-from-the-shell)
|
||||
|
||||
Tested in Ubuntu 20.04.2 LTS in:
|
||||
|
||||
```
|
||||
bash 5.0.17(1)-release (x86_64-pc-linux-gnu)
|
||||
zsh 5.8 (x86_64-ubuntu-linux-gnu)
|
||||
```
|
||||
|
||||
## Version history
|
||||
|
||||
```
|
||||
v1.0 - initial
|
||||
v1.1 - arg(): improved skipping uninteresting args
|
||||
- arg(): check next arg to be valid value
|
||||
v1.2 - removed all 'return' statements
|
||||
- arg(): error message corrected
|
||||
- new examples
|
||||
v1.3 - argl(): improved flag check
|
||||
- some text corrections
|
||||
v1.4 - new function argn()
|
||||
- some text corrections
|
||||
v1.5 - arg(), grep_match(): fixed searching for -e argument
|
||||
- grep_match(): redirect output into /dev/null
|
||||
```
|
||||
268
helpers/arg-parser/args.sh
Normal file
268
helpers/arg-parser/args.sh
Normal file
@@ -0,0 +1,268 @@
|
||||
#!/bin/bash
|
||||
#########################################################################
|
||||
# #
|
||||
# Argument parser for bash scripts #
|
||||
# #
|
||||
# Author: Anthony Axenov (Антон Аксенов) #
|
||||
# Version: 1.5 #
|
||||
# License: MIT #
|
||||
# #
|
||||
#########################################################################
|
||||
# #
|
||||
# With 'getopt' you cannot combine different #
|
||||
# arguments for different nested functions. #
|
||||
# #
|
||||
# 'getopts' does not support long arguments with #
|
||||
# values (like '--foo=bar'). #
|
||||
# #
|
||||
# These functions supports different arguments and #
|
||||
# their combinations: #
|
||||
# -a -b -c #
|
||||
# -a avalue -b bvalue -c cvalue #
|
||||
# -cab bvalue #
|
||||
# --arg #
|
||||
# --arg=value -ab -c cvalue --foo #
|
||||
# #
|
||||
# Tested in Ubuntu 20.04.2 LTS in: #
|
||||
# bash 5.0.17(1)-release (x86_64-pc-linux-gnu) #
|
||||
# zsh 5.8 (x86_64-ubuntu-linux-gnu) #
|
||||
# #
|
||||
#########################################################################
|
||||
|
||||
#purpose Little helper to check if string matches PCRE
|
||||
#argument $1 - some string
|
||||
#argument $2 - regex
|
||||
#exitcode 0 - string valid
|
||||
#exitcode 1 - string is not valid
|
||||
grep_match() {
|
||||
printf "%s" "$1" | grep -qP "$2" >/dev/null
|
||||
}
|
||||
|
||||
#purpose Find short argument or its value
|
||||
#argument $1 - (string) argument (without leading dashes; only first letter will be processed)
|
||||
#argument $2 - (number) is it flag? 1 if is, otherwise 0 or nothing
|
||||
#argument $3 - (string) variable to return value into
|
||||
# (if not specified then it will be echo'ed in stdout)
|
||||
#returns (string) 1 (if $2 == 1), value (if correct and if $2 != 1) or nothing
|
||||
#usage To get value into var: arg v 0 myvar or myvalue=$(arg 'v')
|
||||
#usage To find flag into var: arg f 1 myvar or flag=$(arg 'f')
|
||||
#usage To echo value: arg v
|
||||
#usage To echo 1 if flag exists: arg f
|
||||
arg() {
|
||||
local need=${1:0:1} # argument to find (only first letter)
|
||||
[ $need ] || {
|
||||
echo "Argument is not specified!" >&2
|
||||
exit 1
|
||||
}
|
||||
local isflag=$2 || 0 # should we find the value or just the presence of the $need?
|
||||
local retvar=$3 || 0 # var to return value into (if 0 then value will be echo'ed in stdout)
|
||||
local args=(${__MAIN_ARGS[0]}) # args we need are stored in 1st element of __MAIN_ARGS
|
||||
for ((idx=0; idx<${#args[@]}; ++idx)) do # going through args
|
||||
local arg=${args[$idx]} # current argument
|
||||
# skip $arg if it starts with '--', letter or digit
|
||||
grep_match "$arg" "^(\w{1}|-{2})" && continue
|
||||
# clear $arg from special and duplicate characters
|
||||
# e.g. 'fas-)dfs' will become 'fasd'
|
||||
local chars="$(printf "%s" "${arg}" | tr -s [${arg}] | tr -d "[:punct:][:blank:]")"
|
||||
# now we can check if $need is one of $chars
|
||||
if grep_match "-$need" "^-[$chars]$"; then # if it is
|
||||
if [[ $isflag = 1 ]]; then # and we expect it as a flag
|
||||
# then return '1' back into $3 (if exists) or echo in stdout
|
||||
[ $retvar ] && eval "$retvar='1'" || echo "1"
|
||||
else # but if $arg is not a flag
|
||||
# then get next argument as value of current one
|
||||
local value="${args[$idx+1]}"
|
||||
# check if it is valid value
|
||||
if grep_match "$value" "^\w+$"; then
|
||||
# and return it back back into $3 (if exists) or echo in stdout
|
||||
[ $retvar ] && eval "$retvar='$value'" || echo "$value"
|
||||
break
|
||||
else # otherwise throw error message into stderr (just in case)
|
||||
echo "Argument '$arg' must have a correct value!" >&2
|
||||
break
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
#purpose Find long argument or its value
|
||||
#argument $1 - argument (without leading dashes)
|
||||
#argument $2 - is it flag? 1 if is, otherwise 0 or nothing
|
||||
#argument $3 - variable to return value into
|
||||
# (if not specified then it will be echo'ed in stdout)
|
||||
#returns (string) 1 (if $2 == 1), value (if correct and if $2 != 1) or nothing
|
||||
#usage To get value into var: arg v 0 myvar or myvalue=$(arg 'v')
|
||||
#usage To find flag into var: arg f 1 myvar or flag=$(arg 'f')
|
||||
#usage To echo value: arg v
|
||||
#usage To echo 1 if flag exists: arg f
|
||||
argl() {
|
||||
local need=$1 # argument to find
|
||||
[ $need ] || {
|
||||
echo "Argument is not specified!" >&2
|
||||
exit 1
|
||||
}
|
||||
local isflag=$2 || 0 # should we find the value or just the presence of the $need?
|
||||
local retvar=$3 || 0 # var to return value into (if 0 then value will be echo'ed in stdout)
|
||||
local args=(${__MAIN_ARGS[0]}) # args we need are stored in 1st element of __MAIN_ARGS
|
||||
for ((idx=0; idx<${#args[@]}; ++idx)) do
|
||||
local arg=${args[$idx]} # current argument
|
||||
# if we expect $arg as a flag
|
||||
if [[ $isflag = 1 ]]; then
|
||||
# and if $arg has correct format (like '--flag')
|
||||
if grep_match "$arg" "^--$need"; then
|
||||
# then return '1' back into $3 (if exists) or echo in stdout
|
||||
[ ! $retvar = 0 ] && eval "$retvar=1" || echo "1"
|
||||
break
|
||||
fi
|
||||
else # but if $arg is not a flag
|
||||
# check if $arg has correct format (like '--foo=bar')
|
||||
if grep_match "$arg" "^--$need=.+$"; then # if it is
|
||||
# then return part from '=' to arg's end as value back into $3 (if exists) or echo in stdout
|
||||
[ ! $retvar = 0 ] && eval "$retvar=${arg#*=}" || echo "${arg#*=}"
|
||||
break
|
||||
fi
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
#purpose Get argument by its index
|
||||
#argument $1 - (number) arg index
|
||||
#argument $2 - (string) variable to return arg's name into
|
||||
# (if not specified then it will be echo'ed in stdout)
|
||||
#returns (string) arg name or nothing
|
||||
#usage To get arg into var: argn 1 myvar or arg=$(argn 1)
|
||||
#usage To echo in stdout: argn 1
|
||||
argn() {
|
||||
local idx=$1 # argument index
|
||||
[ $idx ] || {
|
||||
error "Argument index is not specified!"
|
||||
exit 1
|
||||
}
|
||||
local retvar=$2 || 0 # var to return value into (if 0 then value will be echo'ed in stdout)
|
||||
local args=(${__MAIN_ARGS[0]}) # args we need are stored in 1st element of __MAIN_ARGS
|
||||
local arg=${args[$idx]} # current argument
|
||||
if [ $arg ]; then
|
||||
[ ! $retvar = 0 ] && eval "$retvar=$arg" || echo "$arg"
|
||||
fi
|
||||
}
|
||||
|
||||
# Keep in mind:
|
||||
# 1. Short arguments can be specified contiguously or separately
|
||||
# and their order does not matter, but before each of them
|
||||
# (or the first of them) one leading dash must be specified.
|
||||
# Valid combinations: '-a -b -c', '-cba', '-b -ac'
|
||||
# 2. Short arguments can have values and if are - value must go
|
||||
# next to argument itself.
|
||||
# Valid combinations: '-ab avalue', '-ba avalue', '-a avalue -b'
|
||||
# 3. Long arguments cannot be combined like short ones and each
|
||||
# of them must be specified separately with two leading dashes.
|
||||
# Valid combinations: '--foo --bar', '--bar --foo'
|
||||
# 4. Long arguments can have a value which must be specified after '='.
|
||||
# Valid combinations: '--foo=value --bar', '--bar --foo=value'
|
||||
# 5. Values cannot contain spaces even in quotes both for short and
|
||||
# long args, otherwise first word will return as value.
|
||||
# 6. You can use arg() or argl() to check presence of any arg, no matter
|
||||
# if it has value or not.
|
||||
|
||||
### USAGE ###
|
||||
# This is simple examples which you can play around with.
|
||||
|
||||
# first we must save the original arguments passed
|
||||
# to the script when it was called:
|
||||
__MAIN_ARGS=$@
|
||||
|
||||
echo -e "\n1. Short args (vars):"
|
||||
arg a 1 a # -a
|
||||
arg v 0 v # -v v_value
|
||||
arg c 1 c # -c
|
||||
arg z 1 z # -z (not exists)
|
||||
echo "1.1 a=$a"
|
||||
echo "1.2 v=$v"
|
||||
echo "1.3 c=$c"
|
||||
echo "1.4 z=$z"
|
||||
|
||||
echo -e "\n2. Short args (echo):"
|
||||
echo "2.1 a=$(arg a 1)"
|
||||
echo "2.2 v=$(arg v 0)"
|
||||
echo "2.3 c=$(arg c 1)"
|
||||
echo "2.4 z=$(arg z 1)"
|
||||
|
||||
echo -e "\n3. Long args (vars):"
|
||||
argl flag 1 flag # --flag
|
||||
argl param1 0 param1 # --param1=test
|
||||
argl param2 0 param2 # --param2=password
|
||||
argl bar 1 bar # --bar (not exists)
|
||||
echo "3.1 flag=$flag"
|
||||
echo "3.2 param1=$param1"
|
||||
echo "3.3 param2=$param2"
|
||||
echo "3.4 bar=$bar"
|
||||
|
||||
echo -e "\n4. Long args (echo):"
|
||||
echo "4.1 flag=$(argl flag 1)"
|
||||
echo "4.2 param1=$(argl param1 0)"
|
||||
echo "4.3 param2=$(argl param2 0)"
|
||||
echo "4.4 bar=$(argl bar 1)"
|
||||
|
||||
echo -e "\n5. Args by index:"
|
||||
argn 1 first
|
||||
echo "5.1 arg[1]=$first"
|
||||
echo "5.2 arg[3]=$(argn 3)"
|
||||
|
||||
# Well, now we will try to get global args inside different functions
|
||||
|
||||
food() {
|
||||
echo -e "\n=== food() ==="
|
||||
arg f 0 food
|
||||
argl 'food' 0 food
|
||||
[ $food ] && echo "Om nom nom! $food is very tasty" || echo "Uh oh" >&2
|
||||
}
|
||||
|
||||
hello() {
|
||||
echo -e "\n=== hello() ==="
|
||||
arg n 0 name
|
||||
argl name 0 name
|
||||
[ $name ] && echo "Hi, $name! How u r doin?" || echo "Hello, stranger..." >&2
|
||||
}
|
||||
|
||||
hello
|
||||
food
|
||||
|
||||
### OUTPUT ###
|
||||
|
||||
# Command to run:
|
||||
# bash args.sh -va asdf --flag --param1=paramvalue1 -c --param2="somevalue2 sdf" --name="John" -f Seafood
|
||||
|
||||
# 1. Short args (vars):
|
||||
# 1.1 a=1
|
||||
# 1.2 v=v_value
|
||||
# 1.3 c=1
|
||||
# 1.4 z=
|
||||
#
|
||||
# 2. Short args (echo):
|
||||
# 2.1 a=1
|
||||
# 2.2 v=v_value
|
||||
# 2.3 c=1
|
||||
# 2.4 z=
|
||||
#
|
||||
# 3. Long args (vars):
|
||||
# 3.1 longflag=1
|
||||
# 3.2 param1=test
|
||||
# 3.3 param2=password
|
||||
# 3.4 barflag=
|
||||
#
|
||||
# 4. Long args (echo):
|
||||
# 4.1 longflag=1
|
||||
# 4.2 param1=test
|
||||
# 4.3 param2=password
|
||||
# 4.4 barflag=
|
||||
#
|
||||
# 5. Args by index:
|
||||
# 5.1 arg[1]=asdf
|
||||
# 5.2 arg[3]=--param1=paramvalue1
|
||||
#
|
||||
# === hello() ===
|
||||
# Hi, John! How u r doin?
|
||||
#
|
||||
# === food() ===
|
||||
# Om nom nom! Seafood is very tasty
|
||||
87
helpers/basic.sh
Normal file
87
helpers/basic.sh
Normal file
@@ -0,0 +1,87 @@
|
||||
#!/bin/bash
|
||||
source $( dirname $(readlink -e -- "${BASH_SOURCE}"))/io.sh || exit 255
|
||||
|
||||
########################################################
|
||||
# Little handy helpers for scripting
|
||||
########################################################
|
||||
|
||||
# convert relative path $1 to full one
|
||||
abspath() {
|
||||
echo $(realpath -q "${1/#\~/$HOME}")
|
||||
}
|
||||
|
||||
# check if path $1 is writable
|
||||
is_writable() {
|
||||
[ -w "$(abspath $1)" ]
|
||||
}
|
||||
|
||||
# check if path $1 is a directory
|
||||
is_dir() {
|
||||
[ -d "$(abspath $1)" ]
|
||||
}
|
||||
|
||||
# check if path $1 is a file
|
||||
is_file() {
|
||||
[ -f "$(abspath $1)" ]
|
||||
}
|
||||
|
||||
# check if an argument is a shell function
|
||||
is_function() {
|
||||
declare -F "$1" > /dev/null
|
||||
}
|
||||
|
||||
# check if string $1 matches regex $2
|
||||
regex_match() {
|
||||
printf "%s" "$1" | grep -qP "$2"
|
||||
}
|
||||
|
||||
# check if array $2 contains string $1
|
||||
in_array() {
|
||||
local find=$1
|
||||
shift
|
||||
for e in "$@"; do
|
||||
[[ "$e" == "$find" ]] && return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# join all elements of array $2 with delimiter $1
|
||||
implode() {
|
||||
local d=${1-}
|
||||
local f=${2-}
|
||||
if shift 2; then
|
||||
printf %s "$f" "${@/#/$d}"
|
||||
fi
|
||||
}
|
||||
|
||||
# open url $1 in system web-browser
|
||||
open_url() {
|
||||
if which xdg-open > /dev/null; then
|
||||
xdg-open "$1" </dev/null >/dev/null 2>&1 & disown
|
||||
elif which gnome-open > /dev/null; then
|
||||
gnome-open "$1" </dev/null >/dev/null 2>&1 & disown
|
||||
fi
|
||||
}
|
||||
|
||||
# unpack .tar.gz file $1 into path $2
|
||||
unpack_targz() {
|
||||
require tar
|
||||
tar -xzf "$1" -C "$2"
|
||||
}
|
||||
|
||||
# make soft symbolic link of path $1 to path $2
|
||||
symlink() {
|
||||
ln -sf "$1" "$2"
|
||||
}
|
||||
|
||||
# download file $1 into path $2 using wget
|
||||
download() {
|
||||
require wget
|
||||
wget "$1" -O "$2"
|
||||
}
|
||||
|
||||
# download file $1 into path $2 using curl
|
||||
cdownload() {
|
||||
require curl
|
||||
curl -fsSL "$1" -o "$2"
|
||||
}
|
||||
26
helpers/debug.sh
Normal file
26
helpers/debug.sh
Normal file
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
source $( dirname $(readlink -e -- "${BASH_SOURCE}"))/io.sh || exit 255
|
||||
|
||||
########################################################
|
||||
# Functions to debug scripts
|
||||
########################################################
|
||||
|
||||
var_dump() {
|
||||
debug "$1 = ${!1}"
|
||||
}
|
||||
|
||||
print_stacktrace() {
|
||||
STACK=""
|
||||
local i
|
||||
local stack_size=${#FUNCNAME[@]}
|
||||
debug "Callstack:"
|
||||
# for (( i=$stack_size-1; i>=1; i-- )); do
|
||||
for (( i=1; i<$stack_size; i++ )); do
|
||||
local func="${FUNCNAME[$i]}"
|
||||
[ x$func = x ] && func=MAIN
|
||||
local linen="${BASH_LINENO[$(( i - 1 ))]}"
|
||||
local src="${BASH_SOURCE[$i]}"
|
||||
[ x"$src" = x ] && src=non_file_source
|
||||
debug " at $func $src:$linen"
|
||||
done
|
||||
}
|
||||
178
helpers/git.sh
Normal file
178
helpers/git.sh
Normal file
@@ -0,0 +1,178 @@
|
||||
#!/bin/bash
|
||||
_dir=$( dirname $(readlink -e -- "${BASH_SOURCE}"))
|
||||
source $_dir/io.sh || exit 255
|
||||
source $_dir/basic.sh || exit 255
|
||||
source $_dir/pkg.sh || exit 255
|
||||
|
||||
########################################################
|
||||
# Shorthands for git
|
||||
########################################################
|
||||
|
||||
git.clone_quick() {
|
||||
require git
|
||||
git clone --depth=1 --single-branch $*
|
||||
}
|
||||
|
||||
git.is_repo() {
|
||||
require git
|
||||
[ "$1" ] || die "Path is not specified" 101
|
||||
require_dir "$1/"
|
||||
check_dir "$1/.git"
|
||||
}
|
||||
|
||||
git.require_repo() {
|
||||
require git
|
||||
git.is_repo "$1" || die "'$1' is not git repository!" 10
|
||||
}
|
||||
|
||||
git.cfg() {
|
||||
require git
|
||||
[ "$1" ] || die "Key is not specified" 101
|
||||
if [[ "$2" ]]; then
|
||||
git config --global --replace-all "$1" "$2"
|
||||
else
|
||||
echo $(git config --global --get-all "$1")
|
||||
fi
|
||||
}
|
||||
|
||||
git.set_user() {
|
||||
require git
|
||||
[ "$1" ] || die "git.set_user: Repo is not specified" 100
|
||||
git.cfg "$1" "user.name" "$2"
|
||||
git.cfg "$1" "user.email" "$3"
|
||||
success "User set to '$name <$email>' in ${FWHITE}$1"
|
||||
}
|
||||
|
||||
git.fetch() {
|
||||
require git
|
||||
if [ "$1" ]; then
|
||||
if git.remote_branch_exists "origin/$1"; then
|
||||
git fetch origin "refs/heads/$1:refs/remotes/origin/$1" --progress --prune --quiet 2>&1 || die "Could not fetch $1 from origin" 12
|
||||
else
|
||||
warn "Tried to fetch branch 'origin/$1' but it does not exist."
|
||||
fi
|
||||
else
|
||||
git fetch origin --progress --prune --quiet 2>&1 || exit 12
|
||||
fi
|
||||
}
|
||||
|
||||
git.reset() {
|
||||
require git
|
||||
git reset --hard HEAD
|
||||
git clean -fd
|
||||
}
|
||||
|
||||
git.clone() {
|
||||
require git
|
||||
git clone $* 2>&1
|
||||
}
|
||||
|
||||
git.co() {
|
||||
require git
|
||||
git checkout $* 2>&1
|
||||
}
|
||||
|
||||
git.is_it_current_branch() {
|
||||
require git
|
||||
[ "$1" ] || die "git.is_it_current_branch: Branch is not specified" 19
|
||||
[[ "$(git.current_branch)" = "$1" ]]
|
||||
}
|
||||
|
||||
git.pull() {
|
||||
require git
|
||||
[ "$1" ] && BRANCH=$1 || BRANCH=$(git.current_branch)
|
||||
# note "Updating branch $BRANCH..."
|
||||
git pull origin "refs/heads/$BRANCH:refs/remotes/origin/$BRANCH" --prune --force --quiet 2>&1 || exit 13
|
||||
git pull origin --tags --force --quiet 2>&1 || exit 13
|
||||
# [ "$1" ] || die "git.pull: Branch is not specified" 19
|
||||
# if [ "$1" ]; then
|
||||
# note "Updating branch $1..."
|
||||
# git pull origin "refs/heads/$1:refs/remotes/origin/$1" --prune --force --quiet 2>&1 || exit 13
|
||||
# else
|
||||
# note "Updating current branch..."
|
||||
# git pull
|
||||
# fi
|
||||
}
|
||||
|
||||
git.current_branch() {
|
||||
require git
|
||||
git branch --show-current || exit 18
|
||||
}
|
||||
|
||||
git.local_branch_exists() {
|
||||
require git
|
||||
[ -n "$(git for-each-ref --format='%(refname:short)' refs/heads/$1)" ]
|
||||
}
|
||||
|
||||
git.update_refs() {
|
||||
require git
|
||||
info "Updating local refs..."
|
||||
git remote update origin --prune 1>/dev/null 2>&1 || exit 18
|
||||
}
|
||||
|
||||
git.delete_remote_branch() {
|
||||
require git
|
||||
[ "$1" ] || die "git.remote_branch_exists: Branch is not specified" 19
|
||||
if git.remote_branch_exists "origin/$1"; then
|
||||
git push origin :"$1" # || die "Could not delete the remote $1 in $ORIGIN"
|
||||
return 0
|
||||
else
|
||||
warn "Trying to delete the remote branch $1, but it does not exists in origin"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
git.is_clean_worktree() {
|
||||
require git
|
||||
git rev-parse --verify HEAD >/dev/null || exit 18
|
||||
git update-index -q --ignore-submodules --refresh
|
||||
git diff-files --quiet --ignore-submodules || return 1
|
||||
git diff-index --quiet --ignore-submodules --cached HEAD -- || return 2
|
||||
return 0
|
||||
}
|
||||
|
||||
git.is_branch_merged_into() {
|
||||
require git
|
||||
[ "$1" ] || die "git.remote_branch_exists: Branch1 is not specified" 19
|
||||
[ "$2" ] || die "git.remote_branch_exists: Branch2 is not specified" 19
|
||||
git.update_refs
|
||||
local merge_hash=$(git merge-base "$1"^{} "$2"^{})
|
||||
local base_hash=$(git rev-parse "$1"^{})
|
||||
[ "$merge_hash" = "$base_hash" ]
|
||||
}
|
||||
|
||||
git.remote_branch_exists() {
|
||||
require git
|
||||
[ "$1" ] || die "git.remote_branch_exists: Branch is not specified" 19
|
||||
git.update_refs
|
||||
[ -n "$(git for-each-ref --format='%(refname:short)' refs/remotes/$1)" ]
|
||||
}
|
||||
|
||||
git.new_branch() {
|
||||
require git
|
||||
[ "$1" ] || die "git.new_branch: Branch is not specified" 19
|
||||
if [ "$2" ] && ! git.local_branch_exists "$2" && git.remote_branch_exists "origin/$2"; then
|
||||
git.co -b "$1" origin/"$2"
|
||||
else
|
||||
git.co -b "$1" "$2"
|
||||
fi
|
||||
}
|
||||
|
||||
git.require_clean_worktree() {
|
||||
require git
|
||||
if ! git.is_clean_worktree; then
|
||||
warn "Your working tree is dirty! Look at this:"
|
||||
git status -bs
|
||||
_T="What should you do now?\n"
|
||||
_T="${_T}\t${BOLD}${FWHITE}0.${RESET} try to continue as is\t- errors may occur!\n"
|
||||
_T="${_T}\t${BOLD}${FWHITE}1.${RESET} hard reset\t\t\t- clear current changes and new files\n"
|
||||
_T="${_T}\t${BOLD}${FWHITE}2.${RESET} stash changes (default)\t- save all changes in safe to apply them later via 'git stash pop'\n"
|
||||
_T="${_T}\t${BOLD}${FWHITE}3.${RESET} cancel\n"
|
||||
ask "${_T}${BOLD}${FWHITE}Your choice [0-3]" reset_answer
|
||||
case $reset_answer in
|
||||
1 ) warn "Clearing your work..." && git.reset ;;
|
||||
3 ) exit ;;
|
||||
* ) git stash -a -u -m "WIP before switch to $branch_task" ;;
|
||||
esac
|
||||
fi
|
||||
}
|
||||
112
helpers/io.sh
Normal file
112
helpers/io.sh
Normal file
@@ -0,0 +1,112 @@
|
||||
#!/bin/bash
|
||||
|
||||
########################################################
|
||||
# Simple and fancy input & output
|
||||
########################################################
|
||||
|
||||
IINFO="( i )"
|
||||
INOTE="( * )"
|
||||
IWARN="( # )"
|
||||
IERROR="( ! )"
|
||||
IFATAL="( @ )"
|
||||
ISUCCESS="( ! )"
|
||||
IASK="( ? )"
|
||||
IDEBUG="(DBG)"
|
||||
IVRB="( + )"
|
||||
|
||||
BOLD="\e[1m"
|
||||
DIM="\e[2m"
|
||||
NOTBOLD="\e[22m" # sometimes \e[21m
|
||||
NOTDIM="\e[22m"
|
||||
NORMAL="\e[20m"
|
||||
RESET="\e[0m"
|
||||
|
||||
FRESET="\e[39m"
|
||||
FBLACK="\e[30m"
|
||||
FWHITE="\e[97m"
|
||||
FRED="\e[31m"
|
||||
FGREEN="\e[32m"
|
||||
FYELLOW="\e[33m"
|
||||
FBLUE="\e[34m"
|
||||
FLRED="\e[91m"
|
||||
FLGREEN="\e[92m"
|
||||
FLYELLOW="\e[93m"
|
||||
FLBLUE="\e[94m"
|
||||
|
||||
BRESET="\e[49m"
|
||||
BBLACK="\e[40m"
|
||||
BWHITE="\e[107m"
|
||||
BRED="\e[41m"
|
||||
BGREEN="\e[42m"
|
||||
BYELLOW="\e[43m"
|
||||
BBLUE="\e[44m"
|
||||
BLRED="\e[101m"
|
||||
BLGREEN="\e[102m"
|
||||
BLYELLOW="\e[103m"
|
||||
BLBLUE="\e[104m"
|
||||
|
||||
dt() {
|
||||
echo "[$(date +'%H:%M:%S')] "
|
||||
}
|
||||
|
||||
ask() {
|
||||
IFS= read -rp "$(print ${BOLD}${BBLUE}${FWHITE}${IASK}${BRESET}\ ${BOLD}$1 ): " $2
|
||||
}
|
||||
|
||||
print() {
|
||||
echo -e "$*${RESET}"
|
||||
}
|
||||
|
||||
debug() {
|
||||
if [ "$2" ]; then
|
||||
print "${DIM}${BOLD}${RESET}${DIM}$(dt)${IDEBUG} ${FUNCNAME[1]:-?}():${BASH_LINENO:-?}\t$1 " >&2
|
||||
else
|
||||
print "${DIM}${BOLD}${RESET}${DIM}$(dt)${IDEBUG} $1 " >&2
|
||||
fi
|
||||
}
|
||||
|
||||
verbose() {
|
||||
print "${BOLD}$(dt)${IVRB}${RESET}${FYELLOW} $1 "
|
||||
}
|
||||
|
||||
info() {
|
||||
print "${BOLD}$(dt)${FWHITE}${BLBLUE}${IINFO}${RESET}${FWHITE} $1 "
|
||||
}
|
||||
|
||||
note() {
|
||||
print "${BOLD}$(dt)${DIM}${FWHITE}${INOTE}${RESET} $1 "
|
||||
}
|
||||
|
||||
success() {
|
||||
print "${BOLD}$(dt)${BGREEN}${FWHITE}${ISUCCESS}${BRESET}$FGREEN $1 "
|
||||
}
|
||||
|
||||
warn() {
|
||||
print "${BOLD}$(dt)${BYELLOW}${FBLACK}${IWARN}${BRESET}${FYELLOW} Warning:${RESET} $1 "
|
||||
}
|
||||
|
||||
error() {
|
||||
print "${BOLD}$(dt)${BLRED}${FWHITE}${IERROR} Error: ${BRESET}${FLRED} $1 " >&2
|
||||
}
|
||||
|
||||
fatal() {
|
||||
print "${BOLD}$(dt)${BRED}${FWHITE}${IFATAL} FATAL: $1 " >&2
|
||||
print_stacktrace
|
||||
}
|
||||
|
||||
die() {
|
||||
error "${1:-halted}"
|
||||
exit ${2:-255}
|
||||
}
|
||||
|
||||
# var='test var_dump'
|
||||
# var_dump var
|
||||
# debug 'test debug'
|
||||
# verbose 'test verbose'
|
||||
# info 'test info'
|
||||
# note 'test note'
|
||||
# success 'test success'
|
||||
# warn 'test warn'
|
||||
# error 'test error'
|
||||
# fatal 'test fatal'
|
||||
# die 'test die'
|
||||
13
helpers/log.sh
Normal file
13
helpers/log.sh
Normal file
@@ -0,0 +1,13 @@
|
||||
#!/bin/bash
|
||||
|
||||
########################################################
|
||||
# Logging functions
|
||||
########################################################
|
||||
|
||||
# write some message $1 in log file and stdout with timestamp
|
||||
log_path="/home/$USER/logs"
|
||||
log() {
|
||||
[ ! -d "$log_path" ] && log_path="./log"
|
||||
[ ! -d "$log_path" ] && mkdir -p "$log_path"
|
||||
echo -e "[$(date '+%H:%M:%S')] $*" | tee -a "$log_path/$(date '+%Y%m%d').log"
|
||||
}
|
||||
66
helpers/misc.sh
Normal file
66
helpers/misc.sh
Normal file
@@ -0,0 +1,66 @@
|
||||
#!/bin/bash
|
||||
|
||||
########################################################
|
||||
# Misc
|
||||
########################################################
|
||||
|
||||
# https://askubuntu.com/a/30414
|
||||
is_full_screen() {
|
||||
local WINDOW=$(echo $(xwininfo -id $(xdotool getactivewindow) -stats | \
|
||||
egrep '(Width|Height):' | \
|
||||
awk '{print $NF}') | \
|
||||
sed -e 's/ /x/')
|
||||
local SCREEN=$(xdpyinfo | grep -m1 dimensions | awk '{print $2}')
|
||||
if [ "$WINDOW" = "$SCREEN" ]; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
curltime() {
|
||||
curl -w @- -o /dev/null -s "$@" <<'EOF'
|
||||
time_namelookup: %{time_namelookup} sec\n
|
||||
time_connect: %{time_connect} sec\n
|
||||
time_appconnect: %{time_appconnect} sec\n
|
||||
time_pretransfer: %{time_pretransfer} sec\n
|
||||
time_redirect: %{time_redirect} sec\n
|
||||
time_starttransfer: %{time_starttransfer} sec\n
|
||||
---------------\n
|
||||
time_total: %{time_total} sec\n
|
||||
EOF
|
||||
}
|
||||
|
||||
ytm() {
|
||||
youtube-dl \
|
||||
--extract-audio \
|
||||
--audio-format flac \
|
||||
--audio-quality 0 \
|
||||
--format bestaudio \
|
||||
--write-info-json \
|
||||
--output "$HOME/Downloads/ytm/%(playlist_title)s/%(channel)s - %(title)s.%(ext)s" \
|
||||
$*
|
||||
}
|
||||
|
||||
docker.ip() { # not finished
|
||||
if [ "$1" ]; then
|
||||
if [ "$1" = "-a" ]; then
|
||||
docker ps -aq \
|
||||
| xargs -n 1 docker inspect --format '{{.Name}}{{range .NetworkSettings.Networks}} {{.IPAddress}}{{end}}' \
|
||||
| sed -e 's#^/##' \
|
||||
| column -t
|
||||
elif [ "$1" = "-c" ]; then
|
||||
docker-compose ps -q \
|
||||
| xargs -n 1 docker inspect --format '{{.Name}}{{range .NetworkSettings.Networks}} {{.IPAddress}}{{end}}' \
|
||||
| sed -e 's#^/##' \
|
||||
| column -t
|
||||
else
|
||||
docker inspect --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$1"
|
||||
docker port "$1"
|
||||
fi
|
||||
else
|
||||
docker ps -q \
|
||||
| xargs -n 1 docker inspect --format '{{.Name}}{{range .NetworkSettings.Networks}} {{.IPAddress}}{{end}}' \
|
||||
| sed -e 's#^/##' \
|
||||
| column -t
|
||||
fi
|
||||
}
|
||||
32
helpers/notifications.sh
Normal file
32
helpers/notifications.sh
Normal file
@@ -0,0 +1,32 @@
|
||||
#!/bin/bash
|
||||
source $( dirname $(readlink -e -- "${BASH_SOURCE}"))/packages.sh || exit 255
|
||||
|
||||
########################################################
|
||||
# Desktop notifications
|
||||
########################################################
|
||||
|
||||
notify () {
|
||||
require "notify-send"
|
||||
[ -n "$1" ] && local title="$1" || local title="My notification"
|
||||
local text="$2"
|
||||
local level="$3"
|
||||
local icon="$4"
|
||||
case $level in
|
||||
"critical") local timeout=0 ;;
|
||||
"low") local timeout=5000 ;;
|
||||
*) local timeout=10000 ;;
|
||||
esac
|
||||
notify-send "$title" "$text" -a "MyScript" -u "$level" -i "$icon" -t $timeout
|
||||
}
|
||||
|
||||
notify_error() {
|
||||
notify "Error" "$1" "critical" "dialog-error"
|
||||
}
|
||||
|
||||
notify_warning() {
|
||||
notify "Warning" "$1" "normal" "dialog-warning"
|
||||
}
|
||||
|
||||
notify_info() {
|
||||
notify "" "$1" "low" "dialog-information"
|
||||
}
|
||||
72
helpers/packages.sh
Normal file
72
helpers/packages.sh
Normal file
@@ -0,0 +1,72 @@
|
||||
#!/bin/bash
|
||||
source $( dirname $(readlink -e -- "${BASH_SOURCE}"))/io.sh || exit 255
|
||||
|
||||
########################################################
|
||||
# Functions to control system packages
|
||||
########################################################
|
||||
|
||||
installed() {
|
||||
command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
installed_pkg() {
|
||||
dpkg --list | grep -qw "ii $1"
|
||||
}
|
||||
|
||||
apt_ppa_add() {
|
||||
sudo add-apt-repository -y $*
|
||||
}
|
||||
|
||||
apt_ppa_remove() {
|
||||
sudo add-apt-repository -ry $*
|
||||
}
|
||||
|
||||
apt_update() {
|
||||
sudo apt update $*
|
||||
}
|
||||
|
||||
apt_install() {
|
||||
sudo apt install -y $*
|
||||
}
|
||||
|
||||
apt_remove() {
|
||||
sudo apt purge -y $*
|
||||
}
|
||||
|
||||
dpkg_install() {
|
||||
sudo dpkg -i $*
|
||||
}
|
||||
|
||||
dpkg_remove() {
|
||||
sudo dpkg -r $*
|
||||
}
|
||||
|
||||
dpkg_arch() {
|
||||
dpkg --print-architecture
|
||||
}
|
||||
|
||||
require() {
|
||||
sw=()
|
||||
for package in "$@"; do
|
||||
if ! installed "$package" && ! installed_pkg "$package"; then
|
||||
sw+=("$package")
|
||||
fi
|
||||
done
|
||||
if [ ${#sw[@]} -gt 0 ]; then
|
||||
info "These packages will be installed in your system:\n${sw[*]}"
|
||||
apt_install ${sw[*]}
|
||||
[ $? -gt 0 ] && die "installation cancelled" 201
|
||||
fi
|
||||
}
|
||||
|
||||
require_pkg() {
|
||||
sw=()
|
||||
for package in "$@"; do
|
||||
if ! installed "$package" && ! installed_pkg "$package"; then
|
||||
sw+=("$package")
|
||||
fi
|
||||
done
|
||||
if [ ${#sw[@]} -gt 0 ]; then
|
||||
die "These packages must be installed in your system:\n${sw[*]}" 200
|
||||
fi
|
||||
}
|
||||
89
helpers/testing.sh
Normal file
89
helpers/testing.sh
Normal file
@@ -0,0 +1,89 @@
|
||||
#!/bin/bash
|
||||
source $( dirname $(readlink -e -- "${BASH_SOURCE}"))/io.sh || exit 255
|
||||
|
||||
########################################################
|
||||
# Testing functions
|
||||
########################################################
|
||||
|
||||
# $1 - command to exec
|
||||
assert_exec() {
|
||||
[ "$1" ] || exit 1
|
||||
local prefix="$(dt)${BOLD}${FWHITE}[TEST EXEC]"
|
||||
if $($1 1>/dev/null 2>&1); then
|
||||
local text="${BGREEN} PASSED"
|
||||
else
|
||||
local text="${BLRED} FAILED"
|
||||
fi
|
||||
print "${prefix} ${text} ${BRESET} ($?):${RESET} $1"
|
||||
}
|
||||
# usage:
|
||||
|
||||
# func1() {
|
||||
# return 0
|
||||
# }
|
||||
# func2() {
|
||||
# return 1
|
||||
# }
|
||||
# assert_exec "func1" # PASSED
|
||||
# assert_exec "func2" # FAILED
|
||||
# assert_exec "whoami" # PASSED
|
||||
|
||||
|
||||
# $1 - command to exec
|
||||
# $2 - expected output
|
||||
assert_output() {
|
||||
[ "$1" ] || exit 1
|
||||
[ "$2" ] && local expected="$2" || local expected=''
|
||||
local prefix="$(dt)${BOLD}${FWHITE}[TEST OUTP]"
|
||||
local output=$($1 2>&1)
|
||||
local code=$?
|
||||
if [[ "$output" == *"$expected"* ]]; then
|
||||
local text="${BGREEN} PASSED"
|
||||
else
|
||||
local text="${BLRED} FAILED"
|
||||
fi
|
||||
print "${prefix} ${text} ${BRESET} (${code}|${expected}):${RESET} $1"
|
||||
# print "\tOutput > $output"
|
||||
}
|
||||
# usage:
|
||||
|
||||
# func1() {
|
||||
# echo "some string"
|
||||
# }
|
||||
# func2() {
|
||||
# echo "another string"
|
||||
# }
|
||||
# expect_output "func1" "string" # PASSED
|
||||
# expect_output "func2" "some" # FAILED
|
||||
# expect_output "func2" "string" # PASSED
|
||||
|
||||
|
||||
# $1 - command to exec
|
||||
# $2 - expected exit-code
|
||||
assert_code() {
|
||||
[ "$1" ] || exit 1
|
||||
[ "$2" ] && local expected=$2 || local expected=0
|
||||
local prefix="$(dt)${BOLD}${FWHITE}[TEST CODE]"
|
||||
$($1 1>/dev/null 2>&1)
|
||||
local code=$?
|
||||
if [[ $code -eq $expected ]]; then
|
||||
local text="${BGREEN} PASSED"
|
||||
else
|
||||
local text="${BLRED} FAILED"
|
||||
fi
|
||||
print "${prefix} ${text} ${BRESET} (${code}|${expected}):${RESET} $1"
|
||||
}
|
||||
# usage:
|
||||
|
||||
# func1() {
|
||||
# # exit 0
|
||||
# return 0
|
||||
# }
|
||||
# func2() {
|
||||
# # exit 1
|
||||
# return 1
|
||||
# }
|
||||
# expect_code "func1" 0 # PASSED
|
||||
# expect_code "func1" 1 # FAILED
|
||||
# expect_code "func2" 0 # FAILED
|
||||
# expect_code "func2" 1 # PASSED
|
||||
Reference in New Issue
Block a user