Adds a new utility to print styled & colored text
Patched with some code optimizations. Output file descriptor is
now parameterized (figured out how to make this work without eval).
All stylized output is now combined into a single printf call.
Patched to allow user to override standard styles for error,
warning, info, and link, to accommodate preferences and different
screen backgrounds (dark and light).
Change-Id: Ib443044fb42dc85ad55a863b8284304f5af0a35d
diff --git a/devshell/lib/style.sh b/devshell/lib/style.sh
new file mode 100644
index 0000000..15a4d6d
--- /dev/null
+++ b/devshell/lib/style.sh
@@ -0,0 +1,208 @@
+# Copyright 2017 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# This script adds text style options to echo, cat, and printf. For
+# example:
+#
+# style::echo --stderr --bold --underline --color red -n Yo ho ho
+#
+# print "Yo ho ho" without a newline (echo's -n flag), in bold red
+# with underline. The text is written to stderr instead of the default
+# stdout.
+#
+# style::info, style::warning, and style::error use echo to stderr
+# with default color and bold text. For example:
+#
+# style::warning "WARNING: This is a warning!"
+#
+# style::link uses echo to stdout, with dark_blue underlined text
+#
+# You can override these styles with your own preferences, for example:
+#
+# export STYLE_WARNING="--stderr --faint --dark_red --background dark_yellow"
+#
+
+# This script should be sourced. It is compatible with Bash 3.
+# MacOS still comes with Bash 3, so unfortunately no associative arrays.
+
+[[ "${STYLE_ERROR}" != "" ]] || STYLE_ERROR="--stderr --bold --color red"
+[[ "${STYLE_WARNING}" != "" ]] || STYLE_WARNING="--stderr --bold --color dark_yellow"
+[[ "${STYLE_INFO}" != "" ]] || STYLE_INFO="--stderr --bold --color dark_green"
+[[ "${STYLE_LINK}" != "" ]] || STYLE_LINK="--underline --color dark_blue"
+
+declare -i TERM_ATTRIBUTES__reset=0
+declare -i TERM_ATTRIBUTES__bold=1
+declare -i TERM_ATTRIBUTES__faint=2
+declare -i TERM_ATTRIBUTES__italic=3
+declare -i TERM_ATTRIBUTES__underline=4
+declare -i TERM_ATTRIBUTES__blink=5
+
+declare -i TERM_COLORS__default=39
+declare -i TERM_COLORS__black=30
+declare -i TERM_COLORS__dark_red=31
+declare -i TERM_COLORS__dark_green=32
+declare -i TERM_COLORS__dark_yellow=33
+declare -i TERM_COLORS__dark_blue=34
+declare -i TERM_COLORS__dark_magenta=35
+declare -i TERM_COLORS__purple=35
+declare -i TERM_COLORS__dark_cyan=36
+declare -i TERM_COLORS__light_gray=37
+declare -i TERM_COLORS__gray=90
+declare -i TERM_COLORS__red=91
+declare -i TERM_COLORS__green=92
+declare -i TERM_COLORS__yellow=93
+declare -i TERM_COLORS__blue=94
+declare -i TERM_COLORS__magenta=95
+declare -i TERM_COLORS__pink=95
+declare -i TERM_COLORS__cyan=96
+declare -i TERM_COLORS__white=97
+
+style::attribute() {
+ local name="$1"
+ local fallback="$2"
+ local var=TERM_ATTRIBUTES__${name}
+ local -i attribute=${!var}
+ if ! (( attribute )); then
+ if [[ $fallback != "" ]]; then
+ echo "${fallback}"
+ return 0
+ else
+ >&2 echo "Invalid attribute name: $name"
+ return 1
+ fi
+ fi
+ echo ${attribute}
+}
+
+style::color() {
+ local name="$1"
+ local fallback="$2"
+ local var=TERM_COLORS__${name}
+ local -i color=${!var}
+ if ! (( color )); then
+ if [[ $fallback != "" ]]; then
+ echo "${fallback}"
+ return 0
+ else
+ >&2 echo "Invalid color name: $name"
+ return 1
+ fi
+ fi
+ echo ${color}
+}
+
+style::background() {
+ local color
+ color=$(style::color "$1" "$2" || exit $?) || return $?
+ echo $((10+${color}))
+}
+
+_STYLE_RESET="\033[0m"
+
+style::stylize() {
+ local command="$1"; shift
+
+ local get_flags=true
+ local -i fd=1
+ local styles
+ local semicolon
+ local name
+ local -i code
+
+ while $get_flags; do
+ case "$1" in
+ --stderr)
+ fd=2
+ shift
+ ;;
+ --color)
+ shift; name="$1"; shift
+ styles="${styles}${semicolon}$(style::color $name || exit $?)" || return $?
+ semicolon=';'
+ ;;
+ --background)
+ shift; name="$1"; shift
+ styles="${styles}${semicolon}$(style::background $name || exit $?)" || return $?
+ semicolon=';'
+ ;;
+ --*)
+ name="${1:2}"
+ code=$(style::attribute $name 0)
+ if (( code )); then
+ shift
+ styles="${styles}${semicolon}${code}"
+ semicolon=';'
+ else
+ code=$(style::color $name 0)
+ if (( code )); then
+ shift
+ styles="${styles}${semicolon}${code}"
+ semicolon=';'
+ else
+ get_flags=false
+ fi
+ fi
+ ;;
+ *)
+ get_flags=false
+ ;;
+ esac
+ done
+
+ local status=0
+
+ if [ ! -t $fd ]; then
+ # Output is not to a TTY so don't stylize
+ >&${fd} "${command}" "$@" || status=$?
+ return $status
+ fi
+
+ local if_newline=''
+ local text
+ # Add placeholder (.) so command substitution doesn't strip trailing newlines
+ text="$("${command}" "$@" || exit $?;echo '.')" || status=$?
+ local -i len=$((${#text}-2))
+ if [[ "${text:$len:1}" == $'\n' ]]; then
+ if_newline='\n'
+ else
+ ((len++))
+ fi
+ # Strip trailing newline, if any, and placeholder
+ # Last newline should not be stylized.
+ # TODO(richkadel): We may want to remove style from all newlines.
+ # Background color looks odd when newlines are styled.
+ text="${text:0:$((len))}"
+
+ >&${fd} printf "\033[${styles}m%s${_STYLE_RESET}${if_newline}" "${text}"
+
+ return $status
+}
+
+style::echo() {
+ style::stylize "${FUNCNAME[0]:7}" "$@" || return $?
+}
+
+style::cat() {
+ style::stylize "${FUNCNAME[0]:7}" "$@" || return $?
+}
+
+style::printf() {
+ style::stylize "${FUNCNAME[0]:7}" "$@" || return $?
+}
+
+style::error() {
+ style::echo ${STYLE_ERROR} "$@" || return $?
+}
+
+style::warning() {
+ style::echo ${STYLE_WARNING} "$@" || return $?
+}
+
+style::info() {
+ style::echo ${STYLE_INFO} "$@" || return $?
+}
+
+style::link() {
+ style::echo ${STYLE_LINK} "$@" || return $?
+}