~nilium/dotfiles

a0073a163f054307bde7df89a4f49371539bbe08 — Noel Cower 4 months ago 8d2adb9
Add utils section, update `git s`, adjust INSTALL

  * Add the utils section to install scripts I keep copying between
    machines. Really should've set this one up sooner, but kind of just
    didn't and don't really know why.

  * Update the `git s` alias to use `git status -uno` instead of `git
    show` to match my current work configuration (i.e., where
    I primarily use git nowadays, so its config and setup tends to be
    ahead of dotfiles a lot of the time).

  * Adjust the `INSTALL` variable under Makefiles to remove the `-b` by
    default (this is only used if running with `bmake -r`, which
    I never documented, so probably wouldn't have been noticed in most
    cases). Add an `INSTALL_FILE` variable that contains an additional
    `-T` argument under Linux to ensure that src/dest are both treated
    as files instead of file/dir respectively.
M Makefile => Makefile +15 -3
@@ 12,10 12,20 @@ PLATFORM != uname -s | tr A-Z a-z
FACTS += PLATFORM PREFIX BIN_DIR

SYMLINK ?= ln -snf
FACTS += SYMLINK
VERSION_CONTROL ?= numbered
.export-env VERSION_CONTROL
INSTALL ?= install -b
INSTALL ?= install
GMAKE   ?= make
FACTS += GMAKE

.if ${PLATFORM} == darwin
INSTALL_FILE ?= ${INSTALL}
.else
INSTALL_FILE ?= ${INSTALL} -T
.endif
FACTS += INSTALL
FACTS += INSTALL_FILE

help::
all::


@@ 36,6 46,7 @@ SUBDIRS += kitty
SUBDIRS += pact
SUBDIRS += polybar
SUBDIRS += tmux
SUBDIRS += utils
SUBDIRS += vim
SUBDIRS += zsh



@@ 71,11 82,12 @@ help::
	@echo "Targets:"
	@echo "  * help: Print this help text (default)"
	@echo "  * all: Install all config for the current platform (${PLATFORM:Q})"
	@echo "  * facts: Print computed variables for the Makefile"
.for target in ${CONF_TARGETS:O}
	@echo "  * ${target:Q}: ${HELP_${target}}"
.endfor
	@echo ""
	@echo "Facts:"

facts::
.for fact in ${FACTS:O}
	@echo "  ${fact:Q}=${${fact}:Q}"
.endfor

M README.md => README.md +12 -0
@@ 13,6 13,10 @@ The following targets are available:

    Install all targets for the running platform.

  * facts

    Print computed variables for the Makefile.

  * init

    Initialize the repository, including submodules.


@@ 21,6 25,10 @@ The following targets are available:

    Initialize submodules.

  * install-brew

    Install packages named by the brew/Brewfile. Only availabe on Darwin.

  * install-dunst

    Install dunst config.


@@ 42,6 50,10 @@ The following targets are available:

    Install tmux config files (Darwin/Linux).

  * install-utils

    Install utility scripts.

  * install-vim

    Vim config files. Requires submodules to be initialized.

M git/Makefile => git/Makefile +4 -4
@@ 19,13 19,13 @@ GIT_TOOLS += git-w2
install-git:: ${GIT_CONF}

.for gt in ${GIT_TOOLS}
bin_${gt} := ${GIT}/bin/${gt}
bin_git_${gt} := ${GIT}/bin/${gt}

install-git:: ${PREFIX}/bin/${gt}

${PREFIX}/bin/${gt}: ${bin_${gt}}
	${INSTALL} -T ${bin_${gt}:Q} ${.TARGET:Q}
${PREFIX}/bin/${gt}: ${bin_git_${gt}}
	${INSTALL_FILE} ${bin_git_${gt}:Q} ${.TARGET:Q}
.endfor

