~dricottone/my-utils

ef6d4cd1b378cc0ebbcb6e1460b3909f0cdd71b6 — Dominic Ricottone 3 days ago a98b020 dev
Incomplete work on hardware/media utils
M Makefile => Makefile +8 -0
@@ 17,8 17,10 @@ install:
	$(call subdir_make,documents,install)
	$(call subdir_make,emulation,install)
	$(call subdir_make,games,install)
	$(call subdir_make,images,install)
	$(call subdir_make,network,install)
	$(call subdir_make,sound,install)
	$(call subdir_make,videos,install)

uninstall:
	$(call subdir_make,core,uninstall)


@@ 28,8 30,10 @@ uninstall:
	$(call subdir_make,documents,uninstall)
	$(call subdir_make,emulation,uninstall)
	$(call subdir_make,games,uninstall)
	$(call subdir_make,images,uninstall)
	$(call subdir_make,network,uninstall)
	$(call subdir_make,sound,uninstall)
	$(call subdir_make,videos,uninstall)

test: clean
	$(call subdir_make,core,test)


@@ 39,8 43,10 @@ test: clean
	$(call subdir_make,documents,test)
	$(call subdir_make,emulation,test)
	$(call subdir_make,games,test)
	$(call subdir_make,images,test)
	$(call subdir_make,network,test)
	$(call subdir_make,sound,test)
	$(call subdir_make,videos,test)

clean:
	$(call subdir_make,core,clean)


@@ 50,6 56,8 @@ clean:
	$(call subdir_make,documents,clean)
	$(call subdir_make,emulation,clean)
	$(call subdir_make,games,clean)
	$(call subdir_make,images,clean)
	$(call subdir_make,network,clean)
	$(call subdir_make,sound,clean)
	$(call subdir_make,videos,clean)


M core/Makefile => core/Makefile +2 -0
@@ 7,6 7,7 @@ COMP_DIR?=/usr/local/etc/bash_completion.d
install:
	install -m755 ctdir $(BIN_DIR)/ctdir
	install -m755 debom $(BIN_DIR)/debom
	install -m755 dired $(BIN_DIR)/dired
	install -m755 enumerate $(BIN_DIR)/enumerate
	install -m755 gitstat $(BIN_DIR)/gitstat
	install -m755 mkbak $(BIN_DIR)/mkbak


@@ 32,6 33,7 @@ install:

uninstall:
	rm $(BIN_DIR)/ctdir
	rm $(BIN_DIR)/dired
	rm $(BIN_DIR)/debom
	rm $(BIN_DIR)/enumerate
	rm $(BIN_DIR)/gitstat

A core/dired => core/dired +42 -0
@@ 0,0 1,42 @@
#!/bin/sh

name="dired"
version="1.0"
usage_message="Usage: dired [TARGET]"
help_message=$(/usr/bin/cat <<-EOF
	Open Emacs in dired mode
	Usage: dired [TARGET]
	Options:
	 -h, --help     print this message and exit
	 -q, --quiet    suppress error messages
	 -v, --version  print version number and exit
EOF
)

. /usr/local/lib/myminiparse.sh

if [ "$#" -eq 0 ]; then
  exec emacs -nw .
elif [ "$#" -eq 1 ] && [ "$quiet" -eq 1 ]; then
  exit 1
fi

# loop through arguments
for arg; do
  case "$arg" in
  -q|--quiet)
    #ignore these
    ;;

  *)
    if [ ! -d "$arg" ]; then
      if [ "$quiet" -eq 0 ]; then
        (>&2 /usr/bin/printf "%s: No such directory '%s'\n" "$name" "$arg")
      fi
      exit 1
    fi
    exec emacs -nw "$arg"
    ;;
  esac
done


A core/git-blobs => core/git-blobs +24 -0
@@ 0,0 1,24 @@
#!/bin/sh

name="git-blobs"
version="1.0"
usage_message="Usage: git-big [OPTIONS]"
help_message=$(/usr/bin/cat <<-EOF
	List blobs in git history
	Usage: git-blob [OPTIONS]
	Options:
	 -h, --help     print this message and exit
	 -q, --quiet    suppress error messages
	 -v, --version  print version number and exit
EOF
)

. /usr/local/lib/myminiparse.sh

git rev-list --objects --all |
  git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' |
  sed -n 's/^blob //p' |
  sort --numeric-sort --key=2 |
  cut -c 1-12,41- |
  numfmt --field=2 --to=iec-i --suffix=B --padding=7 --round=nearest


A core/test/dired.bats => core/test/dired.bats +138 -0
@@ 0,0 1,138 @@
#!/usr/bin/env bats
bats_require_minimum_version 1.5.0

