| # 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::cat --color black --background cyan --indent 4 <<EOF |
| # Multi-line text with expanded bash ${variables} |
| # can be styled and indented. |
| # EOF |
| # |
| # 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" |
| # |
| # Visual tests (and demonstration of capabilities) can be run from: |
| # //scripts/tests/style-test-visually |
| |
| # This script should be sourced. It is compatible with Bash 3. |
| # MacOS still comes with Bash 3, so unfortunately no associative arrays. |
| |
| STYLE_TO_TTY_ONLY=false # Set to true to suppress styling if output is redirected |
| |
| [[ "${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::colors() { |
| set | sed -n "s/^TERM_COLORS__\([^=]*\)=.*$/\1/p" >&2 |
| } |
| |
| style::attributes() { |
| set | sed -n "s/^TERM_ATTRIBUTES__\([^=]*\)=.*$/--\1/p" >&2 |
| } |
| |
| style::usage() { |
| local help_option="$1"; shift |
| if [[ "${help_option}" == "colors" ]]; then |
| style::colors |
| return |
| elif [[ "${help_option}" == "attributes" ]]; then |
| style::attributes |
| return |
| fi |
| local function_call="$1" |
| local -a words=( $function_call ) |
| local funcname="${words[0]}" |
| local command="$2" |
| local specifics="$3" |
| |
| >&2 echo " |
| Usage: ${function_call} [style options] [command parameters]" |
| |
| if [[ "${specifics}" != "" ]]; then |
| >&2 echo " |
| ${specifics}" |
| fi |
| >&2 cat << EOF |
| |
| style options include: |
| --bold, --faint, --underline, etc. |
| --color <color_name> |
| --background <color_name> |
| --indent <spaces_count> |
| --stderr (output to standard error instead of standard out) |
| |
| echo "This is \$(style::echo -f --bold LOUD) and soft." |
| |
| command parameters are those supported by the ${command} command. |
| |
| Use ${funcname} --help colors for a list of colors or backgrounds |
| Use ${funcname} --help attributes for a list of style attribute flags |
| EOF |
| } |
| |
| 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::stylize() { |
| if [[ "$1" == --* || "$1" == "" ]]; then |
| style::usage "$2" "${FUNCNAME[0]} <command>" "stylized" "\ |
| <command> is any command with output to stylize, followed by style options, |
| and then the command's normal parameters." |
| return |
| fi |
| |
| local command="$1"; shift |
| if [[ "$1" == "--help" ]]; then |
| style::usage "$2" "style::${command}" "'${command}'" |
| return |
| fi |
| |
| local get_flags=true |
| local -i fd=1 |
| local styles |
| local semicolon |
| local name |
| local -i indent=0 |
| local prefix |
| local -i code=0 |
| |
| while $get_flags; do |
| case "$1" in |
| --stderr) |
| fd=2 |
| shift |
| ;; |
| --stdout) |
| fd=1 |
| 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=';' |
| ;; |
| --indent) |
| shift; indent=$1; shift |
| prefix="$(printf "%${indent}s")" |
| ;; |
| --*) |
| 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 |
| |
| if [ ! -t ${fd} ] && ${STYLE_TO_TTY_ONLY}; then |
| # Output is not to a TTY so don't stylize |
| if [[ "${prefix}" == "" ]]; then |
| >&${fd} "${command}" "$@" || status=$? |
| else |
| >&${fd} "${command}" "$@" | sed "s/^/${prefix}/" |
| if (( ${PIPESTATUS[0]} != 0 )); then |
| status=${PIPESTATUS[0]} |
| fi |
| fi |
| return 0 |
| fi |
| |
| local if_newline='' |
| local text |
| |
| # Add placeholder (.) so command substitution doesn't strip trailing newlines |
| text="$("${command}" "$@" || exit $?;echo -n '.')" || return $? |
| if [[ "${prefix}" != "" ]]; then |
| text="$(echo "${text}" | sed "s/^/${prefix}/;\$s/^${prefix}[.]\$/./")" |
| fi |
| |
| local -i len=$((${#text}-2)) |
| if [[ "${text:$len:1}" == $'\n' ]]; then |
| # Save last newline to add back after styling. |
| if_newline='\n' |
| else |
| ((len++)) |
| fi |
| # Strip trailing newline, if any, and placeholder. |
| text="${text:0:$((len))}" |
| |
| # Style everything except newlines, otherwise background color highlights |
| # entire line. Add extra line with a character so sed does not add it's own |
| # last newline, then delete the line after substitutions. |
| local styled=$(printf '%s\n.' "${text}" | sed -e $'s/$/\033[0m/;s/^/\033['"${styles}"'m/;$d') |
| |
| >&${fd} printf "%s${if_newline}" "${styled}" |
| |
| return 0 |
| } |
| |
| 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::_echo_with_styles() { |
| local funcname="$1";shift |
| local style_options="$1";shift |
| if [[ "$1" == "--help" ]]; then |
| |
| style::usage "$2" "${funcname}" "echo" "\ |
| Default style options for ${funcname}: |
| $(style::echo ${style_options} --stdout \"${style_options}\")" |
| |
| return |
| fi |
| style::echo ${style_options} "$@" || return $? |
| } |
| |
| style::error() { |
| style::_echo_with_styles "${FUNCNAME[0]}" "${STYLE_ERROR}" "$@" || return $? |
| } |
| |
| style::warning() { |
| style::_echo_with_styles "${FUNCNAME[0]}" "${STYLE_WARNING}" "$@" || return $? |
| } |
| |
| style::info() { |
| style::_echo_with_styles "${FUNCNAME[0]}" "${STYLE_INFO}" "$@" || return $? |
| } |
| |
| style::link() { |
| style::_echo_with_styles "${FUNCNAME[0]}" "${STYLE_LINK}" "$@" || return $? |
| } |