Skip to content

Instantly share code, notes, and snippets.

@roktas
Created October 14, 2023 22:52
Show Gist options
  • Save roktas/4dc95a476715d2a51dae55dd0558a978 to your computer and use it in GitHub Desktop.
Save roktas/4dc95a476715d2a51dae55dd0558a978 to your computer and use it in GitHub Desktop.
#!/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