@test "dired version" {
  run --separate-stderr dired --version
  [ "$status" -eq 0 ]
  [ "$output" = "dired 1.0" ]
  [ "$stderr" = "" ]
}

@test "dired version - quiet" {
  run --separate-stderr dired --version --quiet
  [ "$status" -eq 0 ]
  [ "$output" = "dired 1.0" ]
  [ "$stderr" = "" ]
}

@test "dired version - quiet short" {
  run --separate-stderr dired --version -q
  [ "$status" -eq 0 ]
  [ "$output" = "dired 1.0" ]
  [ "$stderr" = "" ]
}

@test "dired version short" {
  run --separate-stderr dired -v
  [ "$status" -eq 0 ]
  [ "$output" = "dired 1.0" ]
  [ "$stderr" = "" ]
}

@test "dired version short - quiet" {
  run --separate-stderr dired -v --quiet
  [ "$status" -eq 0 ]
  [ "$output" = "dired 1.0" ]
  [ "$stderr" = "" ]
}

@test "dired version short - quiet short" {
  run --separate-stderr dired -v -q
  [ "$status" -eq 0 ]
  [ "$output" = "dired 1.0" ]
  [ "$stderr" = "" ]
}

@test "dired help" {
  run --separate-stderr dired --help
  [ "$status" -eq 0 ]
  [ "${lines[0]}" = "Open Emacs in dired mode" ]
  [ "${lines[1]}" = "Usage: dired [TARGET]" ]
  [ "${lines[2]}" = "Options:" ]
  [ "${lines[3]}" = " -h, --help     print this message and exit" ]
  [ "${lines[4]}" = " -q, --quiet    suppress error messages" ]
  [ "${lines[5]}" = " -v, --version  print version number and exit" ]
  [ "$stderr" = "" ]
}

@test "dired help - quiet" {
  run --separate-stderr dired --help --quiet
  [ "$status" -eq 0 ]
  [ "${lines[0]}" = "Open Emacs in dired mode" ]
  [ "${lines[1]}" = "Usage: dired [TARGET]" ]
  [ "${lines[2]}" = "Options:" ]
  [ "${lines[3]}" = " -h, --help     print this message and exit" ]
  [ "${lines[4]}" = " -q, --quiet    suppress error messages" ]
  [ "${lines[5]}" = " -v, --version  print version number and exit" ]
  [ "$stderr" = "" ]
}

@test "dired help - quiet short" {
  run --separate-stderr dired --help -q
  [ "$status" -eq 0 ]
  [ "${lines[0]}" = "Open Emacs in dired mode" ]
  [ "${lines[1]}" = "Usage: dired [TARGET]" ]
  [ "${lines[2]}" = "Options:" ]
  [ "${lines[3]}" = " -h, --help     print this message and exit" ]
  [ "${lines[4]}" = " -q, --quiet    suppress error messages" ]
  [ "${lines[5]}" = " -v, --version  print version number and exit" ]
  [ "$stderr" = "" ]
}

@test "dired help short" {
  run --separate-stderr dired -h
  [ "$status" -eq 0 ]
  [ "${lines[0]}" = "Open Emacs in dired mode" ]
  [ "${lines[1]}" = "Usage: dired [TARGET]" ]
  [ "${lines[2]}" = "Options:" ]
  [ "${lines[3]}" = " -h, --help     print this message and exit" ]
  [ "${lines[4]}" = " -q, --quiet    suppress error messages" ]
  [ "${lines[5]}" = " -v, --version  print version number and exit" ]
  [ "$stderr" = "" ]
}

@test "dired help short - quiet" {
  run --separate-stderr dired -h --quiet
  [ "$status" -eq 0 ]
  [ "${lines[0]}" = "Open Emacs in dired mode" ]
  [ "${lines[1]}" = "Usage: dired [TARGET]" ]
  [ "${lines[2]}" = "Options:" ]
  [ "${lines[3]}" = " -h, --help     print this message and exit" ]
  [ "${lines[4]}" = " -q, --quiet    suppress error messages" ]
  [ "${lines[5]}" = " -v, --version  print version number and exit" ]
  [ "$stderr" = "" ]
}

@test "dired help short - quiet short" {
  run --separate-stderr dired -h -q
  [ "$status" -eq 0 ]
  [ "${lines[0]}" = "Open Emacs in dired mode" ]
  [ "${lines[1]}" = "Usage: dired [TARGET]" ]
  [ "${lines[2]}" = "Options:" ]
  [ "${lines[3]}" = " -h, --help     print this message and exit" ]
  [ "${lines[4]}" = " -q, --quiet    suppress error messages" ]
  [ "${lines[5]}" = " -v, --version  print version number and exit" ]
  [ "$stderr" = "" ]
}