${GIT_CONF}: ${GIT_CONF_SRC}
	${INSTALL} -T ${GIT_CONF_SRC:Q} ${.TARGET:Q}
	${INSTALL_FILE} -b ${GIT_CONF_SRC:Q} ${.TARGET:Q}

M git/config => git/config +1 -1
@@ 105,7 105,7 @@
        pff = pull --ff-only
        rh = reset HEAD
        ri = rebase --interactive
        s = show
        s = status -uno
        sm = submodule
        st = status
        who = shortlog -s --

M herbstluftwm/Makefile => herbstluftwm/Makefile +1 -1
@@ 22,7 22,7 @@ ${BIN_DIR}/hlwm-use: ${HLWM}/hlwm-use
# No dependency here since we only want to install the default config if it
# doesn't already exist.
${HLWM_DIR}/polybar-conf:
	${INSTALL} -T ${POLY_CONF_SRC:Q}/conf.example ${.TARGET:Q}
	${INSTALL_FILE} -b ${POLY_CONF_SRC:Q}/conf.example ${.TARGET:Q}

.for file in ${HLWM_FILES}
install-herbstluftwm:: ${HLWM_DIR}/${file:T}

M pact/Makefile => pact/Makefile +1 -1
@@ 11,7 11,7 @@ install-pact:: submodules .WAIT ${PREFIX}/bin/pact
PACT_BIN := ${PACT}/src/pact

${PREFIX}/bin/pact: ${PREFIX}/bin ${PACT_BIN}
	${INSTALL} -T ${PACT_BIN:tA:Q} ${.TARGET:Q}
	${INSTALL_FILE} ${PACT_BIN:tA:Q} ${.TARGET:Q}

${PACT}/src/pact: ${PACT}/src/pact.c
	cd ${.TARGET:H:Q} && ${GMAKE} pact

M tmux/Makefile => tmux/Makefile +1 -1
@@ 11,4 11,4 @@ FACTS += TMUX_CONF
install-tmux:: ${TMUX_CONF}

${TMUX_CONF}: ${TMUX_CONF_SRC}
	${INSTALL} -T ${TMUX_CONF_SRC:Q} ${.TARGET:Q}
	${INSTALL_FILE} -b ${TMUX_CONF_SRC:Q} ${.TARGET:Q}

A utils/Makefile => utils/Makefile +24 -0
@@ 0,0 1,24 @@
UTILS := ${.PARSEDIR:tA}
TARGET := install-utils
HELP := Install utility scripts

TARGETS += install-utils

UTIL_BINS += bool
UTIL_BINS += cleanroom
UTIL_BINS += jpatch
UTIL_BINS += json
UTIL_BINS += kv
UTIL_BINS += list
UTIL_BINS += str
UTIL_BINS += strf
UTIL_BINS += within

.for ub in ${UTIL_BINS}
bin_utils_${ub} := ${UTILS}/bin/${ub}

install-utils:: ${PREFIX}/bin/${ub}

${PREFIX}/bin/${ub}: ${bin_utils_${ub}}
	${INSTALL_FILE} ${bin_utils_${ub}:Q} ${.TARGET:Q}
.endfor

A utils/bin/bool => utils/bin/bool +24 -0
@@ 0,0 1,24 @@
#!/usr/bin/env bash
# Attempt to convert an argument to a boolean (accepting
# t|T|true|True|TRUE|non-zero integers).
#
# If conversion fails, return JSON false.
#
# Arguments beginning with an '@', such as '@file', are read from the
# file paths following the @. To always treat an input as literal,
# prefix is with an '@@' (the '@@' will be trimmed from the input).
tobool() {
  jq -nc "$@" '$v | gsub("(?:\\s+|[\\r\\n]+)$";"") | test("^(?:[Tt](?:rue)?|TRUE|[-+]?[1-9]\\d*(?:\\.\\d+)?)$")'
}
for v; do
  if [[ "$v" != @@* ]] && [[ "$v" == @?* ]]; then
    file="${v#@}"
    cat "$file" |
      tobool --rawfile v /dev/stdin
  else
    if [[ "$v" == @@* ]]; then
      v="${v#@@}"
    fi
    tobool --arg v "$v"
  fi
