summaryrefslogtreecommitdiff
path: root/devel
diff options
context:
space:
mode:
authorjmmv <jmmv>2012-08-15 21:18:13 +0000
committerjmmv <jmmv>2012-08-15 21:18:13 +0000
commit20696293939591867ec3aa1686107a170286ba28 (patch)
treec38f41c3d799ce17653e6ec8ba7b4046c3307566 /devel
parent91824f572ead5bac0c01368b15017bbda4a23121 (diff)
downloadpkgsrc-20696293939591867ec3aa1686107a170286ba28.tar.gz
Initial addition of shtk 1.0.
Despite its pretentious name, this package is just an attempt to generalize a bunch of code that I keep reusing every time I implement shell scripts. In particular, this is about to remove tons of duplicate stuff from both sysbuild and sysupgrade in subsequent commits and I'll probably use it again for some other stuff I have in mind. Description follows: The Shell Toolkit, or shtk for short, is a collection of modules written in sh(1) that provide common functionality to simplify the implementation of complex shell scripts. These modules provide things like utilities to manipulate data types, helpers to expose a common CLI, or higher-level abstractions such as the processing of configuration files. The included shtk(1) utility exposes convenience functionality to let the user "build" shell scripts that use shtk. Build, in this case, just means adding common boilerplate code to the initialization of the script to load the shtk common code.
Diffstat (limited to 'devel')
-rw-r--r--devel/shtk/DESCR10
-rw-r--r--devel/shtk/Makefile76
-rw-r--r--devel/shtk/PLIST18
-rw-r--r--devel/shtk/buildlink3.mk12
-rw-r--r--devel/shtk/files/Kyuafile11
-rw-r--r--devel/shtk/files/base.subr63
-rw-r--r--devel/shtk/files/base_test.sh86
-rw-r--r--devel/shtk/files/bootstrap.subr48
-rw-r--r--devel/shtk/files/cli.subr94
-rw-r--r--devel/shtk/files/cli_test.sh103
-rw-r--r--devel/shtk/files/config.subr301
-rw-r--r--devel/shtk/files/config_test.sh424
-rw-r--r--devel/shtk/files/cvs.subr111
-rw-r--r--devel/shtk/files/cvs_test.sh184
-rw-r--r--devel/shtk/files/list.subr68
-rw-r--r--devel/shtk/files/list_test.sh73
-rw-r--r--devel/shtk/files/process.subr50
-rw-r--r--devel/shtk/files/process_test.sh87
-rw-r--r--devel/shtk/files/shtk.1148
-rw-r--r--devel/shtk/files/shtk.sh176
-rw-r--r--devel/shtk/files/shtk_test.sh216
21 files changed, 2359 insertions, 0 deletions
diff --git a/devel/shtk/DESCR b/devel/shtk/DESCR
new file mode 100644
index 00000000000..53866c46ea3
--- /dev/null
+++ b/devel/shtk/DESCR
@@ -0,0 +1,10 @@
+The Shell Toolkit, or shtk for short, is a collection of modules written in
+sh(1) that provide common functionality to simplify the implementation of
+complex shell scripts. These modules provide things like utilities to
+manipulate data types, helpers to expose a common CLI, or higher-level
+abstractions such as the processing of configuration files.
+
+The included shtk(1) utility exposes convenience functionality to let the
+user "build" shell scripts that use shtk. Build, in this case, just means
+adding common boilerplate code to the initialization of the script to load
+the shtk common code.
diff --git a/devel/shtk/Makefile b/devel/shtk/Makefile
new file mode 100644
index 00000000000..4880dd4459e
--- /dev/null
+++ b/devel/shtk/Makefile
@@ -0,0 +1,76 @@
+# $NetBSD: Makefile,v 1.1 2012/08/15 21:18:13 jmmv Exp $
+
+DISTNAME= shtk-1.0
+CATEGORIES= devel
+MASTER_SITES= # empty
+DISTFILES= # empty
+
+MAINTAINER= jmmv@NetBSD.org
+COMMENT= Shell-scripting modules that provide common functionality
+LICENSE= modified-bsd
+
+PKG_INSTALLATION_TYPES= overwrite pkgviews
+PKG_DESTDIR_SUPPORT= user-destdir
+
+WRKSRC= ${WRKDIR}
+NO_CONFIGURE= YES
+
+BUILD_SUBST+= -e 's,@SHTK_MODULESDIR@,${PREFIX}/share/shtk,g'
+BUILD_SUBST+= -e 's,@SHTK_SHELL@,${SH},g'
+BUILD_SUBST+= -e 's,@SHTK_VERSION@,${PKGVERSION},g'
+
+PKG_OPTIONS_VAR= PKG_OPTIONS.shtk
+PKG_SUPPORTED_OPTIONS= tests
+PKG_SUGGESTED_OPTIONS= tests
+
+.include "../../mk/bsd.options.mk"
+
+.if $(PKG_OPTIONS:Mtests)
+TEST_PROGS= base_test cli_test config_test cvs_test list_test \
+ process_test shtk_test
+
+PLIST_SUBST+= TESTS=
+. include "../../devel/atf-libs/buildlink3.mk"
+
+do-build: build-tests
+build-tests: build-shtk
+ cp ${FILESDIR}/Kyuafile ${WRKSRC}
+.for file in ${TEST_PROGS}
+ SHTK_MODULESDIR=${FILESDIR} ${WRKSRC}/shtk build \
+ -s ${BUILDLINK_PREFIX.atf-libs}/bin/atf-sh \
+ -m '' -o ${WRKSRC}/${file} ${FILESDIR}/${file}.sh
+.endfor
+
+INSTALLATION_DIRS+= tests/shtk
+
+do-install: install-tests
+install-tests:
+ ${INSTALL_DATA} ${WRKSRC}/Kyuafile ${DESTDIR}${PREFIX}/tests/shtk
+.for file in ${TEST_PROGS}
+ ${INSTALL_SCRIPT} ${WRKSRC}/${file} ${DESTDIR}${PREFIX}/tests/shtk/
+.endfor
+
+do-test:
+ cd ${WRKSRC} && PATH="${WRKSRC}:${PATH}" SHTK_MODULESDIR="${FILESDIR}" \
+ kyua test
+.else
+PLIST_SUBST+= TESTS=@comment
+.endif
+
+do-build: build-shtk
+build-shtk:
+ ${SED} ${BUILD_SUBST} ${FILESDIR}/shtk.sh >${WRKSRC}/shtk
+ ${CHMOD} +x ${WRKSRC}/shtk
+ ${SED} ${BUILD_SUBST} ${FILESDIR}/shtk.1 >${WRKSRC}/shtk.1
+
+INSTALLATION_DIRS+= bin ${PKGMANDIR}/man1 share/shtk
+
+do-install: install-shtk
+install-shtk:
+ ${INSTALL_SCRIPT} ${WRKSRC}/shtk ${DESTDIR}${PREFIX}/bin/
+ ${INSTALL_MAN} ${WRKSRC}/shtk.1 ${DESTDIR}${PREFIX}/${PKGMANDIR}/man1
+.for module in base bootstrap cli config cvs list process
+ ${INSTALL_DATA} ${FILESDIR}/${module}.subr ${DESTDIR}${PREFIX}/share/shtk
+.endfor
+
+.include "../../mk/bsd.pkg.mk"
diff --git a/devel/shtk/PLIST b/devel/shtk/PLIST
new file mode 100644
index 00000000000..a48c158c8a3
--- /dev/null
+++ b/devel/shtk/PLIST
@@ -0,0 +1,18 @@
+@comment $NetBSD: PLIST,v 1.1 2012/08/15 21:18:13 jmmv Exp $
+bin/shtk
+man/man1/shtk.1
+share/shtk/base.subr
+share/shtk/bootstrap.subr
+share/shtk/cli.subr
+share/shtk/config.subr
+share/shtk/cvs.subr
+share/shtk/list.subr
+share/shtk/process.subr
+${TESTS}tests/shtk/Kyuafile
+${TESTS}tests/shtk/base_test
+${TESTS}tests/shtk/cli_test
+${TESTS}tests/shtk/config_test
+${TESTS}tests/shtk/cvs_test
+${TESTS}tests/shtk/list_test
+${TESTS}tests/shtk/process_test
+${TESTS}tests/shtk/shtk_test
diff --git a/devel/shtk/buildlink3.mk b/devel/shtk/buildlink3.mk
new file mode 100644
index 00000000000..a50216a5a57
--- /dev/null
+++ b/devel/shtk/buildlink3.mk
@@ -0,0 +1,12 @@
+# $NetBSD: buildlink3.mk,v 1.1 2012/08/15 21:18:13 jmmv Exp $
+
+BUILDLINK_TREE+= shtk
+
+.if !defined(SHTK_BUILDLINK3_MK)
+SHTK_BUILDLINK3_MK:=
+
+BUILDLINK_API_DEPENDS.shtk+= shtk>=1.0
+BUILDLINK_PKGSRCDIR.shtk?= ../../devel/shtk
+.endif # SHTK_BUILDLINK3_MK
+
+BUILDLINK_TREE+= -shtk
diff --git a/devel/shtk/files/Kyuafile b/devel/shtk/files/Kyuafile
new file mode 100644
index 00000000000..72c7c0edb97
--- /dev/null
+++ b/devel/shtk/files/Kyuafile
@@ -0,0 +1,11 @@
+syntax("kyuafile", 1)
+
+test_suite("shtk")
+
+atf_test_program{name="base_test"}
+atf_test_program{name="cli_test"}
+atf_test_program{name="config_test"}
+atf_test_program{name="cvs_test"}
+atf_test_program{name="list_test"}
+atf_test_program{name="process_test"}
+atf_test_program{name="shtk_test"}
diff --git a/devel/shtk/files/base.subr b/devel/shtk/files/base.subr
new file mode 100644
index 00000000000..1a7df0c52a8
--- /dev/null
+++ b/devel/shtk/files/base.subr
@@ -0,0 +1,63 @@
+# Copyright 2012 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# * Neither the name of Google Inc. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+# \file bootstrap.subr
+# Fundamental stuff that gets loaded unconditionally.
+#
+# Be aware that this code runs very early in the script. Therefore, we
+# cannot assume that any of the other shtk libraries have been loaded (and
+# we cannot attempt to load them). The only thing we can assume is that
+# bootstrap.subr has been loaded and that we are being called by that piece
+# of code.
+
+
+# List of already-loaded modules.
+_Shtk_LoadedModules=
+
+
+# Loads a new module.
+#
+# If a module has already been loaded, this has no effect.
+#
+# \param module The name of the shtk module to load.
+shtk_import() {
+ local module="${1}"; shift
+
+ for loaded_module in ${_Shtk_LoadedModules}; do
+ [ "${loaded_module}" != "${module}" ] || return 0
+ done
+
+ local file="${SHTK_MODULESDIR}/${module}.subr"
+ if [ -f "${file}" ]; then
+ . "${file}"
+ _Shtk_LoadedModules="${Shtk_LoadedModules} ${module}"
+ else
+ echo "${0##*/}: E: Cannot load module ${module}; tried ${file}" 1>&2
+ exit 1
+ fi
+}
diff --git a/devel/shtk/files/base_test.sh b/devel/shtk/files/base_test.sh
new file mode 100644
index 00000000000..8c5748b8d5d
--- /dev/null
+++ b/devel/shtk/files/base_test.sh
@@ -0,0 +1,86 @@
+# Copyright 2012 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# * Neither the name of Google Inc. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+# No imports: we can assume the bootstrap code to be present.
+
+
+# Creates a "mock" module for shtk_import.
+#
+# \post SHTK_MODULESDIR is adjusted to point to our test directory.
+create_mock_module() {
+ mkdir -p modules
+ cat >modules/mock.subr <<EOF
+if [ -z "\${mock_value}" ]; then
+ mock_value=1
+else
+ mock_value="\$((\${mock_value} + 1))"
+fi
+EOF
+ SHTK_MODULESDIR="$(pwd)/modules"
+}
+
+
+atf_test_case import__ok
+import__ok_body() {
+ create_mock_module
+ [ -z "${mock_value}" ] || atf_fail "mock_value already defined"
+ shtk_import mock
+ atf_check_equal 1 "${mock_value}"
+}
+
+
+atf_test_case import__idempotent
+import__idempotent_body() {
+ create_mock_module
+ [ -z "${mock_value}" ] || atf_fail "mock_value already defined"
+ shtk_import mock
+ atf_check_equal 1 "${mock_value}"
+ shtk_import mock
+ atf_check_equal 1 "${mock_value}"
+}
+
+
+atf_test_case import__not_found
+import__not_found_body() {
+ SHTK_MODULESDIR=$(pwd)
+ if ( shtk_import abcde ) >out 2>err; then
+ atf_fail "import of a non-existent module succeeded"
+ else
+ cat >experr <<EOF
+base_test: E: Cannot load module abcde; tried $(pwd)/abcde.subr
+EOF
+ atf_check -o file:experr cat err
+ fi
+}
+
+
+atf_init_test_cases() {
+ atf_add_test_case import__ok
+ atf_add_test_case import__idempotent
+ atf_add_test_case import__not_found
+}
diff --git a/devel/shtk/files/bootstrap.subr b/devel/shtk/files/bootstrap.subr
new file mode 100644
index 00000000000..2b038702385
--- /dev/null
+++ b/devel/shtk/files/bootstrap.subr
@@ -0,0 +1,48 @@
+#! __SHTK_SH__
+# Copyright 2012 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# * Neither the name of Google Inc. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+# \file bootstrap.sh
+# Script initialization code.
+#
+# The contents of this file get prepended to all the scripts generated by
+# "shtk build". As such, the contents in here should be as simple as
+# possible, should be able to detect problems during initialization and
+# cannot depend on other shtk code.
+
+set -e
+
+: ${SHTK_MODULESDIR:=__SHTK_MODULESDIR__}
+
+if [ ! -f "${SHTK_MODULESDIR}/base.subr" ]; then
+ echo "${0##*/}: E: Cannot open ${SHTK_MODULESDIR}/base.subr" 1>&2
+ echo "${0##*/}: E: Does SHTK_MODULESDIR point to the right location?" 1>&2
+ exit 1
+fi
+
+. "${SHTK_MODULESDIR}/base.subr"
diff --git a/devel/shtk/files/cli.subr b/devel/shtk/files/cli.subr
new file mode 100644
index 00000000000..1f58f78079d
--- /dev/null
+++ b/devel/shtk/files/cli.subr
@@ -0,0 +1,94 @@
+# Copyright 2012 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# * Neither the name of Google Inc. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+# \file cli.subr
+# Utilities to implement command-line interfaces.
+
+
+# Directory where the running script lives.
+_Shtk_Cli_DirName="$(dirname "${0}")"
+
+
+# Base name of the running script.
+_Shtk_Cli_ProgName="${0##*/}"
+
+
+# Gets the directory where the current program lives.
+#
+# \post The directory is printed to stdout.
+shtk_cli_dirname() {
+ echo "${_Shtk_Cli_DirName}"
+}
+
+
+# Gets the program name.
+#
+# \post The program name, without any directory components, is printed to
+# stdout.
+shtk_cli_progname() {
+ echo "${_Shtk_Cli_ProgName}"
+}
+
+
+# Prints a runtime error and exits.
+#
+# \param ... The message to print. Can be provided as multiple words and, in
+# that case, they are joined together by a single whitespace.
+shtk_cli_error() {
+ echo "${_Shtk_Cli_ProgName}: E: $*" 1>&2
+ exit 1
+}
+
+
+# Prints an informational message.
+#
+# \param ... The message to print. Can be provided as multiple words and, in
+# that case, they are joined together by a single whitespace.
+shtk_cli_info() {
+ echo "${_Shtk_Cli_ProgName}: I: $*" 1>&2
+}
+
+
+# Prints a runtime warning.
+#
+# \param ... The message to print. Can be provided as multiple words and, in
+# that case, they are joined together by a single whitespace.
+shtk_cli_warning() {
+ echo "${_Shtk_Cli_ProgName}: W: $*" 1>&2
+}
+
+
+# Prints an usage error and exits.
+#
+# \param ... The message to print. Can be provided as multiple words and, in
+# that case, they are joined together by a single whitespace.
+shtk_cli_usage_error() {
+ echo "${_Shtk_Cli_ProgName}: E: $*" 1>&2
+ echo "Type 'man ${_Shtk_Cli_ProgName}' for help" 1>&2
+ exit 1
+}
diff --git a/devel/shtk/files/cli_test.sh b/devel/shtk/files/cli_test.sh
new file mode 100644
index 00000000000..6d67b4b8037
--- /dev/null
+++ b/devel/shtk/files/cli_test.sh
@@ -0,0 +1,103 @@
+# Copyright 2012 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# * Neither the name of Google Inc. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+shtk_import cli
+
+
+# Saves the original value of argv[0] for testing purposes.
+_Original_Arg0="${0}"
+
+
+atf_test_case dirname
+dirname_body() {
+ atf_check_equal "$(dirname "${_Original_Arg0}")" "$(shtk_cli_dirname)"
+}
+
+
+atf_test_case progname
+progname_body() {
+ atf_check_equal "$(basename "${_Original_Arg0}")" "$(shtk_cli_progname)"
+}
+
+
+atf_test_case error
+error_body() {
+ if ( shtk_cli_error "This is" "a message"; echo "not seen" ) >out 2>err
+ then
+ atf_fail "shtk_cli_error did not exit with an error"
+ else
+ grep "cli_test: E: This is a message" err >/dev/null \
+ || atf_fail "Expected error message not found"
+ [ ! -s out ] || atf_fail "Unexpected output in stdout"
+ fi
+}
+
+
+atf_test_case info
+info_body() {
+ ( shtk_cli_info "This is" "a message"; echo "continuing" ) >out 2>err
+ grep "cli_test: I: This is a message" err >/dev/null \
+ || atf_fail "Expected info message not found"
+ grep "continuing" out >/dev/null || atf_fail "Execution aborted"
+}
+
+
+atf_test_case usage_error
+usage_error_body() {
+ if ( shtk_cli_usage_error "This is" "a message"; echo "not seen" ) >out 2>err
+ then
+ atf_fail "shtk_cli_usage_error did not exit with an error"
+ else
+ grep "cli_test: E: This is a message" err >/dev/null \
+ || atf_fail "Expected error message not found"
+ grep "Type 'man cli_test' for help" err >/dev/null \
+ || atf_fail "Expected instructional message not found"
+ [ ! -s out ] || atf_fail "Unexpected output in stdout"
+ fi
+}
+
+
+atf_test_case warning
+warning_body() {
+ ( shtk_cli_warning "This is" "a message"; echo "continuing" ) >out 2>err
+ grep "cli_test: W: This is a message" err >/dev/null \
+ || atf_fail "Expected info message not found"
+ grep "continuing" out >/dev/null || atf_fail "Execution aborted"
+}
+
+
+atf_init_test_cases() {
+ atf_add_test_case dirname
+ atf_add_test_case progname
+
+ atf_add_test_case error
+ atf_add_test_case info
+ atf_add_test_case warning
+
+ atf_add_test_case usage_error
+}
diff --git a/devel/shtk/files/config.subr b/devel/shtk/files/config.subr
new file mode 100644
index 00000000000..f62ec8a678e
--- /dev/null
+++ b/devel/shtk/files/config.subr
@@ -0,0 +1,301 @@
+# Copyright 2012 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# * Neither the name of Google Inc. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+# \file config.subr
+# Configuration file processing and queries.
+
+shtk_import cli
+
+
+# List of valid configuration variables.
+#
+# This is initialized by shtk_config_init and should remain unchanged thorough the
+# execution of the program.
+_Shtk_ConfigVars=
+
+
+# List of overrides to apply by shtk_config_apply_overrides.
+#
+# The overrides are recorded and applied separately because they need to happen
+# after a configuration file has been loaded. The contents of this list are
+# words of the form: set:<variable> or unset:<variable>. For those variables
+# listed in set:, there is a corresponding shtk_config_override_var_<variable>
+# variable containing the new value.
+_Shtk_ConfigOverrides=
+
+
+# Initializes the configuration module.
+#
+# \param ... List of configuration variables to recognize in configuration files
+# and user overrides.
+shtk_config_init() {
+ _Shtk_ConfigVars="${@}"
+}
+
+
+# Checks if a configuration variable is known.
+#
+# \param var Name of the variable to check.
+#
+# \return True if the variable was registered by shtk_config_init, false otherwise.
+shtk_config_is_valid() {
+ local var="${1}"; shift
+
+ local known_var
+ for known_var in ${_Shtk_ConfigVars}; do
+ if [ "${known_var}" = "${var}" ]; then
+ return 0
+ fi
+ done
+ return 1
+}
+
+
+# Checks if a configuration variable is defined.
+#
+# \param var The name of the variable to check.
+#
+# \return True if the variable is defined (even if empty), false otherwise.
+shtk_config_has() {
+ local var="${1}"; shift
+
+ local is_unset
+ eval is_unset="\${shtk_config_var_${var}-yes_this_is_unset}"
+ if [ "${is_unset}" = yes_this_is_unset ]; then
+ return 1
+ else
+ return 0
+ fi
+}
+
+
+# Gets the value of a defined configuration variable.
+#
+# \post The value of the variable is printed to stdout, if any.
+#
+# \post If the variable is not defined, this terminates execution with an error.
+#
+# \param var Name of the configuration variable to query.
+shtk_config_get() {
+ local var="${1}"; shift
+
+ if shtk_config_has "${var}"; then
+ eval echo "\${shtk_config_var_${var}}"
+ else
+ shtk_cli_error "Required configuration variable ${var} not set"
+ fi
+}
+
+
+# Gets the value of configuration variable interpreting it as a boolean.
+#
+# \param var The variable to query.
+#
+# \return True if the variable is set to a truth value, false if its value is
+# false or if it is not defined.
+shtk_config_get_bool() {
+ local var="${1}"; shift
+
+ if shtk_config_has "${var}"; then
+ case "$(shtk_config_get "${var}")" in
+ [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee])
+ return 0
+ ;;
+ [Nn][Oo]|[Ff][Aa][Ll][Ss][Ee])
+ return 1
+ ;;
+ *)
+ shtk_cli_error "Invalid boolean value in variable ${var}"
+ ;;
+ esac
+ else
+ return 1
+ fi
+}
+
+
+# Gets the value of a configuration variable with a default fallback.
+#
+# \post The value of the variable is printed to stdout, if any.
+#
+# \param var Name of the configuration variable to query.
+# \param default Default value to return if the variable is not defined.
+shtk_config_get_default() {
+ local var="${1}"; shift
+ local default="${1}"; shift
+
+ if shtk_config_has "${var}"; then
+ shtk_config_get "${var}"
+ else
+ echo "${default}"
+ fi
+}
+
+
+# Sets a configuration variable.
+#
+# \post Execution terminates if the variable is not valid.
+#
+# \param var The name of the configuration variable to set.
+# \param value The value to set.
+shtk_config_set() {
+ local var="${1}"; shift
+ local value="${1}"; shift
+
+ shtk_config_is_valid "${var}" \
+ || shtk_cli_usage_error "Unknown configuration variable ${var}"
+ eval "shtk_config_var_${var}=\"\${value}\""
+}
+
+
+# Unsets a configuration variable.
+#
+# \param var The name of the configuration variable to unset.
+shtk_config_unset() {
+ local var="${1}"; shift
+
+ shtk_config_is_valid "${var}" \
+ || shtk_cli_usage_error "Unknown configuration variable ${var}"
+ eval unset "shtk_config_var_${var}"
+}
+
+
+# Loads a configuration file.
+#
+# \pre shtk_config_init should have been called to register the valid configuration
+# variables. Any non-registered configuration variable found in the file will
+# not be available through any of the functions in this module.
+#
+# \post The configuration module is updated with the values defined in the
+# configuration file.
+#
+# \post Any errors in the processing of the configuration file terminate the
+# execution of the script.
+#
+# \param config_file Path to the file to load.
+shtk_config_load() {
+ local config_file="${1}"; shift
+
+ [ -e "${config_file}" ] || shtk_cli_error "Configuration file" \
+ "${config_file} does not exist"
+
+ # User-facing variables.
+ local var
+ for var in ${_Shtk_ConfigVars}; do
+ unset "${var}" || true
+ eval local "${var}"
+ done
+
+ local real_config_file
+ case "${config_file}" in
+ */*) real_config_file="${config_file}" ;;
+ *) real_config_file="./${config_file}" ;;
+ esac
+ ( . "${real_config_file}" ) || shtk_cli_error "Failed to load" \
+ "configuration file ${config_file}"
+ . "${real_config_file}"
+
+ for var in ${_Shtk_ConfigVars}; do
+ local value
+ eval value="\${${var}-unset}"
+ [ "${value-unset}" != unset ] || continue
+
+ if [ -n "${value}" ]; then
+ shtk_config_set "${var}" "${value}"
+ else
+ unset "shtk_config_var_${var}" || true
+ fi
+ done
+
+ shtk_config_apply_overrides
+}
+
+
+# Applies recorded overrides to the current configuration.
+#
+# This is just a helper function for shtk_config_load and should not be used
+# directly.
+#
+# \post The configuration data in memory is modified according to the data
+# recorded in the overrides.
+#
+# \post The contents of _Shtk_ConfigOverrides is cleared.
+shtk_config_apply_overrides() {
+ for override in ${_Shtk_ConfigOverrides}; do
+ case "${override}" in
+ set:*)
+ local var="$(echo ${override} | cut -d : -f 2)"
+ local value
+ eval value="\"\${shtk_config_override_var_${var}}\""
+ shtk_config_set "${var}" "${value}"
+ ;;
+ unset:*)
+ local var="$(echo ${override} | cut -d : -f 2)"
+ unset "shtk_config_var_${var}" || true
+ ;;
+ esac
+ done
+ _Shtk_ConfigOverrides=
+}
+
+
+# Records an override to be applied to the configuration.
+#
+# Overrides are configuration variables set through the command line. These can
+# be set before loading the configuration file with shtk_config_load.
+#
+# \post Any errors in the processing of the configuration override terminate the
+# execution of the script.
+#
+# \param arg An override of the form variable=value.
+shtk_config_override() {
+ local arg="${1}"; shift
+
+ case "${arg}" in
+ *=*)
+ ;;
+ *)
+ shtk_cli_usage_error "Invalid configuration override" \
+ "${arg}; must be of the form variable=value"
+ ;;
+ esac
+ local var="$(echo "${arg}" | cut -d = -f 1)"
+ local value="$(echo "${arg}" | cut -d = -f 2-)"
+
+ [ -n "${var}" ] || shtk_cli_usage_error "Invalid configuration override" \
+ "${arg}; must be of the form variable=value"
+ shtk_config_is_valid "${var}" \
+ || shtk_cli_usage_error "Unknown configuration variable ${var}"
+
+ if [ -n "${value}" ]; then
+ eval "shtk_config_override_var_${var}=\"${value}\""
+ _Shtk_ConfigOverrides="${_Shtk_ConfigOverrides} set:${var}"
+ else
+ _Shtk_ConfigOverrides="${_Shtk_ConfigOverrides} unset:${var}"
+ fi
+}
diff --git a/devel/shtk/files/config_test.sh b/devel/shtk/files/config_test.sh
new file mode 100644
index 00000000000..2a5b9a873c3
--- /dev/null
+++ b/devel/shtk/files/config_test.sh
@@ -0,0 +1,424 @@
+# Copyright 2012 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# * Neither the name of Google Inc. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+shtk_import config
+
+
+atf_test_case is_valid__true
+is_valid__true_body() {
+ shtk_config_init VAR1 VAR3
+ for var in VAR1 VAR3; do
+ shtk_config_is_valid "${var}" || atf_fail "${var} not found"
+ done
+}
+
+
+atf_test_case is_valid__false
+is_valid__false_body() {
+ shtk_config_init VAR1 VAR3
+ for var in VAR11 VAR2 VAR; do
+ if shtk_config_is_valid "${var}"; then
+ atf_fail "${var} found but was not registered"
+ fi
+ done
+}
+
+
+atf_test_case has__true__empty
+has__true__empty_body() {
+ shtk_config_init TESTVAR
+ shtk_config_set TESTVAR ""
+ shtk_config_has TESTVAR || atf_fail "Expected variable not found"
+}
+
+
+atf_test_case has__true__not_empty
+has__true__not_empty_body() {
+ shtk_config_init TESTVAR
+ shtk_config_set TESTVAR "foo"
+ shtk_config_has TESTVAR || atf_fail "Expected variable not found"
+}
+
+
+atf_test_case has__false
+has__false_body() {
+ shtk_config_init TESTVAR
+ if shtk_config_has TESTVAR; then
+ atf_fail "Unexpected variable found"
+ fi
+}
+
+
+atf_test_case get__ok__empty
+get__ok__empty_body() {
+ shtk_config_init TESTVAR
+
+ shtk_config_set TESTVAR ""
+ [ -z "$(shtk_config_get TESTVAR)" ] || atf_fail "Failed to query value"
+}
+
+
+atf_test_case get__ok__not_empty
+get__ok__not_empty_body() {
+ shtk_config_init TESTVAR
+
+ shtk_config_set TESTVAR some-value
+ [ "$(shtk_config_get TESTVAR)" = some-value ] || atf_fail "Failed to query value"
+}
+
+
+atf_test_case get__undefined_variable
+get__undefined_variable_body() {
+ shtk_config_init TESTVAR
+
+ if ( shtk_config_get TESTVAR ) >out 2>err; then
+ atf_fail "Got unset variable successfully"
+ else
+ grep "Required configuration variable TESTVAR not set" err >/dev/null \
+ || atf_fail "Expected error message not found"
+ fi
+}
+
+
+atf_test_case get_bool__true
+get_bool__true_body() {
+ shtk_config_init TESTVAR
+
+ for value in yes Yes true True; do
+ shtk_config_set TESTVAR "${value}"
+ shtk_config_get_bool TESTVAR || atf_fail "Expected true, but got false"
+ done
+}
+
+
+atf_test_case get_bool__false
+get_bool__false_body() {
+ shtk_config_init TESTVAR
+
+ for value in no No false False; do
+ shtk_config_set TESTVAR "${value}"
+ if shtk_config_get_bool TESTVAR; then
+ atf_fail "Expected false, but got true"
+ fi
+ done
+}
+
+
+atf_test_case get_bool__undefined_variable
+get_bool__undefined_variable_body() {
+ shtk_config_init TESTVAR
+
+ if shtk_config_get_bool TESTVAR; then
+ atf_fail "Expected false, but got true"
+ fi
+}
+
+
+atf_test_case get_bool__invalid_value
+get_bool__invalid_value_body() {
+ shtk_config_init TESTVAR
+
+ shtk_config_set TESTVAR not-a-boolean
+ if ( shtk_config_get_bool TESTVAR ) >out 2>err; then
+ atf_fail "Got invalid boolean value successfully"
+ else
+ grep "Invalid boolean value in variable TESTVAR" err >/dev/null \
+ || atf_fail "Expected error message not found"
+ fi
+}
+
+
+atf_test_case get_default__defined__empty
+get_default__defined__empty_body() {
+ shtk_config_init TESTVAR
+ shtk_config_set TESTVAR ""
+ [ "$(shtk_config_get_default TESTVAR 'foo')" = "" ] \
+ || atf_fail "Did not fetch defined value"
+}
+
+
+atf_test_case get_default__defined__not_empty
+get_default__defined__not_empty_body() {
+ shtk_config_init TESTVAR
+ shtk_config_set TESTVAR "bar"
+ [ "$(shtk_config_get_default TESTVAR 'foo')" = "bar" ] \
+ || atf_fail "Did not fetch defined value"
+}
+
+
+atf_test_case get_default__default__empty
+get_default__default__empty_body() {
+ shtk_config_init TESTVAR
+ [ "$(shtk_config_get_default TESTVAR '')" = "" ] \
+ || atf_fail "Did not fetch default value"
+}
+
+
+atf_test_case get_default__default__not_empty
+get_default__default__not_empty_body() {
+ shtk_config_init TESTVAR
+ [ "$(shtk_config_get_default TESTVAR 'foo')" = "foo" ] \
+ || atf_fail "Did not fetch default value"
+}
+
+
+atf_test_case set__ok
+set__ok_body() {
+ shtk_config_init TESTVAR
+
+ shtk_config_set TESTVAR some-value
+ [ "${shtk_config_var_TESTVAR}" = some-value ] || atf_fail "Failed to set value"
+}
+
+
+atf_test_case set__unknown_variable
+set__unknown_variable_body() {
+ shtk_config_init TESTVAR
+
+ if ( shtk_config_set TESTVAR2 some-value ) >out 2>err; then
+ atf_fail "Set unknown variable successfully"
+ else
+ grep "Unknown configuration variable TESTVAR2" err >/dev/null \
+ || atf_fail "Expected error message not found"
+ fi
+}
+
+
+atf_test_case unset__ok
+unset__ok_body() {
+ shtk_config_init TESTVAR
+
+ shtk_config_var_TESTVAR=some-value
+ shtk_config_unset TESTVAR
+ [ "${shtk_config_var_TESTVAR-unset}" = unset ] \
+ || atf_fail "Failed to unset variable"
+}
+
+
+atf_test_case unset__unknown_variable
+unset__unknown_variable_body() {
+ shtk_config_init TESTVAR
+
+ if ( shtk_config_unset TESTVAR2 ) >out 2>err; then
+ atf_fail "Unset unknown variable successfully"
+ else
+ grep "Unknown configuration variable TESTVAR2" err >/dev/null \
+ || atf_fail "Expected error message not found"
+ fi
+}
+
+
+atf_test_case load__filter_variables
+load__filter_variables_body() {
+ shtk_config_init Z VAR1 EMPTY
+
+ cat >test.conf <<EOF
+A=foo
+Z=bar
+VAR1="some text"
+VAR2="some other text"
+EOF
+
+ shtk_config_load $(pwd)/test.conf || atf_fail "Failed to load test configuration"
+
+ [ "${shtk_config_var_Z}" = bar ] || \
+ atf_fail "Z not found in configuration"
+ [ "${shtk_config_var_VAR1}" = "some text" ] || \
+ atf_fail "VAR1 not found in configuration"
+
+ [ "${shtk_config_var_EMPTY-has_not_been_set}" = has_not_been_set ] || \
+ atf_fail "Undefined variable set, but should not have been"
+
+ [ "${shtk_config_var_A-unset}" = unset ] || \
+ atf_fail "A set in configuration, but not expected"
+ [ "${shtk_config_var_VAR2-unset}" = unset ] || \
+ atf_fail "VAR2 set in configuration, but not expected"
+}
+
+
+atf_test_case load__allow_undefine
+load__allow_undefine_body() {
+ shtk_config_init UNDEFINE
+
+ cat >test.conf <<EOF
+UNDEFINE=
+EOF
+
+ shtk_config_set UNDEFINE "remove me"
+ shtk_config_load $(pwd)/test.conf || atf_fail "Failed to load test configuration"
+ if shtk_config_has UNDEFINE; then
+ atf_fail "Undefine attempt from configuration did not work"
+ fi
+}
+
+
+atf_test_case load__current_directory
+load__current_directory_body() {
+ shtk_config_init A
+
+ cat >test.conf <<EOF
+A=foo
+EOF
+
+ shtk_config_load test.conf || atf_fail "Failed to load test configuration"
+
+ [ "${shtk_config_var_A}" = foo ] || \
+ atf_fail "A not found in configuration"
+}
+
+
+atf_test_case load__missing_file
+load__missing_file_body() {
+ if ( shtk_config_load missing.conf ) >out 2>err; then
+ atf_fail "Missing configuration file load succeeded"
+ else
+ grep "Configuration file missing.conf does not exist" err >/dev/null \
+ || atf_fail "Expected error message not found"
+ fi
+}
+
+
+atf_test_case load__invalid_file
+load__invalid_file_body() {
+ echo "this file is invalid" >invalid.conf
+
+ if ( shtk_config_load invalid.conf ) >out 2>err; then
+ atf_fail "Invalid configuration file load succeeded"
+ else
+ cat err
+ grep "Failed to load configuration file invalid.conf" err >/dev/null \
+ || atf_fail "Expected error message not found"
+ fi
+}
+
+
+atf_test_case override__ok_before_load
+override__ok_before_load_body() {
+ shtk_config_init VAR1 VAR2
+
+ cat >test.conf <<EOF
+VAR1="override me"
+VAR2="do not override me"
+EOF
+
+ shtk_config_override "VAR1=new value"
+ shtk_config_load test.conf || atf_fail "Failed to load test configuration"
+
+ [ "${shtk_config_var_VAR1}" = "new value" ] || atf_fail "Override failed"
+ [ "${shtk_config_var_VAR2}" = "do not override me" ] \
+ || atf_fail "Overrode more than one variable"
+}
+
+
+atf_test_case override__not_ok_after_load
+override__not_ok_after_load_body() {
+ shtk_config_init VAR1 VAR2
+
+ cat >test.conf <<EOF
+VAR1="override me"
+VAR2="do not override me"
+EOF
+
+ shtk_config_load test.conf || atf_fail "Failed to load test configuration"
+ shtk_config_override "VAR1=new value"
+
+ [ "${shtk_config_var_VAR1}" = "override me" ] \
+ || atf_fail "Override succeeded, but it should not have"
+ [ "${shtk_config_var_VAR2}" = "do not override me" ] \
+ || atf_fail "Overrode more than one variable"
+}
+
+
+atf_test_case override__invalid_format
+override__invalid_format_body() {
+ for arg in foo =bar ''; do
+ if ( shtk_config_override "${arg}" ) >out 2>err; then
+ atf_fail "Invalid configuration override ${arg} succeeded"
+ else
+ cat err
+ grep "Invalid configuration override ${arg}" err >/dev/null \
+ || atf_fail "Expected error message not found"
+ fi
+ done
+}
+
+
+atf_test_case override__unknown_variable
+override__unknown_variable_body() {
+ shtk_config_init Z VAR1
+ for arg in A=b VAR2=d; do
+ if ( shtk_config_override "${arg}" ) >out 2>err; then
+ atf_fail "Invalid configuration override ${arg} succeeded"
+ else
+ cat err
+ grep "Unknown configuration variable ${var}" err >/dev/null \
+ || atf_fail "Expected error message not found"
+ fi
+ done
+}
+
+
+atf_init_test_cases() {
+ atf_add_test_case is_valid__true
+ atf_add_test_case is_valid__false
+
+ atf_add_test_case has__true__empty
+ atf_add_test_case has__true__not_empty
+ atf_add_test_case has__false
+
+ atf_add_test_case get__ok__empty
+ atf_add_test_case get__ok__not_empty
+ atf_add_test_case get__undefined_variable
+
+ atf_add_test_case get_bool__true
+ atf_add_test_case get_bool__false
+ atf_add_test_case get_bool__undefined_variable
+ atf_add_test_case get_bool__invalid_value
+
+ atf_add_test_case get_default__defined__empty
+ atf_add_test_case get_default__defined__not_empty
+ atf_add_test_case get_default__default__empty
+ atf_add_test_case get_default__default__not_empty
+
+ atf_add_test_case set__ok
+ atf_add_test_case set__unknown_variable
+
+ atf_add_test_case unset__ok
+ atf_add_test_case unset__unknown_variable
+
+ atf_add_test_case load__filter_variables
+ atf_add_test_case load__allow_undefine
+ atf_add_test_case load__current_directory
+ atf_add_test_case load__missing_file
+ atf_add_test_case load__invalid_file
+
+ atf_add_test_case override__ok_before_load
+ atf_add_test_case override__not_ok_after_load
+ atf_add_test_case override__invalid_format
+ atf_add_test_case override__unknown_variable
+}
diff --git a/devel/shtk/files/cvs.subr b/devel/shtk/files/cvs.subr
new file mode 100644
index 00000000000..28cda156f28
--- /dev/null
+++ b/devel/shtk/files/cvs.subr
@@ -0,0 +1,111 @@
+# Copyright 2012 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# * Neither the name of Google Inc. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+# \file cvs.subr
+# Utilities to invoke CVS and wrappers to simplify its operation.
+
+shtk_import cli
+shtk_import process
+
+
+# Checks out or updates a working copy.
+#
+# \param cvsroot Location of the CVS repository.
+# \param module Module to check out, when the working copy does not exist yet.
+# \param tag CVS tag to use, if any; may be empty.
+# \param directory Target directory in which to store the checked out files or
+# the updated files.
+shtk_cvs_fetch() {
+ local cvsroot="${1}"; shift
+ local module="${1}"; shift
+ local tag="${1}"; shift
+ local directory="${1}"; shift
+
+ if [ -d "${directory}" ]; then
+ shtk_cvs_update "${cvsroot}" "${tag}" "${directory}"
+ else
+ shtk_cvs_checkout "${cvsroot}" "${module}" "${tag}" "${directory}"
+ fi
+}
+
+
+# Checks out a new directory from CVS.
+#
+# \param cvsroot Location of the CVS repository.
+# \param module The name of the module to check out.
+# \param tag CVS tag to use, if any; may be empty.
+# \param directory The directory in which to perform the checkout.
+shtk_cvs_checkout() {
+ local cvsroot="${1}"; shift
+ local module="${1}"; shift
+ local tag="${1}"; shift
+ local directory="${1}"; shift
+
+ local rflag=
+ [ -z "${tag}" ] || rflag="-r${tag}"
+
+ [ ! -d "${directory}" ] || shtk_cli_error "Cannot checkout into" \
+ "${directory}; directory already exists"
+ mkdir -p "${directory}"
+ mkdir "${directory}"/.cvs-checkout \
+ || shtk_cli_error "Failed to create ${directory}"
+ ( cd "${directory}"/.cvs-checkout &&
+ shtk_process_run cvs -d"${cvsroot}" -q checkout -P ${rflag} \
+ "${module}" )
+ mv "${directory}"/.cvs-checkout/"${module}"/* "${directory}"/ \
+ || true # Maybe the checkout yielded no files...
+ rm -rf "${directory}"/.cvs-checkout
+}
+
+
+# Updates an existing directory from CVS.
+#
+# \param cvsroot Location of the CVS repository.
+# \param tag CVS tag to use, if any; may be empty.
+# \param directory The directory in which to perform the update.
+shtk_cvs_update() {
+ local cvsroot="${1}"; shift
+ local tag="${1}"; shift
+ local directory="${1}"; shift
+
+ local rflag=
+ [ -z "${tag}" ] || rflag="-r${tag}"
+
+ [ -d "${directory}" ] || shtk_cli_error "Cannot update ${directory};" \
+ "directory does not exist"
+
+ if [ -d "${directory}/.cvs-checkout" ]; then
+ # Attempt to resume a previously aborted shtk_cvs_checkout.
+ mv "${directory}"/.cvs-checkout/*/* "${directory}"/ \
+ || true # Maybe the checkout yielded no files...
+ rm -rf "${directory}"/.cvs-checkout
+ fi
+
+ ( cd "${directory}" && \
+ shtk_process_run cvs -d"${cvsroot}" -q update -d -P ${rflag} )
+}
diff --git a/devel/shtk/files/cvs_test.sh b/devel/shtk/files/cvs_test.sh
new file mode 100644
index 00000000000..dedb0ee04ba
--- /dev/null
+++ b/devel/shtk/files/cvs_test.sh
@@ -0,0 +1,184 @@
+# Copyright 2012 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# * Neither the name of Google Inc. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+shtk_import cvs
+
+
+MOCK_CVSROOT=":local:$(pwd)/cvsroot"
+
+
+# Creates a local CVS repository with a variety of modules.
+#
+# \param repository Path to the repository to create.
+# \param ... Modules to create.
+init_cvsroot() {
+ local repository="${1}"; shift
+
+ atf_check -o ignore -e ignore cvs -d "${repository}" init
+
+ for module in "${@}"; do
+ mkdir module
+ cd module
+ echo "first revision" >"file-in-${module}"
+ cvs -d "${repository}" import -m "Import." "${module}" VENDOR_TAG \
+ release_tag
+ cd -
+ rm -rf module
+ done
+}
+
+
+atf_test_case fetch
+fetch_body() {
+ init_cvsroot "${MOCK_CVSROOT}" src
+
+ shtk_cvs_fetch "${MOCK_CVSROOT}" src "" first
+ grep "first revision" first/file-in-src >/dev/null \
+ || atf_fail "Unexpected version found"
+
+ cp -rf first second
+ echo "second revision" >second/file-in-src
+ ( cd second && cvs commit -m "Second commit." )
+
+ shtk_cvs_fetch "${MOCK_CVSROOT}" src "" first
+ grep "second revision" first/file-in-src >/dev/null \
+ || atf_fail "Unexpected version found"
+}
+
+
+atf_test_case checkout__same_name
+checkout__same_name_body() {
+ init_cvsroot "${MOCK_CVSROOT}" first second
+ shtk_cvs_checkout "${MOCK_CVSROOT}" first "" $(pwd)/a/b/c/first
+ [ -f a/b/c/first/file-in-first ] || atf_fail "Files not checked out"
+ if [ -f a/b/c/second/file-in-second ]; then
+ atf_fail "Unexpected module checked out"
+ fi
+}
+
+
+atf_test_case checkout__different_name
+checkout__different_name_body() {
+ init_cvsroot "${MOCK_CVSROOT}" first second
+ shtk_cvs_checkout "${MOCK_CVSROOT}" first "" $(pwd)/a/b/c/second
+ [ -f a/b/c/second/file-in-first ] || atf_fail "Files not checked out"
+}
+
+
+atf_test_case checkout__already_exists
+checkout__already_exists_body() {
+ mkdir usr/src
+ if ( shtk_cvs_checkout "${MOCK_CVSROOT}" src "" $(pwd)/usr/src ) >out 2>err
+ then
+ atf_fail "Checkout succeeded, but should not"
+ else
+ grep "Cannot checkout into $(pwd)/usr/src.*exists" err >/dev/null \
+ || atf_fail "Expected error message not found"
+ fi
+}
+
+
+atf_test_case checkout__permission_denied
+checkout__permission_denied_head() {
+ atf_set "require.user" "unprivileged"
+}
+checkout__permission_denied_body() {
+ init_cvsroot "${MOCK_CVSROOT}" src
+ mkdir usr
+ chmod 555 usr
+ if ( shtk_cvs_checkout "${MOCK_CVSROOT}" src "" $(pwd)/usr/src ) >out 2>err
+ then
+ atf_fail "Checkout succeeded, but should not"
+ else
+ grep "Failed to create $(pwd)/usr/src" err >/dev/null \
+ || atf_fail "Expected error message not found"
+ fi
+}
+
+
+atf_test_case update__ok
+update__ok_body() {
+ init_cvsroot "${MOCK_CVSROOT}" first second
+
+ cvs -d "${MOCK_CVSROOT}" checkout first
+ mv first copy
+ cvs -d "${MOCK_CVSROOT}" checkout first
+
+ shtk_cvs_update "${MOCK_CVSROOT}" "" first
+ grep "first revision" first/file-in-first >/dev/null \
+ || atf_fail "Unexpected version found"
+
+ echo "second revision" >copy/file-in-first
+ ( cd copy && cvs commit -m "Second commit." )
+
+ shtk_cvs_update "${MOCK_CVSROOT}" "" first
+ grep "second revision" first/file-in-first >/dev/null \
+ || atf_fail "Unexpected version found"
+}
+
+
+atf_test_case update__resume_checkout
+update__resume_checkout_body() {
+ init_cvsroot "${MOCK_CVSROOT}" first
+
+ cvs -d "${MOCK_CVSROOT}" checkout first
+ mv first copy
+
+ mkdir -p first/.cvs-checkout/first
+ mv copy/CVS first/.cvs-checkout/first
+ rm -rf copy
+
+ shtk_cvs_update "${MOCK_CVSROOT}" "" first
+ grep "first revision" first/file-in-first >/dev/null \
+ || atf_fail "Unexpected version found"
+}
+
+
+atf_test_case update__does_not_exist
+update__does_not_exist_body() {
+ if ( shtk_cvs_update "${MOCK_CVSROOT}" "" src ) >out 2>err; then
+ atf_fail "Update succeeded, but should not"
+ else
+ grep "Cannot update src; .*not exist" err >/dev/null \
+ || atf_fail "Expected error message not found"
+ fi
+}
+
+
+atf_init_test_cases() {
+ atf_add_test_case fetch
+
+ atf_add_test_case checkout__same_name
+ atf_add_test_case checkout__different_name
+ atf_add_test_case checkout__already_exists
+ atf_add_test_case checkout__permission_denied
+
+ atf_add_test_case update__ok
+ atf_add_test_case update__resume_checkout
+ atf_add_test_case update__does_not_exist
+}
diff --git a/devel/shtk/files/list.subr b/devel/shtk/files/list.subr
new file mode 100644
index 00000000000..6f2e31b2913
--- /dev/null
+++ b/devel/shtk/files/list.subr
@@ -0,0 +1,68 @@
+# Copyright 2012 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# * Neither the name of Google Inc. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+# \file list.subr
+# List-manipulation functions.
+#
+# For the purposes of this file, a list is a collection of words separted
+# by IFS. Most of the functions of this file, if not all, take lists as
+# a variable list of arguments.
+
+
+# Looks for an item in a list of items
+#
+# \param item The item to look for.
+# \param ... Contents of the list in which to search for the item.
+#
+# \return 0 if the item is found, 1 otherwise.
+shtk_list_contains() {
+ local item="${1}"; shift
+
+ while [ ${#} -gt 0 ]; do
+ [ "${1}" != "${item}" ] || return 0
+ shift
+ done
+ return 1
+}
+
+
+# Filters a list based on a pattern.
+#
+# \param pattern Pattern to use for filtering.
+# \param ... Contents of the list to filter.
+#
+# \post The matched elements are printed on stdout.
+shtk_list_filter() {
+ local pattern="${1}"; shift
+
+ for arg in "${@}"; do
+ case "${arg}" in
+ ${pattern}) echo "${arg}" ;;
+ esac
+ done
+}
diff --git a/devel/shtk/files/list_test.sh b/devel/shtk/files/list_test.sh
new file mode 100644
index 00000000000..26dce642eb3
--- /dev/null
+++ b/devel/shtk/files/list_test.sh
@@ -0,0 +1,73 @@
+# Copyright 2012 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# * Neither the name of Google Inc. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+shtk_import list
+
+
+atf_test_case contains__yes
+contains__yes_body() {
+ items="bar foo baz"
+ shtk_list_contains foo ${items} || atf_fail "Element not found in list"
+}
+
+
+atf_test_case contains__no
+contains__no_body() {
+ items="bar foo baz"
+ ! shtk_list_contains fo ${items} || atf_fail "Element found in list"
+}
+
+
+atf_test_case filter__no_items
+filter__no_items_body() {
+ atf_check_equal "" "$(shtk_list_filter '*')"
+}
+
+
+atf_test_case filter__no_results
+filter__no_results_body() {
+ items="abc a1 foo a2 a3 bar"
+ atf_check_equal "" "$(shtk_list_filter '*a' ${items})"
+}
+
+
+atf_test_case filter__some_results
+filter__some_results_body() {
+ items="abc a1 foo a2 a3 bar"
+ atf_check_equal "a1 a2 a3" "$(shtk_list_filter 'a[0-9]*' ${items})"
+}
+
+
+atf_init_test_cases() {
+ atf_add_test_case contains__yes
+ atf_add_test_case contains__no
+
+ atf_add_test_case filter__no_items
+ atf_add_test_case filter__no_results
+ atf_add_test_case filter__some_results
+}
diff --git a/devel/shtk/files/process.subr b/devel/shtk/files/process.subr
new file mode 100644
index 00000000000..9a1a67c117f
--- /dev/null
+++ b/devel/shtk/files/process.subr
@@ -0,0 +1,50 @@
+# Copyright 2012 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# * Neither the name of Google Inc. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+# \file process.subr
+# Process manipulation functions.
+
+shtk_import cli
+
+
+# Executes a command with logging.
+#
+# \param ... The command to execute.
+#
+# \return The exit code of the executed command.
+shtk_process_run() {
+ shtk_cli_info "Running '${@}' in $(pwd)"
+ local ret=0
+ "${@}" || ret="${?}"
+ if [ ${ret} -eq 0 ]; then
+ shtk_cli_info "Command finished successfully"
+ else
+ shtk_cli_warning "Command failed with code ${ret}"
+ fi
+ return "${ret}"
+}
diff --git a/devel/shtk/files/process_test.sh b/devel/shtk/files/process_test.sh
new file mode 100644
index 00000000000..75c3a1c9307
--- /dev/null
+++ b/devel/shtk/files/process_test.sh
@@ -0,0 +1,87 @@
+# Copyright 2012 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# * Neither the name of Google Inc. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+shtk_import process
+
+
+atf_test_case run__ok
+run__ok_body() {
+ cat >helper.sh <<EOF
+#! /bin/sh
+echo "This exits cleanly:" "\${@}"
+exit 0
+EOF
+ chmod +x helper.sh
+
+ shtk_process_run ./helper.sh one two three >out 2>err \
+ || atf_fail "Got an unexpected error code"
+
+ cat >expout <<EOF
+This exits cleanly: one two three
+EOF
+ atf_check -o file:expout cat out
+
+ cat >experr <<EOF
+process_test: I: Running './helper.sh one two three' in $(pwd)
+process_test: I: Command finished successfully
+EOF
+ atf_check -o file:experr cat err
+}
+
+
+atf_test_case run__fail
+run__fail_body() {
+ cat >helper.sh <<EOF
+#! /bin/sh
+echo "This exits with an error:" "\${@}"
+exit 42
+EOF
+ chmod +x helper.sh
+
+ code=0
+ shtk_process_run ./helper.sh one two three >out 2>err || code="${?}"
+ [ ${code} -eq 42 ] \
+ || atf_fail "Did not get the expected error code; got ${code}"
+
+ cat >expout <<EOF
+This exits with an error: one two three
+EOF
+ atf_check -o file:expout cat out
+
+ cat >experr <<EOF
+process_test: I: Running './helper.sh one two three' in $(pwd)
+process_test: W: Command failed with code 42
+EOF
+ atf_check -o file:experr cat err
+}
+
+
+atf_init_test_cases() {
+ atf_add_test_case run__ok
+ atf_add_test_case run__fail
+}
diff --git a/devel/shtk/files/shtk.1 b/devel/shtk/files/shtk.1
new file mode 100644
index 00000000000..6eb44bbb541
--- /dev/null
+++ b/devel/shtk/files/shtk.1
@@ -0,0 +1,148 @@
+.\" Copyright 2012 Google Inc.
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions are
+.\" met:
+.\"
+.\" * Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" * Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" * Neither the name of Google Inc. nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+.\" "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+.\" A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+.\" OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+.\" SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+.\" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+.\" OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+.Dd August 15, 2012
+.Dt SHTK 1
+.Os
+.Sh NAME
+.Nm shtk
+.Nd interface to the Shell Toolkit
+.Sh SYNOPSIS
+.Nm
+build
+.Op Fl m Ar main_function
+.Op Fl o Ar output_file
+.Op Fl s Ar shell
+.Op Ar input_file
+.Nm
+version
+.Sh DESCRIPTION
+The Shell Toolkit, or
+.Nm
+for short, is a collection of modules written in
+.Xr sh 1
+that provide common functionality to simplify the implementation of complex
+shell scripts.
+.Pp
+The tool described in this page, also named
+.Nm ,
+is a program that accompanies the Shell Toolkit and provides functionality that
+may be necessary to use the toolkit.
+.Pp
+The interface of
+.Nm
+is that of a program with commands as shown in the synopsis section.
+The following subsections describe the various commands supported by the tool.
+.Ss The build command
+The build command takes a script that uses
+.Nm
+modules and generates an executable script with the necessary boilerplate code
+to be able to locate said modules.
+.Pp
+The
+.Ar input_file
+argument specifies the path to the file to process.
+This file should end with an
+.Sq .sh
+extension, in which case the build command will proceed to generate a binary of
+the same name without any extension.
+If the extension is not present, then the name of the output file must be
+provided by specifying the
+.Fl o
+flag.
+The input file can also be
+.Sq - ,
+in which case the code is read from the standard input.
+.Pp
+The following options are supported:
+.Bl -tag -width XXXX
+.It Fl m Ar main_function
+Name of the function that implements the entry point of the program.
+.Pp
+All scripts that use
+.Nm
+should implement a main function and should avoid placing any code at the top
+level of the file.
+The main function is automatically executed by the generated script.
+If the value of
+.Ar main_function
+is empty, then no call to main is issued which may be necessary if the script
+already has a call to main for whatever reason (e.g. the script also uses
+.Xr atf-sh 1 ) .
+.Pp
+Default:
+.Sq main .
+.It Fl o Ar output_file
+Path to the output file to generate.
+.Pp
+Default: if
+.Ar input_file
+ends in
+.Sq .sh ,
+the output file has the same name as the input file but without the extension.
+Otherwise this flag must be specified.
+.It Fl s Ar shell
+Path to the shell interpreter to write in the shebang line of the file.
+.Pp
+Default:
+.Pa @SHTK_SHELL@ .
+.El
+.Ss The version command
+The version command prints the name of the package and its version to the
+standard output.
+This command always exits successfully.
+.Sh ENVIRONMENT
+.Bl -tag -width XXXX
+.It Va SHTK_MODULESDIR
+Overrides the built-in location of the modules.
+.It Va SHTK_SHELL
+Overrides the built-in location of the default shell interpreter.
+.El
+.Sh FILES
+.Bl -tag -width XXXX
+.It Pa @SHTK_MODULESDIR@/
+Location of the
+.Nm
+modules.
+.El
+.Sh SEE ALSO
+Unfortunately, there currently is no standalone documentation of the
+.Nm .
+The documentation exists, but is in the form of docstrings next to the
+functions they describe.
+Such docstrings document the purpose of the function, the parameters it
+takes, its return value (if any) and any side-effects.
+.Pp
+Please look at the files in
+.Pa @SHTK_MODULESDIR@
+for additional details.
+.Sh AUTHORS
+The
+.Nm
+package was developed by
+.An Julio Merino
+.Aq jmmv@NetBSD.org .
diff --git a/devel/shtk/files/shtk.sh b/devel/shtk/files/shtk.sh
new file mode 100644
index 00000000000..fd2c93723d2
--- /dev/null
+++ b/devel/shtk/files/shtk.sh
@@ -0,0 +1,176 @@
+#! @SHTK_SHELL@
+# Copyright 2012 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# * Neither the name of Google Inc. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+# \file shtk.sh
+# Entry point and main program logic.
+#
+# For simplicity reasons, we cannot rely on any of our own modules to
+# implement this file. Doing so, while possible, would complicate the
+# creation of the final shtk script for no real advantage.
+
+set -e
+
+
+# Location of the shtk modules.
+: ${SHTK_MODULESDIR:="@SHTK_MODULESDIR@"}
+
+
+# Default shell to use when generating scripts.
+: ${SHTK_SHELL:="@SHTK_SHELL@"}
+
+
+# Version of the package.
+SHTK_VERSION="@SHTK_VERSION@"
+
+
+# Base name of the running script.
+_ProgName="${0##*/}"
+
+
+# Prints a runtime error and exits.
+#
+# \param ... The message to print. Can be provided as multiple words and, in
+# that case, they are joined together by a single whitespace.
+error() {
+ echo "${_ProgName}: E: $*" 1>&2
+ exit 1
+}
+
+
+# Prints an usage error and exits.
+#
+# \param ... The message to print. Can be provided as multiple words and, in
+# that case, they are joined together by a single whitespace.
+usage_error() {
+ echo "${_ProgName}: E: $*" 1>&2
+ echo "Type 'man ${_ProgName}' for help" 1>&2
+ exit 1
+}
+
+
+# Command to build a script that uses shtk libraries.
+#
+# \params ... Options and arguments to the command.
+shtk_build() {
+ local main=main
+ local output=
+ local shell="${SHTK_SHELL}"
+
+ while getopts ':m:o:s:' arg "${@}"; do
+ case "${arg}" in
+ m) # Main function name.
+ main="${OPTARG}"
+ ;;
+
+ o) # Output file.
+ output="${OPTARG}"
+ ;;
+
+ s) # Shell to use.
+ shell="${OPTARG}"
+ ;;
+
+ \?)
+ usage_error "Unknown option -${OPTARG}"
+ ;;
+ esac
+ done
+ shift $((${OPTIND} - 1))
+
+ [ ${#} -eq 1 ] || usage_error "build takes one argument only"
+
+ local input="${1}"; shift
+ case "${input}" in
+ *.sh)
+ [ -n "${output}" ] || output="$(echo ${input} | sed -e 's,\.sh$,,')"
+ ;;
+
+ *)
+ [ -n "${output}" ] || usage_error "Input file should" \
+ "end in .sh or you must specify -o"
+ ;;
+ esac
+
+ [ "${input}" = - -o -e "${input}" ] || error "Cannot open ${input}"
+
+ # Note that we use the built-in value of SHTK_MODULESDIR unconditionally
+ # instead of what the environment says to avoid possible side-effects that
+ # would be easy to debug.
+ sed -e "s,__SHTK_MODULESDIR__,@SHTK_MODULESDIR@,g" \
+ -e "s,__SHTK_SH__,${shell},g" \
+ "${SHTK_MODULESDIR}/bootstrap.subr" \
+ | grep -v '^#[^!].*' | grep -v '^#$' >"${output}.tmp"
+ if [ "${input}" = - ]; then
+ cat >>"${output}.tmp"
+ else
+ cat "${input}" >>"${output}.tmp"
+ fi
+ [ -z "${main}" ] || echo "${main} \"\${@}\"" >>"${output}.tmp"
+ chmod +x "${output}.tmp"
+ if ! mv "${output}.tmp" "${output}"; then
+ rm -f "${output}.tmp"
+ error "Failed to create ${output}"
+ fi
+}
+
+
+# Gets version information about shtk.
+shtk_version() {
+ [ ${#} -eq 0 ] || usage_error "version does not take any arguments"
+
+ echo "shtk ${SHTK_VERSION}"
+}
+
+
+# Entry point to the program.
+#
+# \param ... Command-line arguments to be processed.
+#
+# \return An exit code to be returned to the user.
+shtk_main() {
+ [ ${#} -ge 1 ] || usage_error "No command specified"
+
+ local exit_code=0
+
+ local command="${1}"; shift
+ case "${command}" in
+ build|version)
+ "shtk_${command}" "${@}" || exit_code="${?}"
+ ;;
+
+ *)
+ usage_error "Unknown command ${command}"
+ ;;
+ esac
+
+ return "${exit_code}"
+}
+
+
+shtk_main "${@}"
diff --git a/devel/shtk/files/shtk_test.sh b/devel/shtk/files/shtk_test.sh
new file mode 100644
index 00000000000..41430c6ebc9
--- /dev/null
+++ b/devel/shtk/files/shtk_test.sh
@@ -0,0 +1,216 @@
+# Copyright 2012 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# * Neither the name of Google Inc. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+atf_test_case build__defaults
+build__defaults_body() {
+ cat >script.sh <<EOF
+shtk_import cli
+
+main() {
+ shtk_cli_info "It works"
+}
+EOF
+
+ atf_check shtk build script.sh
+
+ cat >experr <<EOF
+script: I: It works
+EOF
+ atf_check -e file:experr ./script
+}
+
+
+atf_test_case build__mflag__explicit
+build__mflag__explicit_body() {
+ cat >script.sh <<EOF
+shtk_import cli
+
+my_main() {
+ shtk_cli_info "Print this"
+}
+
+main() {
+ shtk_cli_info "Don't print this"
+}
+EOF
+
+ atf_check shtk build -m my_main script.sh
+
+ cat >experr <<EOF
+script: I: Print this
+EOF
+ atf_check -e file:experr ./script
+}
+
+
+atf_test_case build__mflag__empty
+build__mflag__empty_body() {
+ cat >script.sh <<EOF
+shtk_import cli
+
+shtk_cli_info "Outside of main"
+
+main() {
+ shtk_cli_info "Don't print this"
+}
+EOF
+
+ atf_check shtk build -m '' script.sh
+
+ cat >experr <<EOF
+script: I: Outside of main
+EOF
+ atf_check -e file:experr ./script
+}
+
+
+atf_test_case build__oflag__explicit
+build__oflag__explicit_body() {
+ cat >script.sh <<EOF
+shtk_import cli
+
+main() {
+ shtk_cli_info "A string"
+}
+EOF
+ atf_check shtk build -o first script.sh
+ atf_check -e inline:'first: I: A string\n' ./first
+}
+
+
+atf_test_case build__oflag__stdin
+build__oflag__stdin_body() {
+ cat >script.sh <<EOF
+shtk_import cli
+
+main() {
+ shtk_cli_info "A string"
+}
+EOF
+ cat script.sh | atf_check shtk build -o second -
+ atf_check -e inline:'second: I: A string\n' ./second
+}
+
+
+atf_test_case build__oflag__necessary
+build__oflag__necessary_body() {
+ touch script
+
+ cat >experr <<EOF
+shtk: E: Input file should end in .sh or you must specify -o
+Type 'man shtk' for help
+EOF
+ atf_check -s exit:1 -e file:experr shtk build script
+ echo "foo" | atf_check -s exit:1 -e file:experr shtk build -
+}
+
+
+atf_test_case build__sflag
+build__sflag_body() {
+ cat >script.sh <<EOF
+We won't run this anyway.
+EOF
+ atf_check shtk build -s '/custom/interpreter' script.sh
+ atf_check -o inline:'#! /custom/interpreter\n' head -n1 script
+}
+
+
+atf_test_case build__no_args
+build__no_args_body() {
+ cat >experr <<EOF
+shtk: E: build takes one argument only
+Type 'man shtk' for help
+EOF
+ atf_check -s exit:1 -e file:experr shtk build
+}
+
+
+atf_test_case build__too_many_args
+build__too_many_args_body() {
+ cat >experr <<EOF
+shtk: E: build takes one argument only
+Type 'man shtk' for help
+EOF
+ atf_check -s exit:1 -e file:experr shtk build foo bar
+}
+
+
+atf_test_case version__ok
+version__ok_body() {
+ atf_check -s exit:0 -o match:"shtk [0-9]+\.[0-9]+.*" shtk version
+}
+
+
+atf_test_case version__too_many_args
+version__too_many_args_body() {
+ cat >experr <<EOF
+shtk: E: version does not take any arguments
+Type 'man shtk' for help
+EOF
+ atf_check -s exit:1 -e file:experr shtk version foo
+}
+
+
+atf_test_case no_command
+no_command_body() {
+ cat >experr <<EOF
+shtk: E: No command specified
+Type 'man shtk' for help
+EOF
+ atf_check -s exit:1 -e file:experr shtk
+}
+
+
+atf_test_case unknown_command
+unknown_command_body() {
+ cat >experr <<EOF
+shtk: E: Unknown command foo
+Type 'man shtk' for help
+EOF
+ atf_check -s exit:1 -e file:experr shtk foo
+}
+
+
+atf_init_test_cases() {
+ atf_add_test_case build__defaults
+ atf_add_test_case build__mflag__explicit
+ atf_add_test_case build__mflag__empty
+ atf_add_test_case build__oflag__explicit
+ atf_add_test_case build__oflag__stdin
+ atf_add_test_case build__oflag__necessary
+ atf_add_test_case build__sflag
+ atf_add_test_case build__no_args
+ atf_add_test_case build__too_many_args
+
+ atf_add_test_case version__ok
+ atf_add_test_case version__too_many_args
+
+ atf_add_test_case no_command
+ atf_add_test_case unknown_command
+}