tldr-bash-client/tldr

379 lines
14 KiB
Plaintext
Raw Normal View History

2016-01-04 06:33:47 +01:00
#!/bin/bash
set +vx -o pipefail
[[ $- = *i* ]] && echo "Don't source this script!" && return 1
version='0.2'
# tldr-bash-client version 0.2
2017-02-22 17:06:21 +01:00
# Bash client for tldr: community driven man-by-example
2017-02-22 13:57:27 +01:00
# - 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
2017-02-22 18:28:41 +01:00
# '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:= Newline Space Red }"
# How long before an attempt will be made to re-download a page
: "${TLDR_EXPIRY:= 60 }"
# $1: [optional] exit code; Uses: version cachedir
Usage(){
Out "$(cat <<-EOF
$version
USAGE: $GRE$B$(basename "$0")$XB$DEF [$YEL${B}option$XB$DEF] [$BLU${B}platform$XB/$DEF]$CYA$B<command>$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<file>$XB$DEF: Render a local$B$MAG file$DEF$XB as tldr markdown
$B$YEL-m$DEF,$YEL --markdown$DEF$XB $CYA$B<command>$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$cachedir$DEF.
By default, the cached copies will be re-downloaded after $YEL${TLDR_EXPIRY// /}$DEF days.
EOF
)"
exit "${1:-0}"
2016-01-04 06:33:47 +01:00
}
# $1: keep error messages; Uses/Sets: stderr
Err(){ stderr+=$ERRNL$ERRSP$ERR$B$1$XB$XERR$N;}
# $1: keep output; Uses/Sets: stdout
Out(){ stdout+=$1$N;}
# $1: Style specification; Uses: color xcolor bg xbg mode xmode
Style(){
local -l style
2017-02-22 18:28:41 +01:00
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
}
# Sets: color xcolor bg xbg mode xmode
Init_term(){
[[ -t 2 ]] && { # only if interactive session (stderr open)
B=$'\e[1m' # $(tput bold || tput md) # Start bold
2017-02-22 11:59:51 +01:00
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
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)
2017-02-22 11:59:51 +01:00
# 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"
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
2016-01-04 06:33:47 +01:00
}
# $1: page
Recent(){ find "$1" -mtime -"${TLDR_EXPIRY// /}" >/dev/null 2>&1;}
2016-01-04 06:33:47 +01:00
# Download index.json; Uses: index index_url base_url zip_url dl
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
}
2016-01-04 06:33:47 +01:00
}
# Initialize globals, check the environment; Uses: config cachedir version
# Sets: stdout stderr os version dl
Config(){
os=common stdout='' stderr='' Q='"' N=$'\n'
2017-02-22 18:28:41 +01:00
case "$(uname -s)" in
Darwin) os='osx' ;;
Linux) os='linux' ;;
SunOS) os='sunos' ;;
esac
Init_term
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"
# 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 ]] && cachedir=~/.config/tldr || cachedir=~/.tldr
[[ -d "$cachedir" ]] || mkdir -p "$cachedir"
index=$cachedir/index.json
# update if the file doesn't exists, or if it's older than $TLDR_EXPIRY
[[ -f $index ]] && Recent "$index" || Update_index
2016-01-04 06:33:47 +01:00
}
# $1: error message; Uses: md line ln
Unlinted(){
Err "Page $I$md$XI not properly linted!\nLine $I$ln$XI:[$U$line$XU]$N$ERR$B$1"
exit 4
2016-01-04 06:33:47 +01:00
}
# $1: page; Uses: index index_url cachedir base_url platform os dl cached md
# Sets: cached md
Get_tldr(){
local desc err notfound
# convert the local platform name to tldr's version
# extract the platform key from index.json, return preferred subpath to page
desc=$(tr '{' '\n' <$index |grep "\"name\":\"$1\"")
# results in, eg, "name":"netstat","platform":["linux","osx"]},
[[ $desc ]] || return # just not found
err=0
if [[ $platform ]]
then # platform given on commandline
[[ ! $desc =~ \"$platform\" ]] && notfound=$I$platform$XI && err=1 || md=$platform/$1.md
else # check common
[[ $desc =~ \"common\" ]] && md=common/$1.md || { # not in common either
[[ $notfound ]] && notfound+=" or "
notfound+=${I}common$XI
}
fi
# if no page found yet, try the system platform
[[ $md ]] || [[ $platform = "$os" ]] || {
[[ $desc =~ \"$os\" ]] && md=$os/$1.md
} || {
notfound+=" or $I$os$XI"
err=1
}
# if still no page found, get the first entry in index
[[ $md ]] || md=$(cut -d "$Q" -f 8 <<<"$desc")/"$1.md"
((err)) && Err "tldr page $I$1$XI not found in $notfound, page from platform $U${md%/*}$XU instead"
# return the local cached copy of the tldrpage, or retrieve and cache from github
cached=$cachedir/$md
Recent "$cached" || {
mkdir -p "${cached%/*}"
$dl "$cached" "$base_url/$md" || Err "Could not download page $I$cached$XI from index $U$index_url$XU"
}
2016-01-04 06:33:47 +01:00
}
# Uses: page stdout stderr; Sets: ln line
Display_tldr(){
local newfmt len val
ln=0 line=''
# Read full lines, and process even when no newline at the end
while read -r line || [[ $line ]]
do
((++ln))
((ln==1)) && {
[[ ${line:0:1} = '#' ]] && newfmt=0 || newfmt=1
((newfmt)) && {
[[ $line ]] || Unlinted "No title"
Out "$TNL$TSP$T$line$XT"
len=${#line}
read -r; ((++ln))
[[ $line =~ [^=] ]] && Unlinted "Title underline must be equal signs"
((len!=${#line})) && Unlinted "Underline length not equal to title's"
read -r; ((++ln))
}
}
case "${line:0:1}" in # first character
'#') ((newfmt)) && Unlinted "Bad first character"
((${#line} <= 2)) && Unlinted "No title"
[[ ! ${line:1:1} = ' ' ]] && Unlinted "2nd character no space"
Out "$TNL$TSP$T${line:2}$XT" ;;
'>') ((${#line} <= 3)) && Unlinted "No valid desciption"
[[ ! ${line:1:1} = ' ' ]] && Unlinted "2nd character no space"
[[ ! ${line: -1} = '.' ]] && Unlinted "Description doesn't end in full stop"
Out "$DNL$DSP$D${line:2}$XD"
2017-02-22 18:28:41 +01:00
DNL='' ;;
'-') ((newfmt)) && Unlinted "Bad first character"
((${#line} <= 2)) && Unlinted "No example content"
[[ ! ${line:1:1} = ' ' ]] && Unlinted "2nd character no space"
Out "$ENL$ESP$E${line:2}$XE" ;;
' ') ((newfmt)) || Unlinted "Bad first character"
((${#line} <= 4)) && Unlinted "No valid code content"
[[ ${line:0:4} = ' ' ]] || Unlinted "No four spaces before code"
val=${line:4}
# Value: convert {{value}}
val=${val//\{\{/$CX$V}
val=${val//\}\}/$XV$C}
Out "$CNL$CSP$C$val$XC" ;;
'`') ((newfmt)) && Unlinted "Bad first character"
((${#line} <= 2)) && Unlinted "No valid code content"
[[ ! ${line: -1} = '`' ]] && Unlinted "Code doesn't end in backtick"
val=${line:1:-1}
# Value: convert {{value}}
val=${val//\{\{/$CX$V}
val=${val//\}\}/$XV$C}
Out "$CNL$CSP$C$val$XC" ;;
'') continue ;;
*) ((newfmt)) || Unlinted "Bad first character"
[[ -z $line ]] && Unlinted "No example content"
Out "$ENL$EPS$E$line$XE" ;;
esac
done
trap 'less +Gg -~RXQFP"%pB\% tldr $I$page$XI - press Q to exit" <<<"$stdout$stderr"' EXIT
2016-01-04 06:33:47 +01:00
}
# $1: exit code; Uses: platform index
List_pages(){
local platformtext c1 c2 c3
[[ $platform ]] && platformtext=" from platform $I$platform$XI"
Out "${GRE}Known tldr pages$platformtext:"
Out "$(while read -r c1 c2 c3; do printf "%-19s %-19s %-19s %-19s$N" $c1 $c2 $c3; done \
<<<$(tr '{' '\n' <$index |grep "$platform" |cut -d "$Q" -f4))"
2017-02-22 18:28:41 +01:00
exit "$1"
2016-01-04 06:33:47 +01:00
}
# $1: exit code; Uses: dl cachedir zip_url
Cache_fill(){
local tmp unzip
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 -- "${cachedir:?}/"*
mv -- "$tmp/pages/"* "${cachedir:?}/"
rm -rf -- "$tmp"
Out "${GRE}Pages cached in $U$cachedir$XU$DEF"
2017-02-22 18:28:41 +01:00
exit "$1"
2016-01-04 06:33:47 +01:00
}
# $@: commandline parameters; Uses: version cached; Sets: platform page
Main(){
local markdown err
Config
markdown=0 err=0
case "$1" 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
}
2017-02-22 18:28:41 +01:00
List_pages "$err" ;;
-c|--cache) [[ $2 ]] && Err "No more command line arguments allowed" && err=10
2017-02-22 18:28:41 +01:00
Cache_fill "$err" ;;
-v|--version) [[ $2 ]] && Err "No more command line arguments allowed" && err=11
Out "$version"
2017-02-22 18:28:41 +01:00
exit "$err" ;;
-u|--update) [[ $2 ]] && Err "No more command line arguments allowed" && err=12
Update_index
2017-02-22 18:28:41 +01:00
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" ]] && {
2017-02-22 18:28:41 +01:00
Display_tldr <"$2" && exit "$err"
Err "A file error occured"
exit 15
} || Err "No file:$I $2$XI" && exit 16 ;;
-m|--markdown) shift
2017-02-22 18:28:41 +01:00
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
2017-02-22 18:28:41 +01:00
Usage "$err" ;;
-*) Err "Unrecognized option $I$1$XI"; Usage 19 ;;
2017-02-22 18:28:41 +01:00
*) 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
2016-01-04 06:33:47 +01:00
((markdown)) && Out "$(cat "$cached")" || Display_tldr <"$cached"
}
2016-01-04 06:33:47 +01:00
Main "$@"
# The error trap will output the accumulated stdout and stderr
2016-01-04 06:33:47 +01:00
exit 0