#!/bin/bash # generate-configs - Docstring parser for generating xmonad build configs with # default settings for extensions # Author: Alex Tarkovsky # Released into the public domain # This script parses custom docstrings specifying build-time configuration data # from xmonad extension source files, then inserts the data into copies of # xmonad's Config.hs and xmonad.cabal files accordingly. # # Usage: generate-configs [ OPTIONS ] --main MAIN_DIR --contrib CONTRIB_DIR # # OPTIONS: # --active, -a Insert data in active mode (default: passive) # --contrib, -c CONTRIB_DIR Path to contrib repository base directory # --help, -h Show help # --main, -m MAIN_DIR Path to main repository base directory # --output, -o OUTPUT_DIR Output directory (default: CONTRIB_DIR) # # Data parsed from the extension source files is inserted into Config.hs in # either active or passive mode. The default is passive mode, in which the # inserted data is commented out. The --active option inserts the data # uncommented. Data inserted into xmonad.cabal is always inserted in active # mode regardless of specified options. # # The docstring markup can be extended as needed. Currently the following tags # are defined, shown with some examples: # # ~~~~~ # # %cabalbuilddep # # Cabal build dependency. Value is appended to the "build-depends" line in # xmonad.cabal and automatically prefixed with ", ". NB: Don't embed # comments in this tag! # # -- %cabalbuilddep readline>=1.0 # # %def # # General definition. Value is appended to the end of Config.sh. # # -- %def commands :: [(String, X ())] # -- %def commands = defaultCommands # # %import # # Module needed by Config.sh to build the extension. Value is appended to # the end of the default import list in Config.sh and automatically # prefixed with "import ". # # -- %import XMonad.Layout.Accordion # -- %import qualified XMonad.Actions.FlexibleManipulate as Flex # # %keybind # # Tuple defining a key binding. Must be prefixed with ", ". Value is # inserted at the end of the "keys" list in Config.sh. # # -- %keybind , ((modMask, xK_d), date) # # %keybindlist # # Same as %keybind, but instead of a key binding tuple the definition is a # list of key binding tuples (or a list comprehension producing them). This # list is concatenated to the "keys" list must begin with the "++" operator # rather than ", ". # # -- %keybindlist ++ # -- %keybindlist -- mod-[1..9] @@ Switch to workspace N # -- %keybindlist -- mod-shift-[1..9] @@ Move client to workspace N # -- %keybindlist -- mod-control-shift-[1..9] @@ Copy client to workspace N # -- %keybindlist [((m .|. modMask, k), f i) # -- %keybindlist | (i, k) <- zip [0..fromIntegral (workspaces-1)] [xK_1 ..] # -- %keybindlist , (f, m) <- [(view, 0), (shift, shiftMask), (copy, shiftMask .|. controlMask)]] # # %layout # # A layout. Must be prefixed with ", ". Value is inserted at the end of the # "defaultLayouts" list in Config.sh. # # -- %layout , accordion # # %mousebind # # Tuple defining a mouse binding. Must be prefixed with ", ". Value is # inserted at the end of the "mouseBindings" list in Config.sh. # # -- %mousebind , ((modMask, button3), (\\w -> focus w >> Flex.mouseResizeWindow w)) # # ~~~~~ # # NB: '/' and '\' characters must be escaped with a '\' character! # # Tags may also contain comments, as illustrated in the %keybindlist examples # above. Comments are a good place for special user instructions: # # -- %def -- comment out default logHook definition above if you uncomment this: # -- %def logHook = dynamicLog # Markup tag to search for in source files. TAG_CABALBUILDDEP="%cabalbuilddep" TAG_DEF="%def" TAG_IMPORT="%import" TAG_KEYBIND="%keybind" TAG_KEYBINDLIST="%keybindlist" TAG_LAYOUT="%layout" TAG_MOUSEBIND="%mousebind" # Insert markers to search for in Config.sh and xmonad.cabal. Values are # extended sed regular expressions. INS_MARKER_CABALBUILDDEP='^build-depends:.*' INS_MARKER_IMPORT='-- % Extension-provided imports$' INS_MARKER_LAYOUT='-- % Extension-provided layouts$' INS_MARKER_KEYBIND='-- % Extension-provided key bindings$' INS_MARKER_KEYBINDLIST='-- % Extension-provided key bindings lists$' INS_MARKER_MOUSEBIND='-- % Extension-provided mouse bindings$' INS_MARKER_DEF='-- % Extension-provided definitions$' # Literal indentation strings. Values may contain escaped chars such as \t. INS_INDENT_CABALBUILDDEP="" INS_INDENT_DEF="" INS_INDENT_IMPORT="" INS_INDENT_KEYBIND=" " INS_INDENT_KEYBINDLIST=" " INS_INDENT_LAYOUT=" " INS_INDENT_MOUSEBIND=" " # Prefix applied to inserted passive data after indent strings have been applied. INS_PREFIX_DEF="-- " INS_PREFIX_IMPORT="--import " INS_PREFIX_KEYBIND="-- " INS_PREFIX_KEYBINDLIST="-- " INS_PREFIX_LAYOUT="-- " INS_PREFIX_MOUSEBIND="-- " # Prefix applied to inserted active data after indent strings have been applied. ACTIVE_INS_PREFIX_CABALBUILDDEP=", " ACTIVE_INS_PREFIX_DEF="" ACTIVE_INS_PREFIX_IMPORT="import " ACTIVE_INS_PREFIX_KEYBIND="" ACTIVE_INS_PREFIX_KEYBINDLIST="" ACTIVE_INS_PREFIX_LAYOUT="" ACTIVE_INS_PREFIX_MOUSEBIND="" # Don't touch these opt_active=0 opt_contrib="" opt_main="" opt_output="" generate_configs() { for extension_srcfile in $(ls --color=never -1 "${opt_contrib}"/*.hs | head -n -1 | sort -r) ; do for tag in $TAG_CABALBUILDDEP \ $TAG_DEF \ $TAG_IMPORT \ $TAG_KEYBIND \ $TAG_KEYBINDLIST \ $TAG_LAYOUT \ $TAG_MOUSEBIND ; do ifs="$IFS" IFS=$'\n' tags=( $(sed -n -r -e "s/^.*--\s*${tag}\s//p" "${extension_srcfile}") ) IFS="${ifs}" case $tag in $TAG_CABALBUILDDEP) ins_indent=$INS_INDENT_CABALBUILDDEP ins_marker=$INS_MARKER_CABALBUILDDEP ins_prefix=$ACTIVE_INS_PREFIX_CABALBUILDDEP ;; $TAG_DEF) ins_indent=$INS_INDENT_DEF ins_marker=$INS_MARKER_DEF ins_prefix=$INS_PREFIX_DEF ;; $TAG_IMPORT) ins_indent=$INS_INDENT_IMPORT ins_marker=$INS_MARKER_IMPORT ins_prefix=$INS_PREFIX_IMPORT ;; $TAG_KEYBIND) ins_indent=$INS_INDENT_KEYBIND ins_marker=$INS_MARKER_KEYBIND ins_prefix=$INS_PREFIX_KEYBIND ;; $TAG_KEYBINDLIST) ins_indent=$INS_INDENT_KEYBINDLIST ins_marker=$INS_MARKER_KEYBINDLIST ins_prefix=$INS_PREFIX_KEYBINDLIST ;; $TAG_LAYOUT) ins_indent=$INS_INDENT_LAYOUT ins_marker=$INS_MARKER_LAYOUT ins_prefix=$INS_PREFIX_LAYOUT ;; $TAG_MOUSEBIND) ins_indent=$INS_INDENT_MOUSEBIND ins_marker=$INS_MARKER_MOUSEBIND ins_prefix=$INS_PREFIX_MOUSEBIND ;; esac # Insert in reverse so values will ultimately appear in correct order. for i in $( seq $(( ${#tags[*]} - 1 )) -1 0 ) ; do [ -z "${tags[i]}" ] && continue if [[ $tag == $TAG_CABALBUILDDEP ]] ; then sed -i -r -e "s/${ins_marker}/\\0${ins_prefix}${tags[i]}/" "${CABAL_FILE}" else sed -i -r -e "/${ins_marker}/{G;s/$/${ins_indent}${ins_prefix}${tags[i]}/;}" "${CONFIG_FILE}" fi done if [[ $tag != $TAG_CABALBUILDDEP && -n "${tags}" ]] ; then ins_group_comment="${ins_indent}-- For extension $(basename $extension_srcfile .hs):" sed -i -r -e "/${ins_marker}/{G;s/$/${ins_group_comment}/;}" "${CONFIG_FILE}" fi done done } parse_opts() { [[ -z "$1" ]] && show_usage 1 while [[ $# > 0 ]] ; do case "$1" in --active|-a) opt_active=1 shift ;; --contrib|-c) shift if [[ -z "$1" || ! -d "$1" ]] ; then echo "Error: Option --contrib requires a directory as argument. See: generate-configs -h" exit 1 fi opt_contrib="$1" shift ;; --help|-h) show_usage ;; --main|-m) shift if [[ -z "$1" || ! -d "$1" ]] ; then echo "Error: Option --main requires a directory as argument. See: generate-configs -h" exit 1 fi opt_main="$1" shift ;; --output|-o) shift if [[ -z "$1" || ! -d "$1" ]] ; then echo "Error: Option --output requires a directory as argument. See: generate-configs -h" exit 1 fi opt_output="$1" shift ;; -*) echo "Error: Unknown option ${1}. See: generate-configs -h" exit 1 ;; *) show_usage 1 ;; esac done if [[ -z "$opt_main" ]] ; then echo "Error: Missing required option --main. See: generate-configs -h" exit 1 fi if [[ -z "$opt_contrib" ]] ; then echo "Error: Missing required option --contrib. See: generate-configs -h" exit 1 fi } show_usage() { cat << EOF Usage: generate-configs [ OPTIONS ] --main MAIN_DIR --contrib CONTRIB_DIR OPTIONS: --active, -a Insert data in active mode (default: passive) --contrib, -c CONTRIB_DIR Path to contrib repository base directory --help, -h Show help --main, -m MAIN_DIR Path to main repository base directory --output, -o OUTPUT_DIR Output directory (default: CONTRIB_DIR) EOF exit ${1:-0} } parse_opts $* [[ -z "$opt_output" ]] && opt_output="$opt_contrib" CABAL_FILE="${opt_output}/xmonad.cabal" CONFIG_FILE="${opt_output}/Config.hs" cp -f "${opt_main}/xmonad.cabal" "${CABAL_FILE}" cp -f "${opt_main}/Config.hs" "${CONFIG_FILE}" if [[ $opt_active == 1 ]] ; then INS_PREFIX_DEF=$ACTIVE_INS_PREFIX_DEF INS_PREFIX_IMPORT=$ACTIVE_INS_PREFIX_IMPORT INS_PREFIX_KEYBIND=$ACTIVE_INS_PREFIX_KEYBIND INS_PREFIX_KEYBINDLIST=$ACTIVE_INS_PREFIX_KEYBINDLIST INS_PREFIX_LAYOUT=$ACTIVE_INS_PREFIX_LAYOUT INS_PREFIX_MOUSEBIND=$ACTIVE_INS_PREFIX_MOUSEBIND fi generate_configs