aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Ryder <tom@sanctum.geek.nz>2016-09-20 20:54:47 +1200
committerTom Ryder <tom@sanctum.geek.nz>2016-09-20 20:54:47 +1200
commit641495f7be6b0b0f4310b55127dd6a0bccd3d861 (patch)
tree2bce8ea0a3cdb5c74fcbb9c1a897d20a26d5cbe3
parentMerge branch 'master' into openbsd (diff)
parentClearer glob for version test (diff)
downloaddotfiles-641495f7be6b0b0f4310b55127dd6a0bccd3d861.tar.gz
dotfiles-641495f7be6b0b0f4310b55127dd6a0bccd3d861.zip
Merge branch 'master' into openbsd
-rw-r--r--README.markdown1
-rw-r--r--bash/bash_completion.d/mex.bash15
-rw-r--r--bash/bashrc7
-rw-r--r--bash/bashrc.d/prompt.bash206
-rwxr-xr-xbin/mex53
-rw-r--r--man/man1/mex.1df22
6 files changed, 195 insertions, 109 deletions
diff --git a/README.markdown b/README.markdown
index 3e566177..66864981 100644
--- a/README.markdown
+++ b/README.markdown
@@ -443,6 +443,7 @@ Installed by the `install-bin` target:
* `maybe(1df)` is like `true(1)` or `false(1)`; given a probability of
success,
it exits with success or failure. Good for quick tests.
+* `mex(1df)` makes given filenames in `$PATH` executable.
* `mftl(1df)` finds usable-looking targets in Makefiles.
* `mkcp(1df)` creates a directory and copies preceding arguments into it.
* `mkmv(1df)` creates a directory and moves preceding arguments into it.
diff --git a/bash/bash_completion.d/mex.bash b/bash/bash_completion.d/mex.bash
new file mode 100644
index 00000000..d25f1824
--- /dev/null
+++ b/bash/bash_completion.d/mex.bash
@@ -0,0 +1,15 @@
+# Completion setup for mex(1df), completing non-executable files in $PATH
+_mex() {
+ local -a path
+ IFS=: read -ra path < <(printf '%s\n' "$PATH")
+ local dir name
+ for dir in "${path[@]}" ; do
+ [[ -d $dir ]] || continue
+ for name in "$dir"/* ; do
+ [[ -f $name ]] || continue
+ ! [[ -x $name ]] || continue
+ COMPREPLY[${#COMPREPLY[@]}]=${name##*/}
+ done
+ done
+}
+complete -F _mex mex
diff --git a/bash/bashrc b/bash/bashrc
index 1c7d7802..088182ef 100644
--- a/bash/bashrc
+++ b/bash/bashrc
@@ -14,7 +14,7 @@ esac
# shellcheck disable=SC2128
[ -n "$BASH_VERSINFO" ] || return
((BASH_VERSINFO[0] == 2)) &&
- ((10#${BASH_VERSINFO[1]%%[![:digit:]]*} < 5)) &&
+ ((10#${BASH_VERSINFO[1]%%[!0-9]*} < 5)) &&
return
# Don't do anything if running a restricted shell
@@ -32,9 +32,6 @@ HISTTIMEFORMAT='%F %T '
# Use a more compact format for the time builtin's output
TIMEFORMAT='real:%lR user:%lU sys:%lS'
-# Don't allow the creation of aliases, functions are better
-enable -n alias unalias
-
# Autocorrect fudged paths in cd calls
shopt -s cdspell
# Update the hash table properly
@@ -45,8 +42,6 @@ shopt -s checkwinsize
shopt -s cmdhist
# Include dotfiles in pattern matching
shopt -s dotglob
-# Don't use aliases, functions are better
-shopt -u expand_aliases
# Enable advanced pattern matching
shopt -s extglob
# Append rather than overwrite Bash history
diff --git a/bash/bashrc.d/prompt.bash b/bash/bashrc.d/prompt.bash
index e7e285a7..856e20e6 100644
--- a/bash/bashrc.d/prompt.bash
+++ b/bash/bashrc.d/prompt.bash
@@ -31,46 +31,43 @@ prompt() {
# Add terminating "$" or "#" sign
PS1=$PS1'\$'
- # Count available colors
- local -i colors
- colors=$( {
- tput colors || tput Co
- } 2>/dev/null )
-
- # Prepare reset code
- local reset
- reset=$( {
- tput sgr0 || tput me
- } 2>/dev/null )
-
- # Decide prompt color formatting based on color availability
- local format
-
- # Check if we have non-bold bright green available
- if ((colors >= 16)) ; then
- format=$( {
- : "${PROMPT_COLOR:=10}"
- tput setaf "$PROMPT_COLOR" ||
- tput setaf "$PROMPT_COLOR" 0 0 ||
- tput AF "$PROMPT_COLOR" ||
- tput AF "$PROMPT_COLOR" 0 0
- } 2>/dev/null )
-
- # If we have only eight colors, use bold green
- elif ((colors >= 8)) ; then
- format=$( {
- : "${PROMPT_COLOR:=2}"
- tput setaf "$PROMPT_COLOR" ||
- tput AF "$PROMPT_COLOR"
- tput bold || tput md
- } 2>/dev/null )
-
- # Otherwise, we just try bold
- else
- format=$( {
- tput bold || tput md
- } 2>/dev/null )
- fi
+ # Declare variables to contain terminal control strings
+ local format reset
+
+ # Disregard output and error from these tput(1) calls
+ {
+ # Count available colors
+ local -i colors
+ colors=$(tput colors || tput Co)
+
+ # Prepare reset code
+ reset=$(tput sgr0 || tput me)
+
+ # Check if we have non-bold bright green available
+ if ((colors >= 16)) ; then
+ format=$(
+ : "${PROMPT_COLOR:=10}"
+ tput setaf "$PROMPT_COLOR" ||
+ tput setaf "$PROMPT_COLOR" 0 0 ||
+ tput AF "$PROMPT_COLOR" ||
+ tput AF "$PROMPT_COLOR" 0 0
+ )
+
+ # If we have only eight colors, use bold green
+ elif ((colors >= 8)) ; then
+ format=$(
+ : "${PROMPT_COLOR:=2}"
+ tput setaf "$PROMPT_COLOR" ||
+ tput AF "$PROMPT_COLOR"
+ tput bold || tput md
+ )
+
+ # Otherwise, we just try bold
+ else
+ format=$(tput bold || tput md)
+ fi
+
+ } >/dev/null 2>&1
# String it all together
PS1='\['"$format"'\]'"$PS1"'\['"$reset"'\] '
@@ -90,74 +87,77 @@ prompt() {
# Git prompt function
git)
- # Bail if we're not in a work tree--or, implicitly, if we don't
- # have git(1).
- local iswt
- iswt=$(git rev-parse --is-inside-work-tree 2>/dev/null)
- [[ $iswt = true ]] || return
-
- # Refresh index so e.g. git-diff-files(1) is accurate
- git update-index --refresh >/dev/null 2>&1
-
- # Find a local branch, remote branch, or tag (annotated or not), or
- # failing all of that just show the short commit ID, in that order
- # of preference; if none of that works, bail out
- local name
- name=$( {
- git symbolic-ref --quiet HEAD ||
- git describe --tags --exact-match HEAD ||
- git rev-parse --short HEAD
- } 2>/dev/null) || return
- name=${name##*/}
- [[ -n $name ]] || return
-
- # Check various files in .git to flag processes
- local proc
- [[ -d .git/rebase-merge || -d .git/rebase-apply ]] &&
- proc=${proc:+$proc,}'REBASE'
- [[ -f .git/MERGE_HEAD ]] &&
- proc=${proc:+$proc,}'MERGE'
- [[ -f .git/CHERRY_PICK_HEAD ]] &&
- proc=${proc:+$proc,}'PICK'
- [[ -f .git/REVERT_HEAD ]] &&
- proc=${proc:+$proc,}'REVERT'
- [[ -f .git/BISECT_LOG ]] &&
- proc=${proc:+$proc,}'BISECT'
-
- # Collect symbols representing repository state
- local state
-
- # Upstream HEAD has commits after local HEAD; we're "behind"
- local -i behind
- behind=$(git rev-list --count 'HEAD..@{u}' 2>/dev/null)
- ((behind)) && state=${state}'<'
-
- # Local HEAD has commits after upstream HEAD; we're "ahead"
- local -i ahead
- ahead=$(git rev-list --count '@{u}..HEAD' 2>/dev/null)
- ((ahead)) && state=${state}'>'
-
- # Tracked files are modified
- git diff-files --no-ext-diff --quiet ||
- state=${state}'!'
-
- # Changes are staged
- git diff-index --cached --no-ext-diff --quiet HEAD 2>/dev/null ||
- state=${state}'+'
-
- # There are some untracked and unignored files
- git ls-files --directory --error-unmatch --exclude-standard \
- --no-empty-directory --others -- ':/*' >/dev/null 2>&1 &&
- state=${state}'?'
- # There are stashed changes
- git rev-parse --quiet --verify refs/stash >/dev/null &&
- state=${state}'^'
+ # Wrap as compound command; we don't want to see output from any of
+ # these git(1) calls
+ {
+ # Bail if we're not in a work tree--or, implicitly, if we don't
+ # have git(1).
+ [[ -n $(git rev-parse --is-inside-work-tree) ]] ||
+ return
+
+ # Refresh index so e.g. git-diff-files(1) is accurate
+ git update-index --refresh
+
+ # Find a local branch, remote branch, or tag (annotated or
+ # not), or failing all of that just show the short commit ID,
+ # in that order of preference; if none of that works, bail out
+ local name
+ name=$(
+ git symbolic-ref --quiet HEAD ||
+ git describe --tags --exact-match HEAD ||
+ git rev-parse --short HEAD
+ ) || return
+ name=${name##*/}
+ [[ -n $name ]] || return
+
+ # Check various files in .git to flag processes
+ local proc
+ [[ -d .git/rebase-merge || -d .git/rebase-apply ]] &&
+ proc=${proc:+"$proc",}'REBASE'
+ [[ -f .git/MERGE_HEAD ]] &&
+ proc=${proc:+"$proc",}'MERGE'
+ [[ -f .git/CHERRY_PICK_HEAD ]] &&
+ proc=${proc:+"$proc",}'PICK'
+ [[ -f .git/REVERT_HEAD ]] &&
+ proc=${proc:+"$proc",}'REVERT'
+ [[ -f .git/BISECT_LOG ]] &&
+ proc=${proc:+"$proc",}'BISECT'
+
+ # Collect symbols representing repository state
+ local state
+
+ # Upstream HEAD has commits after local HEAD; we're "behind"
+ (($(git rev-list --count 'HEAD..@{u}'))) &&
+ state=${state}'<'
+
+ # Local HEAD has commits after upstream HEAD; we're "ahead"
+ (($(git rev-list --count '@{u}..HEAD'))) &&
+ state=${state}'>'
+
+ # Tracked files are modified
+ git diff-files --no-ext-diff --quiet ||
+ state=${state}'!'
+
+ # Changes are staged
+ git diff-index --cached --no-ext-diff --quiet HEAD ||
+ state=${state}'+'
+
+ # There are some untracked and unignored files
+ git ls-files --directory --error-unmatch --exclude-standard \
+ --no-empty-directory --others -- ':/*' &&
+ state=${state}'?'
+
+ # There are stashed changes
+ git rev-parse --quiet --verify refs/stash &&
+ state=${state}'^'
+
+ } >/dev/null 2>&1
# Print the status in brackets; add a git: prefix only if there
# might be another VCS prompt (because PROMPT_VCS is set)
printf '(%s%s%s%s)' \
- "${PROMPT_VCS:+git:}" "$name" "${proc:+:$proc}" "$state"
+ "${PROMPT_VCS:+git:}" "$name" "${proc:+:"$proc"}" "$state"
;;
# Subversion prompt function
diff --git a/bin/mex b/bin/mex
new file mode 100755
index 00000000..005149d8
--- /dev/null
+++ b/bin/mex
@@ -0,0 +1,53 @@
+#!/bin/sh
+# Make the first non-executable instance of files with the given names in $PATH
+# executable
+self=mex
+
+# Check we have at least one argument
+if [ "$#" -eq 0 ] ; then
+ printf >&2 '%s: At least one name required\n' "$self"
+ exit 2
+fi
+
+# Iterate through the given names
+for name ; do
+
+ # Clear the found variable
+ found=
+
+ # Start iterating through $PATH, with colon prefix/suffix to correctly
+ # handle the fenceposts
+ path=:$PATH:
+ while [ -n "$path" ] ; do
+
+ # Pop the first directory off $path into $dir
+ dir=${path%%:*}
+ path=${path#*:}
+
+ # Check $dir is non-null
+ [ -n "$dir" ] || continue
+
+ # If a file with the needed name exists in the directory and isn't
+ # executable, we've found our candidate and can stop iterating
+ if [ -f "$dir"/"$name" ] && ! [ -x "$dir"/"$name" ] ; then
+ found=$dir/$name
+ break
+ fi
+ done
+
+ # If the "found" variable was defined to something, we'll try to change its
+ # permissions
+ if [ -n "$found" ] ; then
+ chmod +x -- "$found" || ex=1
+
+ # If not, we'll report that we couldn't find it, and flag an error for the
+ # exit status
+ else
+ printf >&2 '%s: No non-executable name "%s" in PATH\n' "$self" "$name"
+ ex=1
+ fi
+done
+
+# We exit 1 if any of the names weren't found or if changing their permissions
+# failed
+exit "${ex:-0}"
diff --git a/man/man1/mex.1df b/man/man1/mex.1df
new file mode 100644
index 00000000..0fa584da
--- /dev/null
+++ b/man/man1/mex.1df
@@ -0,0 +1,22 @@
+.TH MEX 1df "September 2016" "Manual page for mex"
+.SH NAME
+.B mex
+\- make first instance of filenames in $PATH executable
+.SH USAGE
+.B mex
+name
+.br
+.B mex
+name1 name2 name3
+.br
+PATH=/foo:/bar/baz
+name
+.SH DESCRIPTION
+Iterate through the contents of the PATH variable looking for files with any of
+the specified names that do not have the executable permissions bit set, and
+try to set it if found. Exit nonzero if any of the names were not found, or if
+any of the permissions changes failed.
+.SH SEE ALSO
+chmod(1), eds(1df)
+.SH AUTHOR
+Tom Ryder <tom@sanctum.geek.nz>