@test "dired no such directory" {
  run --separate-stderr dired foobarbaz
  [ "$status" -eq 1 ]
  [ "$output" = "" ]
  [ "$stderr" = "dired: No such directory 'foobarbaz'" ]
}

@test "dired no such directory - quiet" {
  run --separate-stderr dired foobarbaz --quiet
  [ "$status" -eq 1 ]
  [ "$output" = "" ]
  [ "$stderr" = "" ]
}

@test "dired no such directory - quiet short" {
  run --separate-stderr dired foobarbaz -q
  [ "$status" -eq 1 ]
  [ "$output" = "" ]
  [ "$stderr" = "" ]
}


A hardware/gpu-ls => hardware/gpu-ls +10 -0
@@ 0,0 1,10 @@
#!/bin/sh

header="Name:GPU:Memory
"
table="$(nvidia-smi --query-gpu name,utilization.gpu,utilization.memory --format=csv \
  | sed 's/ %/%/g' \
  | awk 'BEGIN{FS=", "; OFS=":"} {if(NR>1) print $1,$2,$3}')"

cat <<<"${header}${table}" | column --separator=":" --table


A hardware/gpu-ps => hardware/gpu-ps +24 -0
@@ 0,0 1,24 @@
#!/bin/bash

# nvidia gpu query
query="$(nvidia-smi pmon --count=1 --select=m | sed 's/# /#/')"
memunit="$(awk '{if(NR==2) print $4}' <<<"$query")"

# nvidia table construction
full_header="::Memory Usage:(in ${memunit})
PID:Process:Frame Buffer:Confidential Compute
"
full_table="$(awk 'BEGIN{OFS=":"} {if(NR>2) print $2,$6,$4,$5}' <<<"$query")"

abbrev_header="PID:Process:Memory Usage (${memunit})
"
abbrev_table="$(awk 'BEGIN{OFS=":"} {if(NR>2) print $2,$6,$4}' <<<"$query")"

# switch on whether there is any confidential compute memory usage
ccmemusage="$(awk '{if(NR>2) sum+=$5} END{print sum}' <<<"$query")"
if [[ "$ccmemusage" -eq 0 ]]; then
  cat <<<"${abbrev_header}${abbrev_table}" | column --separator=':' --table
else
  cat <<<"${full_header}${full_table}" | column --separator=':' --table
fi


A images/Makefile => images/Makefile +15 -0
@@ 0,0 1,15 @@
BIN_DIR?=/usr/local/bin
LIB_DIR?=/usr/local/lib
COMP_DIR?=/usr/local/etc/bash_completion.d

.PHONY: install uninstall test clean

install:
	install -m755 pdf-concat $(BIN_DIR)/pdf-concat

uninstall:
	rm $(BIN_DIR)/pdf-concat

test:
	bats test/


A images/README.md => images/README.md +24 -0
@@ 0,0 1,24 @@
# images utilities

Tools for manipulating image files.

`bats` is required for the test suite.


## Specification

These utilities have external dependencies.
That's an inevitability, given that I will not be writing video codecs.

Executable |Description           |Extra Dependencies
:----------|:---------------------|:-----------------
pdf-concat |Concatenate PDF files |`bash`,`pdfjam*`

*All* scripts support `-h` and `--help` for printing built-in documentation.

*All* scripts do nothing if no input arguments are given.

\* Support for alternative backends is avaialble.
Currently this includes `pdfunite`, GhostScript (`gs`), and `pdftk`.



A images/pdf-concat => images/pdf-concat +149 -0
@@ 0,0 1,149 @@
#!/bin/bash

name="pdf-concat"
version="1.0"
read -r -d '' help_message <<-EOF
	Concatenate PDF files
	Usage: pdf-concat [OPTIONS] TARGET [TARGET..]
	Options:
	 -b BACKEND,            select a PDF backend
	     --backend BACKEND
	 -d, --dump             print to STDOUT
	 -h, --help             print this message
	 -l, --list-backends    list available PDF backends
	 -o OUTPUT,             write to a file
	     --output OUTPUT
	 -q, --quiet            suppress error messages and prompts
	 -v, --verbose          show additional messages
	 -V, --version          print version number and exit
EOF

source /usr/local/lib/mylib.bash

select_backend() {
  if command -v pdfjam >/dev/null 2>&1; then
    /usr/bin/printf "pdfjam\n"
  fi
  if command -v pdfunite >/dev/null 2>&1; then
    /usr/bin/printf "pdfunite\n"
  fi
  if command -v gs >/dev/null 2>&1; then
    /usr/bin/printf "gs\n"
  fi
  if command -v pdftk >/dev/null 2>&1; then
    /usr/bin/printf "pdftk\n"
  fi
}

