diff --git a/#tldr b/#tldr new file mode 100644 index 0000000..09b68e9 --- /dev/null +++ b/#tldr @@ -0,0 +1,357 @@ +#!/bin/bash +set +vx -o pipefail +[[ $- = *i* ]] && echo "Don't source this script!" && return 1 +VERSION='0.1a' +# tldr-bash-client version 0.1a +# forked from Ray Lee, http://github.com/raylee/tldr +# modified and expanded by pepa65: http://github.com/pepa65/tldr-bash-client +# Requiring: coreutils, less, grep, unzip, curl/wget + +# The 5 elements in TLDR markup that can be styled with these colors and +# backgrounds (last one specified will be used) and modes (more can apply): +# Colors: Black, Red, Green, Yellow, Blue, Magenta, Cyan, White +# BG: BlackBG, RedBG, GreenBG, YellowBG, BlueBG, MagentaBG, CyanBG, WhiteBG +# Modes: Bold, Underline, Italic, Inverse +# 'Newline' can be added to the Style list to add a newline before the element +# and 'Space' to add a space at the start of the line (not for Value element) +# (The style items are separated by space, lower or uppercase mixed allowed.) +: ${TLDR_TITLE_STYLE:= Newline Space Bold Yellow } +: ${TLDR_DESCRIPTION_STYLE:= Space Yellow } +: ${TLDR_EXAMPLE_STYLE:= Newline Space Bold Green } +: ${TLDR_CODE_STYLE:= Space Bold Blue } +: ${TLDR_VALUE_STYLE:= Space Bold Cyan } +# Color and/or background (Newline and Space also allowed) for error messages +: ${TLDR_ERROR_COLOR= Space Red } + +# How long before an attempt will be made to re-download a page +: ${TLDR_EXPIRY:= 60 } + +Usage(){ # $1: optional exit code + SELF=$(basename $0) + local exit=${1:-0} + Out "$(cat <<-EOF + $version + + USAGE: $GRE$B$SELF$XB$DEF [$YEL${B}option$XB$DEF] [$BLU${B}platform$XB/$DEF]$CYA$B$XB$DEF + + $BLU$B platform$XB/$CYA${B}command$XB$DEF: Show page for$CYA$B command$XB$DEF (from$BLU$B platform$XB$DEF) + + $BLU$B platform$XB$DEF is optionally one of:$YEL common$DEF,$YEL linux$DEF,$YEL osx$DEF,$YEL sunos$DEF + + $YEL$B option$XB$DEF is optionally one of: + $B$YEL-l$DEF,$YEL --list$DEF $BLU[platform]$DEF$XB: Show all available pages (from$BLU$B platform$XB$DEF) + $B$YEL-r$DEF,$YEL --render$DEF$XB $MAG$B$XB$DEF: Render a local$B$MAG file$DEF$XB as tldr markdown + $B$YEL-m$DEF,$YEL --markdown$DEF$XB $CYA$B$XB$DEF: Show the markdown source for$B$CYA command$DEF$XB + $B$YEL-c$DEF,$YEL --cache$DEF$XB: Cache all pages by downloading archive from repo + $B$YEL-u$DEF,$YEL --update$DEF$XB: Re-download index file from repo + $B$YEL-v$DEF,$YEL --version$DEF$XB: Version number and repo location + [$B$YEL-h$DEF,$YEL -?$DEF,$YEL --help$DEF$XB]: This help overview + + Element styling:$T Title$XT$D Description$XD$E Example$XD$C Code$XC$V Value$XV + All pages and the index are cached locally under $YEL$configdir$DEF. + By default, the cached copies will be re-downloaded after $YEL${TLDR_EXPIRY// /}$DEF days. + EOF + )" + exit $exit +} + +Err(){ STDERR+=$ERRNL$ERRSP$ERR$B$1$XB$XERR$N;} # $1: error message for later + +Out(){ STDOUT+=$1$N;} # $1: message for later + +Style(){ # $1: Style specification + local style + STYLES= XSTYLES= COLOR= XCOLOR= NL= SP= + for style in $1 + do + [[ ${style,,} = newline ]] && NL=$N + [[ ${style,,} = space ]] && SP=' ' + COLOR+=${color[${style,,}]:-}${bg[${style,,}]:-} + XCOLOR=${xbg[${style,,}]:-}${xcolor[${style,,}]:-}$XCOLOR + STYLES+=${color[${style,,}]:-}${bg[${style,,}]:-}${mode[${style,,}]:-} + XSTYLES=${xmode[${style,,}]:-}${xbg[${style,,}]:-}${xcolor[${style,,}]:-}$XSTYLES + done +} + +Init_term(){ + [[ -t 2 ]] && { # only if interactive session (stderr open) + B=$'\e[1m' # $(tput bold || tput md) # Start bold + XB=$'\e[0m' # End bold (no tput code...) -- needs echo -e + U=$'\e[4m' # $(tput smul || tput us) # Start underline + XU=$'\e[24m' # $(tput rmul || tput ue) # End underline + I=$'\e[3m' # $(tput sitm || tput ZH) # Start italic + XI=$'\e[23m' # $(tput ritm || tput ZR) # End italic + R=$'\e[7m' # $(tput smso || tput so) # Start reverse + XR=$'\e[27m' # $(tput rmso || tput se) # End reverse + X=$'\e[0m' # $(tput sgr0 || tput me) # End all + + [[ $TERM != *-m ]] && { + BLA=$'\e[30m' # $(tput setaf 0 || tput AF 0) + RED=$'\e[31m' # $(tput setaf 1 || tput AF 1) + GRE=$'\e[32m' # $(tput setaf 2 || tput AF 2) + YEL=$'\e[33m' # $(tput setaf 3 || tput AF 3) + BLU=$'\e[34m' # $(tput setaf 4 || tput AF 4) + MAG=$'\e[35m' # $(tput setaf 5 || tput AF 5) + CYA=$'\e[36m' # $(tput setaf 6 || tput AF 6) + WHI=$'\e[37m' # $(tput setaf 7 || tput AF 7) + DEF=$'\e[39m' # $(tput op) + BLAB=$'\e[40m' # $(tput setab 0 || tput AB 0) + REDB=$'\e[41m' # $(tput setab 1 || tput AB 1) + GREB=$'\e[42m' # $(tput setab 2 || tput AB 2) + YELB=$'\e[43m' # $(tput setab 3 || tput AB 3) + BLUB=$'\e[44m' # $(tput setab 4 || tput AB 4) + MAGB=$'\e[45m' # $(tput setab 5 || tput AB 5) + CYAB=$'\e[46m' # $(tput setab 6 || tput AB 6) + WHIB=$'\e[47m' # $(tput setab 7 || tput AB 7) + DEFB=$'\e[49m' # $(tput op) + } + } + + declare -A color=(['black']=$BLA ['red']=$RED ['green']=$GRE ['yellow']=$YEL \ + ['blue']=$BLU ['magenta']=$MAG ['cyan']=$CYA ['white']=$WHI) + declare -A xcolor=(['black']=$DEF ['red']=$DEF ['green']=$DEF ['yellow']=$DEF \ + ['blue']=$DEF ['magenta']=$DEF ['cyan']=$DEF ['white']=$DEF) + declare -A bg=(['blackbg']=$BLAB ['redbg']=$REDB ['greenbg']=$GREB ['yellowbg']=$YELB \ + ['bluebg']=$BLUB ['magentabg']=$MAGB ['cyanbg']=$CYAB ['whitebg']=$WHIB) + declare -A xbg=(['blackbg']=$DEFB ['redbg']=$DEFB ['greenbg']=$DEFB ['yellowbg']=$DEFB \ + ['bluebg']=$DEFB ['magentabg']=$DEFB ['cyanbg']=$DEFB ['whitebg']=$DEFB) + declare -A mode=(['bold']=$B ['underline']=$U ['italic']=$I ['inverse']=$R) + declare -A xmode=(['bold']=$XB ['underline']=$XU ['italic']=$XI ['inverse']=$XR) + + # the five main tldr page styles and error message colors + Style "$TLDR_TITLE_STYLE" + T=$STYLES XT=$XSTYLES TNL=$NL TSP=$SP + Style "$TLDR_DESCRIPTION_STYLE" + D=$STYLES XD=$XSTYLES DNL=$NL DSP=$SP + Style "$TLDR_EXAMPLE_STYLE" + E=$STYLES XE=$XSTYLES ENL=$NL ESP=$SP + Style "$TLDR_CODE_STYLE" + C=$STYLES XC=$XSTYLES CNL=$NL CSP=$SP + Style "$TLDR_VALUE_STYLE" + V=$STYLES XV=$XSTYLES + Style "$TLDR_ERROR_COLOR" + ERR=$COLOR XERR=$XCOLOR ERRNL=$NL ERRSP=$SP +} + +Recent(){ find "$1" -mtime -${TLDR_EXPIRY// /} &>/dev/null;} # $1: page + +Update_index(){ + $DL "$index" "$index_url" && Out "${GRE}Index file $I$index$XI re-downloaded$DEF" || { + Err "Could not download index from $I$index_url$XI" + exit 2 + } +} + +Config(){ # initialize globals, sanity check the environment, etc. + PLATFORM=common STDOUT= STDERR= Q='"' N=$'\n' + case $(uname -s) in + Darwin) PLATFORM='osx' ;; + Linux) PLATFORM='linux' ;; + SunOS) PLATFORM='sunos' ;; + esac + Init_term + trap 'echo -ne "$STDOUT$STDERR" |less -RXQFP"%db/%D Press Q to exit "' EXIT + + version="$GRE$B tldr-bash-client version $VERSION$XB $YEL http://github.com/pepa65/tldr-bash-client$DEF" + + # Select download method + DL="$(type -p curl) -sfo" || { + DL="$(type -p wget) -qNO" || { + Err "tldr requires$I curl$XI or$I wget $XI installed in your path" + exit 3 + } + } + + base_url='https://raw.githubusercontent.com/tldr-pages/tldr/master/pages' + zip_url='http://tldr-pages.github.io/assets/tldr.zip' + index_url='http://tldr-pages.github.io/assets/index.json' + + [[ -d ~/.config ]] && configdir=~/.config/tldr || configdir=~/.tldr + [[ -d "$configdir" ]] || mkdir -p "$configdir" + index=$configdir/index.json + # update if the file doesn't exists, or if it's older than $TLDR_EXPIRY + [[ -f $index ]] && Recent "$index" || Update_index +} + +Unlinted(){ # $1: error message + Err "Page $I$PAGE$XI not properly linted!\nLine $I$L$XI:[$U$REPLY$XU]$N$ERR$B$1" + exit 4 +} + +Get_tldr(){ # $1: page + # convert the local platform name to tldr's version + # extract the platform key from index.json, return preferred subpath to page + local desc=$(tr '{' '\n' <$index |grep "\"name\":\"$1\"") + # results in, eg, "name":"netstat","platform":["linux","osx"]}, + + [[ $desc ]] || return # just not found + + local err=0 + if [[ $platform ]] + then # platform given on commandline + [[ ! $desc =~ \"$platform\" ]] && notfound=$I$platform$XI && err=1 || PAGE=$platform/$1.md + else # check common + [[ $desc =~ \"common\" ]] && PAGE=common/$1.md || { # not in common either + [[ $notfound ]] && notfound+=" or " + notfound+=${I}common$XI + } + fi + # if no page found yet, try the system platform + [[ $PAGE ]] || [[ $platform = $PLATFORM ]] || { + [[ $desc =~ \"$PLATFORM\" ]] && PAGE=$PLATFORM/$1.md + } || { + notfound+=" or $I$PLATFORM$XI" + err=1 + } + # if still no page found, get the first entry in index + [[ $PAGE ]] || PAGE=$(cut -d $Q -f 8 <<<"$desc")/"$1.md" + ((err)) && Err "tldr page $I$1$XI not found in $notfound, page from platform $U${PAGE%/*}$XU instead" + + # return the local cached copy of the tldrpage, or retrieve and cache from github + CACHED=$configdir/$PAGE + Recent "$CACHED" || { + mkdir -p "${CACHED%/*}" + $DL "$CACHED" "$base_url/$PAGE" || Err "Could not download page $I$CACHED$XI from index $U$index_url$XU" + } +} + +Display_tldr(){ + # read one line at a time, don't strip whitespace ('IFS='), and + # process last line even if it doesn't have a newline at the end + L=0 + local newfmt len line + while read -r || [[ $REPLY ]] + do + ((++L)) + ((L==1)) && { + [[ ${REPLY:0:1} = '#' ]] && newfmt=0 || newfmt=1 + ((newfmt)) && { + [[ $REPLY ]] || Unlinted "No title" + Out "$TNL$TSP$T$REPLY$XT" + len=${#REPLY} + read -r; ((++L)) + [[ $REPLY =~ [^=] ]] && Unlinted "Title underline must be equal signs" + ((len!=${#REPLY})) && Unlinted "Underline length not equal to title's" + read -r; ((++L)) + } + } + case ${REPLY:0:1} in # first character + '#') ((newfmt)) && Unlinted "Bad first character" + ((${#REPLY} <= 2)) && Unlinted "No title" + [[ ! ${REPLY:1:1} = ' ' ]] && Unlinted "2nd character no space" + Out "$TNL$TSP$T${REPLY:2}$XT" ;; + '>') ((${#REPLY} <= 3)) && Unlinted "No valid desciption" + [[ ! ${REPLY:1:1} = ' ' ]] && Unlinted "2nd character no space" + [[ ! ${REPLY: -1} = '.' ]] && Unlinted "Description doesn't end in full stop" + Out "$DNL$DSP$D${REPLY:2}$XD" + DNL= ;; + '-') ((newfmt)) && Unlinted "Bad first character" + ((${#REPLY} <= 2)) && Unlinted "No example content" + [[ ! ${REPLY:1:1} = ' ' ]] && Unlinted "2nd character no space" + Out "$ENL$ESP$E${REPLY:2}$XE" ;; + ' ') ((newfmt)) || Unlinted "Bad first character" + ((${#REPLY} <= 4)) && Unlinted "No valid code content" + [[ ${REPLY:0:4} = ' ' ]] || Unlinted "No four spaces before code" + line=${REPLY:4} + # Value: convert {{value}} + line=${line//\{\{/$CX$V} + line=${line//\}\}/$XV$C} + Out "$CNL$CSP$C$line$XC" ;; + '`') ((newfmt)) && Unlinted "Bad first character" + ((${#REPLY} <= 2)) && Unlinted "No valid code content" + [[ ! ${REPLY: -1} = '`' ]] && Unlinted "Code doesn't end in backtick" + line=${REPLY:1:-1} + # Value: convert {{value}} + line=${line//\{\{/$CX$V} + line=${line//\}\}/$XV$C} + Out "$CNL$CSP$C$line$XC" ;; + '') continue ;; + *) ((newfmt)) || Unlinted "Bad first character" + [[ -z $REPLY ]] && Unlinted "No example content" + Out "$ENL$EPS$E$REPLY$XE" ;; + esac + done +} + +List_pages(){ # $1: exit code + local platformtext + [[ $platform ]] && platformtext=" from platform $I$platform$XI" + Out "${GRE}Known tldr pages$platformtext:" + Out "$(while read c1 c2 c3; do printf "%-19s %-19s %-19s %-19s$N" $c1 $c2 $c3; done \ + <<<$(tr '{' '\n' <"$index" |grep "$platform" |cut -d $Q -f4))" + exit $1 +} + +Cache_fill(){ # $1: exit code + local tmp=$(mktemp -d) + $DL "$tmp/pages.zip" "$zip_url" || { + rm -- "$tmp" + Err "Could not download pages archive from $U$zip_url$XU" + exit 6 + } + unzip="$(type -p unzip) -q" || { + rm -- "$tmp" + Err "Unzip is necessary to fill the cache" + exit 7 + } + $unzip "$tmp/pages.zip" -d "$tmp" 'pages/*' + rm -rf -- "$configdir/"* + mv -- "$tmp/pages/"* "$configdir/" + rm -rf -- "$tmp" + Out "${GRE}Pages cached in $U$configdir$XU$DEF" + exit $1 +} + +Config +markdown=0 err=0 +arg=$1 +case "$arg" in + -l|--list) [[ $2 ]] && { + platform=$2 + [[ ,common,linux,osx,sunos, = *,$platform,* ]] || { + Err "Unknown platform $I$platform$XI" + Usage 8 + } + [[ $3 ]] && Err "No more command line arguments allowed" && err=9 + } + List_pages $err ;; + -c|--cache) [[ $2 ]] && Err "No more command line arguments allowed" && err=10 + Cache_fill $err ;; + -v|--version) [[ $2 ]] && Err "No more command line arguments allowed" && err=11 + Out "$version" + exit $err ;; + -u|--update) [[ $2 ]] && Err "No more command line arguments allowed" && err=12 + Update_index + exit $err ;; + -r|--render) [[ -z $2 ]] && Err "Specify a file to render" && Usage 13 + [[ $3 ]] && Err "No more command line arguments allowed" && err=14 + [[ -f "$2" ]] && { + Display_tldr <"$2" && exit $err + Err "A file error occured" + exit 15 + } || Err "No file:$I $2$XI" && exit 16 ;; + -m|--markdown) shift + page=$@ + [[ -z $page ]] && Err "Specify a page to display" && Usage 17 + [[ -f "$page" && ${page: -3:3} = .md ]] && Out "$(cat "$page")" && exit 0 + markdown=1 ;; + ''|-h|-\?|--help) [[ $2 ]] && Err "No more command line arguments allowed" && err=18 + Usage $err ;; + -*) Err "Unrecognized option $I$1$XI"; Usage 19 ;; + *) page=$@ ;; +esac + +[[ -z $page ]] && Err "No command specified" && Usage 20 +[[ $page =~ ' -' || ${page:0:1} = '-' ]] && Err "Only one option allowed" && Usage 21 +[[ $page = */* ]] && platform=${page%/*} && page=${page##*/} +[[ $platform && ,common,linux,osx,sunos, != *,$platform,* ]] && { + Err "Unknown platform $I$platform$XI" + Usage 22 +} + +Get_tldr ${page// /-} +[[ ! -s $CACHED ]] && Err "tldr page for command $I$page$XI not found" && exit 23 + +((markdown)) && Out "$(cat "$CACHED")" || Display_tldr <"$CACHED" +# The error trap will output the accumulated stdout and stderr +exit 0 diff --git a/README.md b/README.md index e1b7b45..5c48c48 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ Also the error color and page expiry can easily be set: Please file an issue for a question, a bug or a feature request. Or even better, send a pull request! -[This tldr-bash-client github page](http://github.com/pepa65/tldr-bash-client "github.com/pepa65/tldr-bash-client") +[tldr-bash-client github page](http://github.com/pepa65/tldr-bash-client "github.com/pepa65/tldr-bash-client") tldr markdown screenshot diff --git a/tldr b/tldr index dde8d2e..d0ebab1 100755 --- a/tldr +++ b/tldr @@ -55,9 +55,9 @@ Usage(){ # $1: optional exit code exit $exit } -Err(){ STDERR+=$ERRNL$ERRSP$ERR$B$1$XB$XERR$N;} # $1: error message for later +Err(){ STDERR+=$ERRNL$ERRSP$ERR$B$1$XB$XERR$N;} # $1: keep error messages -Out(){ STDOUT+=$1$N;} # $1: message for later +Out(){ STDOUT+=$1$N;} # $1: keep output Style(){ # $1: Style specification local style @@ -76,7 +76,7 @@ Style(){ # $1: Style specification Init_term(){ [[ -t 2 ]] && { # only if interactive session (stderr open) B=$'\e[1m' # $(tput bold || tput md) # Start bold - XB=$'\e[0m' # End bold (no tput code...) -- needs echo -e + XB=$'\e[0m' # End bold (no tput code...) U=$'\e[4m' # $(tput smul || tput us) # Start underline XU=$'\e[24m' # $(tput rmul || tput ue) # End underline I=$'\e[3m' # $(tput sitm || tput ZH) # Start italic @@ -105,12 +105,6 @@ Init_term(){ WHIB=$'\e[47m' # $(tput setab 7 || tput AB 7) DEFB=$'\e[49m' # $(tput op) } - # osx's termcap doesn't have italics. The below adds support for iTerm2 - # and is harmless on Terminal.app - #[[ $(uname -s) = Darwin ]] && { - # I=$(echo -e "\e[3m") - # XI=$(echo -e "\e[23m") - #} } declare -A color=(['black']=$BLA ['red']=$RED ['green']=$GRE ['yellow']=$YEL \ @@ -124,7 +118,7 @@ Init_term(){ declare -A mode=(['bold']=$B ['underline']=$U ['italic']=$I ['inverse']=$R) declare -A xmode=(['bold']=$XB ['underline']=$XU ['italic']=$XI ['inverse']=$XR) - # the five main tldr page styles and error message colors + # the 5 main tldr page styles and error message colors Style "$TLDR_TITLE_STYLE" T=$STYLES XT=$XSTYLES TNL=$NL TSP=$SP Style "$TLDR_DESCRIPTION_STYLE" @@ -141,14 +135,14 @@ Init_term(){ Recent(){ find "$1" -mtime -${TLDR_EXPIRY// /} &>/dev/null;} # $1: page -Update_index(){ +Update_index(){ # Download index.json $DL "$index" "$index_url" && Out "${GRE}Index file $I$index$XI re-downloaded$DEF" || { Err "Could not download index from $I$index_url$XI" exit 2 } } -Config(){ # initialize globals, sanity check the environment, etc. +Config(){ # Initialize globals, check the environment PLATFORM=common STDOUT= STDERR= Q='"' N=$'\n' case $(uname -s) in Darwin) PLATFORM='osx' ;; @@ -156,7 +150,7 @@ Config(){ # initialize globals, sanity check the environment, etc. SunOS) PLATFORM='sunos' ;; esac Init_term - trap 'echo -ne "$STDOUT$STDERR" |less -RXMQF' EXIT + trap 'less -RXQFP"Press Q to exit " <<<"$STDOUT$STDERR"' EXIT version="$GRE$B tldr-bash-client version $VERSION$XB $YEL http://github.com/pepa65/tldr-bash-client$DEF" @@ -277,6 +271,7 @@ Display_tldr(){ Out "$ENL$EPS$E$REPLY$XE" ;; esac done + trap 'less +Gg -RXQFP"%pB\% tldr $I$page$XI - press Q to exit" <<<"$STDOUT$STDERR"' EXIT } List_pages(){ # $1: exit code