added some more stuff and changed prompt

This commit is contained in:
Nikolas Weger 2017-07-24 01:55:33 +02:00
parent cb62f96109
commit ae447242b5
11 changed files with 3181 additions and 1 deletions

675
bin/ansi Executable file
View file

@ -0,0 +1,675 @@
#!/usr/bin/env bash
#
# ANSI code generator
#
# © Copyright 2015 Tyler Akins
# Licensed under the MIT license with an additional non-advertising clause
# See http://github.com/fidian/ansi
ansi::addCode() {
local N
if [[ "$1" == *=* ]]; then
N="${1#*=}"
N="${N//,/;}"
else
N=""
fi
OUTPUT="$OUTPUT$CSI$N$2"
}
ansi::addColor() {
OUTPUT="$OUTPUT$CSI${1}m"
if [ ! -z "$2" ]; then
SUFFIX="$CSI${2}m$SUFFIX"
fi
}
ansi::colorTable() {
local FNB_LOWER FNB_UPPER PADDED
FNB_LOWER="$(ansi::colorize 2 22 f)n$(ansi::colorize 1 22 b)"
FNB_UPPER="$(ansi::colorize 2 22 F)N$(ansi::colorize 1 22 B)"
printf 'bold %s ' "$(ansi::colorize 1 22 Sample)"
printf 'faint %s ' "$(ansi::colorize 2 22 Sample)"
printf 'italic %s\n' "$(ansi::colorize 3 23 Sample)"
printf 'underline %s ' "$(ansi::colorize 4 24 Sample)"
printf 'blink %s ' "$(ansi::colorize 5 25 Sample)"
printf 'inverse %s\n' "$(ansi::colorize 7 27 Sample)"
printf 'invisible %s\n' "$(ansi::colorize 8 28 Sample)"
printf 'strike %s ' "$(ansi::colorize 9 29 Sample)"
printf 'fraktur %s ' "$(ansi::colorize 20 23 Sample)"
printf 'double-underline%s\n' "$(ansi::colorize 21 24 Sample)"
printf 'frame %s ' "$(ansi::colorize 51 54 Sample)"
printf 'encircle %s ' "$(ansi::colorize 52 54 Sample)"
printf 'overline%s\n' "$(ansi::colorize 53 55 Sample)"
printf '\n'
printf ' black red green yellow blue magenta cyan white\n'
for BG in 40:black 41:red 42:green 43:yellow 44:blue 45:magenta 46:cyan 47:white; do
PADDED="bg-${BG:3} "
PADDED="${PADDED:0:13}"
printf '%s' "$PADDED"
for FG in 30 31 32 33 34 35 36 37; do
printf '%s%s;%sm' "$CSI" "${BG:0:2}" "${FG}"
printf '%s' "$FNB_LOWER"
printf '%s%sm' "$CSI" "$(( FG + 60 ))"
printf '%s' "$FNB_UPPER"
printf '%s0m ' "${CSI}"
done
printf '\n'
printf ' +intense '
for FG in 30 31 32 33 34 35 36 37; do
printf '%s%s;%sm' "$CSI" "$(( ${BG:0:2} + 60 ))" "${FG}"
printf '%s' "$FNB_LOWER"
printf '%s%sm' "$CSI" "$(( FG + 60 ))"
printf '%s' "$FNB_UPPER"
printf '%s0m ' "${CSI}"
done
printf '\n'
done
printf '\n'
printf 'Legend:\n'
printf ' Normal color: f = faint, n = normal, b = bold.\n'
printf ' Intense color: F = faint, N = normal, B = bold.\n'
}
ansi::colorize() {
printf '%s%sm%s%s%sm' "$CSI" "$1" "$3" "$CSI" "$2"
}
ansi::isAnsiSupported() {
# Idea: CSI c
# Response = CSI ? 6 [234] ; 2 2 c
# The "22" means ANSI color
printf "can't tell yet\n"
}
ansi::report() {
local BUFF C
REPORT=""
printf "%s%s" "$CSI" "$1"
read -r -N ${#2} -s -t 1 BUFF
if [ "$BUFF" != "$2" ]; then
return 1
fi
read -r -N ${#3} -s -t 1 BUFF
while [ "$BUFF" != "$3" ]; do
REPORT="$REPORT${BUFF:0:1}"
read -r -N 1 -s -t 1 C || exit 1
BUFF="${BUFF:1}$C"
done
}
ansi::showHelp() {
cat <<EOF
Generate ANSI escape codes
Please keep in mind that your terminal must support the code in order for you
to see the effect properly.
Usage
ansi [OPTIONS] [TEXT TO OUTPUT]
Option processing stops at the first unknown option or at "--". Options
are applied in order as specified on the command line. Unless --no-restore
is used, the options are unapplied in reverse order, restoring your
terminal to normal.
Optional parameters are surrounded in brackets and use reasonable defaults.
For instance, using --down will move the cursor down one line and --down=10
moves the cursor down 10 lines.
Display Manipulation
--insert-chars[=N], --ich[=N]
Insert blanks at cursor, shifting the line right.
--erase-display[=N], --ed[=N]
Erase in display. 0=below, 1=above, 2=all,
3=saved.
--erase-line[=N], --el[=N]
Erase in line. 0=right, 1=left, 2=all.
--insert-lines[=N], --il[=N]
--delete-lines[=N], --dl[=N]
--delete-chars[=N], --dch[=N]
--scroll-up[=N], --su[=N]
--scroll-down[=N], --sd[=N]
--erase-chars[=N], --ech[=N]
--repeat[=N], --rep[=N] Repeat preceding character N times.
Cursor:
--up[=N], --cuu[=N]
--down[=N], --cud[=N]
--forward[=N], --cuf[=N]
--backward[=N], --cub[=N]
--next-line[=N], --cnl[=N]
--prev-line[=N], --cpl[=N]
--column[=N], --cha[=N]
--position[=[ROW],[COL]], --cup[=[ROW],[=COL]]
--tab-forward[=N] Move forward N tab stops.
--tab-backward[=N] Move backward N tab stops.
--column-relative[=N], --hpr[=N]
--line[=N], --vpa[=N]
--line-relative[=N], --vpr[=N]
--save-cursor Saves the cursor position. Restores the cursor
after writing text to the terminal unless
--no-restore is also used.
--restore-cursor Just restores the cursor.
--hide-cursor Will automatically show cursor unless --no-restore
is also used.
--show-cursor
Colors:
Attributes:
--bold, --faint, --italic, --underline, --blink, --inverse,
--invisible, --strike, --fraktur, --double-underline, --frame,
--encircle, --overline
Foreground:
--black, --red, --green, --yellow, --blue, --magenta, --cyan, --white,
--black-intense, --red-intense, --green-intense, --yellow-intense,
--blue-intense, --magenta-intense, --cyan-intense, --white-intense
Background:
--bg-black, --bg-red, --bg-green, --bg-yellow, --bg-blue,
--bg-magenta, --bg-cyan, --bg-white, --bg-black-intense,
--bg-red-intense, --bg-green-intense, --bg-yellow-intense,
--bg-blue-intense, --bg-magenta-intense, --bg-cyan-intense,
--bg-white-intense
Reset:
--reset-attrib Removes bold, italics, etc.
--reset-foreground Sets foreground to default color.
--reset-background Sets background to default color.
--reset-color Resets attributes, foreground, background.
Report:
** NOTE: These require reading from stdin. Results are sent to stdout.
** If no response from terminal in 1 second, these commands fail.
--report-position ROW,COL
--report-window-state "open" or "iconified"
--report-window-position X,Y
--report-window-pixels HEIGHT,WIDTH
--report-window-chars ROWS,COLS
--report-screen-chars ROWS,COLS of the entire screen
--report-icon
--report-title
Miscellaneous:
--color-table Display a color table.
--icon=NAME Set the terminal's icon name.
--title=TITLE Set the terminal's window title.
--no-restore Do not issue reset codes when changing colors.
For example, if you change the color to green,
normally the color is restored to default
afterwards. With this flag, the color will
stay green even when the command finishes.
-n, --newline Add a newline at the end.
--escape Allow text passed in to contain escape sequences.
--bell Add the terminal's bell sequence to the output.
--reset Reset colors, clear screen, show cursor, move
cursor to 1,1.
EOF
}
ansi() {
# Handle long options until we hit an unrecognized thing
local CONTINUE=true
local RESTORE=true
local NEWLINE=false
local ESCAPE=false
local ESC=$'\033'
local CSI="${ESC}["
local OSC="${ESC}]"
local ST="${ESC}\\"
local OUTPUT=""
local SUFFIX=""
local BELL=$'\007'
while $CONTINUE; do
case "$1" in
--help | -h | -\?)
ansi::showHelp
;;
# Display Manipulation
--insert-chars | --insert-chars=* | --ich | --ich=*)
ansi::addCode "$1" @
;;
--erase-display | --erase-display=* | --ed | --ed=*)
ansi::addCode "$1" J
;;
--erase-line | --erase-line=* | --el | --el=*)
ansi::addCode "$1" K
;;
--insert-lines | --insert-lines=* | --il | --il=*)
ansi::addCode "$1" L
;;
--delete-lines | --delete-lines=* | --dl | --dl=*)
ansi::addCode "$1" M
;;
--delete-chars | --delete-chars=* | --dch | --dch=*)
ansi::addCode "$1" P
;;
--scroll-up | --scroll-up=* | --su | --su=*)
ansi::addCode "$1" S
;;
--scroll-down | --scroll-down=* | --sd | --sd=*)
ansi::addCode "$1" T
;;
--erase-chars | --erase-chars=* | --ech | --ech=*)
ansi::addCode "$1" X
;;
--repeat | --repeat=* | --rep | --rep=N)
ansi::addCode "$1" b
;;
# Cursor Positioning
--up | --up=* | --cuu | --cuu=*)
ansi::addCode "$1" A
;;
--down | --down=* | --cud | --cud=*)
ansi::addCode "$1" B
;;
--forward | --forward=* | --cuf | --cuf=*)
ansi::addCode "$1" C
;;
--backward | --backward=*| --cub | --cub=*)
ansi::addCode "$1" D
;;
--next-line | --next-line=* | --cnl | --cnl=*)
ansi::addCode "$1" E
;;
--prev-line | --prev-line=* | --cpl | --cpl=*)
ansi::addCode "$1" F
;;
--column | --column=* | --cha | --cha=*)
ansi::addCode "$1" G
;;
--position | --position=* | --cup | --cup=*)
ansi::addCode "$1" H
;;
--tab-forward | --tab-forward=* | --cht | --cht=*)
ansi::addCode "$1" I
;;
--tab-backward | --tab-backward=* | --cbt | --cbt=*)
ansi::addCode "$1" Z
;;
--column-relative | --column-relative=* | --hpr | --hpr=*)
ansi::addCode "$1" 'a'
;;
--line | --line=* | --vpa | --vpa=*)
ansi::addCode "$1" 'd'
;;
--line-relative | --line-relative=* | --vpr | --vpr=*)
ansi::addCode "$1" 'e'
;;
--save-cursor)
OUTPUT="$OUTPUT${CSI}s"
SUFFIX="${CSI}u$SUFFIX"
;;
--restore-cursor)
OUTPUT="$OUTPUT${CSI}u"
;;
--hide-cursor)
OUTPUT="$OUTPUT${CSI}?25l"
SUFFIX="${CSI}?25h"
;;
--show-cursor)
OUTPUT="$OUTPUT${CSI}?25h"
;;
# Colors - Attributes
--bold)
ansi::addColor 1 22
;;
--faint)
ansi::addColor 2 22
;;
--italic)
ansi::addColor 3 23
;;
--underline)
ansi::addColor 4 24
;;
--blink)
ansi::addColor 5 25
;;
--inverse)
ansi::addColor 7 27
;;
--invisible)
ansi::addColor 8 28
;;
--strike)
ansi::addColor 9 20
;;
--fraktur)
ansi::addColor 20 23
;;
--double-underline)
ansi::addColor 21 24
;;
--frame)
ansi::addColor 51 54
;;
--encircle)
ansi::addColor 52 54
;;
--overline)
ansi::addColor 53 55
;;
# Colors - Foreground
--black)
ansi::addColor 30 39
;;
--red)
ansi::addColor 31 39
;;
--green)
ansi::addColor 32 39
;;
--yellow)
ansi::addColor 33 39
;;
--blue)
ansi::addColor 34 39
;;
--magenta)
ansi::addColor 35 39
;;
--cyan)
ansi::addColor 36 39
;;
--white)
ansi::addColor 37 39
;;
--black-intense)
ansi::addColor 90 39
;;
--red-intense)
ansi::addColor 91 39
;;
--green-intense)
ansi::addColor 92 39
;;
--yellow-intense)
ansi::addColor 93 39
;;
--blue-intense)
ansi::addColor 94 39
;;
--magenta-intense)
ansi::addColor 95 39
;;
--cyan-intense)
ansi::addColor 96 39
;;
--white-intense)
ansi::addColor 97 39
;;
# Colors - Background
--bg-black)
ansi::addColor 40 49
;;
--bg-red)
ansi::addColor 41 49
;;
--bg-green)
ansi::addColor 42 49
;;
--bg-yellow)
ansi::addColor 43 49
;;
--bg-blue)
ansi::addColor 44 49
;;
--bg-magenta)
ansi::addColor 45 49
;;
--bg-cyan)
ansi::addColor 46 49
;;
--bg-white)
ansi::addColor 47 49
;;
--bg-black-intense)
ansi::addColor 100 49
;;
--bg-red-intense)
ansi::addColor 101 49
;;
--bg-green-intense)
ansi::addColor 102 49
;;
--bg-yellow-intense)
ansi::addColor 103 49
;;
--bg-blue-intense)
ansi::addColor 104 49
;;
--bg-magenta-intense)
ansi::addColor 105 49
;;
--bg-cyan-intense)
ansi::addColor 106 49
;;
--bg-white-intense)
ansi::addColor 107 49
;;
# Colors - Reset
--reset-attrib)
OUTPUT="$OUTPUT${CSI}22;23;24;25;27;28;29;54;55m"
;;
--reset-foreground)
OUTPUT="$OUTPUT${CSI}39m"
;;
--reset-background)
OUTPUT="$OUTPUT${CSI}39m"
;;
--reset-color)
OUTPUT="$OUTPUT${CSI}0m"
;;
# Reporting
--report-position)
ansi::report 6n "$CSI" R || exit 1
printf '%s\n' "${REPORT//;/,}"
;;
--report-window-state)
ansi::report 11t "$CSI" t || exit 1
case "$REPORT" in
1)
printf 'open\n'
;;
2)
printf 'iconified\n'
;;
*)
printf 'unknown (%s)\n' "$REPORT"
;;
esac
;;
--report-window-position)
ansi::report 13t "${CSI}3;" t || exit 1
printf '%s\n' "${REPORT//;/,}"
;;
--report-window-pixels)
ansi::report 14t "${CSI}4;" t || exit 1
printf '%s\n' "${REPORT//;/,}"
;;
--report-window-chars)
ansi::report 18t "${CSI}8;" t || exit 1
printf '%s\n' "${REPORT//;/,}"
;;
--report-screen-chars)
ansi::report 19t "${CSI}9;" t || exit 1
printf '%s\n' "${REPORT//;/,}"
;;
--report-icon)
ansi::report 20t "${OSC}L" "$ST" || exit 1
printf '%s\n' "$REPORT"
;;
--report-title)
ansi::report 21t "${OSC}l" "$ST" || exit 1
printf '%s\n' "$REPORT"
;;
# Miscellaneous
--color-table)
ansi::colorTable
;;
--icon=*)
OUTPUT="$OUTPUT${OSC}1;${1#*=}$ST"
;;
--title=*)
OUTPUT="$OUTPUT${OSC}2;${1#*=}$ST"
;;
--no-restore)
RESTORE=false
;;
-n | --newline)
NEWLINE=true
;;
--escape)
ESCAPE=true
;;
--bell)
OUTPUT="$OUTPUT$BELL"
;;
--reset)
# 0m - reset all colors and attributes
# 2J - clear terminal
# 1;1H - move to 1,1
# ?25h - show cursor
OUTPUT="$OUTPUT${CSI}0m${CSI}2J${CSI}1;1H${CSI}?25h"
;;
--)
CONTINUE=false
shift
;;
*)
CONTINUE=false
;;
esac
if $CONTINUE; then
shift
fi
done
printf '%s' "$OUTPUT"
if $ESCAPE; then
printf '%s' "${1+"$@"}"
else
printf '%s' "${1+"$@"}"
fi
if $RESTORE; then
printf '%s' "$SUFFIX"
fi
if $NEWLINE; then
printf '\n'
fi
}
# Run if not sourced
if [[ "$0" == "${BASH_SOURCE[0]}" ]]; then
ansi "$@"
fi