done

A utils/bin/cleanroom => utils/bin/cleanroom +52 -0
@@ 0,0 1,52 @@
#!/usr/bin/env bash
# cleanroom: run a command or shell in a clean, temporary copy of a repository.

case "$1" in
-h|-help|--help)
  cat <<'USAGE'
Usage: cleanroom [-h|-help|--help] [CMD...]

Execute a command in a clone of the current repository, made under
a temporary directory.

The original repository's path is available as $REPO_TOPDIR and the
original working tree as $REPO_WORKDIR.

If no command is given, it runs the current shell in interactive mode.
USAGE
  exit 2
  ;;
esac

quiet() {
  "$@" >/dev/null 2>&1
}

dir="$(pwd)"
top="$(git rev-parse --show-toplevel)"
subdir="${dir#${top}}"
subdir="${subdir#/}"

export REPO_TOPDIR="$top"
export REPO_WORKDIR="$dir"

if ! tempdir="$(mktemp -d)"; then
  exit 1
fi
trap 'rm -rf "${tempdir}"' EXIT

set -e
quiet git clone --reference "${top}" "${top}" "${tempdir}"
quiet cp "${top}/.git/config" "${tempdir}/.git/config"
quiet cd "${tempdir}${subdir:+/$subdir}"
if [ $# -eq 0 ]; then
  "$SHELL" -i
else
  cmd="$1"
  shift
  cmdline="$(printf '%q' "$cmd")"
  if [ $# -gt 0 ]; then
    cmdline+="$(printf ' %q' "$@")"
  fi
  "$SHELL" -ic "$cmdline" cleanroom-script
fi

A utils/bin/jpatch => utils/bin/jpatch +70 -0
@@ 0,0 1,70 @@
#!/usr/bin/env bash
usage() {
  cat <<USAGE
Usage: jpatch [ops]

Create JSON patches. Accepts arguments of the following form for each
operation:

  {-a|add} <path> <value>
      Add a value at the given path.
  {-r|rm|remove} <path>
      Remove the value at the given path.
  {-p|put|replace} <path> <value>
      Put a value at the given path, if the path already exists.
  {-c|cp|copy} <srcPath> <destPath>
      Copy the value at the srcPath to the destPath.
  {-m|mv|move} <srcPath> <destPath>
      Move the value from the srcPath to the destPath.
  {-t|test} <path> <value>
      Test whether the path has a given value.
USAGE
}
case "${1-}" in
-h|help)
  usage
  exit 2
  ;;
esac
while [ $# -gt 0 ]; do
  path="$2"
  case "$1" in
  -a|add)
    op=add
    val="$3"
    shift 3
    kv op "$op" path "$path" value "$val"
    ;;
  -r|rm|remove)
    op=remove
    shift 2
    kv op "$op" path "$path"
    ;;
  -p|put|replace)
    op=replace
    val="$3"
    shift 3
    kv op "$op" path "$path" value "$val"
    ;;
  -c|cp|copy)
    op=copy
    from="$path"
    path="$3"
    shift 3
    kv op "$op" from "$from" path "$path"
    ;;
  -m|mv|move)
    op=move
    from="$path"
    path="$3"
    shift 3
    kv op "$op" from "$from" path "$path"
    ;;
  -t|test)
    op=test
    val="$3"
    shift 3
    kv op "$op" path "$path" value "$val"
    ;;
  esac
done | jq --slurp -c .