list_backends() {
  if command -v pdfjam >/dev/null 2>&1; then
    msg "pdfjam    Interface to the pdfpages package for LaTeX"
  fi
  if command -v pdfunite >/dev/null 2>&1; then
    msg "pdfunite  Utility built on FreeDesktop's Poppler library"
  fi
  if command -v gs >/dev/null 2>&1; then
    msg "gs        PostScript interpreter"
  fi
  if command -v pdftk >/dev/null 2>&1; then
    msg "pdftk     General-purpose PDF toolkit"
  fi
}

positional=()
quiet=0
verbose=0
dump=0
backend="$(select_backend)"
outfile=""
while [[ $# -gt 0 ]]; do
  case "$1" in

  -b|--backend)
    debug_msg "Setting backend option to ${2} (was ${backend})"
    dump="$2"
    shift; shift
    ;;

  -d|--dump)
    debug_msg "Setting dump option to 1 (was ${dump})"
    dump=1
    shift
    ;;

  -h|--help)
    help_msg
    shift
    ;;

  -l|--list-backends)
    list_backends
    shift
    ;;

  -o|--output)
    debug_msg "Setting outfile option to ${2} (was ${outfile})"
    outfile="$2"
    shift; shift
    ;;

  -q|--quiet)
    debug_msg "Setting quiet option to 1 (was ${quiet})"
    quiet=1
    shift
    ;;

  -v|--verbose)
    debug_msg "Setting verbose option to 1 (was ${verbose})"
    verbose=1
    shift
    ;;

  -V|--version)
    version_msg
    ;;

  *)
    debug_msg "Argument '${1}' added to positional array"
    positional+=("$1")
    shift
    ;;
  esac
done

# sanitize infiles
infiles=()
infiles_count=0
for target in "${positional[@]}"; do
  if [[ ! -f "$target" ]]; then
    error_msg "No such file '${target}'"
  else
    infiles+=("$target")
    infiles_count=1
  fi
done
if [[ "$infiles_count" -eq 0 ]]; then
  usage_msg
fi
infiles_string="$(/usr/bin/printf "%q " "${infiles[@]}")"

# sanitize outfile
if [[ -f "$outfile" ]]; then
  if ! prompt_overwrite "$outfile"; then
    error_msg "No valid output"
  fi
fi

# dispatch to backend
if [[ "$backend" -eq "pdfjam" ]]; then
  pdfjam -o "$outfile" $infiles_string
elif [[ "$backend" -eq "pdfunite" ]]; then
  pdfunite $infiles_string "$outfile"
elif [[ "$backend" -eq "gs" ]]; then
  gs -dNOPAUSE -dQUIET -dBATCH -sDEVICE=pdfwrite -sOutputFile="$outfile" $infiles_string
elif [[ "$backend" -eq "pdftk" ]]; then
  pdftk $infiles_string cat output "$outfile"
else
  error_msg "No valid backend"
fi


A images/test/pdf-concat.bats => images/test/pdf-concat.bats +180 -0
@@ 0,0 1,180 @@
#!/usr/bin/env bats
bats_require_minimum_version 1.5.0

@test "pdf-concat usage" {
  run --separate-stderr pdf-concat
  [ "$status" -eq 1 ]
  [ "$output" = "" ]
  [ "$stderr" = "Usage: pdf-concat [OPTIONS] TARGET [TARGET..]" ]
}

@test "pdf-concat usage - quiet" {
  run --separate-stderr pdf-concat --quiet
  [ "$status" -eq 1 ]
  [ "$output" = "" ]
  [ "$stderr" = "Usage: pdf-concat [OPTIONS] TARGET [TARGET..]" ]
}

@test "pdf-concat usage - quiet short" {
  run --separate-stderr pdf-concat -q
  [ "$status" -eq 1 ]
  [ "$output" = "" ]
  [ "$stderr" = "Usage: pdf-concat [OPTIONS] TARGET [TARGET..]" ]
}

@test "pdf-concat version" {
  run --separate-stderr pdf-concat --version
  [ "$status" -eq 0 ]
  [ "$output" = "pdf-concat 1.0" ]
  [ "$stderr" = "" ]
}

@test "pdf-concat version - quiet" {
  run --separate-stderr pdf-concat --version --quiet
  [ "$status" -eq 0 ]
  [ "$output" = "pdf-concat 1.0" ]
  [ "$stderr" = "" ]
}

@test "pdf-concat version - quiet short" {
  run --separate-stderr pdf-concat --version -q
  [ "$status" -eq 0 ]
  [ "$output" = "pdf-concat 1.0" ]
  [ "$stderr" = "" ]
}