334
bin/desk Executable file
View file

@ -0,0 +1,334 @@
#!/usr/bin/env bash
# vim: set filetype=sh:
PREFIX="${DESK_DIR:-$HOME/.desk}"
DESKS="${DESK_DESKS_DIR:-$PREFIX/desks}"
DESKFILE_NAME=Deskfile
## Commands
cmd_version() {
echo "◲ desk 0.6.0"
}
cmd_usage() {
cmd_version
echo
cat <<_EOF
Usage:
$PROGRAM
List the current desk and any associated aliases. If no desk
is being used, display available desks.
$PROGRAM init
Initialize desk configuration.
$PROGRAM (list|ls)
List all desks along with a description.
$PROGRAM (.|go) [<desk-name-or-path> [shell-args...]]
Activate a desk. Extra arguments are passed onto shell. If called with
no arguments, look for a Deskfile in the current directory. If not a
recognized desk, try as a path to directory containing a Deskfile.
$PROGRAM run <desk-name> <cmd>
Run a command within a desk's environment then exit. Think '\$SHELL -c'.
$PROGRAM edit [desk-name]
Edit (or create) a deskfile with the name specified, otherwise
edit the active deskfile.
$PROGRAM help
Show this text.
$PROGRAM version
Show version information.
Since desk spawns a shell, to deactivate and "pop" out a desk, you
simply need to exit or otherwise end the current shell process.
_EOF
}
cmd_init() {
if [ -d "$PREFIX" ]; then
echo "Desk dir already exists at ${PREFIX}"
exit 1
fi
read -p "Where do you want to store your deskfiles? (default: ${PREFIX}): " \
NEW_PREFIX
[ -z "${NEW_PREFIX}" ] && NEW_PREFIX="$PREFIX"
if [ ! -d "${NEW_PREFIX}" ]; then
echo "${NEW_PREFIX} doesn't exist, attempting to create."
mkdir -p "$NEW_PREFIX/desks"
fi
local SHELLTYPE=$(get_running_shell)
case "${SHELLTYPE}" in
bash) local SHELLRC="${HOME}/.bashrc" ;;
fish) local SHELLRC="${HOME}/.config/fish/config.fish" ;;
zsh) local SHELLRC="${HOME}/.zshrc" ;;
esac
read -p "Where's your shell rc file? (default: ${SHELLRC}): " \
USER_SHELLRC
[ -z "${USER_SHELLRC}" ] && USER_SHELLRC="$SHELLRC"
if [ ! -f "$USER_SHELLRC" ]; then
echo "${USER_SHELLRC} doesn't exist"
exit 1
fi
printf "\n# Hook for desk activation\n" >> "$USER_SHELLRC"
# Since the hook is appended to the rc file, its exit status becomes
# the exit status of `source $USER_SHELLRC` which typically
# indicates if something went wrong. If $DESK_ENV is void, `test`
# sets exit status to 1. That, however, is part of desk's normal
# operation, so we clear exit status after that.
if [ "$SHELLTYPE" == "fish" ]; then
echo "test -n \"\$DESK_ENV\"; and . \"\$DESK_ENV\"; or true" >> "$USER_SHELLRC"
else
echo "[ -n \"\$DESK_ENV\" ] && source \"\$DESK_ENV\" || true" >> "$USER_SHELLRC"
fi
echo "Done. Start adding desks to ${NEW_PREFIX}/desks!"
}
cmd_go() {
# TODESK ($1) may either be
#
# - the name of a desk in $DESKS/
# - a path to a Deskfile
# - a directory containing a Deskfile
# - empty -> `./Deskfile`
#
local TODESK="$1"
local DESKEXT=$(get_deskfile_extension)
local DESKPATH="$(find "${DESKS}/" -name "${TODESK}${DESKEXT}" 2>/dev/null)"
local POSSIBLE_DESKFILE_DIR="${TODESK%$DESKFILE_NAME}"
if [ -z "$POSSIBLE_DESKFILE_DIR" ]; then
POSSIBLE_DESKFILE_DIR="."
fi
# If nothing could be found in $DESKS/, check to see if this is a path to
# a Deskfile.
if [[ -z "$DESKPATH" && -d "$POSSIBLE_DESKFILE_DIR" ]]; then
if [ ! -f "${POSSIBLE_DESKFILE_DIR}/${DESKFILE_NAME}" ]; then
echo "No Deskfile found in '${POSSIBLE_DESKFILE_DIR}'"
exit 1
fi
local REALPATH=$( cd $POSSIBLE_DESKFILE_DIR && pwd )
DESKPATH="${REALPATH}/${DESKFILE_NAME}"
TODESK=$(basename "$REALPATH")
fi
# Shift desk name so we can forward on all arguments to the shell.
shift;
if [ -z "$DESKPATH" ]; then
echo "Desk $TODESK (${TODESK}${DESKEXT}) not found in $DESKS"
exit 1
else
local SHELL_EXEC="$(get_running_shell)"
DESK_NAME="${TODESK}" DESK_ENV="${DESKPATH}" exec "${SHELL_EXEC}" "$@"
fi
}
cmd_run() {
local TODESK="$1"
shift;
cmd_go "$TODESK" -ic "$@"
}
# Usage: desk list [options]
# Description: List all desks along with a description
# --only-names: List only the names of the desks
# --no-format: Use ' - ' to separate names from descriptions
cmd_list() {
if [ ! -d "${DESKS}/" ]; then
echo "No desk dir! Run 'desk init'."
exit 1
fi
local SHOW_DESCRIPTIONS DESKEXT AUTO_ALIGN name desc len longest out
while [[ $# -gt 0 ]]; do
case "$1" in
--only-names) SHOW_DESCRIPTIONS=false && AUTO_ALIGN=false ;;
--no-format) AUTO_ALIGN=false ;;
esac
shift
done
DESKEXT=$(get_deskfile_extension)
out=""
longest=0
while read -d '' -r f; do
name=$(basename "${f/${DESKEXT}//}")
if [[ "$SHOW_DESCRIPTIONS" = false ]]; then
out+="$name"$'\n'
else
desc=$(echo_description "$f")
out+="$name - $desc"$'\n'
len=${#name}
(( len > longest )) && longest=$len
fi
done < <(find "${DESKS}/" -name "*${DESKEXT}" -print0)
if [[ "$AUTO_ALIGN" != false ]]; then
print_aligned "$out" "$longest"
else
printf "%s" "$out"
fi
}
# Usage: desk [options]
# Description: List the current desk and any associated aliases. If no desk is being used, display available desks
# --no-format: Use ' - ' to separate alias/export/function names from their descriptions
cmd_current() {
if [ -z "$DESK_ENV" ]; then
printf "No desk activated.\n\n%s" "$(cmd_list)"
exit 1
fi
local DESKPATH=$DESK_ENV
local CALLABLES=$(get_callables "$DESKPATH")
local AUTO_ALIGN len longest out
while [[ $# -gt 0 ]]; do
case "$1" in
--no-format) AUTO_ALIGN=false ;;
esac
shift
done
printf "%s - %s\n" "$DESK_NAME" "$(echo_description "$DESKPATH")"
if [[ -n "$CALLABLES" ]]; then
longest=0
out=$'\n'
for NAME in $CALLABLES; do
# Last clause in the grep regexp accounts for fish functions.
len=$((${#NAME} + 2))
(( len > longest )) && longest=$len
local DOCLINE=$(
grep -B 1 -E \
"^(alias ${NAME}=|export ${NAME}=|(function )?${NAME}( )?\()|function $NAME" "$DESKPATH" \
| grep "#")
if [ -z "$DOCLINE" ]; then
out+=" ${NAME}"$'\n'
else
out+=" ${NAME} - ${DOCLINE##\# }"$'\n'
fi
done
if [[ "$AUTO_ALIGN" != false ]]; then
print_aligned "$out" "$longest"
else
printf "%s" "$out"
fi
fi
}
cmd_edit() {
if [ $# -eq 0 ]; then
if [ "$DESK_NAME" == "" ]; then
echo "No desk activated."
exit 3
fi
local DESKNAME_TO_EDIT="$DESK_NAME"
elif [ $# -eq 1 ]; then
local DESKNAME_TO_EDIT="$1"
else
echo "Usage: ${PROGRAM} edit [desk-name]"
exit 1
fi
local DESKEXT=$(get_deskfile_extension)
local EDIT_PATH="${DESKS}/${DESKNAME_TO_EDIT}${DESKEXT}"
${EDITOR:-vi} "$EDIT_PATH"
}
## Utilities
FNAME_CHARS='[a-zA-Z0-9_-]'
# Echo the description of a desk. $1 is the deskfile.
echo_description() {
local descline=$(grep -E "#\s+Description" "$1")
echo "${descline##*Description: }"
}
# Echo a list of aliases, exports, and functions for a given desk
get_callables() {
local DESKPATH=$1
grep -E "^(alias |export |(function )?${FNAME_CHARS}+ ?\()|function $NAME" "$DESKPATH" \
| sed 's/alias \([^= ]*\)=.*/\1/' \
| sed 's/export \([^= ]*\)=.*/\1/' \
| sed -E "s/(function )?(${FNAME_CHARS}+) ?\(\).*/\2/" \
| sed -E "s/function (${FNAME_CHARS}+).*/\1/"
}
get_running_shell() {
# Echo the name of the parent shell via procfs, if we have it available.
# Otherwise, try to use ps with bash's parent pid.
if [ -e /proc ]; then
# Get cmdline procfile of the process running this script.
local CMDLINE_FILE="/proc/$(grep PPid /proc/$$/status | cut -f2)/cmdline"
if [ -f "$CMDLINE_FILE" ]; then
# Strip out any verion that may be attached to the shell executable.
# Strip leading dash for login shells.
local CMDLINE_SHELL=$(sed -r -e 's/\x0.*//' -e 's/^-//' "$CMDLINE_FILE")
fi
else
# Strip leading dash for login shells.
# If the parent process is a login shell, guess bash.
local CMDLINE_SHELL=$(ps -o args -p $PPID | tail -1 | sed -e 's/login/bash/' -e 's/^-//')
fi
if [ ! -z "$CMDLINE_SHELL" ]; then
basename "$CMDLINE_SHELL"
exit
fi
# Fall back to $SHELL otherwise.
basename "$SHELL"
exit
}
# Echo the extension of recognized deskfiles (.fish for fish)
get_deskfile_extension() {
if [ "$(get_running_shell)" == "fish" ]; then
echo '.fish'
else
echo '.sh'
fi
}
# Echo first argument as key/value pairs aligned into two columns; second argument is the longest key
print_aligned() {
local out=$1 longest=$2
printf "%s" "$out" | awk -v padding="$longest" -F' - ' '{
printf "%-*s %s\n", padding, $1, substr($0, index($0, " - ")+2, length($0))
}'
}
PROGRAM="${0##*/}"
case "$1" in
init) shift; cmd_init "$@" ;;
help|--help) shift; cmd_usage "$@" ;;
version|--version) shift; cmd_version "$@" ;;
ls|list) shift; cmd_list "$@" ;;
go|.) shift; cmd_go "$@" ;;
run) shift; cmd_run "$@" ;;
edit) shift; cmd_edit "$@" ;;
*) cmd_current "$@" ;;
esac
exit 0

