summaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/.gitignore3
-rw-r--r--src/lib/HACKING.md15
-rw-r--r--src/lib/Makefile45
-rw-r--r--src/lib/blacklist.sh96
l---------src/lib/blacklist.sh.3.ronn1
-rw-r--r--src/lib/common.sh.3.ronn16
-rw-r--r--src/lib/common.sh.head25
-rw-r--r--src/lib/common.sh.tail1
-rw-r--r--src/lib/conf.sh.3.ronn126
-rw-r--r--src/lib/conf.sh.in243
-rwxr-xr-xsrc/lib/libreblacklist83
-rw-r--r--src/lib/libreblacklist.1.ronn23
-rwxr-xr-xsrc/lib/librelib98
-rw-r--r--src/lib/librelib.1.ronn55
-rw-r--r--src/lib/librelib.7.ronn49
-rwxr-xr-xsrc/lib/libremessages25
-rw-r--r--src/lib/libremessages.1.ronn241
-rwxr-xr-xsrc/lib/librexgettext226
-rw-r--r--src/lib/librexgettext.1.ronn18
-rw-r--r--src/lib/messages.sh212
l---------src/lib/messages.sh.3.ronn1
21 files changed, 1602 insertions, 0 deletions
diff --git a/src/lib/.gitignore b/src/lib/.gitignore
new file mode 100644
index 0000000..650c85f
--- /dev/null
+++ b/src/lib/.gitignore
@@ -0,0 +1,3 @@
+common.sh
+common.sh.in
+conf.sh
diff --git a/src/lib/HACKING.md b/src/lib/HACKING.md
new file mode 100644
index 0000000..8bebaf6
--- /dev/null
+++ b/src/lib/HACKING.md
@@ -0,0 +1,15 @@
+Special stuff about hacking ih the /src/lib directory:
+
+ - Everything should be GPLv2 AND GPLv3 compatible. No GPLv3 only.
+ - Name a file `libre${NAME}` if it should be executable directly, or
+ `${name}.sh` if it should only be available to be sourced.
+ - When printing a message that is internal to /src/lib, and not part
+ of the programm calling the library; prefix the print command with
+ `_l`. `_l()` is defined in `common.sh` (and `librelib`, since it
+ cannot use any libraries itself).
+ - When changing the message functions, be aware that some are
+ duplicated in:
+ * /src/chroot-tools/chcleanup
+ * /src/chroot-tools/distcc-tool
+ * /src/lib/librelib
+ And that they probably need to be updated as well.
diff --git a/src/lib/Makefile b/src/lib/Makefile
new file mode 100644
index 0000000..9e9b4a8
--- /dev/null
+++ b/src/lib/Makefile
@@ -0,0 +1,45 @@
+include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk
+include $(topsrcdir)/automake.head.mk
+
+libretools-libs += common.sh conf.sh
+devtools-files = common.sh.in
+
+# Build ##############################################################
+
+$(outdir)/conf.sh: $(var)sysconfdir $(var)pkgconfdir
+
+$(outdir)/common.sh: $(outdir)/%: $(srcdir)/%.in $(srcdir)/%.head $(srcdir)/%.tail $(outdir)/Makefile
+ @echo "OUT $@"
+ @{ \
+ cat '$(<D)/$*.head' && \
+ echo && \
+ sed -r \
+ -e '/encoding problem/d;/LANG=/d' \
+ -e 's/mesg=\$$(.)/mesg="$$(_ "$$\1")"/' \
+ -e 's/gettext /_l _ /g' \
+ -e "s/^(\s+)(msg|error) '/\1_l \2 '/" \
+ -e 's|lock\(\)\s*\{|lock()\n{|' \
+ '$(<D)/$*.in' && \
+ echo && \
+ cat '$(<D)/$*.tail' && \
+ :; } > '$@'
+
+# Translate ##########################################################
+
+$(outdir)/blacklist.sh.pot: $(srcdir)/blacklist.sh $(srcdir)/librexgettext
+ @echo "OUT $@"
+ @{ \
+ sed -n '/^# Usage:/,/()/{ /^#/ { =; p; } }' $< | \
+ sed -r -e 's/^# (.*)/msgid "\1"\nmsgstr ""\n/' \
+ -e 's|^[0-9]*$$|#. embedded usage text\n#: $<:&|' && \
+ $(<D)/librexgettext --simple=_l:2 $< && \
+ :; } | $(pofmt) > $@
+$(outdir)/common.sh.pot : LIBREXGETTEXT_FLAGS += --simple=_l:2
+$(outdir)/conf.sh.pot : LIBREXGETTEXT_FLAGS += --simple=_l:2
+$(outdir)/librelib.pot : LIBREXGETTEXT_FLAGS += --simple=_l:2
+$(outdir)/messages.sh.pot : LIBREXGETTEXT_FLAGS += --simple=_l:2
+$(outdir)/librexgettext.pot: LIBREXGETTEXT_FLAGS += --simple=errusage
+
+######################################################################
+
+include $(topsrcdir)/automake.tail.mk
diff --git a/src/lib/blacklist.sh b/src/lib/blacklist.sh
new file mode 100644
index 0000000..0a3cc39
--- /dev/null
+++ b/src/lib/blacklist.sh
@@ -0,0 +1,96 @@
+#!/usr/bin/env bash
+# This may be included with or without `set -euE`
+
+# Copyright (C) 2013-2014, 2016 Luke Shumaker <lukeshu@sbcglobal.net>
+#
+# License: GNU GPLv2+
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# make sure XDG_CACHE_HOME is set
+. "$(librelib conf)"
+
+# Usage: blacklist-normalize <$file
+# Normalizes the syntax of the blacklist on stdin.
+blacklist-normalize() {
+ sed -e '/^#/d' -e 's/^[^:]*$/&::/' -e 's/^[^:]*:[^:]*$/&:/'
+}
+
+# Usage: blacklist-cat
+# Prints the blacklist.
+# Uses the cache, but downloads it if it doesn't exist. Also normalizes the blacklist for easier parsing.
+blacklist-cat() {
+ local file="$XDG_CACHE_HOME/libretools/blacklist.txt"
+ if ! [[ -e $file ]]; then
+ # exit on failure, whether set -e or not
+ blacklist-update || return $?
+ fi
+ blacklist-normalize < "$file"
+}
+
+# Usage: blacklist-update
+# Updates (or creates) the cached copy of the blacklist.
+blacklist-update() (
+ . libremessages
+ load_files libretools || return 1
+ check_vars libretools BLACKLIST || return 1
+
+ local remote_blacklist="$BLACKLIST"
+ local local_blacklist="$XDG_CACHE_HOME/libretools/blacklist.txt"
+
+ _l stat_busy "Downloading blacklist of proprietary software packages"
+
+ mkdir -p "${local_blacklist%/*}"
+ if wget -N -q -O "${local_blacklist}.part" "$remote_blacklist" 2>/dev/null; then
+ stat_done
+ mv -f "${local_blacklist}.part" "$local_blacklist"
+ else
+ stat_done
+ rm "${local_blacklist}.part"
+ if [[ -e "$local_blacklist" ]]; then
+ _l warning "Using local copy of blacklist"
+ else
+ _l error "Download failed, exiting"
+ return 1
+ fi
+
+ fi
+)
+
+# Usage: blacklist-cat | blacklist-lookup $pkgname
+# Filters to obtain the line for $pkgname from the blacklist on stdin.
+# Exits successfully whether a line is found or not.
+blacklist-lookup() {
+ local pkg=$1
+ # we accept that $pkg contains no regex-nes
+ blacklist-normalize | grep "^$pkg:" || true
+}
+
+# Usage: blacklist-cat | blacklist-get-pkg
+# Prints only the package name field of the blacklist line(s) on stdin.
+blacklist-get-pkg() {
+ blacklist-normalize | cut -d: -f1
+}
+
+# Usage: blacklist-cat | blacklist-get-rep
+# Prints only the replacement package field of the blacklist line(s) on stdin.
+blacklist-get-rep() {
+ blacklist-normalize | cut -d: -f2
+}
+
+# Usage: blacklist-cat | blacklist-get-reason
+# Prints only the reason field of the blacklist line(s) on stdin.
+blacklist-get-reason() {
+ blacklist-normalize | cut -d: -f3-
+}
diff --git a/src/lib/blacklist.sh.3.ronn b/src/lib/blacklist.sh.3.ronn
new file mode 120000
index 0000000..9001445
--- /dev/null
+++ b/src/lib/blacklist.sh.3.ronn
@@ -0,0 +1 @@
+libreblacklist.1.ronn \ No newline at end of file
diff --git a/src/lib/common.sh.3.ronn b/src/lib/common.sh.3.ronn
new file mode 100644
index 0000000..30003e0
--- /dev/null
+++ b/src/lib/common.sh.3.ronn
@@ -0,0 +1,16 @@
+common.sh -- common Bash routines from devtools
+===============================================
+
+## SYNOPSIS
+
+`. "$(librelib common)"`
+
+## DESCRIPTION
+
+In short, don't use this. Use `libremessages`(1) instead.
+`libremessages` uses this internally.
+
+## SEE ALSO
+
+ * librelib(7)
+ * libremessages(1)/messages.sh(3)
diff --git a/src/lib/common.sh.head b/src/lib/common.sh.head
new file mode 100644
index 0000000..23bfeb8
--- /dev/null
+++ b/src/lib/common.sh.head
@@ -0,0 +1,25 @@
+#!/hint/bash
+# This may be included with or without `set -euE`
+
+# This file is included by libremessages.
+# You should probably use libremessages instead of this.
+
+# License: Unspecified
+
+shopt -s extglob
+
+if [[ -z ${_INCLUDE_COMMON_SH:-} ]]; then
+_INCLUDE_COMMON_SH=true
+
+[[ -n ${TEXTDOMAIN:-} ]] || export TEXTDOMAIN='libretools'
+[[ -n ${TEXTDOMAINDIR:-} ]] || export TEXTDOMAINDIR='/usr/share/locale'
+
+if type gettext &>/dev/null; then
+ _() { gettext "$@"; }
+else
+ _() { echo "$@"; }
+fi
+
+_l() {
+ TEXTDOMAIN='librelib' TEXTDOMAINDIR='/usr/share/locale' "$@"
+}
diff --git a/src/lib/common.sh.tail b/src/lib/common.sh.tail
new file mode 100644
index 0000000..e133fad
--- /dev/null
+++ b/src/lib/common.sh.tail
@@ -0,0 +1 @@
+fi
diff --git a/src/lib/conf.sh.3.ronn b/src/lib/conf.sh.3.ronn
new file mode 100644
index 0000000..0974bdb
--- /dev/null
+++ b/src/lib/conf.sh.3.ronn
@@ -0,0 +1,126 @@
+conf.sh(3) -- easy loading of configuration files
+=================================================
+
+## SYNOPSIS
+
+`. "$(librelib conf)"`
+
+## DESCRIPTION
+
+`conf.sh` is a Bash(1) library to easily load various configuration
+files related to Arch Linux/Parabola(7) and libretools(7).
+
+### VARIABLES
+
+When loading configuration files in a program run with `sudo`(8), it
+is often desirable to load the configuration files from the home
+directory of the user who called `sudo`, instead of from /root.
+
+To accommodate this, instead of using the usual $<USER> and $<HOME>,
+`conf.sh` sets $<LIBREUSER> and $<LIBREHOME>, which it then uses.
+
+ * <LIBREUSER>:
+ If $<SUDO_USER> is set, then $<LIBREUSER> is set to
+ that. Otherwise, $<LIBREUSER> is set to the value of $<USER>.
+ * <LIBREHOME>:
+ If $<LIBREUSER> == $<USER>, then $<LIBREHOME> is set to the value
+ of $<HOME>. Otherwise, it is set to the default home directory
+ of the user $<LIBREUSER>.
+
+Further, `conf.sh` works with XDG; it sets and uses
+$<XDG_CONFIG_HOME> and $<XDG_CACHE_HOME> with the following values:
+
+ * <XDG_CONFIG_HOME>:
+ If it isn't already set, it is set to "$<LIBREHOME>/.config" and
+ exported.
+ * <XDG_CACHE_HOME>:
+ If it isn't already set, it is set to "$<LIBREHOME>/.cache" and
+ exported.
+
+Note that only the XDG_* variables are exported.
+
+### GENERIC ROUTINES
+
+The following routines take a "slug" to refer to a group of
+configuration files; that is the basename of the files. For example,
+<SLUG>='abs' will identify `/etc/abs.conf` and `$<LIBREHOME>/.abs.conf`.
+
+The routines you will likely actually use are:
+
+ * `load_files` <SLUG>:
+ Loads the configuration files for <SLUG>, loading the files in
+ the correct order, and checking the appropriate environmental
+ variables.
+ * `check_vars` <SLUG> <VARS>...:
+ Checks to see if all of <VARS> are defined. If any of them
+ aren't, it prints a message telling the user to configure them in
+ the configuration files for <SLUG>, and returns with a non-zero
+ status.
+ * `get_var` <SLUG> <VAR> <DEFAULT>:
+ If <VAR> is set in the configuration for <SLUG>, print its
+ value, considering environmental variables. If it is not set,
+ return <DEFAULT>. This does NOT work for array variables.
+ * `set_var` <SLUG> <VAR> <VALUE>:
+ Set the variable <VAR> equal to <VALUE> in the configuration file
+ for <SLUG> of highest precedence that already exists and is
+ writable. If no files fit this description, the routine does
+ nothing and returns a non-zero exit status. This does NOT work
+ for array variables.
+
+There are two more routines the above routines use internally that are
+used internally by by the above routines. You are unlikely to use
+them directly, but they might be useful for debugging, or at least
+describing behavior.
+
+ * `list_files` <SLUG>:
+ Lists (newline-separated) the configuration files that must be
+ considered for <SLUG>. Files listed later take precedence over
+ files listed earlier.
+ * `list_envvars` <SLUG>:
+ Lists (newline-separated) the environmental variables that take
+ precedence over the settings in the configuration files for
+ <SLUG>. For example, in `makepkg.conf`(8) (<SLUG>=makepkg), if the
+ <PACKAGER> environmental variable is set, the value in the
+ configuration file is ignored.
+
+### PKGBUILD ROUTINES
+
+These two routines deal with loading `PKGBUILD`(5) files.
+
+ * `unset_PKGBUILD`:
+ Unsets all variables and functions that might be set in a
+ `PKGBUILD`(5) file, including those specific to `librefetch`(8).
+ * `load_PKGBUILD` [<FILE>]:
+ "Safely" loads a PKGBUILD. Everything that it would normally set
+ is unset first, $<CARCH> is set according to `makepkg.conf`(5),
+ then the file is loaded. The file to be loaded is <FILE>, or
+ "./PKGBUILD" by default. This isn't safe, security wise, in that
+ the PKGBUILD is free to execute code.
+
+### SLUGS
+
+The differences in behavior for anything that takes a slug comes down
+to the differences in the output for `list_files` and `list_envvars`.
+
+The "known" slugs are "abs", "makepkg", "libretools", and anything
+beginning with "xbs". If anything else is given, then:
+
+ * `list_files` will give back "/etc/libretools.d/<SLUG>.conf" and
+ "$<XDG_CONFIG_HOME>/libretools/<SLUG>.conf"
+ * `list_envvars` will give back an empty list.
+
+The rules for <SLUG>=(abs|makepkg|libretools) are more easily
+expressed in code than prose, so it is recommended that you look at
+that.
+
+## BUGS
+
+`get_var` and `set_var` do not work with arrays.
+
+## SEE ALSO
+
+librelib(7)
+
+abs.conf(5), makepkg.conf(5), libretools.conf(5), PKGBUILD(5)
+
+chroot.conf(5), librefetch.conf(5)
diff --git a/src/lib/conf.sh.in b/src/lib/conf.sh.in
new file mode 100644
index 0000000..8394801
--- /dev/null
+++ b/src/lib/conf.sh.in
@@ -0,0 +1,243 @@
+#!/hint/bash
+# This may be included with or without `set -euE`
+
+# Copyright (C) 2012-2015 Luke Shumaker <lukeshu@sbcglobal.net>
+#
+# License: GNU GPLv2+
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+LIBREUSER="${SUDO_USER:-$USER}"
+if [[ $LIBREUSER == $USER ]]; then
+ LIBREHOME=$HOME
+else
+ eval "LIBREHOME=~$LIBREUSER"
+fi
+if [[ -z ${XDG_CONFIG_HOME:-} ]]; then
+ export XDG_CONFIG_HOME="${LIBREHOME}/.config"
+fi
+if [[ -z ${XDG_CACHE_HOME:-} ]]; then
+ export XDG_CACHE_HOME="${LIBREHOME}/.cache"
+fi
+
+# Low-level generic functions ##################################################
+
+# Usage: list_files $slug
+# Lists the configuration files to be considered for $slug.
+# Later files should take precedence over earlier files.
+list_files() {
+ local slug=$1
+ local sysconfdir=${_librelib_conf_sh_sysconfdir:-@sysconfdir@}
+ local pkgconfdir=${_librelib_conf_sh_pkgconfdir:-@pkgconfdir@}
+ case $slug in
+ abs)
+ echo "${sysconfdir}/$slug.conf"
+ echo "$LIBREHOME/.$slug.conf"
+ ;;
+ makepkg)
+ local manual="${MAKEPKG_CONF:-}"
+ local system="${sysconfdir}/$slug.conf"
+ local olduser="$LIBREHOME/.$slug.conf"
+ local newuser="$XDG_CONFIG_HOME/pacman/$slug.conf"
+ if [[ "$manual" != "$system" && -r "$manual" ]]; then
+ # Manually-specified file
+ echo "$manual"
+ else
+ # Normal file lookup
+ echo "$system"
+ if [[ -r "$olduser" && ! -r "$newuser" ]]; then
+ echo "$olduser"
+ else
+ echo "$newuser"
+ fi
+ fi
+ ;;
+ xbs*)
+ echo "${sysconfdir}/xbs/$slug.conf"
+ echo "$XDG_CONFIG_HOME/xbs/$slug.conf"
+ ;;
+ libretools)
+ echo "${sysconfdir}/$slug.conf"
+ echo "$XDG_CONFIG_HOME/libretools/$slug.conf"
+ ;;
+ *)
+ echo "${pkgconfdir}/$slug.conf"
+ echo "$XDG_CONFIG_HOME/libretools/$slug.conf"
+ ;;
+ esac
+}
+
+# Usage: list_envvars $slug
+# Lists the environmental variables that take precedence over the configuration
+# files for $slug.
+list_envvars() {
+ local slug=$1
+ case $slug in
+ makepkg)
+ printf '%s\n' \
+ PKGDEST SRCDEST SRCPKGDEST LOGDEST \
+ BUILDDIR \
+ PKGEXT SRCEXT \
+ GPGKEY PACKAGER \
+ CARCH
+ ;;
+ libretools)
+ printf '%s\n' DIFFPROG
+ ;;
+ xbs)
+ printf '%s\n' BUILDSYSTEM
+ ;;
+ *) :;;
+ esac
+}
+
+# High-level generic functions #################################################
+
+# Usage: load_files $slug
+# Loads the configuration files for $slug in the proper order.
+load_files() {
+ local slug=$1
+ local var
+ local file
+
+ # Save the existing versions at _VARNAME
+ for var in $(list_envvars $slug); do
+ [[ -n ${!var:-} ]] && eval "_$var=\${$var}"
+ done
+
+ # Load the files
+ for file in $(list_files $slug); do
+ if [[ -r $file ]]; then
+ . "$file" || return 1
+ fi
+ done
+
+ # Restore the _SAVED versions
+ for var in $(list_envvars $slug); do
+ eval "$var=\${_$var:-\${$var:-}}"
+ done
+}
+
+# Usage: check_vars $slug VAR1 VAR2...
+# Check whether the variables listed are properly set.
+# If not, it prints a message saying to set them in the configuration file(s)
+# for $slug.
+check_vars() (
+ local slug=$1; shift
+
+ local ret=0
+
+ local VAR
+ for VAR in "$@"; do
+ if [[ -z ${!VAR:-} ]]; then
+ type print &>/dev/null || . libremessages
+ if [[ $(list_files $slug|wc -l) -gt 1 ]]; then
+ _l print "Configure '%s' in one of:" "$VAR"
+ list_files $slug | sed 's/./ -> &/'
+ else
+ _l print "Configure '%s' in '%s'" "$VAR" "$(list_files $slug)"
+ fi
+ ret=1
+ fi
+ done >&2
+
+ if [[ $ret != 0 ]]; then
+ return 1
+ fi
+)
+
+# Usage: get_var <slug> <var_name> <default_value>
+# Does not work with arrays
+get_var() (
+ set +euE
+ local slug=$1
+ local setting=$2
+ local default=$3
+ load_files "$slug"
+ printf '%s' "${!setting:-${default}}"
+)
+
+# Usage: set_var <slug> <var_name> <value>
+# Does not work with arrays
+set_var() {
+ local slug=$1
+ local key=$2
+ local val=$3
+ local file
+ for file in $(list_files "$slug"|tac); do
+ if [[ -w $file ]]; then
+ sed -i "/^\s*$key=/d" "$file"
+ printf '%s=%q\n' "$key" "$val" >> "$file"
+ return 0
+ fi
+ done
+ return 1
+}
+
+# PKGBUILD (not configuration, per se) #########################################
+
+unset_PKGBUILD() {
+ # This routine is based primarily off of the PKGBUILD(5) man-page,
+ # version 4.2.0, dated 2014-12-31
+
+ # This is kinda weird, but everything is more readable with
+ # this as a utility function, but I didn't want to introduce a
+ # new global function, so I just introduced it with the name
+ # of a function that we get to unset anyway. So it can't
+ # clash with anything!
+ mksource() {
+ # For each arg, `unset -v` all variables matching ${arg} and ${arg}_*
+ local v
+ for v in "$@"; do
+ unset -v "$v" $(declare -p|sed -rn "s/^declare -\S+ (${v}_[a-zA-Z0-9_]*)=.*/\1/p")
+ done
+ }
+
+ # This line is taken from the makepkg source
+ local known_hash_algos=('md5' 'sha1' 'sha224' 'sha256' 'sha384' 'sha512')
+
+ # From the "OPTIONS AND DIRECTIVES" section (in order of mention)
+ unset -v pkgname pkgver
+ unset -f pkgver
+ unset -v pkgrel pkgdesc epoch url license install changelog
+
+ mksource source
+ unset -v validpgpkeys noextract
+ local sums=("${known_hash_algos[@]/%/sums}")
+ mksource "${sums[@]}"
+
+ unset -v groups arch backup
+ mksource depends makedepends checkdepends optdepends
+ mksource conflicts provides replaces
+ unset -v options
+
+ # From the "PACKAGING FUNCTIONS" section (in order of mention)
+ unset -f package prepare build check
+
+ # From the "PACKAGE SPLITTING" section
+ unset -f $(declare -f|sed -n 's/^\(package_\S*\) ()\s*$/\1/p')
+ unset -v pkgbase
+
+ # These are used by the `librefetch` program
+ unset -v mksource mknoextract "${sums[@]/#/mk}"
+ unset -v mkdepends
+ unset -f mksource
+}
+
+load_PKGBUILD() {
+ local file=${1:-./PKGBUILD}
+ unset_PKGBUILD
+ CARCH="$(get_var makepkg CARCH "`uname -m`")"
+ . "$file"
+}
diff --git a/src/lib/libreblacklist b/src/lib/libreblacklist
new file mode 100755
index 0000000..6c354fe
--- /dev/null
+++ b/src/lib/libreblacklist
@@ -0,0 +1,83 @@
+#!/usr/bin/env bash
+# Copyright (C) 2013-2014, 2016 Luke Shumaker <lukeshu@sbcglobal.net>
+#
+# License: GNU GPLv2+
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+if [[ "${0##*/}" != libreblacklist ]]; then
+ . "$(librelib blacklist)"
+else
+ set -euE
+
+ lib_file="$(librelib blacklist)"
+ . "$lib_file"
+
+ usage-outside() {
+ sed -n '/^# Usage:/,/()/p' "$lib_file" |
+ tr '\n' '\r' | sed 's/\s*()\s*[{(]/\n/g'
+ }
+ # The output format of this is:
+ # - The first line is "Usage:"
+ # - The second line is a brief description
+ # - The last line is the command name (prefixed with "blacklist-")
+ # - The in-between lines are the extended description.
+ usage-inside() {
+ sed 's/\r/\n/g'<<<"$1"|sed -e '/^$/d' -e 's/^# //'
+ }
+
+ usage() {
+ export TEXTDOMAIN='librelib'
+ export TEXTDOMAINDIR='/usr/share/locale'
+ . "$(librelib messages)"
+ if [[ $# -eq 0 ]]; then
+ print "Usage: %s [-h] COMMAND [ARGUMENTS]" "${0##*/}"
+ print "Tool for working with the nonfree software blacklist"
+ echo
+ print "Commands:"
+ usage-outside | while read -r sec; do sec="$(usage-inside "$sec")"
+ cmd=$(<<<"$sec" sed -n '$s/^blacklist-//p')
+ desc="$(_ "$(sed -n 2p <<<"$sec")")"
+ flag "$cmd" "${desc//blacklist-/${0##*/} }"
+ done
+ else
+ usage-outside | while read -r sec; do sec="$(usage-inside "$sec")"
+ cmd=$(<<<"$sec" sed -n '$s/^blacklist-//p')
+ if [[ "$cmd" == "$1" ]]; then
+ <<<"$sec" sed '$d' |
+ while read -r line; do print "$line"; done |
+ sed "s/blacklist-/${0##*/} /g" |
+ fmt -us
+ return 0
+ fi
+ done
+ fi
+ }
+
+ main() {
+ if [[ $# -eq 0 ]]; then
+ usage >&2
+ exit 1
+ fi
+ _blacklist_cmd=$1
+ shift
+ if [[ $_blacklist_cmd == -h ]]; then
+ usage "$@"
+ else
+ "blacklist-$_blacklist_cmd" "$@"
+ fi
+ }
+
+ main "$@"
+fi
diff --git a/src/lib/libreblacklist.1.ronn b/src/lib/libreblacklist.1.ronn
new file mode 100644
index 0000000..d56eb60
--- /dev/null
+++ b/src/lib/libreblacklist.1.ronn
@@ -0,0 +1,23 @@
+libreblacklist(1) -- Tools for working with the your-freedom blacklist
+======================================================================
+
+## SYNOPSIS
+
+`. "$(librelib blacklist)"`<br>
+`. libreblacklist`<br>
+`libreblacklist` [-h] <COMMAND> [<ARGS>...]
+
+## DESCRIPTION
+
+`libreblacklist` is a set of tools for working with the your-freedom
+blacklist.
+
+It may be included into a `Bash`(1) program as a library, which will
+expose it's routines as "blacklist-<COMMAND>", or it may be invoked on
+the command line as "libreblacklist <COMMAND>".
+
+See `libreblacklist -h` for more information.
+
+## SEE ALSO
+
+librelib(7)
diff --git a/src/lib/librelib b/src/lib/librelib
new file mode 100755
index 0000000..e9d184e
--- /dev/null
+++ b/src/lib/librelib
@@ -0,0 +1,98 @@
+#!/usr/bin/env bash
+# Copyright (C) 2013-2014 Luke Shumaker <lukeshu@sbcglobal.net>
+#
+# License: GNU GPLv2+
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+default_libdir=/usr/lib/libretools
+
+if type gettext &>/dev/null; then
+ _() { gettext "$@"; }
+else
+ _() { echo "$@"; }
+fi
+
+_l() {
+ TEXTDOMAIN='librelib' TEXTDOMAINDIR='/usr/share/locale' "$@"
+}
+
+print() {
+ local mesg="$(_ "$1")"
+ shift
+ printf -- "$mesg\n" "$@"
+}
+
+whitespace_collapse() {
+ tr '\n' '\r' | sed -r \
+ -e 's/\r/ /g' -e 's/\t/ /g' \
+ -e 's/(^|[^.!? ]) +/\1 /g' -e 's/([.!?]) +/\1 /g'
+}
+
+prose() {
+ local mesg="$(_ "$(whitespace_collapse <<<"$1")")"; shift
+ printf -- "$mesg" "$@" | fmt -u
+}
+
+cmd=${0##*/}
+usage() {
+ . libremessages
+ print 'Usage: . $(%s LIBRARY)' "$cmd"
+ print 'Usage: %s -h' "$cmd"
+ print "Finds a Bash library file"
+ echo
+ prose "While some libraries can be sourced just by their name because
+ they are installed in PATH (like libremessages), some are not
+ installed there (like conf.sh), so a path must be given.
+ Hardcoding that path is the way of the dark side."
+ echo
+ prose 'By default, it looks for the files in `%s`, but this can be
+ changed with the environmental variable LIBRETOOLS_LIBDIR.' "$default_libdir"
+ echo
+ print "Example usage:"
+ printf ' . $(%s conf)\n' "$cmd"
+ echo
+ print "Options:"
+ flag '-h' 'Show this message'
+}
+
+main() {
+ if [[ $# != 1 ]]; then
+ _l usage >&2
+ return 2
+ fi
+ if [[ $1 == '-h' ]]; then
+ _l usage
+ return 0;
+ fi
+
+ if [[ -z $LIBRETOOLS_LIBDIR ]]; then
+ export LIBRETOOLS_LIBDIR=$default_libdir
+ fi
+
+ lib=$1
+ lib=${lib#libre}
+ lib=${lib%.sh}
+
+ for file in ${lib} libre${lib} ${lib}.sh libre${lib}.sh; do
+ if [[ -f "$LIBRETOOLS_LIBDIR/$file" ]]; then
+ printf '%s\n' "$LIBRETOOLS_LIBDIR/$file"
+ return 0;
+ fi
+ done
+ _l print '%s: could not find library: %s' "$cmd" "$lib" >&2
+ return 1
+}
+
+main "$@"
diff --git a/src/lib/librelib.1.ronn b/src/lib/librelib.1.ronn
new file mode 100644
index 0000000..fe64e92
--- /dev/null
+++ b/src/lib/librelib.1.ronn
@@ -0,0 +1,55 @@
+librelib(1) -- finds a Bash library file
+========================================
+
+## SYNOPSIS
+
+`. "$(librelib LIBRARY)"`<br>
+`librelib -h`
+
+## DESCRIPTION
+
+`librelib` is a program to find a Bash(1) library file at run-time.
+This way, the path does not need to be hard-coded into the
+application; think of it as a sort of dynamic-linker for shell
+programs.
+
+There are several reasons for doing this, instead of hard-coding the
+path:
+
+ * The install path can change in the future without having to change
+ programs that use them.
+ * The install directory can be configured at runtime, by setting
+ `LIBRETOOLS_LIBDIR`, similar to `LD_PRELOAD` (this is used when
+ running the test suite).
+ * The naming scheme of a library can change (such as between
+ `libreNAME` and `NAME.sh` without changing programs that use it.
+
+By default, `librelib` looks in `/usr/lib/libretools`, but that can be
+changed by setting the `LIBRETOOLS_LIBDIR` environmental variable to
+the directory it should look in.
+
+When searching for a library, `librelib` first strips `libre` from the
+beginning of the name, and `.sh` from the end. This means that all of
+the following are equivalent:
+
+ . "$(librelib messages)"
+ . "$(librelib messages.sh)"
+ . "$(librelib libremessages)"
+ . "$(librelib libremessages.sh)"
+
+Once it has the 'base' name of the library it is looking for, it looks
+for a file with that 'base' name (allowing for, but not requiring
+`libre` to be prepended, or `.sh` to be appended) in whichever
+directory it is looking in.
+
+If it cannot find a suitable library file, it will print an error
+message to standard error, and exit with a code of 1.
+
+## Examples
+
+ . "$(librelib messages)"
+ . "$(librelib conf)"
+
+## SEE ALSO
+
+librelib(7)
diff --git a/src/lib/librelib.7.ronn b/src/lib/librelib.7.ronn
new file mode 100644
index 0000000..33b0c55
--- /dev/null
+++ b/src/lib/librelib.7.ronn
@@ -0,0 +1,49 @@
+librelib(7) -- Suite of Bash libraries
+======================================
+
+## SYNOPSIS
+
+Overview of the librelib Bash library suite.
+
+## DESCRIPTION
+
+There are three parts to librelib:
+
+ 1. The `librelib`(1) executable.
+ 2. The non-executable libraries installed in `/usr/lib/libretools`
+ 3. The executable libraries installed in `/usr/bin`
+
+The `librelib` executable isn't very exciting, it just finds the
+libraries installed in `/usr/lib/libretools`. Think of it as a sort
+of dynamic-linker.
+
+The 'core' of librelib are the libraries installed in
+`/usr/lib/libretools`. These are `Bash`(1) libraries that may be
+sourced in Bash programs.
+
+Some of these libraries also make sense as stand-alone programs, where
+if they are invoked directly, the first argument is the library
+routine to be executed. For example, the `messages` library may be
+included, or executed:
+
+ . "$(librelib messages)"
+ msg2 "Foo was found: %s" "$foo"
+ # or
+ libremessages msg2 "Foo was found: %s" "$foo"
+
+The `blacklist` library is similar:
+
+ . "$(librelib blacklist)"
+ blacklist-update
+ # or
+ libreblacklist update
+
+
+
+## SEE ALSO
+
+ * librelib(1)
+ * libremessages(1)/messages.sh(3)
+ * libreblacklist(1)/blacklist.sh(3)
+ * conf.sh(3)
+ * common.sh(3)
diff --git a/src/lib/libremessages b/src/lib/libremessages
new file mode 100755
index 0000000..647204a
--- /dev/null
+++ b/src/lib/libremessages
@@ -0,0 +1,25 @@
+#!/usr/bin/env bash
+# Copyright (C) 2012-2014, 2016 Luke Shumaker <lukeshu@sbcglobal.net>
+#
+# License: GNU GPLv2+
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+if [[ "${0##*/}" != libremessages ]]; then
+ . "$(librelib messages)"
+else
+ set -euE
+ . "$(librelib messages)"
+ "$@"
+fi
diff --git a/src/lib/libremessages.1.ronn b/src/lib/libremessages.1.ronn
new file mode 100644
index 0000000..d4fac85
--- /dev/null
+++ b/src/lib/libremessages.1.ronn
@@ -0,0 +1,241 @@
+libremessages(1) -- common Bash routines
+========================================
+
+## SYNOPSIS
+
+`. "$(librelib messages)"`<br>
+`. libremessages`<br>
+`libremessages` <COMMAND>
+
+## DESCRIPTION
+
+`libremessages` is a shell library containing many common routines.
+The name is a bit of a misnomer, it mostly deals with printing
+messages, but also has other things.
+
+`libremessages` uses `common.sh`(3) internally for a large portion of
+it's functionality. The authors make no promises that functionality
+that is implemented in `libremessages` won't move into `common.sh` or
+vice-versa. So, it is recommended that you use `libremessages`, not
+`common.sh`.
+
+### STAND ALONE USAGE
+
+The "normal" way to use libremessages is to source it, then call the
+provided routines.
+
+However, if you call libremessages directly, the first argument is
+taken as the function to call, and the remaining arguments are passed
+to it. The only cases where this doesn't work are the lockfile
+routines (`lock`, `slock`, and `lock_close`), because lockfiles are
+managed as file descriptors.
+
+### VARIABLES
+
+The following variables for printing terminal color codes are set:
+`ALL_OFF`, `BOLD`, `BLUE`, `GREEN`, `RED`, `YELLOW`. If standard
+error is not a terminal (see `isatty`(3)), they are set, but empty.
+They are marked as readonly, so it is an error to try to set them
+afterwords.
+
+### MESSAGE FORMAT
+
+All routines feed the message/format string through `gettext`(1), if
+it is available.
+
+The descriptions will frequently reference `printf`(1)--this isn't
+really that `printf`. The program described by the manual page
+`printf`(1) is probably the version from GNU coreutils, every time it
+is used here, it is `bash`(1)'s internal implementation; try running
+the command `help printf` from a Bash shell for more information.
+
+### GENERAL ROUTINES
+
+Unless otherwise noted, these do not implicitly call `gettext`.
+
+ * `_` <MESSAGE>:
+ If `gettext` is available, calls `gettext`, otherwise just prints
+ the arguments given.
+
+ * `in_array` <NEEDLE> <HAYSTACK>...:
+ Evaluates whether <HAYSTACK> includes <NEEDLE>; returns 0 if it
+ does, non-zero if it doesn't.
+
+ * `panic`:
+ For the times when you can't reasonably continue, similar to
+ "assert" in some programming languages.
+
+ * `setup_traps` [<HANDLER>]:
+ Sets traps on TERM, HUP, QUIT and INT signals, as sell as the ERR
+ event, similar to makepkg. If <HANDLER> is specified, instead of
+ using the default handler (which is good for most purposes), it
+ will call <HANDLER> with the arguments
+ `<HANDLER> <SIGNAL_NAME> <MESSAGE> [<MESSAGE_ARGS>...]`, where
+ <MESSAGE> is a `printf`(1)-formatted string that is fed through
+ `gettext`, and <MESSAGE_ARGS> are its arguments.
+
+ * `whitespace_collapse`:
+ Collapses whitespace on stadard I/O, similar to HTML whitespace
+ collapsing, with the exception that it puts two spaces between
+ sentences. It considers newline, tab, and space to be
+ whitespace.
+
+### PROSE ROUTINES
+
+These routines print to standard output, and are useful for printing
+word-wrapped prose.
+
+For each of these, <MESSAGE> is fed through `gettext` automatically.
+
+To generate gettext `.pot` files for programs using these routines,
+plain `xgettext`(1) won't work because it doesn't know about
+word-wrapping. Instead, you should use the `librexgettext`(1) program
+which knows how do handle word-wrapping, and knows about each of these
+routines by default.
+
+ * `prose` <MESSAGE> [<ARGS>...]:
+ Takes a `printf`(1)-formatted string, collapses whitespace
+ (HTML-style), and then word-wraps it.
+
+ * `bullet` <MESSAGE> [<ARGS>...]:
+ Similar to `prose`, but prints a bullet point before the first
+ line, and indents the remaining lines.
+
+ * `flag` [<FLAG> <DESCRIPTION>|<HEADING>:]...:
+ Print a flag and description formatted for `--help` text. For
+ example:<br>
+ `flag '-N' 'Disable networking in the chroot'`<br>
+ The description is fed through `gettext`, the flag is not, so if
+ part of the flag needs to be translated, you must do that
+ yourself:<br>
+ `flag "-C <$(_ FILE)>" 'Use this file instead of pacman.conf'`<br>
+ Newlines in the description are ignored; it is
+ whitespace-collapsed (so newlines are stripped), then it is
+ re-word-wrapped, in the same way as `prose` and `bullet`. If you
+ pass in multiple flag/description pairs to the same invocation,
+ the descriptions are all aligned together. The ability to do
+ insert headings without resetting the alignment is the motivation
+ for also allowing headings to be in the list. In order to tell
+ the difference between a flag and a heading, a heading must end
+ with a colon (':'), and a flag must not.
+
+### NOTIFICATION ROUTINES
+
+These routines print to standard error, and all take arguments in the
+same format as `printf`(1), except for `stat_done`, which doesn't take
+any arguments. Each of these print to stderr, not stdout.
+
+For each of these, <MESSAGE> is fed through `gettext` automatically.
+
+To generate gettext `.pot` files for programs using these routines,
+plain `xgettext`(1) will work if given correct flags, but you'll find
+it easier to use `librexgettext`(1), which will handle each of these
+without any extra flags.
+
+ * `plain` <MESSAGE> [<ARGS>...]:
+ Prints a "plain" message in bold, indented with 4 spaces.
+
+ * `msg` <MESSAGE> [<ARGS>...]:
+ Prints a top-level priority notification.
+
+ * `msg2` <MESSAGE> [<ARGS>...]:
+ Prints a secondary notification.
+
+ * `warning` <MESSAGE> [<ARGS>...]:
+ Prints a warning.
+
+ * `error` <MESSAGE> [<ARGS>...]:
+ Prints an error message.
+
+ * `stat_busy` <MESSAGE> [<ARGS>...]:
+ Prints a "working..." type message without a trailing newline.
+
+ * `stat_done`:
+ Prints a "done" type message to terminate `stat_busy`.
+
+ * `print` <MESSAGE> [<ARGS>...]:
+ Like `printf`(1), but `gettext`-aware, and automatically prints a
+ trailing newline.
+
+ * `term_title` <MESSAGE> [<ARGS>...]:
+ Sets the terminal title to the specified message.
+
+### TEMPORARY DIRECTORY MANAGEMENT
+
+These are used by devtools, and not used within the rest of
+libretools.
+
+They work by creating and removing the directory referred to by the
+variable $<WORKDIR>; `libretools.conf`(5) uses the same variable to
+where the user saves all their work. If you aren't careful with
+these, you could end up deleting a lot of someone's work.
+
+ * `setup_workdir`:
+ Creates a temporary directory, and sets the environmental
+ variable $<WORKDIR> to it. Also sets traps for the signals INT,
+ QUIT, TERM and HUP to run `abort`; and EXIT to run `cleanup`
+ (see `signal`(7)).
+
+ * `cleanup` [<EXIT_STATUS>]:
+ *If* `setup_workdir` has been run, `rm -rf "$WORKDIR"`. If given
+ a numeric argument, it will then call `exit`(1) with that
+ argument, otherwise it calls `exit`(1) with a status of 0.
+
+ * `abort`:
+ Calls `msg` with the message "Aborting...", then calls
+ `cleanup 255`.
+
+ * `die` <MESSAGE> [<ARGS>...]:
+ Exactly like `error`, but calls `cleanup` and calls `exit`(1)
+ with a status of 255.
+
+### LOCKFILE ROUTINES
+
+ * `lock` <FD> <LOCKFILE> <MESSAGE> [<MSG_ARGS>...]:
+ Opens (creating if necessary) the file <LOCKFILE> with file
+ descriptor <FD> in the current process, and gets an exclusive
+ lock on it. If another program already has a lock on the file,
+ and this program needs to wait for the lock to be released, then
+ it uses `stat_busy`/`stat_done` to print <MESSAGE>.
+
+ * `slock` <FD> <LOCKFILE> <MESSAGE> [<MSG_ARGS>...]:
+ Identical like `lock`, but opens a shared lock. This is also
+ known as a "read lock". Many programs can have a shared lock at
+ the same time, as long as no one has an exclusive lock on it.
+
+ * `lock_close` <FD>:
+ Closes file descriptor <FD>, releasing the lock opened on it.
+
+### MAKEPKG ROUTINES
+
+These routines relate to `makepkg`(8).
+
+ * `find_cached_package` <PKGNAME> <PKGVER>[-<PKGREL] <ARCH>:
+ Searches for a locally built copy of the specified package, in
+ <PKGDEST> and the current working directory. If <PKGREL> is not
+ specified, any value will match. If multiple matching files are
+ found (not counting duplicate links), then an error is printed to
+ stderr and nothing is printed to stdout.
+
+ * `get_full_version` [<PKGNAME>]:
+ Inspects variables that are set, and prints the full version
+ spec, including <epoch> if necessary, <pkgver>, and <pkgrel>. By
+ default, it will print the information for <pkgbase>, following
+ the normal rules for finding <pkgbase>. If <PKGNAME> is given,
+ it will print the information for that sub-package. The versions
+ for different parts of a split package don't have to be the same!
+
+## BUGS
+
+`term_title` currently only knows about the terminals screen, tmux,
+xterm and rxvt (and their various <TERM> values;
+"rxvt-unicode-256color" is still rxvt).
+
+## SEE ALSO
+
+librexgettext(1), librelib(7), gettext(1), common.sh(3)
+
+Things that were mentioned:
+
+bash(1), xgettext(1), exit(1), isatty(3), libretools.conf(5),
+makepkg(8), printf(1), signal(7)
diff --git a/src/lib/librexgettext b/src/lib/librexgettext
new file mode 100755
index 0000000..db575d6
--- /dev/null
+++ b/src/lib/librexgettext
@@ -0,0 +1,226 @@
+#!/usr/bin/env bash
+# Copyright (C) 2013-2016 Luke Shumaker <lukeshu@sbcglobal.net>
+#
+# License: GNU GPLv2+
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+export TEXTDOMAIN='librelib'
+export TEXTDOMAINDIR='/usr/share/locale'
+
+default_simple=(
+ --keyword={eval_,}{gettext,'ngettext:1,2'}
+ --keyword={_,print,term_title}
+ --keyword={msg,msg2,warning,error,stat_busy,die}
+ --keyword={lock,slock}:3
+)
+default_prose=(--keyword={prose,bullet})
+
+readonly default_simple default_prose
+
+if ! type gettext &>/dev/null; then
+ gettext() { echo "$@"; }
+fi
+
+errusage() {
+ if [[ $# -gt 0 ]]; then
+ fmt="$(gettext "$1")"; shift
+ printf "${0##*/}: $fmt\n" "$@"
+ fi
+ usage >&2
+}
+
+usage() {
+ . libremessages
+ print 'Usage: %s [OPTIONS] FILES...' "${0##*/}"
+ print 'Generates .pot files for programs using libremessages'
+ echo
+ prose 'In librexgettext, there are 2 types of keywords:'
+ bullet 'simple: Simple keywords are just like normal xgettext'
+ bullet 'prose: Prose keywords are similar, but the text is
+ word-wrapped'
+ prose 'The keyword format is the same as in GNU xgettext.'
+ echo
+ prose 'The libremessages `flag` command is also handled
+ specially, and is not configurable as a keyword.'
+ echo
+ prose 'The default simple keywords are: %s' "${default_simple[*]#--keyword=}"
+ echo
+ prose 'The default prose keywords are: %s' "${default_prose[*]#--keyword=}"
+ echo
+ print 'Options:'
+ flag \
+ '--simple=KEYWORD' 'Look for KEYWORD as an additional simple keyword' \
+ '--prose=KEYWORD' 'Look for KEYWORD as an additional prose keyword' \
+ '-k' 'Disable using the default keywords' \
+ '-h, --help' 'Show this text'
+}
+
+xgettext-sh() {
+ xgettext --omit-header --from-code=UTF-8 -L shell -k -o - "$@"
+}
+
+xgettext-flag() {
+ {
+ # Stage 1: Generate
+ #
+ # Get all of the arguments to `flag`. Because `flag`
+ # takes an arbitrary number of arguments, just iterate
+ # through arg1, arg2, ... argN; until we've come up
+ # empty 3 times. Why 3? Because each flag takes 2
+ # arguments, and because we don't keep track of which
+ # one of those we're on, waiting for 3 empties ensures
+ # us that we've had a complete "round" with nothing.
+ #
+ # Why can't I just do i+=2, and not have to keep track
+ # of empties? Because, we also allow for arguments
+ # ending in a colon to be headings, which changes the
+ # offsets.
+ declare -i empties=0
+ declare -i i
+ for (( i=1; empties < 3; i++ )); do
+ local out
+ out="$(xgettext-sh --keyword="flag:$i,\"$i\"" "$@")"
+ if [[ -n $out ]]; then
+ printf -- '%s\n' "$out"
+ else
+ empties+=1
+ fi
+ done
+ } | whitespace-collapse | sed '/^\#, sh-format/d' | {
+ # Stage 2: Parse
+ #
+ # Read in the lines, and group them into an array of
+ # (multi-line) msgs. This just makes working with
+ # them easier.
+ local msgs=()
+ declare -i i=-1
+ local re='^#\. ([0-9]+)$'
+ IFS=''
+ local line
+ while read -r line; do
+ if [[ $line =~ $re ]]; then
+ i+=1
+ fi
+ msgs[$i]+="$line"$'\n'
+ done
+ # Stage 3: Sort
+ #
+ # Now, we have the `msgs` array, and it is
+ # sorted such that it is all of the arg1's to `flag`,
+ # then all of the arg2's, then all of the arg3's, and
+ # so on. We want to re-order them such that it's all
+ # of the args for the first invocation then all of the
+ # args for the second; and so on.
+ #
+ # We do this by simply sorting them by the location
+ # that they appear in the file. Then, when we see the
+ # argument number go back down to 1, we know that a
+ # new invocation has started!
+ IFS=$'\n'
+ local locations=($(
+ local i
+ for i in "${!msgs[@]}"; do
+ declare -i arg row
+ local lines=(${msgs[$i]})
+ arg=${lines[0]#'#. '}
+ row=${lines[1]##*:}
+ printf '%d.%d %d\n' "$row" "$arg" "$i"
+ done | sort -n
+ ))
+ # Stage 4: Output
+ #
+ # Now, we prune out the arguments that aren't
+ # localizable. Also, remove the "#." comment lines.
+ # As explained above (in stage 3), when we see $arg go
+ # to 1, that's the beginning of a new invocation.
+ local expectflag=true
+ local location
+ for location in "${locations[@]}"; do
+ IFS=' .'
+ local row arg i
+ read -r row arg i <<<"$location"
+ local msg="${msgs[$i]#*$'\n'}"
+ # Now we operate based on $row, $arg, and $msg
+ if [[ $arg == 1 ]]; then
+ expectflag=true
+ fi
+ if $expectflag; then
+ IFS=$'\n'
+ local lines=(${msg})
+ if [[ ${lines[1]} == *':"' ]]; then
+ # We expected a flag, but got
+ # a heading
+ printf -- '%s\n' "$msg"
+ else
+ # We expected a flag, and got
+ # one!
+ expectflag=false
+ fi
+ else
+ printf -- '%s\n' "$msg"
+ expectflag=true
+ fi
+ done
+ }
+}
+
+whitespace-collapse() {
+ tr '\n' '\r' | sed 's/"\r\s*"//g' | tr '\r' '\n' | # This removes the awkward word-wrapping done by xgettext
+ sed -r -e 's/(\\n|\\t|\t)/ /g' -e 's/(^|[^.!? ]) +/\1 /g' -e 's/([.!?]) +/\1 /g' # This collapses whitespace
+}
+
+main() {
+ local simple=()
+ local prose=()
+ local files=()
+ local use_defaults=true
+ local error=false
+
+ declare -i i
+ for (( i=1; i <= $#; i++ )); do
+ case "${!i}" in
+ --simple) i+=1; simple+=(--keyword="${!i}");;
+ --simple=*) simple+=(--keyword="${!i#*=}");;
+ --prose) i+=1; prose+=(--keyword="${!i}");;
+ --prose=*) prose+=(--keyword="${!i#*=}");;
+ -k) use_defaults=false;;
+ --help|-h) usage; return 0;;
+ --) i+=1; break;;
+ -*) errusage "unrecognized option: %s" "${!i}"; error=true;;
+ *) files+=("${!i}");;
+ esac
+ done
+ files+=("${@:$i}")
+ if [[ ${#files[@]} -lt 1 ]]; then
+ errusage "no input file given"
+ error=true
+ fi
+ if "$error"; then
+ return 1
+ fi
+ if "$use_defaults"; then
+ simple+=("${default_simple[@]}")
+ prose+=("${default_prose[@]}")
+ fi
+
+ # Main code
+ {
+ xgettext-sh "${simple[@]}" -- "${files[@]}"
+ xgettext-sh "${prose[@]}" -- "${files[@]}" | whitespace-collapse
+ xgettext-flag -- "${files[@]}"
+ } | sed '/^\#, sh-format/d' | msguniq -Fi --to-code=UTF-8
+}
+
+main "$@"
diff --git a/src/lib/librexgettext.1.ronn b/src/lib/librexgettext.1.ronn
new file mode 100644
index 0000000..de6abde
--- /dev/null
+++ b/src/lib/librexgettext.1.ronn
@@ -0,0 +1,18 @@
+librexgettext(1) -- Extrct libremessages gettext strings from sources
+=====================================================================
+
+## SYNOPSIS
+
+`librexgettext` [OPTIONS] FILES<br>
+`librexgettext` [-h|--help]
+
+## DESCRIPTION
+
+`librexgettext` is a tool for extracting gettext strings from programs
+using the fancy word-wrapping utilities of `libremessages`(1).
+
+See `librexgettext --help` for more information.
+
+## SEE ALSO
+
+libremessages(1)
diff --git a/src/lib/messages.sh b/src/lib/messages.sh
new file mode 100644
index 0000000..0125003
--- /dev/null
+++ b/src/lib/messages.sh
@@ -0,0 +1,212 @@
+#!/usr/bin/env bash
+# This may be included with or without `set -euE`
+
+# Copyright (C) 2011 Joshua Ismael Haase Hernández (xihh) <hahj87@gmail.com>
+# Copyright (C) 2012 Nicolás Reynolds <fauno@parabola.nu>
+# Copyright (C) 2012-2014, 2016 Luke Shumaker <lukeshu@sbcglobal.net>
+#
+# For just the setup_traps() function:
+# Copyright (C) 2002-2006 Judd Vinet <jvinet@zeroflux.org>
+# Copyright (C) 2006-2010 Pacman Development Team <pacman-dev@archlinux.org>
+# Copyright (C) 2005 Aurelien Foret <orelien@chez.com>
+# Copyright (C) 2005 Christian Hamar <krics@linuxforum.hu>
+# Copyright (C) 2006 Alex Smith <alex@alex-smith.me.uk>
+# Copyright (C) 2006 Andras Voroskoi <voroskoi@frugalware.org>
+# Copyright (C) 2006 Miklos Vajna <vmiklos@frugalware.org>
+#
+# License: GNU GPLv2+
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+################################################################################
+# Inherit most functions from devtools #
+################################################################################
+
+. "$(librelib common.sh)"
+
+################################################################################
+# Own functions #
+################################################################################
+
+# Usage: panic
+#
+# For programming errors, bails immediately with little fanfare.
+panic() {
+ echo "$(_l _ 'panic: malformed call to internal function')" >&2
+ exit 1
+}
+
+# Usage: print MESG [ARGS...]
+#
+# Like printf, but gettext-aware, and prints a trailing newline
+print() {
+ [[ $# -ge 1 ]] || panic
+ local mesg="$(_ "$1")"
+ shift
+ printf -- "$mesg\n" "$@"
+}
+
+# Usage: whitespace_collapse <<<STRING
+#
+# Collapses whitespace on stadard I/O, similar to HTML whitespace
+# collapsing, with the exception that it puts two spaces between
+# sentences. It considers newline, tab, and space to be whitespace.
+whitespace_collapse() {
+ [[ $# == 0 ]] || panic
+
+ tr '\n' '\r' | sed -r \
+ -e 's/\r/ /g' -e 's/\t/ /g' \
+ -e 's/(^|[^.!? ]) +/\1 /g' -e 's/([.!?]) +/\1 /g'
+}
+
+
+# Usage: prose MESG [ARGS...]
+#
+# Do HTML-style whitespace collapsing on the first argument, translate
+# it (gettext), then word-wrap it to 75 columns. This is useful for
+# printing a paragraph of prose in --help text.
+prose() {
+ [[ $# -ge 1 ]] || panic
+ local mesg="$(_ "$(whitespace_collapse <<<"$1")")"; shift
+ printf -- "$mesg" "$@" | fmt -u
+}
+
+# Usage: bullet MESG [ARGS...]
+# Like prose, but print a bullet "-" before the first line, and indent the
+# remaining lines.
+bullet() {
+ [[ $# -ge 1 ]] || panic
+ local mesg="$(_ "$(whitespace_collapse <<<"$1")")"; shift
+ # Wrap the text to 71 columns; 75 (the default) minus a 4 column indent
+ printf -- "$mesg" "$@" | fmt -u -w 71 | sed -e '1s/^/ - /' -e '2,$s/^/ /'
+}
+
+# Usage: flag [FLAG DESCRIPTION|HEADING:]...
+#
+# Print a flag and description formatted for --help text.
+#
+# ex: flag '-C <FILE>' 'Use this file instead of pacman.conf'
+#
+# The descriptions and headings are fed through gettext, the flags ar
+# not, so if part of a flag needs to be translated, you must do that
+# yourself:
+#
+# ex: flag "-C <$(_ FILE)>" 'Use this file instead of pacman.conf'
+#
+# If you want to line-break the description in the source, so it isn't
+# crazy-long, feel free, it is reflowed/wrapped the same way as prose
+# and bullet. If you pass in multiple flag/description pairs at once,
+# the descriptions are all alligned together.
+#
+# A heading MUST end with a colon (':'), this is how it knows that it
+# is a heading. Similarly, a flag MUST NOT end with a colon.
+flag() {
+ local args=("$@")
+
+ declare -i flaglen=0
+ while [[ $# -gt 0 ]]; do
+ if [[ $1 == *: ]]; then
+ shift 1
+ else
+ if [[ ${#1} -gt $flaglen ]]; then
+ flaglen=${#1}
+ fi
+ shift 2
+ fi
+ done
+ set -- "${args[@]}"
+
+ # Unless the $flaglen is extra-wide, the $desc should start at
+ # column 16 (that is two literal-tabs). If $flaglen is wide,
+ # this should be increased in increments of 8 (that is, a
+ # literal-tab). Everything should be wrapped to 75 columns.
+
+ # The printf-format we use has 4 spaces built into it (two at
+ # the beginning, and two for a seperator). Therefore, the
+ # default indent is 16-4=12 columns. And the width left for
+ # $desc is (75-4)-indent = 71-indent.
+
+ declare -i indent=12
+ while [[ $indent -lt $flaglen ]]; do
+ indent+=8
+ done
+ local fmt2 fmt1
+ fmt2=" %-${indent}s %s\n"
+ printf -v fmt1 " %-${indent}s %%s\n" ''
+
+ while [[ $# -gt 0 ]]; do
+ if [[ $1 == *: ]]; then
+ printf -- ' %s\n' "$(_ "$1")"
+ shift
+ else
+ [[ $# -gt 1 ]] || panic
+ local flag=$1
+ local desc="$(_ "$(whitespace_collapse <<<"$2")")"
+ shift 2
+
+ local lines
+ IFS=$'\n' lines=($(fmt -u -w $((71-indent)) <<<"$desc"))
+ printf -- "$fmt2" "$flag" "${lines[0]}"
+ [[ ${#lines[@]} -lt 2 ]] || printf -- "$fmt1" "${lines[@]:1}"
+ fi
+ done
+}
+
+# Usage: term_title MESG [ARGS...]
+#
+# Sets the terminal title.
+term_title() {
+ [[ $# -ge 1 ]] || panic
+ local fmt=''
+ case "$TERM" in
+ screen|tmux) fmt='\ek%s\e\\';;
+ xterm*|rxvt*) fmt='\e]0;%s\a';;
+ esac
+ printf "$fmt" "$(printf -- "$@")"
+}
+
+# Usage: setup_traps [handler]
+#
+# Sets up traps on TERM, HUP, QUIT and INT signals, as well as the ERR
+# event, similar to makepkg.
+#
+# If `handler` is specified, instead of using the default handler
+# (which is good for most purposes), it will call the command handler
+# with the arguments:
+#
+# ${handler} SIGNAL_NAME MESSAGE_FMT [MESSAGE_PARAMS...]
+#
+# where MESSAGE_* are printf-like stuff.
+#
+# This function is based on code from pacman:makepkg
+setup_traps() {
+ [[ $# -le 1 ]] || panic
+ if [[ $# == 1 ]]; then
+ eval "_libremessages_trap_exit() { $1 \"\$@\"; }"
+ else
+ _libremessages_trap_exit() {
+ local signal=$1; shift
+ echo
+ error "$@"
+ trap -- "$signal"
+ kill "-$signal" "$$"
+ }
+ fi
+ set -E
+ for signal in TERM HUP QUIT; do
+ trap "_libremessages_trap_exit $signal '%s signal caught. Exiting...' $signal" $signal
+ done
+ trap '_libremessages_trap_exit INT "Aborted by user! Exiting..."' INT
+ trap '_libremessages_trap_exit USR1 "An unknown error has occurred. Exiting..."' ERR
+}
diff --git a/src/lib/messages.sh.3.ronn b/src/lib/messages.sh.3.ronn
new file mode 120000
index 0000000..391ecbd
--- /dev/null
+++ b/src/lib/messages.sh.3.ronn
@@ -0,0 +1 @@
+libremessages.1.ronn \ No newline at end of file