@test "pdf-concat version short" {
  run --separate-stderr pdf-concat -V
  [ "$status" -eq 0 ]
  [ "$output" = "pdf-concat 1.0" ]
  [ "$stderr" = "" ]
}

@test "pdf-concat version short - quiet" {
  run --separate-stderr pdf-concat -V --quiet
  [ "$status" -eq 0 ]
  [ "$output" = "pdf-concat 1.0" ]
  [ "$stderr" = "" ]
}

@test "pdf-concat version short - quiet short" {
  run --separate-stderr pdf-concat -V --quiet
  [ "$status" -eq 0 ]
  [ "$output" = "pdf-concat 1.0" ]
  [ "$stderr" = "" ]
}

@test "pdf-concat help" {
  run --separate-stderr pdf-concat --help
  [ "$status" -eq 0 ]
  [ "${lines[0]}" = "Concatenate PDF files" ]
  [ "${lines[1]}" = "Usage: pdf-concat [OPTIONS] TARGET [TARGET..]" ]
  [ "${lines[2]}" = "Options:" ]
  [ "${lines[3]}" = " -b BACKEND,            select a PDF backend" ]
  [ "${lines[4]}" = "     --backend BACKEND" ]
  [ "${lines[5]}" = " -d, --dump             print to STDOUT" ]
  [ "${lines[6]}" = " -h, --help             print this message" ]
  [ "${lines[7]}" = " -l, --list-backends    list available PDF backends" ]
  [ "${lines[8]}" = " -o OUTPUT,             write to a file" ]
  [ "${lines[9]}" = "     --output OUTPUT" ]
  [ "${lines[10]}" = " -q, --quiet            suppress error messages and prompts" ]
  [ "${lines[11]}" = " -v, --verbose          show additional messages" ]
  [ "${lines[12]}" = " -V, --version          print version number and exit" ]
  [ "$stderr" = "" ]
}

@test "pdf-concat help - quiet" {
  run --separate-stderr pdf-concat --help --quiet
  [ "$status" -eq 0 ]
  [ "${lines[0]}" = "Concatenate PDF files" ]
  [ "${lines[1]}" = "Usage: pdf-concat [OPTIONS] TARGET [TARGET..]" ]
  [ "${lines[2]}" = "Options:" ]
  [ "${lines[3]}" = " -b BACKEND,            select a PDF backend" ]
  [ "${lines[4]}" = "     --backend BACKEND" ]
  [ "${lines[5]}" = " -d, --dump             print to STDOUT" ]
  [ "${lines[6]}" = " -h, --help             print this message" ]
  [ "${lines[7]}" = " -l, --list-backends    list available PDF backends" ]
  [ "${lines[8]}" = " -o OUTPUT,             write to a file" ]
  [ "${lines[9]}" = "     --output OUTPUT" ]
  [ "${lines[10]}" = " -q, --quiet            suppress error messages and prompts" ]
  [ "${lines[11]}" = " -v, --verbose          show additional messages" ]
  [ "${lines[12]}" = " -V, --version          print version number and exit" ]
  [ "$stderr" = "" ]
}

@test "pdf-concat help - quiet short" {
  run --separate-stderr pdf-concat --help -q
  [ "$status" -eq 0 ]
  [ "${lines[0]}" = "Concatenate PDF files" ]
  [ "${lines[1]}" = "Usage: pdf-concat [OPTIONS] TARGET [TARGET..]" ]
  [ "${lines[2]}" = "Options:" ]
  [ "${lines[3]}" = " -b BACKEND,            select a PDF backend" ]
  [ "${lines[4]}" = "     --backend BACKEND" ]
  [ "${lines[5]}" = " -d, --dump             print to STDOUT" ]
  [ "${lines[6]}" = " -h, --help             print this message" ]
  [ "${lines[7]}" = " -l, --list-backends    list available PDF backends" ]
  [ "${lines[8]}" = " -o OUTPUT,             write to a file" ]
  [ "${lines[9]}" = "     --output OUTPUT" ]
  [ "${lines[10]}" = " -q, --quiet            suppress error messages and prompts" ]
  [ "${lines[11]}" = " -v, --verbose          show additional messages" ]
  [ "${lines[12]}" = " -V, --version          print version number and exit" ]
  [ "$stderr" = "" ]
}

@test "pdf-concat help short" {
  run --separate-stderr pdf-concat -h
  [ "$status" -eq 0 ]
  [ "${lines[0]}" = "Concatenate PDF files" ]
  [ "${lines[1]}" = "Usage: pdf-concat [OPTIONS] TARGET [TARGET..]" ]
  [ "${lines[2]}" = "Options:" ]
  [ "${lines[3]}" = " -b BACKEND,            select a PDF backend" ]
  [ "${lines[4]}" = "     --backend BACKEND" ]
  [ "${lines[5]}" = " -d, --dump             print to STDOUT" ]
  [ "${lines[6]}" = " -h, --help             print this message" ]
  [ "${lines[7]}" = " -l, --list-backends    list available PDF backends" ]
  [ "${lines[8]}" = " -o OUTPUT,             write to a file" ]
  [ "${lines[9]}" = "     --output OUTPUT" ]
  [ "${lines[10]}" = " -q, --quiet            suppress error messages and prompts" ]
  [ "${lines[11]}" = " -v, --verbose          show additional messages" ]
  [ "${lines[12]}" = " -V, --version          print version number and exit" ]
  [ "$stderr" = "" ]
}

@test "pdf-concat help short - quiet" {
  run --separate-stderr pdf-concat -h --quiet
  [ "$status" -eq 0 ]
  [ "${lines[0]}" = "Concatenate PDF files" ]
  [ "${lines[1]}" = "Usage: pdf-concat [OPTIONS] TARGET [TARGET..]" ]
  [ "${lines[2]}" = "Options:" ]
  [ "${lines[3]}" = " -b BACKEND,            select a PDF backend" ]
  [ "${lines[4]}" = "     --backend BACKEND" ]
  [ "${lines[5]}" = " -d, --dump             print to STDOUT" ]
  [ "${lines[6]}" = " -h, --help             print this message" ]
  [ "${lines[7]}" = " -l, --list-backends    list available PDF backends" ]
  [ "${lines[8]}" = " -o OUTPUT,             write to a file" ]
  [ "${lines[9]}" = "     --output OUTPUT" ]
  [ "${lines[10]}" = " -q, --quiet            suppress error messages and prompts" ]
  [ "${lines[11]}" = " -v, --verbose          show additional messages" ]
  [ "${lines[12]}" = " -V, --version          print version number and exit" ]
  [ "$stderr" = "" ]
}

@test "pdf-concat help short - quiet short" {
  run --separate-stderr pdf-concat --help -q
  [ "$status" -eq 0 ]
  [ "${lines[0]}" = "Concatenate PDF files" ]
  [ "${lines[1]}" = "Usage: pdf-concat [OPTIONS] TARGET [TARGET..]" ]
  [ "${lines[2]}" = "Options:" ]
  [ "${lines[3]}" = " -b BACKEND,            select a PDF backend" ]
  [ "${lines[4]}" = "     --backend BACKEND" ]
  [ "${lines[5]}" = " -d, --dump             print to STDOUT" ]
  [ "${lines[6]}" = " -h, --help             print this message" ]
  [ "${lines[7]}" = " -l, --list-backends    list available PDF backends" ]
  [ "${lines[8]}" = " -o OUTPUT,             write to a file" ]
  [ "${lines[9]}" = "     --output OUTPUT" ]
  [ "${lines[10]}" = " -q, --quiet            suppress error messages and prompts" ]
  [ "${lines[11]}" = " -v, --verbose          show additional messages" ]
  [ "${lines[12]}" = " -V, --version          print version number and exit" ]
  [ "$stderr" = "" ]
}


A videos/Makefile => videos/Makefile +15 -0
@@ 0,0 1,15 @@
BIN_DIR?=/usr/local/bin
LIB_DIR?=/usr/local/lib
COMP_DIR?=/usr/local/etc/bash_completion.d

.PHONY: install uninstall test clean

install:
	install -m755 thumbnail $(BIN_DIR)/thumbnail

uninstall:
	rm $(BIN_DIR)/thumbnail

test:
	bats test/


A videos/README.md => videos/README.md +21 -0
@@ 0,0 1,21 @@
# videos utilities

Tools for manipulating video files.

`bats` is required for the test suite.


## Specification

These utilities have external dependencies.
That's an inevitability, given that I will not be writing video codecs.

Executable      |Description                      |Extra Dependencies
:---------------|:--------------------------------|:-----------------
thumbnail       |Extract a thumbnail from a video |`bash`, `ffmpeg`

*All* scripts support `-h` and `--help` for printing built-in documentation.

*All* scripts do nothing if no input arguments are given.



A videos/test/thumbnail.bats => videos/test/thumbnail.bats +151 -0
@@ 0,0 1,151 @@
#!/usr/bin/env bats
bats_require_minimum_version 1.5.0

@test "thumbnail usage" {
  run --separate-stderr thumbnail
  [ "$status" -eq 1 ]
  [ "$output" = "" ]
  [ "$stderr" = "Usage: thumbnail [OPTIONS] TARGET [TARGET..]" ]
}

@test "thumbnail usage - quiet" {
  run --separate-stderr thumbnail --quiet
  [ "$status" -eq 1 ]
  [ "$output" = "" ]
  [ "$stderr" = "Usage: thumbnail [OPTIONS] TARGET [TARGET..]" ]
}

@test "thumbnail usage - quiet short" {
  run --separate-stderr thumbnail -q
  [ "$status" -eq 1 ]
  [ "$output" = "" ]
  [ "$stderr" = "Usage: thumbnail [OPTIONS] TARGET [TARGET..]" ]
}

@test "thumbnail version" {
  run --separate-stderr thumbnail --version
  [ "$status" -eq 0 ]
  [ "$output" = "thumbnail 1.0" ]
  [ "$stderr" = "" ]
}

@test "thumbnail version - quiet" {
  run --separate-stderr thumbnail --version --quiet
  [ "$status" -eq 0 ]
  [ "$output" = "thumbnail 1.0" ]
  [ "$stderr" = "" ]
}

@test "thumbnail version - quiet short" {
  run --separate-stderr thumbnail --version -q
  [ "$status" -eq 0 ]
  [ "$output" = "thumbnail 1.0" ]
  [ "$stderr" = "" ]
}

@test "thumbnail version short" {
  run --separate-stderr thumbnail -V
  [ "$status" -eq 0 ]
  [ "$output" = "thumbnail 1.0" ]
  [ "$stderr" = "" ]
}

@test "thumbnail version short - quiet" {
  run --separate-stderr thumbnail -V --quiet
  [ "$status" -eq 0 ]
  [ "$output" = "thumbnail 1.0" ]
  [ "$stderr" = "" ]
}

@test "thumbnail version short - quiet short" {
  run --separate-stderr thumbnail -V --quiet
  [ "$status" -eq 0 ]
  [ "$output" = "thumbnail 1.0" ]
  [ "$stderr" = "" ]
}


@test "thumbnail help" {
  run --separate-stderr thumbnail --help
  [ "$status" -eq 0 ]
  [ "${lines[0]}" = "Extract a thumbnail from a video" ]
  [ "${lines[1]}" = "Usage: thumbnail [OPTIONS] TARGET [TARGET..]" ]
  [ "${lines[2]}" = "Options:" ]
  [ "${lines[3]}" = " -h, --help            print this message" ]
  [ "${lines[4]}" = " -q, --quiet           suppress error messages and prompts" ]
  [ "${lines[5]}" = " -t, --timestamp       timestamp to extract thumbnail from" ]
  [ "${lines[6]}" = " -v, --verbose         show additional messages" ]
  [ "${lines[7]}" = " -V, --version         print version number and exit" ]
  [ "$stderr" = "" ]
}

@test "thumbnail help - quiet" {
  run --separate-stderr thumbnail --help --quiet
  [ "$status" -eq 0 ]
  [ "${lines[0]}" = "Extract a thumbnail from a video" ]
  [ "${lines[1]}" = "Usage: thumbnail [OPTIONS] TARGET [TARGET..]" ]
  [ "${lines[2]}" = "Options:" ]
  [ "${lines[3]}" = " -h, --help            print this message" ]
  [ "${lines[4]}" = " -q, --quiet           suppress error messages and prompts" ]
  [ "${lines[5]}" = " -t, --timestamp       timestamp to extract thumbnail from" ]
  [ "${lines[6]}" = " -v, --verbose         show additional messages" ]
  [ "${lines[7]}" = " -V, --version         print version number and exit" ]
  [ "$stderr" = "" ]
}

@test "thumbnail help - quiet short" {
  run --separate-stderr thumbnail --help -q
  [ "$status" -eq 0 ]
  [ "${lines[0]}" = "Extract a thumbnail from a video" ]
  [ "${lines[1]}" = "Usage: thumbnail [OPTIONS] TARGET [TARGET..]" ]
  [ "${lines[2]}" = "Options:" ]
  [ "${lines[3]}" = " -h, --help            print this message" ]
  [ "${lines[4]}" = " -q, --quiet           suppress error messages and prompts" ]
  [ "${lines[5]}" = " -t, --timestamp       timestamp to extract thumbnail from" ]
  [ "${lines[6]}" = " -v, --verbose         show additional messages" ]
  [ "${lines[7]}" = " -V, --version         print version number and exit" ]
  [ "$stderr" = "" ]
}