128
bin/is Executable file
View file

@ -0,0 +1,128 @@
#!/bin/bash
#
# Copyright (c) 2016 Józef Sokołowski
# Distributed under the MIT License
#
# For most current version checkout repository:
# https://github.com/qzb/is.sh
#
is() {
if [ "$1" == "--help" ]; then
cat << EOF
Conditions:
is equal VALUE_A VALUE_B
is matching REGEXP VALUE
is substring VALUE_A VALUE_B
is empty VALUE
is number VALUE
is gt NUMBER_A NUMBER_B
is lt NUMBER_A NUMBER_B
is ge NUMBER_A NUMBER_B
is le NUMBER_A NUMBER_B
is file PATH
is dir PATH
is link PATH
is existing PATH
is readable PATH
is writeable PATH
is executable PATH
is available COMMAND
is older PATH_A PATH_B
is newer PATH_A PATH_B
is true VALUE
is false VALUE
Negation:
is not equal VALUE_A VALUE_B
EOF
exit
fi
if [ "$1" == "--version" ]; then
echo "is.sh 1.1.0"
exit
fi
local condition="$1"
local value_a="$2"
local value_b="$3"
if [ "$condition" == "not" ]; then
shift 1
! is "${@}"
return $?
fi
if [ "$condition" == "a" ] || [ "$condition" == "an" ] || [ "$condition" == "the" ]; then
shift 1
is "${@}"
return $?
fi
case "$condition" in
file)
[ -f "$value_a" ]; return $?;;
dir|directory)
[ -d "$value_a" ]; return $?;;
link|symlink)
[ -L "$value_a" ]; return $?;;
existent|existing|exist|exists)
[ -e "$value_a" ]; return $?;;
readable)
[ -r "$value_a" ]; return $?;;
writeable)
[ -w "$value_a" ]; return $?;;
executable)
[ -x "$value_a" ]; return $?;;
available|installed)
which "$value_a"; return $?;;
empty)
[ -z "$value_a" ]; return $?;;
number)
echo "$value_a" | grep -E '^[0-9]+(\.[0-9]+)?$'; return $?;;
older)
[ "$value_a" -ot "$value_b" ]; return $?;;
newer)
[ "$value_a" -nt "$value_b" ]; return $?;;
gt)
is not a number "$value_a" && return 1;
is not a number "$value_b" && return 1;
awk "BEGIN {exit $value_a > $value_b ? 0 : 1}"; return $?;;
lt)
is not a number "$value_a" && return 1;
is not a number "$value_b" && return 1;
awk "BEGIN {exit $value_a < $value_b ? 0 : 1}"; return $?;;
ge)
is not a number "$value_a" && return 1;
is not a number "$value_b" && return 1;
awk "BEGIN {exit $value_a >= $value_b ? 0 : 1}"; return $?;;
le)
is not a number "$value_a" && return 1;
is not a number "$value_b" && return 1;
awk "BEGIN {exit $value_a <= $value_b ? 0 : 1}"; return $?;;
eq|equal)
[ "$value_a" = "$value_b" ] && return 0;
is not a number "$value_a" && return 1;
is not a number "$value_b" && return 1;
awk "BEGIN {exit $value_a == $value_b ? 0 : 1}"; return $?;;
match|matching)
echo "$value_b" | grep -xE "$value_a"; return $?;;
substr|substring)
echo "$value_b" | grep -F "$value_a"; return $?;;
true)
[ "$value_a" == true ] || [ "$value_a" == 0 ]; return $?;;
false)
[ "$value_a" != true ] && [ "$value_a" != 0 ]; return $?;;
esac > /dev/null
return 1
}
if is not equal "${BASH_SOURCE[0]}" "$0"; then
export -f is
else
is "${@}"
exit $?
fi

156
bin/lj Executable file
View file

@ -0,0 +1,156 @@
#!/usr/bin/env zsh
# Create a mapping of log levels to their names
typeset -A _log_levels
_log_levels=(
'emergency' 0
'alert' 1
'critical' 2
'error' 3
'warning' 4
'notice' 5
'info' 6
'debug' 7
)
###
# Output usage information and exit
###
function _lumberjack_usage() {
echo "\033[0;33mUsage:\033[0;m"
echo " lj [options] [<level>] <message>"
echo
echo "\033[0;33mOptions:\033[0;m"
echo " -h, --help Output help text and exit"
echo " -v, --version Output version information and exit"
echo " -f, --file Set the logfile and exit"
echo " -l, --level Set the log level and exit"
echo
echo "\033[0;33mLevels:\033[0;m"
echo " emergency"
echo " alert"
echo " critical"
echo " error"
echo " warning"
echo " notice"
echo " info"
echo " debug"
}
###
# Output the message to the logfile
###
function _lumberjack_message() {
local level="$1" file="$2" logtype="$3" msg="${(@)@:4}"
# If the file string is empty, output an error message
if [[ -z $file ]]; then
echo "\033[0;31mNo logfile has been set for this process. Use \`lumberjack --file /path/to/file\` to set it\033[0;m"
exit 1
fi
# If the level is not set, assume 5 (notice)
if [[ -z $level ]]; then
level=5
fi
case $logtype in
# If a valid logtype is passed
emergency|alert|critical|error|warning|notice|info|debug )
# We do nothing here
;;
# In all other cases
* )
# Second argument was not a log level, so manually set it to notice
# and include the first parameter in the message
logtype='notice'
msg="${(@)@:3}"
;;
esac
if [[ $_log_levels[$logtype] > $level ]]; then
# The message being recorded is for a higher log level than the one
# currently being recorded, so gracefully exit
exit 0
fi
# Output the message to the logfile
echo "[$(echo $logtype | tr '[a-z]' '[A-Z]')] [$(date '+%Y-%m-%d %H:%M:%S')] $msg" >> $file
}
###
# The main lumberjack process
###
function _lumberjack() {
local help version logfile loglevel dir statefile state
# Create the state directory if it doesn't exist
dir="${ZDOTDIR:-$HOME}/.lumberjack"
if [[ ! -d $dir ]]; then
mkdir -p $dir
fi
# If a statefile already exists, load the level and file
statefile="$dir/$PPID"
if [[ -f $statefile ]]; then
state=$(cat $statefile)
level="$state[1]"
file="${(@)state:2}"
fi
# Parse CLI options
zparseopts -D h=help -help=help \
v=version -version=version \
f:=logfile -file:=logfile \
l:=loglevel -level:=loglevel
# If the help option is passed, output usage information and exit
if [[ -n $help ]]; then
_lumberjack_usage
exit 0
fi
# If the version option is passed, output the version and exit
if [[ -n $version ]]; then
echo "0.1.1"
exit 0
fi
# If the logfile option is passed, set the current logfile
# for the parent process ID
if [[ -n $logfile ]]; then
shift logfile
file=$logfile
# Create the log file if it doesn't exist
if [[ ! -f $file ]]; then
touch $file
fi
fi
# If the loglevel option is passed, set the current loglevel
# for the parent process ID
if [[ -n $loglevel ]]; then
shift loglevel
level=$_log_levels[$loglevel]
fi
if [[ -z $level ]]; then
level=5
fi
# Check if we're setting options rather than logging
if [[ -n $logfile || -n $loglevel ]]; then
# Store the state
echo "$level $file" >! $statefile
# Exit gracefully
exit 0
fi
# Log the message
_lumberjack_message "$level" "$file" "$@"
}
_lumberjack "$@"