A utils/bin/json => utils/bin/json +22 -0
@@ 0,0 1,22 @@
#!/usr/bin/env bash
# Attempt to convert an argument or value to JSON. If conversion fails,
# return a JSON string.
#
# Arguments beginning with an '@', such as '@file', are read from the
# file paths following the @. To always treat an input as literal,
# prefix is with an '@@' (the '@@' will be trimmed from the input).
tojson() {
  jq -nc "$@" 'try ($v|fromjson) catch $v'
}
for v; do
  if [[ "$v" != @@* ]] && [[ "$v" == @?* ]]; then
    file="${v#@}"
    cat "$file" |
      tojson --rawfile v /dev/stdin
  else
    if [[ "$v" == @@* ]]; then
      v="${v#@@}"
    fi
    tojson --arg v "$v"
  fi
done

A utils/bin/kv => utils/bin/kv +16 -0
@@ 0,0 1,16 @@
#!/usr/bin/env bash
# Create a JSON object using key-value pairs from arguments. Odd
# arguments are keys, even arguments are values. For example, kv a b =>
# {"a":"b"}.
#
# Arguments beginning with an '@', such as '@file', are read from the
# file paths following the @. To always treat an input as literal,
# prefix is with an '@@' (the '@@' will be trimmed from the input).
set -eo pipefail
while [ $# -gt 0 ]; do
  {
    json "$1"
    json "$2"
  } | jq -nc '{(input | tostring): (input)}'
  shift; shift
done | jq -sc add

A utils/bin/list => utils/bin/list +7 -0
@@ 0,0 1,7 @@
#!/usr/bin/env bash
# Create a JSON array using values from arguments.
#
# Arguments beginning with an '@', such as '@file', are read from the
# file paths following the @. To always treat an input as literal,
# prefix is with an '@@' (the '@@' will be trimmed from the input).
json "$@" | jq -c --slurp .

A utils/bin/str => utils/bin/str +5 -0
@@ 0,0 1,5 @@
#!/usr/bin/env bash
# Convert one or more input arguments to a single JSON string. Unlike
# kv, list, and so on, this does not accept '@'-prefixed files. To
# convert a file to a string, use strf.
exec jq -nc --arg v "$*" '$v'

A utils/bin/strf => utils/bin/strf +5 -0
@@ 0,0 1,5 @@
#!/usr/bin/env bash
# Convert one or more file inputs to a JSON string. Unlike kv, list, and
# so on, this does not accept '@'-prefixed files. To convert a file to
# a string, use strf.
exec jq -nc --rawfile v <(cat -- "$@") '$v'

A utils/bin/within => utils/bin/within +155 -0
@@ 0,0 1,155 @@
#!/usr/bin/env bash

set -e

usage() {
  cat <<'USAGE'
Usage: within [options] [--] [dir] [cmd] [args...]

Create a tar of a directory (defaults to current directory), along with
a default command to run (if any), and write a bash script to extract
and run commands against the directory.

If mtar is available, it is used instead of either GNU or BSD tar
(depending on environment).

Options:
-h
  Print this usage text.
-x
  Execute commands from within the extracted directory. (default)
-X
  Execute commands from the working directory.
-q, -Q
  Quote (-q), or un-quote (-Q), the CMD and ARGS when writing them to
  the bash script.
-gzip
  Compress tar data using gzip. Requires gzip. (default)
-xz
  Compress tar data using xz. Requires xz.
-zstd
  Compress tar data using zstd. Requires zstd.
-plain
  Do not compress the tar data. (Useful for very limited runtime
  environments.)
USAGE
  exit 2
}

indir=1
quote=1
compress=(gzip -n -9)
decompress='| gzip -d '
for arg; do
  case "$arg" in
  -h) usage;;
  -x) indir=1;;
  -X) indir=0;;
  -q) quote=1;;
  -Q) quote=0;;
  -plain) compress=(cat); decompress='';;
  -gzip) compress=(gzip -n -6); decompress='| gzip -d ';;
  -xz) compress=(xz -6); decompress='| xz -d ';;
  -zstd) compress=(zstd -11); decompress='| zstd -d ';;
  --) shift; break;;
  *) break;;
  esac
  shift