@test "thumbnail help short" {
  run --separate-stderr thumbnail -h
  [ "$status" -eq 0 ]
  [ "${lines[0]}" = "Extract a thumbnail from a video" ]
  [ "${lines[1]}" = "Usage: thumbnail [OPTIONS] TARGET [TARGET..]" ]
  [ "${lines[2]}" = "Options:" ]
  [ "${lines[3]}" = " -h, --help            print this message" ]
  [ "${lines[4]}" = " -q, --quiet           suppress error messages and prompts" ]
  [ "${lines[5]}" = " -t, --timestamp       timestamp to extract thumbnail from" ]
  [ "${lines[6]}" = " -v, --verbose         show additional messages" ]
  [ "${lines[7]}" = " -V, --version         print version number and exit" ]
  [ "$stderr" = "" ]
}

@test "thumbnail help short - quiet" {
  run --separate-stderr thumbnail -h --quiet
  [ "$status" -eq 0 ]
  [ "${lines[0]}" = "Extract a thumbnail from a video" ]
  [ "${lines[1]}" = "Usage: thumbnail [OPTIONS] TARGET [TARGET..]" ]
  [ "${lines[2]}" = "Options:" ]
  [ "${lines[3]}" = " -h, --help            print this message" ]
  [ "${lines[4]}" = " -q, --quiet           suppress error messages and prompts" ]
  [ "${lines[5]}" = " -t, --timestamp       timestamp to extract thumbnail from" ]
  [ "${lines[6]}" = " -v, --verbose         show additional messages" ]
  [ "${lines[7]}" = " -V, --version         print version number and exit" ]
  [ "$stderr" = "" ]
}

@test "thumbnail help short - quiet short" {
  run --separate-stderr thumbnail --help -q
  [ "$status" -eq 0 ]
  [ "${lines[0]}" = "Extract a thumbnail from a video" ]
  [ "${lines[1]}" = "Usage: thumbnail [OPTIONS] TARGET [TARGET..]" ]
  [ "${lines[2]}" = "Options:" ]
  [ "${lines[3]}" = " -h, --help            print this message" ]
  [ "${lines[4]}" = " -q, --quiet           suppress error messages and prompts" ]
  [ "${lines[5]}" = " -t, --timestamp       timestamp to extract thumbnail from" ]
  [ "${lines[6]}" = " -v, --verbose         show additional messages" ]
  [ "${lines[7]}" = " -V, --version         print version number and exit" ]
  [ "$stderr" = "" ]
}


A videos/thumbnail => videos/thumbnail +86 -0
@@ 0,0 1,86 @@
#!/bin/bash

name="thumbnail"
version="1.0"
read -r -d '' help_message <<-EOF
	Extract a thumbnail from a video
	Usage: thumbnail [OPTIONS] TARGET [TARGET..]
	Options:
	 -h, --help            print this message
	 -q, --quiet           suppress error messages and prompts
	 -t, --timestamp       timestamp to extract thumbnail from
	 -v, --verbose         show additional messages
	 -V, --version         print version number and exit
EOF

source /usr/local/lib/mylib.bash

positional=()
quiet=
timestamp="00:00:10.000"
verbose=
while [[ $# -gt 0 ]]; do
  case "$1" in

  -h|--help)
    help_msg
    shift
    ;;

  -q|--quiet)
    debug_msg "Setting quiet option to 1 (was ${quiet})"
    quiet=1
    shift
    ;;

  -t|--time|--timestamp)
    debug_msg "Setting timestamp option to '${2}' (was '${timestamp}')"
    timestamp="$2"
    shift; shift
    ;;

  -v|--verbose)
    debug_msg "Setting verbose option to 1 (was ${verbose})"
    verbose=1
    shift
    ;;

  -V|--version)
    version_msg
    ;;

  *)
    debug_msg "Argument '${1}' added to positional array"
    positional+=("$1")
    shift
    ;;
  esac
done

if [[ "${#positional[@]}" -eq 0 ]]; then
  usage_msg
fi

printf "$timestamp" | grep -e '^[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9]$' >/dev/null 2>&1
if [[ $? -ne 0 ]]; then
  error_msg "Not a valid timestamp ('${timestamp}') - must be 'HH:MM:SS.sss'"
fi

code=0
for filename in "${positional[@]}"; do
  if [[ ! -f "$filename" ]]; then
    nonfatal_error_msg "No such file '$filename'"
    code=1
  elif [[ "$filename" = *.png ]]; then
    nonfatal_error_msg "Skipping file '$filename' - already an image"
  else
    dirname="$(dirname "$filename")"
    basename="$(basename "$filename")"
    basestub="${basename%.*}"
    debug_msg "ffmpeg -i '${basename}' -hide_banner -loglevel error -ss '${timestamp}' -frames:v 1 -update 1 '${basestub}.png'"
    (cd "$dirname" && ffmpeg -i "$basename" -hide_banner -loglevel error -ss "$timestamp" -frames:v 1 -update 1 "$basestub.png")
  fi
done

exit "$code"