986
bin/mo Executable file
View file

@ -0,0 +1,986 @@
#!/usr/bin/env bash
#
#/ Mo is a mustache template rendering software written in bash. It inserts
#/ environment variables into templates.
#/
#/ Simply put, mo will change {{VARIABLE}} into the value of that
#/ environment variable. You can use {{#VARIABLE}}content{{/VARIABLE}} to
#/ conditionally display content or iterate over the values of an array.
#/
#/ Learn more about mustache templates at https://mustache.github.io/
#/
#/ Simple usage:
#/
#/ mo [--false] [--help] [--source=FILE] filenames...
#/
#/ --fail-not-set - Fail upon expansion of an unset variable.
#/ --false - Treat the string "false" as empty for conditionals.
#/ --help - This message.
#/ --source=FILE - Load FILE into the environment before processing templates.
#
# Mo is under a MIT style licence with an additional non-advertising clause.
# See LICENSE.md for the full text.
#
# This is open source! Please feel free to contribute.
#
# https://github.com/tests-always-included/mo
# Public: Template parser function. Writes templates to stdout.
#
# $0 - Name of the mo file, used for getting the help message.
# --fail-not-set - Fail upon expansion of an unset variable. Default behavior
# is to silently ignore and expand into empty string.
# --false - Treat "false" as an empty value. You may set the
# MO_FALSE_IS_EMPTY environment variable instead to a non-empty
# value to enable this behavior.
# --help - Display a help message.
# --source=FILE - Source a file into the environment before processint
# template files.
# -- - Used to indicate the end of options. You may optionally
# use this when filenames may start with two hyphens.
# $@ - Filenames to parse.
#
# Mo uses the following environment variables:
#
# MO_FAIL_ON_UNSET - When set to a non-empty value, expansion of an unset
# env variable will be aborted with an error.
# MO_FALSE_IS_EMPTY - When set to a non-empty value, the string "false"
# will be treated as an empty value for the purposes
# of conditionals.
# MO_ORIGINAL_COMMAND - Used to find the `mo` program in order to generate
# a help message.
#
# Returns nothing.
mo() (
# This function executes in a subshell so IFS is reset.
# Namespace this variable so we don't conflict with desired values.
local moContent f2source files doubleHyphens
IFS=$' \n\t'
files=()
doubleHyphens=false
if [[ $# -gt 0 ]]; then
for arg in "$@"; do
if $doubleHyphens; then
# After we encounter two hyphens together, all the rest
# of the arguments are files.
files=("${files[@]}" "$arg")
else
case "$arg" in
-h|--h|--he|--hel|--help|-\?)
moUsage "$0"
exit 0
;;
--fail-not-set)
# shellcheck disable=SC2030
MO_FAIL_ON_UNSET=true
;;
--false)
# shellcheck disable=SC2030
MO_FALSE_IS_EMPTY=true
;;
--source=*)
f2source="${arg#--source=}"
if [[ -f "$f2source" ]]; then
# shellcheck disable=SC1090
. "$f2source"
else
echo "No such file: $f2source" >&2
exit 1
fi
;;
--)
# Set a flag indicating we've encountered double hyphens
doubleHyphens=true
;;
*)
# Every arg that is not a flag or a option should be a file
files=(${files[@]+"${files[@]}"} "$arg")
;;
esac
fi
done
fi
moGetContent moContent "${files[@]}" || return 1
moParse "$moContent" "" true
)
# Internal: Scan content until the right end tag is found. Creates an array
# with the following members:
#
# [0] = Content before end tag
# [1] = End tag (complete tag)
# [2] = Content after end tag
#
# Everything using this function uses the "standalone tags" logic.
#
# $1 - Name of variable for the array
# $2 - Content
# $3 - Name of end tag
# $4 - If -z, do standalone tag processing before finishing
#
# Returns nothing.
moFindEndTag() {
local content remaining scanned standaloneBytes tag
#: Find open tags
scanned=""
moSplit content "$2" '{{' '}}'
while [[ "${#content[@]}" -gt 1 ]]; do
moTrimWhitespace tag "${content[1]}"
#: Restore content[1] before we start using it
content[1]='{{'"${content[1]}"'}}'
case $tag in
'#'* | '^'*)
#: Start another block
scanned="${scanned}${content[0]}${content[1]}"
moTrimWhitespace tag "${tag:1}"
moFindEndTag content "${content[2]}" "$tag" "loop"
scanned="${scanned}${content[0]}${content[1]}"
remaining=${content[2]}
;;
'/'*)
#: End a block - could be ours
moTrimWhitespace tag "${tag:1}"
scanned="$scanned${content[0]}"
if [[ "$tag" == "$3" ]]; then
#: Found our end tag
if [[ -z "${4-}" ]] && moIsStandalone standaloneBytes "$scanned" "${content[2]}" true; then
#: This is also a standalone tag - clean up whitespace
#: and move those whitespace bytes to the "tag" element
standaloneBytes=( $standaloneBytes )
content[1]="${scanned:${standaloneBytes[0]}}${content[1]}${content[2]:0:${standaloneBytes[1]}}"
scanned="${scanned:0:${standaloneBytes[0]}}"
content[2]="${content[2]:${standaloneBytes[1]}}"
fi
local "$1" && moIndirectArray "$1" "$scanned" "${content[1]}" "${content[2]}"
return 0
fi
scanned="$scanned${content[1]}"
remaining=${content[2]}
;;
*)
#: Ignore all other tags
scanned="${scanned}${content[0]}${content[1]}"
remaining=${content[2]}
;;
esac
moSplit content "$remaining" '{{' '}}'
done
#: Did not find our closing tag
scanned="$scanned${content[0]}"
local "$1" && moIndirectArray "$1" "${scanned}" "" ""
}
# Internal: Find the first index of a substring. If not found, sets the
# index to -1.
#
# $1 - Destination variable for the index
# $2 - Haystack
# $3 - Needle
#
# Returns nothing.
moFindString() {
local pos string
string=${2%%$3*}
[[ "$string" == "$2" ]] && pos=-1 || pos=${#string}
local "$1" && moIndirect "$1" "$pos"
}
# Internal: Generate a dotted name based on current context and target name.
#
# $1 - Target variable to store results
# $2 - Context name
# $3 - Desired variable name
#
# Returns nothing.
moFullTagName() {
if [[ -z "${2-}" ]] || [[ "$2" == *.* ]]; then
local "$1" && moIndirect "$1" "$3"
else
local "$1" && moIndirect "$1" "${2}.${3}"
fi
}
# Internal: Fetches the content to parse into a variable. Can be a list of
# partials for files or the content from stdin.
#
# $1 - Variable name to assign this content back as
# $2-@ - File names (optional)
#
# Returns nothing.
moGetContent() {
local content filename target
target=$1
shift
if [[ "${#@}" -gt 0 ]]; then
content=""
for filename in "$@"; do
#: This is so relative paths work from inside template files
content="$content"'{{>'"$filename"'}}'
done
else
moLoadFile content /dev/stdin || return 1
fi
local "$target" && moIndirect "$target" "$content"
}
# Internal: Indent a string, placing the indent at the beginning of every
# line that has any content.
#
# $1 - Name of destination variable to get an array of lines
# $2 - The indent string
# $3 - The string to reindent
#
# Returns nothing.
moIndentLines() {
local content fragment len posN posR result trimmed
result=""
#: Remove the period from the end of the string.
len=$((${#3} - 1))
content=${3:0:$len}
if [[ -z "${2-}" ]]; then
local "$1" && moIndirect "$1" "$content"
return 0
fi
moFindString posN "$content" $'\n'
moFindString posR "$content" $'\r'
while [[ "$posN" -gt -1 ]] || [[ "$posR" -gt -1 ]]; do
if [[ "$posN" -gt -1 ]]; then
fragment="${content:0:$posN + 1}"
content=${content:$posN + 1}
else
fragment="${content:0:$posR + 1}"
content=${content:$posR + 1}
fi
moTrimChars trimmed "$fragment" false true " " $'\t' $'\n' $'\r'
if [[ -n "$trimmed" ]]; then
fragment="$2$fragment"
fi
result="$result$fragment"
moFindString posN "$content" $'\n'
moFindString posR "$content" $'\r'
# If the content ends in a newline, do not indent.
if [[ "$posN" -eq ${#content} ]]; then
# Special clause for \r\n
if [[ "$posR" -eq "$((posN - 1))" ]]; then
posR=-1
fi
posN=-1
fi
if [[ "$posR" -eq ${#content} ]]; then
posR=-1
fi
done
moTrimChars trimmed "$content" false true " " $'\t'
if [[ -n "$trimmed" ]]; then
content="$2$content"
fi
result="$result$content"
local "$1" && moIndirect "$1" "$result"
}
# Internal: Send a variable up to the parent of the caller of this function.
#
# $1 - Variable name
# $2 - Value
#
# Examples
#
# callFunc () {
# local "$1" && moIndirect "$1" "the value"
# }
# callFunc dest
# echo "$dest" # writes "the value"
#
# Returns nothing.
moIndirect() {
unset -v "$1"
printf -v "$1" '%s' "$2"
}
# Internal: Send an array as a variable up to caller of a function
#
# $1 - Variable name
# $2-@ - Array elements
#
# Examples
#
# callFunc () {
# local myArray=(one two three)
# local "$1" && moIndirectArray "$1" "${myArray[@]}"
# }
# callFunc dest
# echo "${dest[@]}" # writes "one two three"
#
# Returns nothing.
moIndirectArray() {
unset -v "$1"
# IFS must be set to a string containing space or unset in order for
# the array slicing to work regardless of the current IFS setting on
# bash 3. This is detailed further at
# https://github.com/fidian/gg-core/pull/7
eval "$(printf "IFS= %s=(\"\${@:2}\") IFS=%q" "$1" "$IFS")"
}
# Internal: Determine if a given environment variable exists and if it is
# an array.
#
# $1 - Name of environment variable
#
# Be extremely careful. Even if strict mode is enabled, it is not honored
# in newer versions of Bash. Any errors that crop up here will not be
# caught automatically.
#
# Examples
#
# var=(abc)
# if moIsArray var; then
# echo "This is an array"
# echo "Make sure you don't accidentally use \$var"
# fi
#
# Returns 0 if the name is not empty, 1 otherwise.
moIsArray() {
# Namespace this variable so we don't conflict with what we're testing.
local moTestResult
moTestResult=$(declare -p "$1" 2>/dev/null) || return 1
[[ "${moTestResult:0:10}" == "declare -a" ]] && return 0
[[ "${moTestResult:0:10}" == "declare -A" ]] && return 0
return 1
}
# Internal: Determine if the given name is a defined function.
#
# $1 - Function name to check
#
# Be extremely careful. Even if strict mode is enabled, it is not honored
# in newer versions of Bash. Any errors that crop up here will not be
# caught automatically.
#
# Examples
#
# moo () {
# echo "This is a function"
# }
# if moIsFunction moo; then
# echo "moo is a defined function"
# fi
#
# Returns 0 if the name is a function, 1 otherwise.
moIsFunction() {
local functionList functionName
functionList=$(declare -F)
functionList=( ${functionList//declare -f /} )
for functionName in "${functionList[@]}"; do
if [[ "$functionName" == "$1" ]]; then
return 0
fi
done
return 1
}
# Internal: Determine if the tag is a standalone tag based on whitespace
# before and after the tag.
#
# Passes back a string containing two numbers in the format "BEFORE AFTER"
# like "27 10". It indicates the number of bytes remaining in the "before"
# string (27) and the number of bytes to trim in the "after" string (10).
# Useful for string manipulation:
#
# $1 - Variable to set for passing data back
# $2 - Content before the tag
# $3 - Content after the tag
# $4 - true/false: is this the beginning of the content?
#
# Examples
#
# moIsStandalone RESULT "$before" "$after" false || return 0
# RESULT_ARRAY=( $RESULT )
# echo "${before:0:${RESULT_ARRAY[0]}}...${after:${RESULT_ARRAY[1]}}"
#
# Returns nothing.
moIsStandalone() {
local afterTrimmed beforeTrimmed char
moTrimChars beforeTrimmed "$2" false true " " $'\t'
moTrimChars afterTrimmed "$3" true false " " $'\t'
char=$((${#beforeTrimmed} - 1))
char=${beforeTrimmed:$char}
# If the content before didn't end in a newline
if [[ "$char" != $'\n' ]] && [[ "$char" != $'\r' ]]; then
# and there was content or this didn't start the file
if [[ -n "$char" ]] || ! $4; then
# then this is not a standalone tag.
return 1
fi
fi
char=${afterTrimmed:0:1}
# If the content after doesn't start with a newline and it is something
if [[ "$char" != $'\n' ]] && [[ "$char" != $'\r' ]] && [[ -n "$char" ]]; then
# then this is not a standalone tag.
return 2
fi
if [[ "$char" == $'\r' ]] && [[ "${afterTrimmed:1:1}" == $'\n' ]]; then
char="$char"$'\n'
fi
local "$1" && moIndirect "$1" "$((${#beforeTrimmed})) $((${#3} + ${#char} - ${#afterTrimmed}))"
}
# Internal: Join / implode an array
#
# $1 - Variable name to receive the joined content
# $2 - Joiner
# $3-$* - Elements to join
#
# Returns nothing.
moJoin() {
local joiner part result target
target=$1
joiner=$2
result=$3
shift 3
for part in "$@"; do
result="$result$joiner$part"
done
local "$target" && moIndirect "$target" "$result"
}
# Internal: Read a file into a variable.
#
# $1 - Variable name to receive the file's content
# $2 - Filename to load
#
# Returns nothing.
moLoadFile() {
local content len
# The subshell removes any trailing newlines. We forcibly add
# a dot to the content to preserve all newlines.
# As a future optimization, it would be worth considering removing
# cat and replacing this with a read loop.
content=$(cat -- "$2" && echo '.') || return 1
len=$((${#content} - 1))
content=${content:0:$len} # Remove last dot
local "$1" && moIndirect "$1" "$content"
}
# Internal: Process a chunk of content some number of times. Writes output
# to stdout.
#
# $1 - Content to parse repeatedly
# $2 - Tag prefix (context name)
# $3-@ - Names to insert into the parsed content
#
# Returns nothing.
moLoop() {
local content context contextBase
content=$1
contextBase=$2
shift 2
while [[ "${#@}" -gt 0 ]]; do
moFullTagName context "$contextBase" "$1"
moParse "$content" "$context" false
shift
done
}
# Internal: Parse a block of text, writing the result to stdout.
#
# $1 - Block of text to change
# $2 - Current name (the variable NAME for what {{.}} means)
# $3 - true when no content before this, false otherwise
#
# Returns nothing.
moParse() {
# Keep naming variables mo* here to not overwrite needed variables
# used in the string replacements
local moBlock moContent moCurrent moIsBeginning moNextIsBeginning moTag
moCurrent=$2
moIsBeginning=$3
# Find open tags
moSplit moContent "$1" '{{' '}}'
while [[ "${#moContent[@]}" -gt 1 ]]; do
moTrimWhitespace moTag "${moContent[1]}"
moNextIsBeginning=false
case $moTag in
'#'*)
# Loop, if/then, or pass content through function
# Sets context
moStandaloneAllowed moContent "${moContent[@]}" "$moIsBeginning"
moTrimWhitespace moTag "${moTag:1}"
moFindEndTag moBlock "$moContent" "$moTag"
moFullTagName moTag "$moCurrent" "$moTag"
if moTest "$moTag"; then
# Show / loop / pass through function
if moIsFunction "$moTag"; then
#: Consider piping the output to moGetContent
#: so the lambda does not execute in a subshell?
moContent=$($moTag "${moBlock[0]}")
moParse "$moContent" "$moCurrent" false
moContent="${moBlock[2]}"
elif moIsArray "$moTag"; then
eval "moLoop \"\${moBlock[0]}\" \"$moTag\" \"\${!${moTag}[@]}\""
else
moParse "${moBlock[0]}" "$moCurrent" false
fi
fi
moContent="${moBlock[2]}"
;;
'>'*)
# Load partial - get name of file relative to cwd
moPartial moContent "${moContent[@]}" "$moIsBeginning" "$moCurrent"
moNextIsBeginning=${moContent[1]}
moContent=${moContent[0]}
;;
'/'*)
# Closing tag - If hit in this loop, we simply ignore
# Matching tags are found in moFindEndTag
moStandaloneAllowed moContent "${moContent[@]}" "$moIsBeginning"
;;
'^'*)
# Display section if named thing does not exist
moStandaloneAllowed moContent "${moContent[@]}" "$moIsBeginning"
moTrimWhitespace moTag "${moTag:1}"
moFindEndTag moBlock "$moContent" "$moTag"
moFullTagName moTag "$moCurrent" "$moTag"
if ! moTest "$moTag"; then
moParse "${moBlock[0]}" "$moCurrent" false "$moCurrent"
fi
moContent="${moBlock[2]}"
;;
'!'*)
# Comment - ignore the tag content entirely
# Trim spaces/tabs before the comment
moStandaloneAllowed moContent "${moContent[@]}" "$moIsBeginning"
;;
.)
# Current content (environment variable or function)
moStandaloneDenied moContent "${moContent[@]}"
moShow "$moCurrent" "$moCurrent"
;;
'=')
# Change delimiters
# Any two non-whitespace sequences separated by whitespace.
# This tag is ignored.
moStandaloneAllowed moContent "${moContent[@]}" "$moIsBeginning"
;;
'{'*)
# Unescaped - split on }}} not }}
moStandaloneDenied moContent "${moContent[@]}"
moContent="${moTag:1}"'}}'"$moContent"
moSplit moContent "$moContent" '}}}'
moTrimWhitespace moTag "${moContent[0]}"
moFullTagName moTag "$moCurrent" "$moTag"
moContent=${moContent[1]}
# Now show the value
moShow "$moTag" "$moCurrent"
;;
'&'*)
# Unescaped
moStandaloneDenied moContent "${moContent[@]}"
moTrimWhitespace moTag "${moTag:1}"
moFullTagName moTag "$moCurrent" "$moTag"
moShow "$moTag" "$moCurrent"
;;
*)
# Normal environment variable or function call
moStandaloneDenied moContent "${moContent[@]}"
moFullTagName moTag "$moCurrent" "$moTag"
moShow "$moTag" "$moCurrent"
;;
esac
moIsBeginning=$moNextIsBeginning
moSplit moContent "$moContent" '{{' '}}'
done
echo -n "${moContent[0]}"
}
# Internal: Process a partial.
#
# Indentation should be applied to the entire partial.
#
# This sends back the "is beginning" flag because the newline after a
# standalone partial is consumed. That newline is very important in the middle
# of content. We send back this flag to reset the processing loop's
# `moIsBeginning` variable, so the software thinks we are back at the
# beginning of a file and standalone processing continues to work.
#
# Prefix all variables.
#
# $1 - Name of destination variable. Element [0] is the content, [1] is the
# true/false flag indicating if we are at the beginning of content.
# $2 - Content before the tag that was not yet written
# $3 - Tag content
# $4 - Content after the tag
# $5 - true/false: is this the beginning of the content?
# $6 - Current context name
#
# Returns nothing.
moPartial() {
# Namespace variables here to prevent conflicts.
local moContent moFilename moIndent moIsBeginning moPartial moStandalone moUnindented
if moIsStandalone moStandalone "$2" "$4" "$5"; then
moStandalone=( $moStandalone )
echo -n "${2:0:${moStandalone[0]}}"
moIndent=${2:${moStandalone[0]}}
moContent=${4:${moStandalone[1]}}
moIsBeginning=true
else
moIndent=""
echo -n "$2"
moContent=$4
moIsBeginning=$5
fi
moTrimWhitespace moFilename "${3:1}"
# Execute in subshell to preserve current cwd and environment
(
# It would be nice to remove `dirname` and use a function instead,
# but that's difficult when you're only given filenames.
cd "$(dirname -- "$moFilename")" || exit 1
moUnindented="$(
moLoadFile moPartial "${moFilename##*/}" || exit 1
moParse "${moPartial}" "$6" true
# Fix bash handling of subshells and keep trailing whitespace.
# This is removed in moIndentLines.
echo -n "."
)" || exit 1
moIndentLines moPartial "$moIndent" "$moUnindented"
echo -n "$moPartial"
) || exit 1
# If this is a standalone tag, the trailing newline after the tag is
# removed and the contents of the partial are added, which typically
# contain a newline. We need to send a signal back to the processing
# loop that the moIsBeginning flag needs to be turned on again.
#
# [0] is the content, [1] is that flag.
local "$1" && moIndirectArray "$1" "$moContent" "$moIsBeginning"
}
# Internal: Show an environment variable or the output of a function to
# stdout.
#
# Limit/prefix any variables used.
#
# $1 - Name of environment variable or function
# $2 - Current context
#
# Returns nothing.
moShow() {
# Namespace these variables
local moJoined moNameParts
if moIsFunction "$1"; then
CONTENT=$($1 "")
moParse "$CONTENT" "$2" false
return 0
fi
moSplit moNameParts "$1" "."
if [[ -z "${moNameParts[1]}" ]]; then
if moIsArray "$1"; then
eval moJoin moJoined "," "\${$1[@]}"
echo -n "$moJoined"
else
# shellcheck disable=SC2031
if [[ -z "$MO_FAIL_ON_UNSET" ]] || moTestVarSet "$1"; then
echo -n "${!1}"
else
echo "Env variable not set: $1" >&2
exit 1
fi
fi
else
# Further subindexes are disallowed
eval "echo -n \"\${${moNameParts[0]}[${moNameParts[1]%%.*}]}\""
fi
}
# Internal: Split a larger string into an array.
#
# $1 - Destination variable
# $2 - String to split
# $3 - Starting delimiter
# $4 - Ending delimiter (optional)
#
# Returns nothing.
moSplit() {
local pos result
result=( "$2" )
moFindString pos "${result[0]}" "$3"
if [[ "$pos" -ne -1 ]]; then
# The first delimiter was found
result[1]=${result[0]:$pos + ${#3}}
result[0]=${result[0]:0:$pos}
if [[ -n "${4-}" ]]; then
moFindString pos "${result[1]}" "$4"
if [[ "$pos" -ne -1 ]]; then
# The second delimiter was found
result[2]="${result[1]:$pos + ${#4}}"
result[1]="${result[1]:0:$pos}"
fi
fi
fi
local "$1" && moIndirectArray "$1" "${result[@]}"
}
# Internal: Handle the content for a standalone tag. This means removing
# whitespace (not newlines) before a tag and whitespace and a newline after
# a tag. That is, assuming, that the line is otherwise empty.
#
# $1 - Name of destination "content" variable.
# $2 - Content before the tag that was not yet written
# $3 - Tag content (not used)
# $4 - Content after the tag
# $5 - true/false: is this the beginning of the content?
#
# Returns nothing.
moStandaloneAllowed() {
local bytes
if moIsStandalone bytes "$2" "$4" "$5"; then
bytes=( $bytes )
echo -n "${2:0:${bytes[0]}}"
local "$1" && moIndirect "$1" "${4:${bytes[1]}}"
else
echo -n "$2"
local "$1" && moIndirect "$1" "$4"
fi
}
# Internal: Handle the content for a tag that is never "standalone". No
# adjustments are made for newlines and whitespace.
#
# $1 - Name of destination "content" variable.
# $2 - Content before the tag that was not yet written
# $3 - Tag content (not used)
# $4 - Content after the tag
#
# Returns nothing.
moStandaloneDenied() {
echo -n "$2"
local "$1" && moIndirect "$1" "$4"
}
# Internal: Determines if the named thing is a function or if it is a
# non-empty environment variable. When MO_FALSE_IS_EMPTY is set to a
# non-empty value, then "false" is also treated is an empty value.
#
# Do not use variables without prefixes here if possible as this needs to
# check if any name exists in the environment
#
# $1 - Name of environment variable or function
# $2 - Current value (our context)
# MO_FALSE_IS_EMPTY - When set to a non-empty value, this will say the
# string value "false" is empty.
#
# Returns 0 if the name is not empty, 1 otherwise. When MO_FALSE_IS_EMPTY
# is set, this returns 1 if the name is "false".
moTest() {
# Test for functions
moIsFunction "$1" && return 0
if moIsArray "$1"; then
# Arrays must have at least 1 element
eval "[[ \"\${#${1}[@]}\" -gt 0 ]]" && return 0
else
# If MO_FALSE_IS_EMPTY is set, then return 1 if the value of
# the variable is "false".
# shellcheck disable=SC2031
[[ -n "${MO_FALSE_IS_EMPTY-}" ]] && [[ "${!1-}" == "false" ]] && return 1
# Environment variables must not be empty
[[ -n "${!1-}" ]] && return 0
fi
return 1
}
# Internal: Determine if a variable is assigned, even if it is assigned an empty
# value.
#
# $1 - Variable name to check.
#
# Returns true (0) if the variable is set, 1 if the variable is unset.
moTestVarSet() {
[[ "${!1-a}" == "${!1-b}" ]]
}
# Internal: Trim the leading whitespace only.
#
# $1 - Name of destination variable
# $2 - The string
# $3 - true/false - trim front?
# $4 - true/false - trim end?
# $5-@ - Characters to trim
#
# Returns nothing.
moTrimChars() {
local back current front last target varName
target=$1
current=$2
front=$3
back=$4
last=""
shift 4 # Remove target, string, trim front flag, trim end flag
while [[ "$current" != "$last" ]]; do
last=$current
for varName in "$@"; do
$front && current="${current/#$varName}"
$back && current="${current/%$varName}"
done
done
local "$target" && moIndirect "$target" "$current"
}
# Internal: Trim leading and trailing whitespace from a string.
#
# $1 - Name of variable to store trimmed string
# $2 - The string
#
# Returns nothing.
moTrimWhitespace() {
local result
moTrimChars result "$2" true true $'\r' $'\n' $'\t' " "
local "$1" && moIndirect "$1" "$result"
}
# Internal: Displays the usage for mo. Pulls this from the file that
# contained the `mo` function. Can only work when the right filename
# comes is the one argument, and that only happens when `mo` is called
# with `$0` set to this file.
#
# $1 - Filename that has the help message
#
# Returns nothing.
moUsage() {
grep '^#/' "${MO_ORIGINAL_COMMAND}" | cut -c 4-
}
# Save the original command's path for usage later
MO_ORIGINAL_COMMAND="$(cd "${BASH_SOURCE[0]%/*}" || exit 1; pwd)/${BASH_SOURCE[0]##*/}"
# If sourced, load all functions.
# If executed, perform the actions as expected.
if [[ "$0" == "${BASH_SOURCE[0]}" ]] || [[ -z "${BASH_SOURCE[0]}" ]]; then
mo "$@"
fi

490
bin/shml Executable file
View file

@ -0,0 +1,490 @@
#!/usr/bin/env bash
#SHML:START
#************************************************#
# SHML - Shell Markup Language Framework
# v1.0.3
# (MIT)
# by Justin Dorfman - @jdorfman
# && Joshua Mervine - @mervinej
#
# https://maxcdn.github.io/shml/
#************************************************#
# Foreground (Text)
##
fgcolor() {
local __end='\033[39m'
local __color=$__end # end by default
case "$1" in
end|off|reset) __color=$__end;;
black|000000) __color='\033[30m';;
red|F00BAF) __color='\033[31m';;
green|00CD00) __color='\033[32m';;
yellow|CDCD00) __color='\033[33m';;
blue|0286fe) __color='\033[34m';;
magenta|e100cc) __color='\033[35m';;
cyan|00d3cf) __color='\033[36m';;
gray|e4e4e4) __color='\033[90m';;
darkgray|4c4c4c) __color='\033[91m';;
lightgreen|00fe00) __color='\033[92m';;
lightyellow|f8fe00) __color='\033[93m';;
lightblue|3a80b5) __color='\033[94m';;
lightmagenta|fe00fe) __color='\033[95m';;
lightcyan|00fefe) __color='\033[96m';;
white|ffffff) __color='\033[97m';;
esac
if test "$2"; then
echo -en "$__color$2$__end"
else
echo -en "$__color"
fi
}
# Backwards Compatibility
color() {
fgcolor "$@"
}
# Aliases
fgc() {
fgcolor "$@"
}
c() {
fgcolor "$@"
}
# Background
##
bgcolor() {
local __end='\033[49m'
local __color=$__end # end by default
case "$1" in
end|off|reset) __color=$__end;;
black|000000) __color='\033[40m';;
red|F00BAF) __color='\033[41m';;
green|00CD00) __color='\033[42m';;
yellow|CDCD00) __color='\033[43m';;
blue|0286fe) __color='\033[44m';;
magenta|e100cc) __color='\033[45m';;
cyan|00d3cf) __color='\033[46m';;
gray|e4e4e4) __color='\033[47m';;
darkgray|4c4c4c) __color='\033[100m';;
lightred) __color='\033[101m';;
lightgreen|00fe00) __color='\033[102m';;
lightyellow|f8fe00) __color='\033[103m';;
lightblue|3a80b5) __color='\033[104m';;
lightmagenta|fe00fe) __color='\033[105m';;
lightcyan|00fefe) __color='\033[106m';;
white|fffff) __color='\033[107m';;
esac
if test "$2"; then
echo -en "$__color$2$__end"
else
echo -en "$__color"
fi
}
#Backwards Compatibility
background() {
bgcolor "$@"
}
#Aliases
bgc() {
bgcolor "$@"
}
bg() {
bgcolor "$@"
}
## Color Bar
color-bar() {
if test "$2"; then
for i in "$@"; do
echo -en "$(background "$i" " ")"
done; echo
else
for i in {16..21}{21..16}; do
echo -en "\033[48;5;${i}m \033[0m"
done; echo
fi
}
#Alises
cb() {
color-bar "$@"
}
bar() {
color-bar "$@"
}
## Attributes
##
attribute() {
local __end='\033[0m'
local __attr=$__end # end by default
case "$1" in
end|off|reset) __attr=$__end;;
bold) __attr='\033[1m';;
dim) __attr='\033[2m';;
underline) __attr='\033[4m';;
blink) __attr='\033[5m';;
invert) __attr='\033[7m';;
hidden) __attr='\033[8m';;
esac
if test "$2"; then
echo -en "$__attr$2$__end"
else
echo -en "$__attr"
fi
}
a() {
attribute "$@"
}
## Elements
br() {
echo -e "\n\r"
}
tab() {
echo -e "\t"
}
indent() {
local __len=4
if test "$1"; then
if [[ $1 =~ $re ]] ; then
__len=$1
fi
fi
while [ $__len -gt 0 ]; do
echo -n " "
__len=$(( $__len - 1 ))
done
}
i() {
indent "$@"
}
hr() {
local __len=60
local __char='-'
if ! test "$2"; then
re='^[0-9]+$'
if [[ $1 =~ $re ]] ; then
__len=$1
elif test "$1"; then
__char=$1
fi
else
__len=$2
__char=$1
fi
while [ $__len -gt 0 ]; do
echo -n "$__char"
__len=$(( $__len - 1 ))
done
}
# Icons
##
icon() {
local i='';
case "$1" in
check|checkmark) i='\xE2\x9C\x93';;
X|x|xmark) i='\xE2\x9C\x98';;
'<3'|heart) i='\xE2\x9D\xA4';;
sun) i='\xE2\x98\x80';;
'*'|star) i='\xE2\x98\x85';;
darkstar) i='\xE2\x98\x86';;
umbrella) i='\xE2\x98\x82';;
flag) i='\xE2\x9A\x91';;
snow|snowflake) i='\xE2\x9D\x84';;
music) i='\xE2\x99\xAB';;
scissors) i='\xE2\x9C\x82';;
tm|trademark) i='\xE2\x84\xA2';;
copyright) i='\xC2\xA9';;
apple) i='\xEF\xA3\xBF';;
skull|bones) i='\xE2\x98\xA0';;
':-)'|':)'|smile|face) i='\xE2\x98\xBA';;
*)
entity $1; return 0;;
esac
echo -ne "$i";
}
emoji() {
local i=""
case "$1" in
1F603|smiley|'=)'|':-)'|':)') i='😃';;
1F607|innocent|halo) i='😇';;
1F602|joy|lol|laughing) i='😂';;
1F61B|tongue|'=p'|'=P') i='😛';;
1F60A|blush|'^^'|blushing) i='😊';;
1F61F|worried|sadface|sad) i='😟';;
1F622|cry|crying|tear) i='😢';;
1F621|rage|redface) i='😡';;
1F44B|wave|hello|goodbye) i='👋';;
1F44C|ok_hand|perfect|okay|nice) i='👌';;
1F44D|thumbsup|+1|like) i='👍';;
1F44E|thumbsdown|-1|no|dislike) i='👎';;
1F63A|smiley_cat|happycat) i='😺';;
1F431|cat|kitten|:3|kitty) i='🐱';;
1F436|dog|puppy) i='🐶';;
1F41D|bee|honeybee|bumblebee) i='🐝';;
1F437|pig|pighead) i='🐷';;
1F435|monkey_face|monkey) i='🐵';;
1F42E|cow|happycow) i='🐮';;
1F43C|panda_face|panda|shpanda) i='🐼';;
1F363|sushi|raw|sashimi) i='🍣';;
1F3E0|home|house) i='🏠';;
1F453|eyeglasses|bifocals) i='👓';;
1F6AC|smoking|smoke|cigarette) i='🚬';;
1F525|fire|flame|hot|snapstreak) i='🔥';;
1F4A9|hankey|poop|shit) i='💩';;
1F37A|beer|homebrew|brew) i='🍺';;
1F36A|cookie|biscuit|chocolate) i='🍪';;
1F512|lock|padlock|secure) i='🔒';;
1F513|unlock|openpadlock) i='🔓';;
2B50|star|yellowstar) i='⭐';;
1F0CF|black_joker|joker|wild) i='🃏';;
2705|white_check_mark|check) i='✅';;
274C|x|cross|xmark) i='❌';;
1F6BD|toilet|restroom|loo) i='🚽';;
1F514|bell|ringer|ring) i='🔔';;
1F50E|mag_right|search|magnify) i='🔎';;
1F3AF|dart|bullseye|darts) i='🎯';;
1F4B5|dollar|cash|cream) i='💵';;
1F4AD|thought_balloon|thinking) i='💭';;
1F340|four_leaf_clover|luck) i='🍀';;
*)
#entity $1; return 0;;
esac
echo -ne "$i"
}
function e {
emoji "$@"
}
#SHML:END
# Usage / Examples
##
if [[ "$(basename -- "$0")" = "shml.sh" ]]; then
I=2
echo -e "
$(a bold 'SHML Usage / Help')
$(hr '=')
$(a bold 'Section 0: Sourcing')
$(hr '-')
$(i $I)When installed in path:
$(i $I) source \$(which shml.sh)
$(i $I)When installed locally:
$(i $I) source ./shml.sh
$(a bold 'Section 1: Foreground')
$(hr '-')
$(i $I)\$(color red \"foo bar\")
$(i $I)$(color red "foo bar")
$(i $I)\$(color blue \"foo bar\")
$(i $I)$(color blue "foo bar")
$(i $I)\$(fgcolor green)
$(i $I) >>foo bar<<
$(i $I) >>bah boo<<
$(i $I)\$(fgcolor end)
$(i $I)$(fgcolor green)
$(i $I)>>foo bar<<
$(i $I)>>bah boo<<
$(i $I)$(fgcolor end)
$(i $I)Short Hand: $(a underline 'c')
$(i $I)\$(c red 'foo')
$(i $I)Argument list:
$(i $I)black, red, green, yellow, blue, magenta, cyan, gray,
$(i $I)white, darkgray, lightgreen, lightyellow, lightblue,
$(i $I)lightmagenta, lightcyan
$(i $I)Termination: end, off, reset
$(i $I)Default (no arg): end
$(a bold 'Section 2: Background')
$(hr '-')
$(i $I)\$(bgcolor red \"foo bar\")
$(i $I)$(background red "foo bar")
$(i $I)\$(background blue \"foo bar\")
$(i $I)$(background blue "foo bar")
$(i $I)\$(background green)
$(i $I)$(i $I)>>foo bar<<
$(i $I)$(i $I)>>bah boo<<
$(i $I)\$(background end)
$(background green)
$(i $I)>>foo bar<<
$(i $I)>>bah boo<<
$(background end)
$(i $I)Short Hand: $(a underline 'bg')
$(i $I)\$(bg red 'foo')
$(i $I)Argument list:
$(i $I)black, red, green, yellow, blue, magenta, cyan, gray,
$(i $I)white, darkgray, lightred, lightgreen, lightyellow,
$(i $I)lightblue, lightmagenta, lightcyan
$(i $I)Termination: end, off, reset
$(i $I)Default (no arg): end
$(a bold 'Section 3: Attributes')
$(hr '-')
$(i $I)$(a bold "Attributes only work on vt100 compatible terminals.")
$(i $I)> Note:
$(i $I)> $(a underline 'attribute end') turns off everything,
$(i $I)> including foreground and background color.
$(i $I)\$(attribute bold \"foo bar\")
$(i $I)$(attribute bold "foo bar")
$(i $I)\$(attribute underline \"foo bar\")
$(i $I)$(attribute underline "foo bar")
$(i $I)\$(attribute blink \"foo bar\")
$(i $I)$(attribute blink "foo bar")
$(i $I)\$(attribute invert \"foo bar\")
$(i $I)$(attribute invert "foo bar")
$(i $I)\$(attribute dim)
$(i $I)$(i $I)>>foo bar<<
$(i $I)$(i $I)>>bah boo<<
$(i $I)\$(attribute end)
$(i $I)$(attribute dim)
$(i $I)$(i $I)>>foo bar<<
$(i $I)$(i $I)>>bah boo<<
$(i $I)$(attribute end)
$(i $I)Short Hand: $(a underline 'a')
$(i $I)\$(a bold 'foo')
$(i $I)Argument list:
$(i $I)bold, dim, underline, blink, invert, hidden
$(i $I)Termination: end, off, reset
$(i $I)Default (no arg): end
$(a bold 'Section 4: Elements')
$(hr '-')
$(i $I)foo\$(br)\$(tab)bar
$(i $I)foo$(br)$(tab)bar
$(i $I)
$(i $I)foo\$(br)\$(indent)bar\$(br)\$(indent 6)boo
$(i $I)foo$(br)$(indent)bar$(br)$(indent 6)boo
$(i $I)
$(i $I)> Note: short hand for $(a underline 'indent') is $(a underline 'i')
$(i $I)
$(i $I)\$(hr)
$(i $I)$(hr)
$(i $I)
$(i $I)\$(hr 50)
$(i $I)$(hr 50)
$(i $I)
$(i $I)\$(hr '~' 40)
$(i $I)$(hr '~' 40)
$(i $I)
$(i $I)\$(hr '#' 30)
$(i $I)$(hr '#' 30)
$(a bold 'Section 5: Icons')
$(hr '-')
$(i $I)Icons
$(i $I)$(hr '-' 10)
$(i $I)\$(icon check) \$(icon '<3') \$(icon '*') \$(icon ':)')
$(i $I)$(icon check) $(icon '<3') $(icon '*') $(icon 'smile')
$(i $I)Argument list:
$(i $I)check|checkmark, X|x|xmark, <3|heart, sun, *|star,
$(i $I)darkstar, umbrella, flag, snow|snowflake, music,
$(i $I)scissors, tm|trademark, copyright, apple,
$(i $I):-)|:)|smile|face
$(a bold 'Section 6: Emojis')
$(hr '-')
$(i $I)Couldn't peep it with a pair of \$(emoji bifocals)
$(i $I)Couldn't peep it with a pair of $(emoji bifocals)
$(i $I)
$(i $I)I'm no \$(emoji joker) play me as a \$(emoji joker)
$(i $I)I'm no $(emoji joker) play me as a $(emoji joker)
$(i $I)
$(i $I)\$(emoji bee) on you like a \$(emoji house) on \$(emoji fire), \$(emoji smoke) ya
$(i $I)$(emoji bee) on you like a $(emoji house) on $(emoji fire), $(emoji smoke) ya
$(i $I)
$(i $I)$(a bold 'Each Emoji has 1 or more alias')
$(i $I)
$(i $I)\$(emoji smiley) \$(emoji 1F603) \$(emoji '=)') \$(emoji ':-)') \$(emoji ':)')
$(i $I)$(emoji smiley) $(emoji 1F603) $(emoji '=)') $(emoji ':-)') $(emoji ':)')
$(a bold 'Section 7: Color Bar')
$(hr '-')
$(i $I)\$(color-bar)
$(i $I)$(color-bar)
$(i $I)
$(i $I)\$(color-bar red green yellow blue magenta \\
$(i $I)$(i 15)cyan lightgray darkgray lightred \\
$(i $I)$(i 15)lightgreen lightyellow lightblue \\
$(i $I)$(i 15)lightmagenta lightcyan)
$(i $I)$(color-bar red green yellow blue magenta \
cyan lightgray darkgray lightred \
lightgreen lightyellow lightblue \
lightmagenta lightcyan)
$(i $I)Short Hand: $(a underline 'bar')
$(i $I)
$(i $I)\$(bar black yellow black yellow black yellow)
$(i $I)$(bar black yellow black yellow black yellow)
" | less -r
fi
# vim: ft=sh:

View file

@ -14,3 +14,7 @@ export EDITOR='nano'
fpath+=(${DOTF_LIB}/completions/src ${DOTF_LIB}/local)
for file in ${ZSHRCD}/*.zsh; do source $file; done
# Prompt
autoload -U promptinit && promptinit
prompt filthy

View file

@ -1,7 +1,7 @@
export ZSH="${DOTF_LIB}/ohmyzsh"
# Settings
ZSH_THEME="mortalscumbag"
#ZSH_THEME="mortalscumbag"
DISABLE_AUTO_UPDATE="true"
source ${ZSH}/oh-my-zsh.sh

34
lib/local/_desk Normal file
View file

@ -0,0 +1,34 @@
#compdef desk
#autoload
_all_desks() {
desks=($(desk list --only-names))
}
local expl
local -a desks
local -a _subcommands
_subcommands=(
'help:Print a help message.'
'init:Initialize your desk configuration.'
'list:List available desks'
'ls:List available desks'
'edit:Edit or create a desk, defaults to current desk'
'go:Activate a desk'
'.:Activate a desk'
'run:Run a command within a desk environment'
'version:Show the desk version.'
)
if (( CURRENT == 2 )); then
_describe -t commands 'desk subcommand' _subcommands
return
fi
case "$words[2]" in
go|.|edit|run)
_all_desks
_wanted desks expl 'desks' compadd -a desks ;;
esac

61
lib/local/_lj Normal file
View file

@ -0,0 +1,61 @@
#compdef lj
###
# List of log levels
###
_levels=(
'emergency'
'alert'
'critical'
'error'
'warning'
'notice'
'info'
'debug'
)
###
# Describe log levels
###
function _lumberjack_log_levels() {
_describe -t levels 'levels' _levels "$@"
}
###
# Lumberjack completion
###
function _lumberjack() {
typeset -A opt_args
local context state line curcontext="$curcontext"
# Set option arguments
_arguments -A \
'(-h --help)'{-h,--help}'[show help text and exit]' \
'(-v --version)'{-v,--version}'[show version information and exit]' \
'(-f --file)'{-f,--file}'[set log file and exit]' \
'(-l --level)'{-l,--level}'[set log level and exit]' \
# Set log level arguments
_arguments \
'1: :_lumberjack_log_levels' \
'*::arg:->args'
# Complete option arguments
case "$state" in
args )
case "$words[1]" in
--file|-f )
_arguments \
'1:file:_files'
;;
--level|-l )
_arguments \
'1:level:_lumberjack_log_levels'
;;
esac
;;
esac
}
_lumberjack "$@"

View file

@ -0,0 +1,312 @@
#!/usr/bin/env zsh
# Filthy
# by James Dinsdale
# https://github.com/molovo/filthy
# MIT License
# Largely based on Pure by Sindre Sorhus <https://github.com/sindresorhus/pure>
# For my own and others sanity
# git:
# %b => current branch
# %a => current action (rebase/merge)
# prompt:
# %F => color dict
# %f => reset color
# %~ => current path
# %* => time
# %n => username
# %m => shortname host
# %(?..) => prompt conditional - %(condition.true.false)
prompt_filthy_nice_exit_code() {
local exit_status="${1:-$(print -P %?)}";
# nothing to do here
[[ ${FILTHY_SHOW_EXIT_CODE:=0} != 1 || -z $exit_status || $exit_status == 0 ]] && return;
local sig_name;
# is this a signal name (error code = signal + 128) ?
case $exit_status in
129) sig_name=HUP ;;
130) sig_name=INT ;;
131) sig_name=QUIT ;;
132) sig_name=ILL ;;
134) sig_name=ABRT ;;
136) sig_name=FPE ;;
137) sig_name=KILL ;;
139) sig_name=SEGV ;;
141) sig_name=PIPE ;;
143) sig_name=TERM ;;
esac
# usual exit codes
case $exit_status in
-1) sig_name=FATAL ;;
1) sig_name=WARN ;; # Miscellaneous errors, such as "divide by zero"
2) sig_name=BUILTINMISUSE ;; # misuse of shell builtins (pretty rare)
126) sig_name=CCANNOTINVOKE ;; # cannot invoke requested command (ex : source script_with_syntax_error)
127) sig_name=CNOTFOUND ;; # command not found (ex : source script_not_existing)
esac
# assuming we are on an x86 system here
# this MIGHT get annoying since those are in a range of exit codes
# programs sometimes use.... we'll see.
case $exit_status in
19) sig_name=STOP ;;
20) sig_name=TSTP ;;
21) sig_name=TTIN ;;
22) sig_name=TTOU ;;
esac
echo "$ZSH_PROMPT_EXIT_SIGNAL_PREFIX${exit_status}:${sig_name:-$exit_status}$ZSH_PROMPT_EXIT_SIGNAL_SUFFIX ";
}
# turns seconds into human readable time
# 165392 => 1d 21h 56m 32s
prompt_filthy_human_time() {
local tmp=$(( $1 / 1000 ))
local days=$(( tmp / 60 / 60 / 24 ))
local hours=$(( tmp / 60 / 60 % 24 ))
local minutes=$(( tmp / 60 % 60 ))
local seconds=$(( tmp % 60 ))
(( $days > 0 )) && print -n "${days}d "
(( $hours > 0 )) && print -n "${hours}h "
(( $minutes > 0 )) && print -n "${minutes}m "
(( $seconds > 5 )) && print -n "${seconds}s"
(( $tmp <= 5 )) && print -n "${1}ms"
}
# displays the exec time of the last command if set threshold was exceeded
prompt_filthy_cmd_exec_time() {
local stop=$((EPOCHREALTIME*1000))
local start=${cmd_timestamp:-$stop}
integer elapsed=$stop-$start
(($elapsed > ${FILTHY_CMD_MAX_EXEC_TIME:=500})) && prompt_filthy_human_time $elapsed
}
prompt_filthy_preexec() {
cmd_timestamp=$((EPOCHREALTIME*1000))
# shows the current dir and executed command in the title when a process is active
print -Pn "\e]0;"
echo -nE "$PWD:t: $2"
print -Pn "\a"
}
# string length ignoring ansi escapes
prompt_filthy_string_length() {
print ${#${(S%%)1//(\%([KF1]|)\{*\}|\%[Bbkf])}}
}
prompt_filthy_precmd() {
local prompt_filthy_preprompt git_root current_path branch repo_status
# Ensure prompt starts on a new line
prompt_filthy_preprompt="\n"
# Print connection info
prompt_filthy_preprompt+="$(prompt_filthy_connection_info)"
# check if we're in a git repo, and show git info if we are
if command git rev-parse --is-inside-work-tree &>/dev/null; then
# Print the name of the repository
git_root=$(git rev-parse --show-toplevel)
prompt_filthy_preprompt+="%B%F{yellow}$(basename ${git_root})%b%f"
# Print the current_path relative to the git root
current_path=$(git rev-parse --show-prefix)
prompt_filthy_preprompt+=" %F{blue}${${current_path%/}:-"/"}%f"
else
# We're not in a repository, so just print the current path
prompt_filthy_preprompt+="%F{blue}%~%f"
fi
# Print everything so far in the title
# print -Pn '\e]0;${prompt_filthy_preprompt}\a'
# Echo command exec time
prompt_filthy_preprompt+=" %F{yellow}$(prompt_filthy_cmd_exec_time)%f"
if [[ -f "${ZDOTDIR:-$HOME}/.promptmsg" ]]; then
# Echo any stored messages after the pre-prompt
prompt_filthy_preprompt+=" $(cat ${ZDOTDIR:-$HOME}/.promptmsg)"
fi
# We've already added any messages to our prompt, so let's reset them
cat /dev/null >! "${ZDOTDIR:-$HOME}/.promptmsg"
print -P $prompt_filthy_preprompt
# reset value since `preexec` isn't always triggered
unset cmd_timestamp
}
prompt_filthy_rprompt() {
# check if we're in a git repo, and show git info if we are
if command git rev-parse --is-inside-work-tree &>/dev/null; then
# Print the repository status
branch=$(prompt_filthy_git_branch)
repo_status=$(prompt_filthy_git_repo_status)
ci_status=$(prompt_filthy_ci_status)
fi
if builtin type zvm >/dev/null 2>&1; then
zvm_version=" $(zvm current --quiet)"
fi
print "${branch}${repo_status}${ci_status}%F{yellow}${zvm_version}%f"
}
prompt_filthy_ci_status() {
local state git_dir_local state_file
[[ $FILTHY_SHOW_CI_STATUS -eq 0 ]] && return
builtin type hub >/dev/null 2>&1 || return
git_dir_local="$(git rev-parse --git-dir)"
state_file="${git_dir_local}/ci-status"
function _retrieve_ci_status() {
# Delay the asynchronous process, otherwise the status file
# will be empty when we read it
sleep 1
state=$(hub ci-status 2>&1)
cat /dev/null >! "${state_file}" 2>/dev/null
case $state in
success )
print '%F{green}●%f' >> "${state_file}"
;;
pending )
print '%F{yellow}○%f' >> "${state_file}"
;;
failure )
print '%F{red}●%f' >> "${state_file}"
;;
error )
print '%F{red}‼%f' >> "${state_file}"
;;
'no status' )
print '%F{242}○%f' >> "${state_file}"
;;
esac
}
_retrieve_ci_status >/dev/null 2>&1 &!
state=$(cat "${state_file}" 2>/dev/null)
[[ -n $state ]] && print " $state"
}
prompt_filthy_git_repo_status() {
# Do a fetch asynchronously
git fetch > /dev/null 2>&1 &!
local clean
local rtn=""
local count
local up
local down
dirty="$(git diff --ignore-submodules=all HEAD 2>/dev/null)"
[[ $dirty != "" ]] && rtn+=" %F{242}…%f"
staged="$(git diff --staged HEAD 2>/dev/null)"
[[ $staged != "" ]] && rtn+=" %F{242}*%f"
# check if there is an upstream configured for this branch
# exit if there isn't, as we can't check for remote changes
if command git rev-parse --abbrev-ref @'{u}' &>/dev/null; then
# if there is, check git left and right arrow_status
count="$(command git rev-list --left-right --count HEAD...@'{u}' 2>/dev/null)"
# Get the push and pull counts
up="$count[(w)1]"
down="$count[(w)2]"
# Check if either push or pull is needed
[[ $up > 0 || $down > 0 ]] && rtn+=" "
# Push is needed, show up arrow
[[ $up > 0 ]] && rtn+="%F{yellow}⇡%f"
# Pull is needed, show down arrow
[[ $down > 0 ]] && rtn+="%F{yellow}⇣%f"
fi
print $rtn
}
prompt_filthy_git_branch() {
# get the current git status
local branch git_dir_local rtn
branch=$(git status --short --branch -uno --ignore-submodules=all | head -1 | awk '{print $2}' 2>/dev/null)
git_dir_local=$(git rev-parse --git-dir)
# remove reference to any remote tracking branch
branch=${branch%...*}
# check if HEAD is detached
if [[ -d "${git_dir_local}/rebase-merge" ]]; then
branch=$(git status | head -5 | tail -1 | awk '{print $6}')
rtn="%F{red}rebasing interactively%f%F{242} → ${branch//([[:space:]]|\')/}%f"
elif [[ -d "${git_dir_local}/rebase-apply" ]]; then
branch=$(git status | head -2 | tail -1 | awk '{print $6}')
rtn="%F{red}rebasing%f%F{242} → ${branch//([[:space:]]|\')/}%f"
elif [[ -f "${git_dir_local}/MERGE_HEAD" ]]; then
branch=$(git status | head -1 | awk '{print $3}')
rtn="%F{red}merging%f%F{242} → ${branch//([[:space:]]|\')/}%f"
elif [[ "$branch" = "HEAD" ]]; then
commit=$(git status HEAD -uno --ignore-submodules=all | head -1 | awk '{print $4}' 2>/dev/null)
if [[ "$commit" = "on" ]]; then
rtn="%F{yellow}no branch%f"
else
rtn="%F{242}detached@%f"
rtn+="%F{yellow}"
rtn+="$commit"
rtn+="%f"
fi
else
rtn="%F{242}$branch%f"
fi
print "$rtn"
}
prompt_filthy_connection_info() {
# show username@host if logged in through SSH
if [[ "x$SSH_CONNECTION" != "x" ]]; then
echo '%(!.%B%F{red}%n%f%b.%F{242}%n%f)%F{242}@%m%f '
else
echo '%(!.%B%F{red}%n%f%b%F{242}@%m%f .)'
fi
}
prompt_filthy_setup() {
# prevent percentage showing up
# if output doesn't end with a newline
export PROMPT_EOL_MARK=''
prompt_opts=(cr subst percent)
zmodload zsh/datetime
autoload -Uz add-zsh-hook
# autoload -Uz git-info
add-zsh-hook precmd prompt_filthy_precmd
add-zsh-hook preexec prompt_filthy_preexec
# prompt turns red if the previous command didn't exit with 0
PROMPT='%(?.%F{green}.%F{red}$(prompt_filthy_nice_exit_code))%f '
RPROMPT='$(prompt_filthy_rprompt)'
}
prompt_filthy_setup "$@"