Created
October 14, 2023 22:52
-
-
Save roktas/4dc95a476715d2a51dae55dd0558a978 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
# Process spool of scanned images | |
# Copyright (c) 2020, Recai Oktaş | |
# All rights reserved. | |
# | |
# Redistribution and use in source and binary forms, with or without | |
# modification, are permitted provided that the following conditions are met: | |
# | |
# * Redistributions of source code must retain the above copyright notice, this | |
# list of conditions and the following disclaimer. | |
# | |
# * Redistributions in binary form must reproduce the above copyright notice, | |
# this list of conditions and the following disclaimer in the documentation | |
# and/or other materials provided with the distribution. | |
# | |
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
[ -n "${BASH_VERSION:-}" ] || { echo >&2 'Bash required.'; exit 1; } | |
[[ ${BASH_VERSINFO[0]:-} -ge 4 ]] || { echo >&2 'Bash version 4 or higher required.'; exit 1; } | |
set -Eeuo pipefail; shopt -s nullglob; [[ -z ${TRACE:-} ]] || set -x; unset CDPATH; IFS=$' \t\n' | |
export LC_ALL=C.UTF-8 LANG=C.UTF-8 | |
# shellcheck disable=2034 | |
declare -gr PROGNAME=${0##*/} # Program name | |
# shellcheck disable=2120 | |
.cry() { | |
if [[ $# -gt 0 ]]; then | |
echo -e >&2 "W: $*" | |
else | |
echo >&2 "" | |
fi | |
} | |
# shellcheck disable=2120 | |
.die() { | |
if [[ $# -gt 0 ]]; then | |
echo -e >&2 "E: $*" | |
else | |
echo >&2 "" | |
fi | |
exit 1 | |
} | |
# shellcheck disable=2120 | |
.haw() { | |
echo -en "${@-""}" >&2 | |
} | |
# shellcheck disable=2120 | |
.say() { | |
echo -e "${@-""}" >&2 | |
} | |
.available() { | |
command -v "${1?${FUNCNAME[0]}: missing argument}" &>/dev/null | |
} | |
.callable() { | |
[[ $(type -t "${1?${FUNCNAME[0]}: missing argument}" || true) == function ]] | |
} | |
.chmog() { | |
local mog=${1?${FUNCNAME[0]}: missing argument}; shift | |
local dst=${1?${FUNCNAME[0]}: missing argument}; shift | |
local mode owner group | |
IFS=: read -r mode owner group <<<"$mog" | |
[[ -z ${mode:-} ]] || chmod "$mode" "$dst" | |
[[ -z ${owner:-} ]] || chown "$owner" "$dst" | |
[[ -z ${group:-} ]] || chgrp "$group" "$dst" | |
} | |
.cry-() { | |
local default=${1?${FUNCNAME[0]}: missing argument}; shift | |
local mesg | |
while [[ $# -gt 0 ]]; do | |
case $1 in | |
--) | |
shift | |
mesg=$* | |
break | |
;; | |
esac | |
shift | |
done | |
.cry "${mesg:-$default}" | |
} | |
.contains() { | |
: "${1?${FUNCNAME[0]}: missing argument}" | |
local element | |
for element in "${@:2}"; do | |
if [[ $element = "$1" ]]; then | |
return 0 | |
fi | |
done | |
return 1 | |
} | |
.contains-() { | |
local needle="${1?${FUNCNAME[0]}: missing argument}"; shift | |
local -n contains_="${1?${FUNCNAME[0]}: missing argument}"; shift | |
local element | |
for element in "${contains_[@]}"; do | |
if [[ $element = "$needle" ]]; then | |
return 0 | |
fi | |
done | |
return 1 | |
} | |
.die-() { | |
local default=${1?${FUNCNAME[0]}: missing argument}; shift | |
local mesg | |
while [[ $# -gt 0 ]]; do | |
case $1 in | |
--) | |
shift | |
mesg=$* | |
break | |
;; | |
esac | |
shift | |
done | |
.die "${mesg:-$default}" | |
} | |
.expired() { | |
local -i expiry=${1?${FUNCNAME[0]}: missing argument}; shift | |
case $expiry in | |
-1) return 1 ;; | |
0) return 0 ;; | |
esac | |
local file | |
for file; do | |
local t=d | |
[[ -d $file ]] || t=f | |
if [[ -e $file ]] && [[ -z $(find "$file" -maxdepth 0 -type "$t" -mmin +"$expiry" 2>/dev/null) ]]; then | |
return 1 | |
fi | |
done | |
return 0 | |
} | |
.inside() { | |
local dir=${1?${FUNCNAME[0]}: missing argument}; shift | |
[[ $# -gt 0 ]] || return 0 | |
builtin pushd "$dir" >/dev/null || exit | |
"$@" | |
builtin popd >/dev/null || exit | |
if [[ $(type -t "$1" || true) == function ]] && [[ $1 =~ [.]$ ]]; then | |
unset -f "$1" | |
fi | |
} | |
# Execute command in a temp dir | |
.intemp() { | |
[[ $# -gt 0 ]] || return 0 | |
local tmp | |
tmp=$(mktemp -p "${TMPDIR:-/tmp}" -d "$PROGNAME".XXXXXXXX) || exit | |
local err | |
(builtin cd "$tmp" && "$@") || err=$? | |
rm -rf "$tmp" | |
if [[ $(type -t "$1" || true) == function ]] && [[ $1 =~ [.]$ ]]; then | |
unset -f "$1" | |
fi | |
return ${err:-0} | |
} | |
.interactive() { | |
[[ -t 1 ]] | |
} | |
# Join array with the given separator | |
.join() { | |
local IFS=${1?${FUNCNAME[0]}: missing argument}; shift | |
echo -n "$*" | |
} | |
# Join array ref with the given separator | |
.join-() { | |
local IFS=${1?${FUNCNAME[0]}: missing argument}; shift | |
local -n join_=${1?${FUNCNAME[0]}: missing argument}; shift | |
echo -n "${join_[*]}" | |
} | |
# file.sh - File related operations | |
file.cp() { | |
local src=${1?${FUNCNAME[0]}: missing argument}; shift | |
local dst=${1?${FUNCNAME[0]}: missing argument}; shift | |
local mog=${1:-} | |
local dir=${dst%/*} | |
[[ -d $dir ]] || mkdir -p "$dir" | |
cp -a "$src" "$dst" | |
[[ -z ${mog:-} ]] || .chmog "$mog" "$dst" | |
} | |
file.hid() { | |
local src=${1?${FUNCNAME[0]}: missing argument}; shift | |
local dir; dir=$(dirname "$src")/... | |
[[ -d $dir ]] || mkdir -p "$dir" | |
mv "$src" "$dir" | |
} | |
file.ln() { | |
local src=${1?${FUNCNAME[0]}: missing argument}; shift | |
local dst=${1?${FUNCNAME[0]}: missing argument}; shift | |
local mog=${1:-} | |
local dir=${dst%/*} | |
[[ -d $dir ]] || mkdir -p "$dir" | |
src=$(realpath -m --relative-base "${dst%/*}" "$src") | |
ln -sf "$src" "$dst" | |
[[ -z ${mog:-} ]] || .chmog "$mog" "$dst" | |
} | |
file.mv() { | |
local src=${1?${FUNCNAME[0]}: missing argument}; shift | |
local dst=${1?${FUNCNAME[0]}: missing argument}; shift | |
local mog=${1:-} | |
local dir=${dst%/*} | |
[[ -d $dir ]] || mkdir -p "$dir" | |
mv -f "$src" "$dst" | |
[[ -z ${mog:-} ]] || .chmog "$mog" "$dst" | |
} | |
file.upcd() { | |
local cwd=${1?${FUNCNAME[0]}: missing argument}; shift | |
cd "$cwd" || exit | |
while :; do | |
local try | |
for try; do | |
if [[ -e $try ]]; then | |
return 0 | |
fi | |
done | |
# shellcheck disable=2128 | |
if [[ $PWD == "/" ]]; then | |
break | |
fi | |
cd .. || exit | |
done | |
} | |
# debug.sh - Debug utils | |
shopt -s expand_aliases | |
# shellcheck disable=2142 | |
alias .nope='echo -e "+++ ${FUNCNAME[0]}\t$*"; return' | |
.dbg() { | |
[[ $# -gt 0 ]] || return 0 | |
local -n dbg_=$1 | |
echo "${!dbg_}" | |
local key | |
for key in "${!dbg_[@]}"; do | |
printf ' %-16s %s\n' "${key}" "${dbg_[$key]}" | |
done | sort | |
echo | |
} | |
.stacktrace() { | |
local -i i=1 | |
while [[ -n ${BASH_SOURCE[$i]:-} ]]; do | |
echo "${BASH_SOURCE[$i]}":"${BASH_LINENO[$((i-1))]}":"${FUNCNAME[$i]}"\(\) | |
i=$((i + 1)) | |
done | grep -v "^${BASH_SOURCE[0]}" | |
} | |
# flag.sh - Flag handling | |
.bool() { | |
local value=${1:-} | |
value=${value,,} | |
case $value in | |
true|t|1|on|yes|y) | |
return 0 | |
;; | |
false|f|0|off|no|n|"") | |
return 1 | |
;; | |
*) | |
.bug "Invalid boolean: $value" | |
esac | |
} | |
flag.args() { | |
local keys=() | |
mapfile -t keys < <( | |
for key in "${!_[@]}"; do | |
[[ $key =~ ^[1-9][0-9]*$ ]] || continue | |
echo "$key" | |
done | sort -u | |
) | |
local key | |
if [[ $# -gt 0 ]]; then | |
# shellcheck disable=2178 | |
local -n _values_=$1 | |
for key in "${keys[@]}"; do | |
_values_+=("${_[$key]}") | |
done | |
else | |
for key in "${keys[@]}"; do | |
echo "${_[$key]}" | |
done | |
fi | |
} | |
flag.env() { | |
local keys=() | |
mapfile -t keys < <( | |
for key in "${!_[@]}"; do | |
[[ $key =~ ^[[:alpha:]_][[:alnum:]_]*$ ]] || continue | |
echo "$key" | |
done | sort -u | |
) | |
local key | |
if [[ $# -gt 0 ]]; then | |
# shellcheck disable=2178 | |
local -n _values_=$1 | |
for key in "${keys[@]}"; do | |
_values_+=("$key='${_[$key]}'") | |
done | |
else | |
for key in "${keys[@]}"; do | |
echo "$key='${_[$key]}'" | |
done | |
fi | |
} | |
flag.false() { | |
! flag.true "$@" | |
} | |
flag.load() { | |
local -n _load_src_=${1?${FUNCNAME[0]}: missing argument}; shift | |
local key | |
for key in "${!_load_src_[@]}"; do | |
# shellcheck disable=2034 | |
_[$key]=${_load_src_[$key]} | |
done | |
} | |
flag.nil() { | |
[[ ${_[$1]:-} = "$NIL" ]] | |
} | |
flag.parse_() { | |
if .contains -help "$@"; then | |
flag.usage-and-bye | |
fi | |
local -A flag_result_ | |
local -i argc=0 | |
while [[ $# -gt 0 ]]; do | |
local key value | |
if [[ $1 =~ ^-*[[:alpha:]_][[:alnum:]_]*= ]]; then | |
key=${1%%=*}; value=${1#*=} | |
if [[ $key =~ ^-.+$ ]] && [[ ! -v _[$key] ]]; then | |
.die "Unrecognized flag: $key" | |
fi | |
if [[ $key =~ ^-.+$ ]]; then | |
[[ -v _[$key] ]] || .die "Unrecognized flag: $key" | |
elif [[ -n ${_[.raw]:-} ]]; then | |
key=$((++argc)); value=$1 | |
fi | |
elif [[ $1 == '--' ]] && [[ -z ${_[.dash]:-} ]]; then | |
shift | |
break | |
else | |
key=$((++argc)); value=$1 | |
fi | |
# shellcheck disable=2034 | |
flag_result_["$key"]=${value:-${_["$key"]:-}} | |
shift | |
done | |
flag.load flag_result_ | |
flag.validate_ "$argc" | |
} | |
flag.peek_() { | |
if .contains -help "$@"; then | |
flag.usage-and-bye | |
fi | |
local -A flag_result_ | |
local -i argc=0 | |
while [[ $# -gt 0 ]]; do | |
local key value | |
if [[ $1 =~ ^-*[[:alpha:]_][[:alnum:]_]*= ]]; then | |
key=${1%%=*}; value=${1#*=} | |
elif [[ $1 == '--' ]] && [[ -z ${_[.dash]:-} ]]; then | |
shift | |
break | |
else | |
key=$((++argc)); value=$1 | |
fi | |
# shellcheck disable=2034 | |
flag_result_["$key"]=${value:-${_["$key"]:-}} | |
shift | |
done | |
flag.load flag_result_ | |
flag.validate_ "$argc" | |
} | |
flag.true() { | |
.bool "${_[$1]:-}" | |
} | |
flag.usage() { | |
local -a cmdname=("$PROGNAME") | |
[[ -z ${CMDNAME:-} ]] || cmdname+=("$CMDNAME") | |
if [[ -n ${_[.desc]:-} ]]; then | |
# shellcheck disable=2128 | |
.say "Usage: ${cmdname[*]} ${_[.desc]}" | |
else | |
# shellcheck disable=2128 | |
.say "Usage: ${cmdname[*]}" | |
fi | |
} | |
flag.usage-and-die() { | |
flag.usage | |
.die "$@" | |
} | |
# shellcheck disable=2120 | |
flag.usage-and-bye() { | |
flag.usage | |
.bye "$@" | |
} | |
# flag - Private functions | |
flag.args_() { | |
local n=${1?${FUNCNAME[0]}: missing argument}; shift | |
local argc=${_[.argc]:-0} | |
[[ $argc != '-' ]] || return 0 | |
local lo hi | |
if [[ $argc =~ ^[0-9]+$ ]]; then | |
lo=$argc; hi=$argc | |
elif [[ $argc =~ ^[0-9]*-[0-9]*$ ]]; then | |
IFS=- read -r lo hi <<<"$argc" | |
else | |
.bug "Incorrect range: $argc" | |
fi | |
local message | |
if [[ -n ${lo:-} ]] && [[ $n -lt $lo ]]; then | |
message='Too few arguments' | |
elif [[ -n ${hi:-} ]] && [[ $n -gt $hi ]]; then | |
message='Too many arguments' | |
else | |
return 0 | |
fi | |
flag.usage-and-die "$message" | |
} | |
flag.nils_() { | |
local required=() | |
local key | |
for key in "${!_[@]}"; do | |
if flag.nil "$key"; then | |
required+=("$key") | |
fi | |
done | |
[[ ${#required[@]} -eq 0 ]] || .die "Value missing for: ${required[*]}" | |
} | |
flag.validate_() { | |
flag.args_ "$@" | |
flag.nils_ | |
} | |
# flag - Init | |
flag.init_() { | |
shopt -s expand_aliases | |
# shellcheck disable=2142,2154 | |
alias flag.parse='flag.parse_ "$@"; local __argv__=() ARGV=("$@"); flag.args __argv__; set -- "${__argv__[@]}"; unset -v __argv__' | |
# shellcheck disable=2142,2154 | |
alias flag.peek='flag.peek_ "$@"; local __argv__=() ARGV=("$@"); flag.args __argv__; set -- "${__argv__[@]}"; unset -v __argv__' | |
# shellcheck disable=2034 | |
declare -gr NIL="\0" | |
} | |
flag.init_ | |
# ui.sh - UI functions | |
color.code() { | |
local name="${1?${FUNCNAME[0]}: missing argument}"; shift | |
local code="${_color[$name]:-}" | |
[[ -n $code ]] || .bug "No such color: $name" | |
echo -en "$code" | |
} | |
color.echo() { | |
local color="${1?${FUNCNAME[0]}: missing argument}"; shift | |
local code reset | |
code=$(color.code "$color") | |
reset=$(color.code reset) | |
echo -e "${code}${*}${reset}" | |
} | |
color.expand() { | |
while [[ $# -gt 0 ]]; do | |
local -n color_expand_=${1?missing argument} | |
shift | |
local key value | |
for key in "${!color_expand_[@]}"; do | |
value=${color_expand_[$key]} | |
color_expand_[$key]=${_color[$value]} | |
done | |
done | |
} | |
color.out() { | |
local color="${1?${FUNCNAME[0]}: missing argument}"; shift | |
local code reset | |
code=$(color.code "$color") | |
reset=$(color.code reset) | |
echo -en "$code" | |
.out | |
echo -en "$reset" | |
} | |
color.setup() { | |
while [[ $# -gt 0 ]]; do | |
local key=${1%%=*}; value=${1#*=} | |
if [[ -n ${_color[$value]:-} ]]; then | |
_color[$key]=${_color[$value]} | |
else | |
_color[$key]=$value | |
fi | |
shift | |
done | |
} | |
ui.out() { | |
local name=${1:-default} | |
shift || true | |
local sign=${_sign[$name]} | |
# shellcheck disable=2154 | |
local sign_color=${_sign_color[$name]} text_color=${_text_color[$name]} reset=${_color[reset]} | |
echo -en "${sign_color}${sign}${reset} " | |
.out "$@" | |
echo -en "$reset " | |
} | |
# shellcheck disable=2154 | |
ui.echo() { | |
[[ $# -gt 0 ]] || return 0 | |
local message=$1 | |
local name=${FUNCNAME[1]#*.} | |
local sign=${_sign[$name]} | |
local sign_color=${_sign_color[$name]} text_color=${_text_color[$name]} reset=${_color[reset]} | |
if [[ -n ${sign:-} ]]; then | |
echo -e "${sign_color}${sign}${reset} ${text_color}${message}${reset}" | |
else | |
echo -e "${text_color}${message}${reset}" | |
fi | |
} | |
# ui - Init | |
# shellcheck disable=2034 | |
color.init_() { | |
declare -Ag _color=( | |
# Basic colors with variants - prefix +: bold, prefix -: dim, suffix -: reverse | |
[black]='\e[38;5;8m' [+black]='\e[1m\e[38;5;8m' [-black]='\e[38;5;0m' | |
[black-]='\e[48;5;8m' [+black-]='\e[1m\e[48;5;8m' [-black-]='\e[48;5;0m' | |
[blue]='\e[38;5;12m' [+blue]='\e[1m\e[38;5;12m' [-blue]='\e[38;5;4m' | |
[blue-]='\e[48;5;12m' [+blue-]='\e[1m\e[48;5;12m' [-blue-]='\e[48;5;4m' | |
[cyan]='\e[38;5;14m' [+cyan]='\e[1m\e[38;5;14m' [-cyan]='\e[38;5;6m' | |
[cyan-]='\e[48;5;14m' [+cyan-]='\e[1m\e[48;5;14m' [-cyan-]='\e[48;5;6m' | |
[green]='\e[38;5;10m' [+green]='\e[1m\e[38;5;10m' [-green]='\e[38;5;2m' | |
[green-]='\e[48;5;10m' [+green-]='\e[1m\e[48;5;10m' [-green-]='\e[48;5;2m' | |
[magenta]='\e[38;5;13m' [+magenta]='\e[1m\e[38;5;13m' [-magenta]='\e[38;5;5m' | |
[magenta-]='\e[48;5;13m' [+magenta-]='\e[1m\e[48;5;13m' [-magenta-]='\e[48;5;5m' | |
[red]='\e[38;5;9m' [+red]='\e[1m\e[38;5;9m' [-red]='\e[38;5;1m' | |
[red-]='\e[48;5;9m' [+red-]='\e[1m\e[48;5;9m' [-red-]='\e[48;5;1m' | |
[white]='\e[38;5;15m' [+white]='\e[1m\e[38;5;15m' [-white]='\e[38;5;7m' | |
[white-]='\e[48;5;15m' [+white-]='\e[1m\e[48;5;15m' [-white-]='\e[48;5;7m' | |
[yellow]='\e[38;5;11m' [+yellow]='\e[1m\e[38;5;11m' [-yellow]='\e[38;5;3m' | |
[yellow-]='\e[48;5;11m' [+yellow-]='\e[1m\e[48;5;11m' [-yellow-]='\e[48;5;3m' | |
# Attributes | |
[bold]='\e[1m' [dark]='\e[2m' [underlined]='\e[4m' | |
[blink]='\e[5m' [reverse]='\e[7m' [reset]='\e[0m' | |
# Priority aliases | |
[high]='\e[1m' [medium]='' [low]='\e[2m' | |
) | |
} | |
color.init_ | |
# shellcheck disable=2034,2120,2154 | |
ui.init_() { | |
declare -Ag _sign _sign_color _text_color | |
# Style | |
_sign[ask]='?'; _sign_color[ask]=+yellow; _text_color[ask]=high | |
_sign[bug]='✖'; _sign_color[bug]=red; _text_color[bug]=high | |
_sign[bye]='ℹ'; _sign_color[bye]=-yellow; _text_color[bye]=low | |
_sign[cry]='!'; _sign_color[cry]=+yellow; _text_color[cry]=medium | |
_sign[die]='✗'; _sign_color[die]=+red; _text_color[die]=high | |
_sign[hmm]='ℹ'; _sign_color[hmm]=-yellow; _text_color[hmm]=low | |
_sign[say]='' ; _sign_color[say]=+white; _text_color[say]=medium | |
_sign[notok]='✗'; _sign_color[notok]=+red; _text_color[notok]=high | |
_sign[ok]='✓'; _sign_color[ok]=+green; _text_color[ok]=high | |
_sign[calling]='>'; _sign_color[calling]=+cyan; _text_color[calling]=high | |
_sign[getting]='↓'; _sign_color[getting]=+cyan; _text_color[getting]=low | |
_sign[calling]=''; _sign_color[calling]=+yellow; _text_color[calling]=high | |
_sign[running]='∙'; _sign_color[running]=+cyan; _text_color[running]=low | |
_sign[default]='∙'; _sign_color[default]=+white; _text_color[default]=medium | |
color.expand _sign_color _text_color | |
.bug() { ui.echo "$@" >&2; exit 127; } | |
.bye() { ui.echo "$@" >&2; exit 0; } | |
.calling() { ui.echo "$1" >&2; "${@:2}"; } | |
.cry() { ui.echo "$@" >&2; } | |
.die() { ui.echo "$@" >&2; exit 1; } | |
.getting() { ui.echo "$1" >&2; "${@:2}"; } | |
.heading() { ui.echo "$1" >&2; "${@:2}"; } | |
.hmm() { ui.echo "$@" >&2; } | |
.notok() { ui.echo "$@" >&2; } | |
.ok() { ui.echo "$@" >&2; } | |
.running() { ui.echo "$1" >&2; "${@:2}"; } | |
.say() { ui.echo "$@" >&2; } | |
} | |
ui.init_ | |
# lib/common - Common functions | |
# base template for all papers | |
:BASE() { | |
INIT() { | |
: | |
} | |
# PROCESS | |
# PLACE | |
WEED() { | |
: | |
} | |
ARCHIVE() { | |
: | |
} | |
FINAL() { | |
: | |
} | |
} | |
readonly -f :BASE | |
# Pass through template | |
.PASS() { | |
INIT() { | |
: | |
} | |
PROCESS() { | |
local src=${1?${FUNCNAME[0]}: missing argument}; shift | |
local dst=${1?${FUNCNAME[0]}: missing argument}; shift | |
file.mv "$src" "$dst" | |
} | |
WEED() { | |
local src=${1?${FUNCNAME[0]}: missing argument}; shift | |
[[ $(stat -c '%s' "$src" 2>/dev/null) -gt 10000 ]] | |
} | |
PLACE() { | |
local src=${1?${FUNCNAME[0]}: missing argument}; shift | |
# shellcheck disable=2154 | |
file.mv "$src" "${paper[dst]}" | |
verbose "${paper[dst]}/${src##*/} created." | |
} | |
} | |
readonly -f .PASS | |
declare -Ag destinated_=() | |
# shellcheck disable=2034,2120 | |
destinate() { | |
local -n destinate_=${1?${FUNCNAME[0]}: missing argument}; shift | |
local dir=${1?${FUNCNAME[0]}: missing argument}; shift | |
local now; now=$(date +'%Y%m%d%H%M') | |
local dst | |
while true; do | |
dst=$dir/$now.jpg | |
if [[ ! -f $dst ]] && [[ -z ${destinated_[$dst]:-} ]]; then | |
break | |
fi | |
((now++)) | |
done | |
destinated_[$dst]=true | |
destinate_=$dst | |
} | |
timestamp() { | |
date +%s | |
} | |
# lib/spool | |
# shellcheck disable=2154 | |
spool.init() { | |
local -a required_funcs=() missing_funcs=() | |
.callable ALL || required_funcs+=(PROCESS PLACE) | |
local func | |
for func in "${required_funcs[@]}"; do | |
.callable "$func" || missing_funcs+=("$func") | |
done | |
[[ ${#missing_funcs[@]} -eq 0 ]] || .die "$class: missing functions: ${missing_funcs[*]}" | |
local class | |
class=${paper[class]} | |
[[ -d incoming ]] || .die "$class: incoming directory not exists" | |
[[ -r incoming ]] || .die "$class: incoming directory not readable" | |
rm -rf outgoing || .die "$class: outgoing directory not removed" | |
mkdir outgoing || .die "$class: outgoing directory not created" | |
[[ -d archived ]] || { | |
mkdir archived || .die "$class: archived directory not created" | |
} | |
dst=$(readlink -m "${_[-dstdir]}") | |
[[ -d $dst ]] || .die "$class: destination directory not exists: $dst" | |
[[ -w $dst ]] || .die "$class: destination directory not writable: $dst" | |
paper[dst]=$dst | |
paper[timestamp]=$(timestamp) | |
INIT | |
} | |
spool.handle() { | |
local -a files=(incoming/*) | |
if [[ ${#files[@]} -eq 0 ]]; then | |
.cry "Nothing to be done" | |
return 0 | |
fi | |
if .callable ALL; then | |
ALL "${files[@]}" | |
else | |
local file | |
for file in "${files[@]}"; do | |
paper.pick "$file" || { | |
verbose "$file not picked." | |
continue | |
} | |
paper.process "$file" | |
paper.place | |
paper.archive "$file" | |
done | |
fi | |
} | |
spool.final() { | |
FINAL | |
} | |
# lib/paper | |
paper.pick() { | |
local ok=0 | |
case ${paper[type]:-} in | |
pdf) | |
[[ $1 =~ [.](pdf|PDF)$ ]] | |
;; | |
png) | |
[[ $1 =~ [.](png|PNG)$ ]] | |
;; | |
jpg|jpeg) | |
[[ $1 =~ [.](jpg|jpeg|JPG|JPEG)$ ]] | |
;; | |
"") | |
true | |
;; | |
esac || ok=1 | |
[[ -s $1 ]] || ok=1 | |
if [[ $ok -ne 0 ]]; then | |
rm -f "$1" | |
fi | |
return "$ok" | |
} | |
paper.process() { | |
local image=${1?${FUNCNAME[0]}: missing argument}; shift | |
PROCESS "$image" outgoing | |
} | |
paper.place() { | |
local -a files=(outgoing/*) | |
[[ ${#files[@]} -gt 0 ]] || return 0 | |
local file | |
for file in "${files[@]}"; do | |
if WEED "$file"; then | |
PLACE "$file" | |
else | |
verbose "$file weeded out." | |
rm -f "$file" | |
fi | |
done | |
} | |
paper.archive() { | |
local image=${1?${FUNCNAME[0]}: missing argument}; shift | |
ARCHIVE "$image" "${paper[timestamp]}" | |
if [[ -f $image ]]; then | |
local archived="${paper[timestamp]}"."${image##*/}" | |
file.mv "$image" archived/"$archived" | |
fi | |
} | |
# rocketbook - Process images from Rocketbook | |
.RBEH() { | |
# shellcheck disable=2154 | |
paper[type]=jpg | |
INIT() { | |
.available convert || .die 'Imagemagick required.' | |
.available identify || .die 'Imagemagick required.' | |
} | |
PROCESS() { | |
local src=${1?${FUNCNAME[0]}: missing argument}; shift | |
local dst=${1?${FUNCNAME[0]}: missing argument}; shift | |
local upper lower | |
destinate upper "$dst" | |
destinate lower "$dst" | |
local w h; read -r w h < <(identify -format '%[width] %[height]\n' "$src") | |
local c=$((h/2)) u=upper.jpg l=lower.jpg | |
convert -crop "${w}x${c}+0+0" -fuzz 35% -trim -format JPEG "$src" "$u" | |
convert -crop "${w}x${c}+0+${c}" -fuzz 35% -trim -format JPEG "$src" "$l" | |
file.mv "$u" "$upper" | |
file.mv "$l" "$lower" | |
} | |
WEED() { | |
local src=${1?${FUNCNAME[0]}: missing argument}; shift | |
[[ $(stat -c '%s' "$src" 2>/dev/null) -gt 10000 ]] | |
} | |
PLACE() { | |
local src=${1?${FUNCNAME[0]}: missing argument}; shift | |
local review=${paper[dst]}/_ | |
file.mv "$src" "$review" | |
verbose "$review/${src##*/} created." | |
} | |
} | |
:RBEH1() { | |
.RBEH | |
} | |
:RBEH2() { | |
.RBEH | |
} | |
# dropbox - Process images from Dropbox | |
:DROP0() { | |
.PASS | |
} | |
# shellcheck disable=2034 | |
declare -agr classes=( | |
:RBEH1 | |
:RBEH2 | |
:DROP0 | |
) | |
init() { | |
[[ -d ${_[-srcdir]} ]] || .die "No spool directory found: ${_[-srcdir]}" | |
[[ -w ${_[-srcdir]} ]] || .die "Spool directory not writable: ${_[-srcdir]}" | |
[[ -d ${_[-dstdir]} ]] || .die "No destination directory found: ${_[-dstdir]}" | |
[[ -w ${_[-dstdir]} ]] || .die "Destination directory not writable: ${_[-dstdir]}" | |
if flag.true -silent; then | |
verbose() { :; } | |
else | |
verbose() { .hmm "$@"; } | |
fi | |
} | |
main() { | |
local -A _=( | |
[-srcdir]=${EHOME:-$HOME}/var/paper | |
[-dstdir]=${EHOME:-$HOME}/doc | |
[-silent]='' | |
[.desc]='[-srcdir=<dir>] [-dstdir=<dir>] [-silent=<bool>]' | |
[.argc]=0 | |
) | |
flag.parse && init | |
local dir | |
for dir in "${_[-srcdir]}"/*; do | |
[[ -d $dir ]] || continue | |
local class=:${dir##*/} | |
if ! .contains- "$class" classes; then | |
.cry "Unsupported class: $class" | |
continue | |
fi | |
( | |
pushd "$dir" &>/dev/null | |
# shellcheck disable=2034 | |
local -A paper=( | |
[class]=$class | |
) | |
:BASE && "$class" | |
spool.init && spool.handle && spool.final | |
) || .cry "Error processing class: $class" | |
done | |
} | |
main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment