#!/bin/sh #--- DO NOT EDIT - AUTO GENERATED BY tldr-fmt/make-one-script ---# #--- VISIT https://github.com/avih/tldr-fmt ---# if [ "${UNZIPBIN-}" ] && [ "$UNZIPBIN" != unzip ]; then # info-unzip interprets $UNZIP as the file to extract... unzip() { #>&2 printf "+[%s]\n" "$UNZIPBIN" "$@" "$UNZIPBIN" "$@" } fi tldr_fmt_wrapper() { ( # WRAPPER: BEGIN tldr-fmt #!/bin/sh # tldr-fmt - Format `tldr` pages or similar for terminal display. # Home page: https://github.com/avih/tldr-fmt # # BSD 2-Clause License # # Copyright (c) 2020, Avi Halachmi (:avih) avihpit@yahoo.com # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # 2. Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. set -eu LC_ALL=C # internal (`case' patterns) unset -v IFS # our quotes and code assume standard IFS # default styles - keep it simple and ubiquitous. see `tldr-fmt -j`. sH=${TF_H-bold,green} # |# Heading sQ=${TF_Q-green} # |> Quoted content sL=${TF_L-} # natural # |- List item/example sC=${TF_C-bold,yellow} # ... `Code stuff` ... sA=${TF_A-cyan} # ... {{Argument}} ... sU=${TF_U-magenta} # ... ... # default option values for -w, -i, -s, -b, -e wrap=${TF_w-0} ind=${TF_i-0} styles=${TF_s-} bullc=${TF_b--} efail=${TF_e:-0} # Markdown bevior is very naive, but enough for tldr - and then some: # - head/list-item/quote always reset the style stack (no multi-level). # - head/list-item/quote continue implicitly until an empty line. # - right after explicit head/list-item we remove (up to one) empty line, # to match the tldr source formatting, but it still continues implicitly. # so for empty line after explicit head/list - it needs 2 empty input lines. # - currently there's no support for escaping. # - nesting is allowed selectively: # - as noted, head/list-item/quote do not nest - they just replace. # - inside or outside head/list-item/quote: detecting code/arguments/url. # - inside code: detecting only arguments. # - inside arguments/url: not trying to detect anything. # - our wrapping is not bad, but also not perfect: # - in `{{file}}.ext` we incorrectly wrap ".ext" eventhough there's no space. # - each input line is wrapped on its own - no multi-line-reflow, but: # - enough for tldr: single-line list-items, quotes/desc should be short. # - combining quotes unconditionally could hurt author's expectations. # pdksh/posh error incorrectly on $* with no args and set -u. use at least "" echo() { printf %s\\n "$*"; } # fix for pdksh/posh without args: "${*+$*}" DBG() { [ -z "${DBG-}" ] || echo "$@"; } ESC=$(printf \\033) NL=" " ebuf= # buffering 'echo'. requires a final call to becho_flush before exit becho_flush() { [ -z "$ebuf" ] || { printf %s "$ebuf" && ebuf=; }; } becho() { [ "${#ebuf}" -lt 512 ] || becho_flush; ebuf="$ebuf$*$NL"; } usage() { cat <<\USAGE # Usage: `tldr-fmt [{{OPTIONS}}] [{{FILE}}]...` > Format `tldr` pages or similar for terminal display. > With no {{FILE}}, or when {{FILE}} is `-`, read standard input. `-w {{N}}` Wrap at column {{N}}. Negative is from the end. `-i {{N}}` Indent examples by {{N}} characters (`0`..`8`). `-b {{C}}` Bullet {{C}}haracter when indent is 2 or more. `-s {{N}}` Use styles: `1`-yes/force `0`-no (plain text). `-e {{N}}` Exit code for empty input (bad {{OPTIONS}} is `1`, bad {{FILE}} is `2`). `-h` Display this help (styles/formats apply). `-j` Display additional styles/variables help. Requires: `tput` or `stty` if negative `-w` value is used. Report issues at . USAGE } usage_e() { >&2 echo "Usage: tldr-fmt [-hje:w:i:b:s:H:Q:L:C:A:U:] [FILE]..." >&2 echo "Try -h for basic help, -j for extra help." exit 1 } usage_s() { cat <<\USAGE # Usage: `tldr-fmt [-{{hje:w:i:b:s:H:Q:L:C:A:U:}}] [{{FILE}}]...` > Display styles can be set with options: The options `-H` (Heading), `-Q` (Quote), `-L` (List-item), `-C` (Code), `-A` (Argument) and `-U` (URL) set the styles. A Style is comma-separated list of colors, attributes and numeric SGR values which are applied in-order. Empty value will set the default (terminal) colors. Unknown element and any remains are taken literally. E.g. `tldr-fmt -C {{bold}},{{inverse}},{{red}} -H ''` Supported names are: {{bold}}, {{italic}}, {{underline}}, {{inverse}}, {{black}}, {{red}}, {{green}}, {{yellow}}, {{blue}}, {{magenta}}, {{cyan}}, {{white}}. Note: styles don't mix. E.g. green argument inside bold code will be plain green and not bold-green. > Environment variables can override default values: All default values can be overriden with var `TF_{{X}}`, where {{X}} is the option. E.g. `TF_w` sets the default wrap column, `TF_H` for the Heading style, etc. Don't forget to `export` any variables which you set. Command line options always set the final value. Report issues at . USAGE } # $1:STR, $2...: search items # if not found: PRE=$1 R= POST=, else: sets PRE R POST (R - the matched item) # if a search item is a prefix of another, the earlier in $2... is matched first() { full=$1 PRE=$1 R= POST=; shift for f; do p=${full%%"$f"*} [ ${#p} -ge ${#PRE} ] || PRE=$p R=$f POST=${full#*"$f"} done } # $1: wrap-column. $2 allow-break $3: allow-all, $4:STR. # wraps at most once, sets PRE/POST. success if wrap was needed. # if allow-all is 0 then at least one item will stay at PRE, else it's # possible that PRE will be empty and everything will be in POST. wrap1() { if [ ${#4} -le $1 ]; then # it fits, no need to wrap PRE=$4 POST= elif [ $2 = 0 ]; then # doesn't fit and can't break - all or nothing [ $3 = 1 ] && PRE= POST=$4 || PRE=$4 POST= else wcol=$1 wpre= wpost=$4; [ $3 = 0 ] && wall= || wall=1 case $wpost in " "*) wsp=1; wcol=$((wcol-1));; *) wsp=; esac while [ "$wpost" ]; do first "$wpost" " " wcan=${wpre:+$wpre }$PRE if [ ${#wcan} -le $wcol ]; then wpre=$wcan wpost=$POST wall=1 else [ "$wall" ] || wpre=$wcan wpost=$POST break; fi done PRE=${wsp:+ }$wpre POST=${wpost#" "} PRE=${PRE%" "} fi [ "$POST" ] } # process markdown tldr_style() { spush() { # $1: style code (H/Q/L/C/A/U) eval sS=\$s$1 s=$1 stack=$stack/$1 out=$out$sS } spop() { stack=${stack%/*} s=${stack##*/} # empty is fine, else its value starts with a reset [ "$s" ] && eval sS=\$s$s || sS= # sS used after reset, can be empty [ "$s" ] && out=$out$sS || out=$out$sR # but pop should reset if empty } do_last_line() { # if not direct, stdin ends in 0/1 from cat success/fail. errin= [ "$direct" ] || { case $line in *1) errin=1; esac line=${line%?} } [ "$line" ] } len= s= sS= stack= skipnl= # s is the top/current state at stack, sS is its seq while IFS= read -r line || do_last_line; do out= len=0 [ "$line" ] || [ -z "$skipnl" ] || { skipnl=; continue; } skipnl= [ "$line" ] || { s= sS= stack=; becho ""; continue; } case $line in # explicit "top" containers reset the stack. '> '*) s= stack=; spush Q; line=${line#??};; '# '*) s= stack=; spush H; line=${line#??} skipnl=1;; '- '*) s= stack=; spush L; line=${line#??} skipnl=1;; *) out=$out$sS;; # terminal was reset after the prev input line esac case $skipnl-$stack in # abuse skipnl to indicate explicit list-item 1-*L) out=$sR$indent0$sS len=$ind;; # new indent/bullet -*L*) out=$sR$indent$sS len=$ind;; # continued empty indent esac while [ "$line" ]; do case $s in # find (or not) different marks depending on state U) first "$line" '>';; A) first "$line" '}}';; C) first "$line" '`' '{{' [ "$R" != '`' ] || R=\' # code end is changed to ' (`foo') ;; *) first "$line" '`' '{{' '<' esac if [ "$wrap" ] && [ $((len+${#PRE})) -gt $wrap ]; then case $stack in *L*) c0=$ind trail=$sR$NL$indent$sS;; *) c0=0 trail=$sR$NL$sS esac # we wrap PRE (text to next mark), but save/restore R and POST. # - FIXME: in {{file}}.ext we wrap ".ext" incorrectly (no space) # because "file" was already processed when we get to ".ext", # so now we can only choose between wrap/overflow. we wrap. pre=$PRE r=$R post=$POST; [ "$s" = U ] && brk=0 || brk=1 while wrap1 $((wrap-len)) $brk $((len>c0)) "$pre"; do out=$out$PRE$trail len=$c0 pre=$POST done R=$r POST=$post # PRE stays from wrap1 (can trimmed on "fail") fi len=$((len+${#PRE})) out=$out$PRE case $R in # process the next detected mark, if at all '<') spush U;; '`') spush C;; '{{') spush A;; ">") spop; POST=" "$POST;; # URL+space helps dbl-click-select "}}"|"'") spop;; esac line=$POST done becho "$out$sR" done becho_flush [ -z "$errin" ] || exit 2 [ "$len" ] || exit "$efail" } # "$*": style codes sequence to apply. result is in $R, remains are in $POST # this implementation always returns a sequence which starts with SGR0 style_seq() { R=0 L=$* while [ "$L" ]; do n=${L%%[!0-9]*} # longest prefix sequence of digits (or empty) if [ "$n" ]; then L=${L#$n} else v=${L%%[!a-z]*} # lower-case prefix or empty case $v in bold) n=1;; italic) n=3;; underline) n=4;; inverse) n=7;; black) n=30;; red) n=31;; green) n=32;; yellow) n=33;; blue) n=34;; magenta) n=35;; cyan) n=36;; white) n=37;; *) break esac L=${L#$v} fi L=${L#?} # strip delimiter to next item, if there is one R=$R\;$n done R=$ESC[${R}m POST=$L } is_num() { [ ${#1} -le 7 ] && case x$1 in x|x*[!0-9]*) false; esac; } wrap_ok() { [ -z "${1#-}" ] || is_num "${1#-}"; } # "-"/empty are ok tsize() { stty size || stty -F/dev/tty size || tput cols; } norm_wrap() { [ "${wrap#-}" ] || wrap=${wrap}0 case $wrap in -*) # disable wrap if not a tty, else find or fallback to 80 [ -t 1 ] || { wrap=; return; } w=$( [ -t 0 ] && tsize || tsize /dev/null && w=${w#*" "} \ && is_num "$w" || w=80 wrap=$((w-${wrap#?})) esac [ $wrap -gt 0 ] || wrap= } # main help= badv= # and set on top: wrap, ind, styles, sH, sQ... # ignore TF_b, only warn/ignore bad vars (else e.g. -h is blocked on bad env) wrap_ok "$wrap" || badv=\$TF_w case $ind in 0|1|2|3|4|5|6|7|8);; *) badv=$badv" "\$TF_i; esac case x$styles in x0|x1|x);; *) badv=$badv" "\$TF_p; esac if [ "$badv" ]; then >&2 echo tldr-fmt: ignoring bad $badv value wrap=0 ind=0 styles= fi while getopts hje:w:i:b:s:H:Q:L:C:A:U: o; do case $o in b) bullc=$OPTARG;; s) styles=$OPTARG;; i) ind=$OPTARG;; w) wrap=$OPTARG;; e) efail=$OPTARG;; h) help=usage;; # keep collecting options j) help=usage_s;; [HQLCAU]) eval s$o=\$OPTARG;; *) usage_e; exit 1 esac done shift $((OPTIND-1)) # verification, normalization case x$styles in x0|x1|x);; *) usage_e; esac is_num "$efail" || usage_e wrap_ok "$wrap" || usage_e norm_wrap case $ind in 0|1|2|3|4|5|6|7|8);; *) usage_e; esac indent= while [ ${#indent} -lt $ind ]; do indent=$indent" "; done [ "$bullc" ] || bullc=" "; [ $ind -lt 2 ] && indent0=$indent || indent0=${indent#??}$bullc" " # styles disabled by default if not a tty. -s0/1 can always override it. if ! [ "$styles" ] && ! [ -t 1 ] || [ "$styles" = 0 ]; then sR= sH= sQ= sL= sC= sU= sA= else # translate style variable codes to actual escape sequences sR= # used to print reset for sv in sR sH sQ sL sC sU sA; do eval 'style_seq "$'$sv'"; '$sv'=$R$POST' # append leftovers literally done fi # finally... if [ "$help" ]; then $help | direct=1 tldr_style # format markdown regardless of styles else [ "$#" -gt 1 ] || [ "${1--}" != - ] || { direct=1; tldr_style; exit; } direct=; { cat -- ${1+"$@"} && printf 0 || printf 1; } | tldr_style fi # WRAPPER: END tldr-fmt ) } TT_FMT=${TT_FMT:-tldr_fmt_wrapper} # WRAPPER: BEGIN ttldr #!/bin/sh # ttldr: Tiny-ish tldr client, uses tldr-fmt for display # Copyright (c) 2020, Avi Halachmi (:avih) avihpit@yahoo.com # BSD 2-Clause License - see LICENSE file set -euf self=${0##*/} version="v0.2 - https://github.com/avih/tldr-fmt" # defaults: # url=${TT_URL:-https://tldr.sh/assets/tldr.zip} url=${TT_URL:-https://github.com/tldr-pages/tldr/releases/latest/download/tldr.zip} zipfile=${TT_ZIPFILE:-${HOME:-~}/.cache/ttldr/tldr.zip} # not extracted by default days=${TT_DAYS-30} # days old cache before trying to update. empty is never lang=${TT_LANG-} # default -L value plat=${TT_PLAT-} # default -p value plats=${TT_PLATS-} # after 'common', try these before others. e.g. "linux osx windows" progress=${TT_PROGRESS-yes} # empty to suppress messages on auto update/extract q=${TT_Q:-0} # default -q value strict=${TT_STRICT-} # if empty - try useful non-posix stuff before posix. fmt=${TT_FMT:-tldr-fmt} # [/path/to/]tldr-fmt lsum=${TT_LSUM:-no} # default -s value hascmd() { command -v -- "$1" >/dev/null; } echo() { printf %s\\n "${1+$*}"; } wmsg() { [ "$q" != 0 ] || >&2 echo "${1+$*}"; } warn() { wmsg "$self: ${1+$*}"; } err() { # $1:err-str (empty to skip) $2:non-empty to add usage note [ -z "${1}" ] || [ "$q" = 2 ] || >&2 echo "$self: ${1-}" [ "${2-}" ] || exit 1 [ "$q" = 2 ] || >&2 echo "Usage: $self [-$opts] CMD [tldr-fmt options]..." [ "$q" = 2 ] || >&2 echo "try $self -h for help." exit 1 } help() { echo "Usage: $self [OPTIONS] CMD [tldr-fmt options]..." echo "Display tldr page (mini-help) for CMD." echo h|| echo " Replacing auto-detection of platform or language:" echo " -p P1,P2... Try tldr-platforms P1,P2... before 'common'." h|| echo " -L L1,L2... Try languages L1,L2... before 'en' (English)." h|| echo " The special value 'all' with -p/-L is for all available values." h|| echo h|| echo " To try only P1,P2../L1,L2.. without fallbacks, use @ e.g. -L @it,es" h|| echo " To try also the normal auto-detection, add a comma e.g. -L it,es," h|| echo " E.g. '-L @XX' is XX, '-L XX' is XX+en, '-L XX,' is XX+detect+en." echo h|| echo " List commands for +common platforms (for all: -p all):" echo " If CMD is STR/ List all commands which begin with STR." echo " If CMD is STR// List all commands which contain STR." echo " -l (without CMD) List all commands (same as the CMD '/')." echo " -l (with CMD) List all platforms/languages with CMD entry." echo " -s no|NUM|a|- List with summaries: no | crop-(NUM|auto|no)." echo h|| echo " Other options:" h|| echo " -U Unzip/extract the pages now - and on every future update." h|| echo " $self displays pages directly from the tldr zip file without" h|| echo " extracting it. This may be slow on some systems (not all)." h|| echo " Use -U (once) for faster page access on such systems." h|| echo " To get back to zip-only mode (until -U is used again):" h|| echo " rm -rf -- '$unzipdir'" h|| echo echo " -u Update the tldr pages${days:+ (auto if $days days old)}." h|| echo " -r Print the raw page source without any formatting." h|| echo " -q N Quiet - suppress 1: warnings 2: warnings+errors." h|| echo " -v Display $self version and exit." echo " -h Display basic help and exit." echo " -H Display advanced help and exit." echo echo "tldr-fmt is used to format/render pages, and all options" echo "after CMD are forwarded. Try 'tldr-fmt -h' for help." echo h|| echo "Default values and override variables are documented" h|| echo "at top of of this script: $(command -v -- "$0")" h|| echo echo "Requires: tldr-fmt, unzip, curl/wget, POSIX utilities." echo "Report issues at https://github.com/avih/tldr-fmt ." } # ensure $zipfile is sane. we only create/destruct/touch paths with this prefix # (one exception: mkdir -p -- "$(dirname "$zipfile")" may create upper dirs). # at least one '/', absolute or not starting with '-', + '.zip' ('./.zip' is ok) # we avoid '-' because busybox doesn't support unzip ... -- "$zipfile" case $zipfile in /*.zip | [!-]*/*.zip);; *) err "\$zipfile must be a sane path, preferably absolute -- $zipfile" esac index=$zipfile.index unzipdir=$zipfile.extract opts=FhHL:lp:q:rs:uUv raw= update_now= unzip_now= xnow= list= listcmds= pall= lall= cmd= fmt_only= while getopts $opts o; do case $o in F) shift; "$fmt" "$@"; exit;; # exactly -F followed by tdlr-fmt opts h) h() { :; }; help; exit;; H) h() { false; }; help; exit;; L) lang=$OPTARG;; l) list=yes;; p) plat=$OPTARG;; q) q=$OPTARG; case $q in 0|1|2) ;; *) err "bad -q value -- $q" usage; esac;; r) raw=yes;; s) lsum=$OPTARG;; u) update_now=yes xnow=yes;; U) unzip_now=yes xnow=yes;; v) echo "$self $version"; exit;; *) err "" usage esac done shift $((OPTIND-1)) # -- download/update/index/extract pages -- cat_haz() { # like "cat -" but error if empty (pipefail-ish). needs normal IFS set -- $(dd status=none bs=1 count=1 | od -b -v -A n) [ "${1-}" ] && printf "\\$1" && cat } cat_haz() { # like "cat -" but error if empty (pipefail-ish). needs normal IFS set -- $(head -c 1 | od -b -v -A n) [ "${1-}" ] && printf "\\$1" && cat } # $1:url $2:target-file-name(dir created) $3:tmp-file-name(dir not created) save_as() { # doesn't touch target file on [mid-download] failure [ "${1-}" ] && [ "${2-}" ] && [ "${3-}" ] || return 1 # silent fail hascmd curl || hascmd wget || { warn curl/wget not found; return 1; } mkdir -p -- "$(dirname "$2")" || return 1; { hascmd curl && curl -L -sS -o "$3" -- "$1"; } \ || { hascmd wget && wget -q -O "$3" -- "$1"; } \ || { rm -- "$3" 2>/dev/null || : warn "error saving $1 as $3"; return 1; } touch -- "$3" && mv -f -- "$3" "$2" # keep tmp file on failed finalize } # download archive if requested or missing, else try to update if old if [ "$update_now" ] || ! [ -f "$zipfile" ]; then wmsg "Downloading tldr pages... -> $zipfile" save_as "$url" "$zipfile" "$zipfile.tmp" \ || err "failed to download/save pages archive" elif [ "$days" ] && [ "$(find "$zipfile" -mtime +"$days" -print)" ]; then [ -z "$progress" ] || wmsg "Updating tldr pages..." save_as "$url" "$zipfile" "$zipfile.tmp" \ || { touch -- "$zipfile" || :; warn "update failed (retry in $days days)"; } fi # $1:path $2:reference-path -> tests $1 newer than $2 (bash test "$1" -nt "$2") newer() { [ -z "$strict" ] && ([ "$1" -nt "$2" ] && ! [ "$2" -nt "$1" ]) 2>/dev/null \ || [ "$(find -- "$1" -prune -newer "$2" 2>/dev/null)" ] } # create index if missing/outdated newer "$index" "$zipfile" || unzip -l "$zipfile" | sed 's/.* //' | grep '^pages.*/.*/.*\.md$' | sort -t/ -k3 \ | cat_haz >"$index" || { rm -- "$index" || :; err "can't index archive"; } # extract if requested or exists+outdated [ -d "$unzipdir" ] && use_dir=yes || use_dir= if [ "$unzip_now" ] || { [ "$use_dir" ] && ! newer "$unzipdir" "$zipfile"; }; then [ -z "$xnow$progress" ] || wmsg "Extracting pages...${xnow:+ -> $unzipdir/}" tmpnew=$unzipdir.tmpnew tmpold=$unzipdir.old.delete-me rm -rf -- "$tmpnew" && rm -rf -- "$tmpold" \ && mkdir -p -- "$tmpnew" \ && unzip -q "$zipfile" -d "$tmpnew" \ && { ! [ -e "$unzipdir" ] || mv -f -- "$unzipdir" "$tmpold"; } \ && mv -f -- "$tmpnew" "$unzipdir" \ && { rm -rf -- "$tmpold" || warn "ready, but final cleanup failed. please rm -rf '$tmpold'"; } \ && use_dir=yes || err "failed to extract $zipfile to $unzipdir/" fi # -- string utilities -- # $1:haystack $2:needle $3:replacement result->$R replace() { R= R1=$1; while case $R1 in *"$2"*) R=$R${R1%%"$2"*}$3; R1=${R1#*"$2"} ;; *) false; esac; do :; done; R=$R$R1 } # $1:haystack $2...: one-char args to '\'-escape in-order result->$R escape() { R=$1; shift; for R1; do replace "$R" "$R1" "\\$R1"; done; } #escape() { R=$1; shift; for R1; do replace "$R" "$R1" "[$R1]"; done; } # $1:str result->$R: escaped str for literal match in ZIPPATH/BRE/ERE escZIP() { case $1 in *['[\*?']*) escape "$1" \\ \[ \* \?;; *) R=$1; esac; } # infozip on windows turns \[ on windows into \[, but this works [[] #escZIP() { case $1 in *['[\*?']*) escape "$1" \\ \* \?;; *) R=$1; esac; replace "$R" '[' '[[]'; } escBRE() { case $1 in *['[\.*^$']*) escape "$1" \\ \[ . \* \^ \$;; *) R=$1; esac; } escERE() { escBRE "$1"; case $R in *['(){|+?']*) escape "$R" \( \) { \| + \?; esac; } tolower() { R=$(echo "$*" | LC_ALL=C tr A-Z a-z && echo x) && R=${R%??}; } # -- CMD: dash-joined args, space->"-", tolower, find pages if not listing -- for c; do case $c in -*) break; esac; cmd=$cmd-$c; shift; done; cmd=${cmd#?} case $cmd in *" "*) replace "$cmd" " " -; cmd=$R; esac case $cmd in *[ABCDEFGHIJKLMNOPQRSTUVWXYZ]*) tolower "$cmd"; cmd=$R; esac # $cmd is now ready, possibly followed by /[@] or //[@] case $cmd in */|*/@) case $cmd in *@) plat=all cmd=${cmd%?}; esac case $cmd in *//) listcmds=// cmd=${cmd%??};; *) listcmds=/ cmd=${cmd%?}; esac esac [ "$cmd" ] || [ -z "$list" ] || listcmds=/ # -l without CMD if [ -z "$listcmds" ]; then # normal command page, or platform list (-l CMD) [ "$cmd" ] || { [ "$xnow" ] && exit || err "missing CMD" usage; } escBRE "$cmd"; cmdBRE=$R cmdfiles=$(grep "/$cmdBRE\\.md\$" <"$index") \ || err "entry not found -- $cmd" fi # -- listing and/or there are pages for $cmd -> process platform/language -- TAB=" " NL=" "; IFS=" ,@:$NL" # we mainly field-split plat/lang, without path-expansion uniqleft() { # keep only leftmost of each non-empty recurring arg R=; for RI; do [ "$RI" ] && case " $R " in *" $RI "*);; *) R="$R $RI"; esac || : done; R=${R#?} } # platform case $plat in @*);; *) d= case $plat\x in x|*,x) # auto-detect (empty or requested) un=$(uname) case $un in Darwin) d=osx;; SunOS) d=sunos;; Linux) [ "${ANDROID_ROOT+x}" ] && d=android,linux || d=linux;; *CYGWIN*|*MSYS*|*UWIN*|*Win*|*WIN*|*MINGW*|*NT*) d=common,windows;; # we use posix env -> prioritize unix *BSD*|*DragonFly*) d=bsd;; # no tldr pages currently. that's fine *) d=$(echo "$un" | LC_ALL=C tr A-Z a-z);; # tolower and hope... esac esac plat=$plat,$d,common # requested, detected, common if [ -z "$listcmds" ]; then # commands list stops at common plat=$plat,-,$plats # warning-marker, preferred others-order for a in $cmdfiles; do # and all platforms from the found pages a=${a#*/}; plat=$plat,${a%%/*} done fi esac uniqleft $plat; plat=$R case " $R " in *" all "*) pall=yes; esac # language case $lang in @*);; *) d= case $lang\x in x|*,x) # auto-detect (empty or requested) [ "${LC_ALL:+x}" ] && d=${LC_ALL} \ || { [ "${LANG:+x}" ] && d=${LANGUAGE-},$LANG; } esac al=$lang,$d,en; lang= # requested, detected, en for l in $al; do l=${l%%.*}; lang=$lang,$l,${l%_*} # add both ll_CC and ll done esac uniqleft $lang; lang=$R case " $R " in *" all "*) lall=yes # '-l all' is rarely useful, but there could be CMDs not in en if [ -z "$listcmds" ]; then for a in $cmdfiles; do # add all languages from the found pages a=${a%%/*}; [ "$a" = pages ] && a=en || a=${a#pages.} lang="$lang $a" done # we could uniq again, but no need - page loop ends on 1st match fi esac #exit if [ "$use_dir" ]; then cmdfile_out() { cat "$unzipdir/$1"; } cmdfile_to() { R="$unzipdir/$1"; shift; [ -e "$R" ] && "$@" <"$R"; } else cmdfile_out() { escZIP "$1"; unzip -p "$zipfile" "$R"; } cmdfile_to() { escZIP "$1"; shift; unzip -p "$zipfile" "$R" | "$@"; } fi # listing commands starts and ends here if [ "$listcmds" ]; then escERE "$cmd"; cre=$R.\* pre= lre= # cmd/plat/lang ERE to grep the index [ "$listcmds" = / ] && ltype="begin with" || ltype=contain cre=.\*$cre if [ "$pall" ]; then pre="common|linux|windows|osx|sunos|android"; else for x in $plat; do escERE "$x"; pre=$pre\|$R; done; pre=${pre#?} fi if [ "$lall" ]; then lre="[^/]*"; else for x in $lang; do [ "$x" = en ] && x=pages || x=pages.$x escERE "$x"; lre=$lre\|$R done; lre=${lre#?} fi ere="^($lre)/($pre)/$cre\\.md\$" is_uint() { case $1 in *[!0123456789]*) ! :;; *) [ "${#1}" -le 7 ]; esac; } [ "$lsum" = no ] || { [ "$lsum" = - ] && lsum=; } || is_uint "$lsum" || { ! [ -t 1 ] && lsum= || { tsize() { stty size 2>/dev/null || { hascmd tput && tput cols; }; } lsum=${COLUMNS:-$(tsize || ([ -e /dev/tty ] && tsize ...' readl() { IFS= read -r "${1-R}"; } outline() { readl && readl && readl && [ "$R" ] && out "$1" "$R"; } trim() { awk '{if (length($0)>'$1') print substr($0,1,'$1'-3)"..."; else print $0}'; } trim() { awk "{if (length(\$0)>$1) print substr(\$0,1,$1-3)\"...\"; else print \$0}"; } #trim() { awk 'length($0)>'$1'{print substr($0,1,'$1'-3)"..."; next} 1 {print $0}'; } #repeat() { # $1:STR $2:count(uint) result->R #case $2 in 0) R=;; 1) R=$1;; 2) R=$1$1;; 3) R=$1$1$1;; 4) R=$1$1$1$1;; *) #repeat "$1" "$(($2 / 4))" #case $(($2%4)) in 0) R=$R$R$R$R;; 1) R=$R$R$R$R$1;; #2) R=$R$R$R$R$1$1;; *) R=$R$R$R$R$1$1$1;; esac #esac #} #ellip() { #[ "${#1}" -le "$2" ] && R=$1 \ #|| { repeat "?" "$((${#1} + 3 - $2))"; R=${1%$R}...; } #} #rpad() { #[ "${#1}" -ge "$2" ] && R=$1 \ #|| { repeat " " "$(($2 - ${#1}))"; R=$1$R; } #} #trim() { #R1=$1; shift; while readl; do #ellip "$R" "$R1"; set -- ${1+"$@"} "$R" #[ "$#" -lt 10 ] || { printf %s\\n "$@"; set --; } #done #[ "$#" = 0 ] || printf %s\\n "$@" #} #xprintlines() { #getdesc() { readl && readl && readl && [ "$R" ]; } #c= pc= #set -- #(while readl; do # desc in en, from the 1st platform we bump into #c=${R##*/}; [ "$c" = "$pc" ] && continue || pc=$c #cmdfile_to "pages/${R#*/}" getdesc || R="> (English entry missing)" #set -- ${1+"$@"} "$c" "$R" #[ "$#" -lt 10 ] || { printf %s\\n "$@"; set --; } #done #|md5sum #2>/dev/null #[ "$#" = 0 ] || printf %s\\n "$@") | { #out() { rpad "${1%.md}" 14; ellip "$R ${2#??}" "$lsum"; echo "$R"; } #[ "$lsum" ] || out() { rpad "${1%.md}" 14; echo "$R ${2#??}"; } #while IFS= read -r c && IFS= read -r d; do #rpad "${c%.md}" 14 #R="$R ${d#??}"; [ -z "$lsum" ] || ellip "$R" "$lsum" #set -- ${1+"$@"} "$R" #[ "$#" -lt 10 ] || { printf %s\\n "$@"; set --; } #done #[ "$#" = 0 ] || printf %s\\n "$@" #} #} ##printlines |md5sum printlines() { c= pc= while readl; do # desc in en, from the 1st platform we bump into c=${R##*/}; [ "$c" = "$pc" ] && continue || pc=$c cmdfile_to "pages/${R#*/}" outline "$c" \ || out "$c" "> (English entry missing)" # dummy desc done 2>/dev/null } # trim to $COLUMNS-1 - not all terminals support delayed wrap [ "$lsum" ] && { printlines | trim "$((lsum-1))" || :; } \ || printlines fi } | cat_haz || err "no matches ($ltype) -- $cmd" exit fi # -- normal page display, or platforms search for CMD -- cmdpath() { # readlink -f, or parse `ls -l` once (ok for absolute final link) case $1 in */*);; *) set -- "$(command -v -- "$1")"; esac [ -L "$1" ] || { echo "$1"; return; } hascmd readlink && readlink -f -- "$1" 2>/dev/null \ || ls -l -- "$1" | sed 's/.* -> //' } dofmt() { hascmd "$fmt" || { selfpath=$(cmdpath "$0") && fmt=${selfpath%/*}/$fmt; } "$fmt" ${1+"$@"} } # search the page to display warn= [ "$list" ] || for p in $plat; do [ "$p" != - ] || { [ "$pall" ] || warn=yes; continue; } for l in $lang; do [ "$l" = en ] && L=pages || L=pages.$l case $cmdfiles in *"$L/$p/$cmd.md"*) if [ "$raw" ] then cmdfile_out "$L/$p/$cmd.md" else cmdfile_to "$L/$p/$cmd.md" dofmt -e127 ${1+"$@"} fi [ -z "$warn" ] || wmsg "$NL<< $self: note - this entry is for $p >>" exit esac done done # [page not found and] list available platforms/langs for CMD r= pout= if [ "$list" ] || [ "$q" = 0 ]; then # if not list, warn-level - suppressible [ "$list" ] && indent= || indent=" " langs=$(echo "$cmdfiles" | cut -d/ -f1 | sort | uniq) plats=$(echo "$cmdfiles" | cut -d/ -f2 | sort | uniq) for p in $plats; do for l in $langs; do [ "$l" = pages ] && L=en || L=${l#pages.} case $cmdfiles in *"$l/$p/"*) pout="$pout $L"; esac done uniqleft $pout; pout=\ $R [ -z "$pout" ] || { r="$r$NL$indent$p: ${pout#?}"; pout=; } done fi [ "$list" ] && echo "${r#?}" \ || err "entry not found -- $cmd${r:+${NL}available for:$r}" # WRAPPER: END ttldr