done

tarit() {
  if command >/dev/null 2>&1 -v mtar; then
    mtar -U -Fustar -C "$1" .
  else
    tar -cf - -C "$1" .
  fi
}

dir=.
if [[ $# -gt 0 ]]; then
  dir="${1}"
  shift
  if [[ -z "$dir" ]]; then
    echo >&2 'error: no directory passed.'
    exit 1
  fi
fi
if ! [[ -d "$dir" ]]; then
  echo >&2 "error: path is not a directory: $dir"
  exit 1
fi

# Write the shared prelude for all within-scripts.
cat <<'PRELUDE'
#!/usr/bin/env bash
set -e

have() { command -v >/dev/null 2>&1; }
debase64() {
  # Try base6 -d if the executable is available.
  if have base64; then base64 -d
  # Fallback: decode with openssl
  elif have openssl; then tr -d '\n' | openssl enc -base64 -d -A
  else
    echo >&2 'error: no base64 or openssl executable available'
    exit 1
  fi
}

pwd="$(pwd)"
dir="$(mktemp -d)"
trap "$(printf 'rm -rf %q' "$dir")" EXIT
PRELUDE

# If indir is set, run the program from within the extracted directory.
if [[ $indir = 1 ]]; then
  echo 'cd "$dir"'
fi

tarball="$(mktemp)"
trap "$(printf 'rm %q' "$tarball")" EXIT
tarit "$dir" | "${compress[@]}" >"$tarball"

# Write the decode-decompress-extract commands.
tardoc="TARBALL_$(<"$tarball" openssl dgst -hex -sha256)"
echo "debase64 <<'${tardoc}' ${decompress}| tar -xf - -C "'"$dir"'
<"$tarball" base64 | fold
echo "${tardoc}"
echo

# If no arguments are passed to the script and no default arguments are
# given, default to an interactive shell.
if [ $# -eq 0 ]; then
  echo '[ $# -eq 0 ] && set -- "$SHELL" -i'
fi

# Print env line for executing commands.
# Run it in a subshell to prevent messing with the atexit.
echo '('
# Directory the script was run from.
echo 'export RUNDIR="${pwd}"'
echo 'export ENVIDR="${dir}"'
# If bin, lib, or include directories are found, make them available
# from PATH, LD_LIBRARY_PATH, and CPATH, respectively.
if [[ -d bin ]]; then
  echo 'export PATH="${dir}/bin${PATH:+:${PATH}}"'
fi
if [[ -d lib ]]; then
  echo 'export LD_LIBRARY_PATH="${dir}/lib${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}"'
fi
if [[ -d include ]]; then
  echo 'export CPATH="${dir}/include${CPATH:+:${CPATH}}"'
fi

# env exec.
echo -n 'exec env'
if [[ $# -gt 0 ]]; then
  if [[ "$quote" = 1 ]]; then
    printf ' %q' "$@"
  else
    printf ' bash -c %q -' "$*"
  fi
fi
echo ' "$@"'

echo ')'

M zsh/Makefile => zsh/Makefile +2 -2
@@ 58,12 58,12 @@ zsh-themes::
zsh-themes:: ${ZSH_THEMES_DIR}/${theme}

${ZSH_THEMES_DIR}/${theme}: ${ZSH_THEMES_DIR} ${ZSH}/themes/${theme}
	${INSTALL} -T ${ZSH}/themes/${theme:Q} ${.TARGET:Q}
	${INSTALL_FILE} ${ZSH}/themes/${theme:Q} ${.TARGET:Q}
.endfor

.for file in ${ZSH_DOT_FILES}
install-zsh:: ${PREFIX}/.${file:T}

${PREFIX}/.${file:T}: ${file}
	${INSTALL} -T ${file:Q} ${.TARGET:Q}
	${INSTALL_FILE} -b ${file:Q} ${.TARGET:Q}
.endfor