├── LICENSE ├── README.md ├── Screenshot.png ├── test └── tldr /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Ray Lee 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tldr 2 | 3 | A fully-functional POSIX shell client for [tldr-pages](https://github.com/tldr-pages/tldr). 4 | This version aims to be the easiest, smallest, and most universal client to set up 5 | on a new account, without sacrificing any features. It uses only `/bin/sh` features 6 | and `curl`, and tested on Linux, OSX, FreeBSD, with `bash`, `sh`, `dash`, `ksh`, 7 | `zsh`, `csh`. 8 | 9 | ![tldr screenshot](Screenshot.png?raw=true) 10 | 11 | ## Installation 12 | ```bash 13 | mkdir -p ~/bin 14 | curl -o ~/bin/tldr https://raw.githubusercontent.com/raylee/tldr/master/tldr 15 | chmod +x ~/bin/tldr 16 | ``` 17 | 18 | Then try using the command! If you get an error such as _-bash: tldr: command not found_, 19 | you may need to add `~/bin` to your `$PATH`. On OSX edit `~/.bash_profile` 20 | (`~/.bashrc` on Linux), and add the following line to the bottom of the file: 21 | ```bash 22 | export PATH=$PATH:~/bin 23 | ``` 24 | 25 | If you'd like to enable shell completion (eg. `tldr w` to get a 26 | list of all commands which start with w) then add the following to the same 27 | startup script: 28 | 29 | ```bash 30 | complete -W "$(tldr 2>/dev/null --list)" tldr 31 | ``` 32 | 33 | Or for `~/.zshrc`, add: 34 | ```bash 35 | [ -f ~/bin/tldr ] && compctl -k "($( tldr 2>/dev/null --list))" tldr 36 | ``` 37 | 38 | ## Prerequisites 39 | 40 | `curl` and `unzip` need to be available somewhere in your `$PATH`. The script is otherwise self-contained. 41 | 42 | ## Usage 43 | 44 | ``` 45 | tldr [options] 46 | 47 | [options] 48 | -l, --list show all available pages 49 | -L, --language [code] override language detection, set preferred language 50 | -p, --platform [name] show page from specific platform 51 | -u, --update update cached copies of tldr page files 52 | -h, --help this help overview 53 | -v, --version show version information 54 | -n, --no-cache display pages directly from GitHub (watch ratelimits) 55 | 56 | 57 | Show the tldr page for command. 58 | 59 | The client caches a copy of the tldr pages under $(cache_dir) 60 | By default, cached copies will be refreshed after $(cache_days) days. 61 | 62 | Examples: 63 | Show an overivew of unzip: 64 | tldr unzip 65 | Show commands for all platforms: 66 | tldr -l -p all 67 | If you have fzf installed, try: 68 | tldr -l -p all | fzf --preview 'tldr {}' 69 | Show the Russian page for tar: 70 | tldr -L ru tar 71 | List pages in the Android section: 72 | tldr -p android -l 73 | ``` 74 | 75 | 76 | ## Customization 77 | You can change the styling of the output from `tldr` by defining some environment variables. For 78 | example, try adding the following lines to your `~/.bash_profile` file (OSX) or `~/.bashrc` file 79 | (Linux). 80 | 81 | ```bash 82 | export TLDR_HEADER='magenta bold underline' 83 | export TLDR_QUOTE='italic' 84 | export TLDR_DESCRIPTION='green' 85 | export TLDR_CODE='red' 86 | export TLDR_PARAM='blue' 87 | ``` 88 | 89 | Possible settings are: `black`, `red`, `green`, `yellow`, `blue`, `magenta`, `cyan`, 90 | `white`, `onblue`, `ongrey`, `reset`, `bold`, `underline`, `italic`, `eitalic`, `default` 91 | _(some variables may not work in some shells)_. 92 | 93 | NB: You will need to log into a new session to see the effect. Just run the commands in the 94 | terminal directly to see the change immediately and temporarily. 95 | 96 | ## Contributing 97 | 98 | This is the result of a Sunday afternoon project. It's been lightly tested under Mac OS X 10.9 99 | and Ubuntu Linux 15.10. I've tried to make the project as portable as possible, but if there's 100 | something I missed I'd love your help. 101 | 102 | * Want a new feature? Feel free to file an issue for a feature request. 103 | * Find a bug? Open an issue please, or even better send me a pull request. 104 | 105 | Contributions are always welcome at any time! 106 | -------------------------------------------------------------------------------- /Screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raylee/tldr-sh-client/35e652da07f57c2a44db1002d1d5e6bf4e23d9a9/Screenshot.png -------------------------------------------------------------------------------- /test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | tests=( 4 | 'tldr -h' 5 | 'tldr tar' 6 | 'tldr abcdefg' 7 | 'tldr git config' 8 | 'tldr --list' 9 | ) 10 | 11 | if [ "$TLDR_NETWORK_TESTS" = "true" ]; then 12 | tests+=( 13 | 'tldr -u; printf "Update exit code: %d\n" $?' 14 | ) 15 | fi 16 | 17 | run_all() { 18 | target=$1 19 | shift 20 | 21 | ln -sf "$target" .tldr-ephemeral-runner 22 | 23 | for t in "$@"; do 24 | ./${t/tldr/.tldr-ephemeral-runner} 2>/dev/null 25 | done 26 | 27 | rm .tldr-ephemeral-runner 28 | } 29 | 30 | expected=$(run_all ./tldr "${tests[@]}") 31 | 32 | test_shell() { 33 | shell=$1 34 | tldr=./.tldr--$shell 35 | 36 | shellPath="$(command -v $shell)" 37 | if [ -z "$shellPath" ]; then 38 | false 39 | return 40 | fi 41 | 42 | # the next line is, y'know, annoying, but it's a reliable way to 43 | # guarantee we run under that shell 44 | sed "1s:#!/bin/bash:#!$shellPath:" ./tldr >$tldr 45 | chmod +x $tldr 46 | result=$(run_all $tldr "${tests[@]}") 47 | rm $tldr 48 | 49 | if [ "$result" != "$expected" ]; then 50 | diff -U 0 <(echo "$expected") <(echo "$result") | 51 | tail +4 >fail-$shell.log 52 | 53 | false 54 | else 55 | true 56 | fi 57 | } 58 | 59 | test_shells() { 60 | fmt="%s\t%s\n" 61 | for shell in "$@"; do 62 | if test_shell $shell; then 63 | printf $fmt $shell PASS 64 | else 65 | printf $fmt $shell FAIL 66 | fi 67 | done 68 | } 69 | 70 | # On Debian based distros, most of these shells can be installed via: 71 | # sudo apt install csh tcsh bash dash ksh zsh 72 | # `gosh` is written in Go, and can be installed using: 73 | # go install mvdan.cc/sh/v3/cmd/gosh@latest 74 | 75 | test_shells sh bash dash zsh ksh csh tcsh gosh 76 | -------------------------------------------------------------------------------- /tldr: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # tldr client by Ray Lee, http://github.com/raylee/tldr 3 | # tldr-sh-client version: 2021-12-18 spec: v1.5 4 | 5 | # The following is written in a mildly functional style. It makes the code 6 | # marginally slower, but more easily traceable, debuggable, and composable. 7 | # And for me, at least, easier to reason about. 8 | 9 | # In scripts "$@" often means 'all the arguments'. Below, it's also used 10 | # for function indirection, executing the first parameter ($1) of $@ as a 11 | # command, $2 onward as parameters, and wrapping it with other behaviors. 12 | 13 | # Q: Why implement tldr in a plain POSIX shell script at all? It's nice to have 14 | # a low-dependency way to see the pages, independent of local architecture. For 15 | # some of us, POSIX sh is everywhere. 16 | 17 | # silent executes its arguments as a command with args, silently. 18 | silent() { 19 | >/dev/null 2>&1 "$@" 20 | } 21 | 22 | set -uf 23 | 24 | main() { 25 | check_requirements 26 | 27 | # Set up the local platform as the default for pages. 28 | preferred_platform "$(local_platform)" 29 | 30 | # Some people like running this without saving any state. 31 | cache true 32 | if [ "${TLDR_CACHE:-}" = no ]; then 33 | cache false 34 | debug log Disabling cache 35 | fi 36 | 37 | Cmd='' 38 | bool listing 39 | while [ $# -gt 0 ]; do 40 | case $1 in 41 | -h|--help) 42 | usage > /dev/stderr 43 | exit 0 44 | ;; 45 | -l|--list) 46 | listing true 47 | ;; 48 | -L|--language) 49 | preferred_language "$2" 50 | shift 51 | ;; 52 | -n|--no-cache) 53 | cache false 54 | ;; 55 | -p|--platform) 56 | preferred_platform "$2" 57 | shift 58 | ;; 59 | -u|--update) 60 | update_tldr_cache 61 | exit 0 62 | ;; 63 | -v|--version) 64 | version_info 65 | exit 0 66 | ;; 67 | -*) 68 | panic "Unrecognized option '$1'" 69 | ;; 70 | *) 71 | # By the tldr client spec, the first argument 72 | # that does not start with a dash signals the 73 | # start of the command name. Spaces are 74 | # converted to dashes, and uppercase to lower. 75 | Cmd=$(printf %s "$*" | tr 'A-Z ' 'a-z-') 76 | break 77 | ;; 78 | esac 79 | shift 80 | done 81 | 82 | # Download a copy of the pages locally if we don't have them already. 83 | if ! exists "$(page_cache)/index.json"; then 84 | update_tldr_cache 85 | fi 86 | 87 | if listing; then 88 | list_pages 89 | exit 0 90 | fi 91 | 92 | if [ -z "$Cmd" ]; then 93 | # If stdin is a pipeline or a file, display it as a page. 94 | if ! interactive; then 95 | display_tldr "$(cat)" 96 | else 97 | usage > /dev/stderr 98 | fi 99 | exit 0 100 | fi 101 | 102 | tldr=$(get_page_md "$Cmd" 2>/dev/null) 103 | if [ -z "$tldr" ]; then 104 | panic "tldr page for command $Cmd not found" 105 | fi 106 | printf %s "$(display_tldr "$tldr")" 107 | echo 108 | 109 | # Freshen our local copy of the pages if they're out of date. 110 | if ! recent "$(page_cache)/index.json"; then 111 | update_tldr_cache 112 | fi 113 | } 114 | 115 | 116 | # panic exits the script with a final message to stderr. 117 | panic() { 118 | >/dev/stderr printf "%s\n" "$@" 119 | exit 1 120 | } 121 | 122 | # have tests the availability of command $1 123 | have() { 124 | silent command -v "$1" 125 | } 126 | 127 | # Check to see if we have the required external tools. 128 | check_requirements() { 129 | # if sed is available we assume the smaller utils such as tr, cut, 130 | # sort, uniq, grep... are as well. 131 | have sed || panic 'tldr requires sed installed and available' 132 | have unzip || panic 'tldr requires unzip installed and available' 133 | have curl || have wget || panic "tldr requires curl or wget installed and available" 134 | } 135 | 136 | 137 | # constants and read-only values 138 | 139 | base_url() { 140 | echo https://raw.githubusercontent.com/tldr-pages/tldr/main/pages 141 | } 142 | 143 | upstream_pages() { 144 | echo https://raw.githubusercontent.com/tldr-pages/tldr-pages.github.io/main/assets/tldr.zip 145 | } 146 | 147 | # cache_dir returns the base path for caching tldr data files. 148 | cache_dir() { 149 | if [ -n "${XDG_CACHE_HOME:-}" ]; then 150 | echo "${XDG_CACHE_HOME}/tldr" 151 | return 152 | fi 153 | 154 | if [ -n "${HOME:-}" ]; then 155 | if [ -d "$HOME/.cache" ]; then 156 | echo "$HOME/.cache/tldr" 157 | else 158 | echo "$HOME/.tldr" 159 | fi 160 | return 161 | fi 162 | 163 | echo "/tmp" 164 | } 165 | 166 | 167 | # page_cache returns the base path for tldr markdown pages. 168 | page_cache() { 169 | echo "$(cache_dir)/page-source" 170 | } 171 | 172 | # cache_days is how long to wait before refreshing our local copy. 173 | cache_days() { 174 | echo 14 175 | } 176 | 177 | version_info() { 178 | grep '^# tldr-sh-client version: ' "$0" | cut -c3- 179 | echo "Check https://github.com/raylee/tldr-sh-client for updates." 180 | } 181 | 182 | # exists tests whether a file exists. 183 | exists() { 184 | silent test -e "$1" 185 | } 186 | 187 | # recent tests whether "$1" exists and is more recent than $(cache_days) old. 188 | recent() { 189 | exists "$(find "$1" -mtime "-$(cache_days)" 2>/dev/null)" 190 | } 191 | 192 | # basename acts like the command of the same name. 193 | basename() { 194 | fn=${1##*/} 195 | if [ $# -eq 2 ]; then 196 | fn=${fn%"$2"} 197 | fi 198 | printf "%s\n" "$fn" 199 | } 200 | 201 | # extension returns the portion of the filename after the last ., if any. 202 | extension() { 203 | echo "${1##*.}" 204 | } 205 | 206 | # lang_cc returns the ${language}_${country_code} portion of a locale. 207 | # example: lang_cc en_DK.utf8 => en_DK 208 | lang_cc() { 209 | for arg; do 210 | echo "${arg%.*}" 211 | done 212 | } 213 | 214 | Get() { 215 | if have curl; then 216 | curl -sfL "$1" 217 | else 218 | wget -O - "$1" 219 | fi 220 | } 221 | 222 | 223 | # Calling `cache true` enables the cache, any other parameter disables it. 224 | # Calling `cache` with no parameters changes the success code to true or false. 225 | cache() { 226 | case $# in 227 | 0) [ "${cache_bool:-}" = true ] ;; 228 | 1) cache_bool=$1 ;; 229 | esac 230 | } 231 | 232 | get_page_md() { 233 | if cache; then 234 | cat "$(page_path "$1")" 235 | else 236 | Get "$(base_url)/{$(local_platform),common}/$1.md" 237 | fi 238 | } 239 | 240 | # Update the local cached copy of the tldr source pages. 241 | update_tldr_cache() { 242 | if cache; then 243 | mkdir -p "$(page_cache)" "$(cache_dir)/local" 244 | Get "$(upstream_pages)" > "$(cache_dir)/tldr.zip" 245 | silent unzip -o -d "$(page_cache)" -u "$(cache_dir)/tldr.zip" 246 | fi 247 | } 248 | 249 | # # list the languages used in the current tldr zip archive. 250 | # tldr_languages() { 251 | # for path in "$(page_cache)"/pages.*; do 252 | # extension "$path" 253 | # done 254 | # } 255 | 256 | # Call preferred_platform with no arguments to get the current value, or pass 257 | # values to set. 258 | preferred_platform() { 259 | case $# in 260 | 0) echo "$preferred_platform_name" ;; 261 | *) preferred_platform_name="$*" ;; 262 | esac 263 | } 264 | 265 | # platform_order returns a list of search paths, with one (harmless) duplicate. 266 | platform_order() { 267 | echo local "$(preferred_platform)" common linux osx windows sunos android 268 | } 269 | 270 | # Call preferred_language with no arguments to get the current value, or pass 271 | # values to set. 272 | preferred_language() { 273 | case $# in 274 | 0) echo "${preferred_language_code:-}" ;; 275 | *) preferred_language_code="$*" ;; 276 | esac 277 | } 278 | 279 | # i18n_paths returns the prioritized list of language page paths to search. 280 | i18n_paths() { 281 | if [ -n "$(preferred_language)" ]; then 282 | echo pages."$(preferred_language)" 283 | # by the spec, if a preferred language is set, English is no 284 | # longer a fallback. 285 | return 286 | fi 287 | 288 | # if $LANG is unset or empty, English is the fallback. 289 | if [ -z "${LANG:-}" ]; then 290 | debug log i18n: LANGless 291 | echo pages 292 | return 293 | fi 294 | 295 | # else the languages are ordered by $LANGUAGE, then $LANG, then English. 296 | for i in $(split : "${LANGUAGE:-}") $LANG; do 297 | case $i in 298 | C|POSIX) continue ;; 299 | esac 300 | i=$(lang_cc "$i") 301 | echo "pages.$i" 302 | debug log i18n: pages."$i" 303 | done 304 | echo pages 305 | } 306 | 307 | # split separates the arguments based on a single split character in $1. 308 | split() { 309 | IFS="$1" 310 | shift 311 | # shellcheck disable=2048 312 | for x in $*; do 313 | echo "$x" 314 | done 315 | } 316 | 317 | # page_path takes a (normalized) page name in $1, and localizes it for language 318 | # and platform. Platform pages are given preference over languages per the tldr 319 | # client spec. The path to the first page found in that order is returned. 320 | page_path() { 321 | if exists "$(cache_dir)/local/$1.md"; then 322 | echo "$(cache_dir)/local/$1.md" 323 | return 324 | fi 325 | for p in $(platform_order); do 326 | for l in $(i18n_paths); do 327 | if exists "$(page_cache)/$l/$p/$1.md"; then 328 | echo "$(page_cache)/$l/$p/$1.md" 329 | return 330 | fi 331 | done 332 | done 333 | } 334 | 335 | 336 | # term emits termcodes for style names passed as arguments. 337 | term() { 338 | for arg; do 339 | # shellcheck disable=2086 340 | case $arg in 341 | reset) tput sgr0 || tput me ;; 342 | bold) tput bold || tput md ;; 343 | underline) tput smul || tput us ;; 344 | italic) tput sitm || tput ZH || printf "\033[3m" ;; 345 | eitalic) tput ritm || tput ZH || printf "\033[23m" ;; 346 | default) tput op ;; 347 | black) tput setaf 0 || tput AF 0 ;; 348 | red) tput setaf 1 || tput AF 1 ;; 349 | green) tput setaf 2 || tput AF 2 ;; 350 | yellow) tput setaf 3 || tput AF 3 ;; 351 | blue) tput setaf 4 || tput AF 4 ;; 352 | magenta) tput setaf 5 || tput AF 5 ;; 353 | cyan) tput setaf 6 || tput AF 6 ;; 354 | white) tput setaf 7 || tput AF 7 ;; 355 | onblue) tput setab 4 || tput AB 4 ;; 356 | ongrey) tput setab 7 || tput AB 7 ;; 357 | 358 | # custom styling if set in environment variable 359 | sheader) term ${TLDR_HEADER:-red} ;; 360 | squote) term ${TLDR_QUOTE:-italic} ;; 361 | sdescription) term ${TLDR_DESCRIPTION:-reset} ;; 362 | scode) term ${TLDR_CODE:-bold} ;; 363 | sparam) term ${TLDR_PARAM:-italic} ;; 364 | eparam) term ${TLDR_EPARAM:-eitalic} ;; 365 | esac 366 | done 2>/dev/null 367 | } 368 | 369 | # heading formats its arguments as a heading. 370 | heading() { 371 | printf "%s\n" "$(term sheader)$*$(term reset)" 372 | } 373 | 374 | # quotation formats its arguments as a quote. 375 | quotation() { 376 | echo "$(term squote)$*$(term reset)" 377 | } 378 | 379 | # list_item formats its arguments as a list item. 380 | list_item() { 381 | echo "$(term sdescription)$*$(term reset)" 382 | } 383 | 384 | # style_params styles each {{...}} span as parameters. 385 | style_params() { 386 | printf %s "$*" | sed "s/{{/$(term sparam)/g;s/}}/$(term eparam)/g" 387 | } 388 | 389 | # style_code styles all single-backtick blocks as code. 390 | style_code() { 391 | # Surround everything between two backticks with styling. 392 | printf "%s\n" "$*" \ 393 | | sed "s/\`\([^\`]*\)\`/ $(term scode)\1$(term reset)/g" 394 | } 395 | 396 | # text styles its arguments as plain text. 397 | text() { 398 | echo "$*" 399 | } 400 | 401 | 402 | # contains tests whether $1 occurs inside $2. 403 | contains() { 404 | # shellcheck disable=2295 405 | test "${2#*$1}" != "$2" 406 | } 407 | 408 | # whitespace tests whether all its arguments are just whitespace. 409 | whitespace() { 410 | case "$*" in 411 | *[![:blank:]]*) false ;; 412 | *) true ;; 413 | esac 414 | } 415 | 416 | # display_tldr is a rudimentary-level recognition of tldr's CommonMark, plus 417 | # {{ }} which surround values in an example that users may edit. 418 | display_tldr() { 419 | last_token='' 420 | md="$1" 421 | md=$(style_params "$md") 422 | md=$(style_code "$md") 423 | 424 | # Read one line at a time, don't strip whitespace ('IFS='), and process 425 | # the last line even if it doesn't have a newline at the end. 426 | printf %s "$md" | while IFS= read -r line || [ -n "$line" ]; do 427 | # omit empty lines after list items 428 | if whitespace "$line" && [ "$last_token" = "list_item" ]; then 429 | continue 430 | fi 431 | 432 | case "$line" in 433 | \#*) 434 | heading "${line#??}" 435 | last_token="heading" 436 | ;; 437 | \>*) 438 | quotation "${line#??}" 439 | last_token="quotation" 440 | ;; 441 | -*) 442 | list_item "$line" 443 | last_token="list_item" 444 | ;; 445 | \`*) 446 | code "$line" 447 | last_token="code" 448 | ;; 449 | *) 450 | printf '%s\n' "$line" 451 | last_token="text" 452 | ;; 453 | esac 454 | done 455 | } 456 | 457 | 458 | # Convert the local platform name to tldr's version 459 | local_platform() { 460 | case $(uname -s) in 461 | Darwin) echo "osx" ;; 462 | Linux) echo "linux" ;; 463 | SunOS) echo "sunos" ;; 464 | CYGWIN* | MINGW32* | MSYS*) echo "windows" ;; 465 | *) echo "common" ;; 466 | # android? 467 | esac 468 | } 469 | 470 | no_warn() { 471 | 2>/dev/null "$@" 472 | } 473 | 474 | # list_pages lists pages for the current platform to stdout. Special platform 475 | # 'all' shows the name of every page, per the spec. 476 | list_pages() { 477 | { 478 | no_warn find "$(cache_dir)"/local -name '*.md' 479 | case $(preferred_platform) in 480 | all) 481 | find "$(page_cache)" -name '*.md' 482 | ;; 483 | *) 484 | find "$(page_cache)" -name '*.md' \ 485 | -path '*/'"$(preferred_platform)"'/*' 486 | ;; 487 | esac 488 | } \ 489 | | sed -e 's:.*/::' -e 's:.md$::' \ 490 | | sort \ 491 | | uniq 492 | } 493 | 494 | # usage shows the help text. 495 | usage() { 496 | cat < 498 | 499 | [options] 500 | -l, --list show all available pages 501 | -L, --language [code] override language detection, set preferred language 502 | -p, --platform [name] show page from specific platform 503 | -u, --update update cached copies of tldr page files 504 | -h, --help this help overview 505 | -v, --version show version information 506 | -n, --no-cache display pages directly from GitHub (watch ratelimits) 507 | 508 | 509 | Show the tldr page for command. 510 | 511 | The client caches a copy of the tldr pages under $(cache_dir) 512 | Local pages matching $(cache_dir)/local/*.md will be also be used 513 | and override provided pages. Cached pages will be refreshed after $(cache_days) days. 514 | 515 | Examples: 516 | Show an overivew of unzip: 517 | tldr unzip 518 | Show commands for all platforms: 519 | tldr -l -p all 520 | If you have fzf installed, try: 521 | tldr -l -p all | fzf --preview 'tldr {}' 522 | Show the Russian page for tar: 523 | tldr -L ru tar 524 | List pages in the Android section: 525 | tldr -p android -l 526 | EOF 527 | } 528 | 529 | # trace is used for debugging function paths. eg: trace display_tldr "$tldr" 530 | trace() { 531 | set -x 532 | "$@" 533 | set +x 534 | } 535 | 536 | # profile runs its continuation with profiling turned on (only works in bash). 537 | # eg `profile main "$@"` 538 | profile() { 539 | PS4=' $(linestamp) ' trace "$@" 540 | } 541 | 542 | # linestamp gives the current time and function caller formatted for a gutter. 543 | # For use in bash, not sh. 544 | linestamp() { 545 | # shellcheck disable=3028 disable=3054 546 | printf "%0.3f %-20s |" "$EPOCHREALTIME" "${FUNCNAME[1]}" 547 | } 548 | 549 | # debug only executes the continuation if the environment variable 550 | # tldrDebug exists. 551 | debug() { 552 | [ -n "${tldrDebug+x}" ] && "$@" 553 | } 554 | 555 | # italic executes the continuation with italics on, eg: `italic cat ...`. 556 | italic() { 557 | term italic 558 | "$@" 559 | term reset 560 | } 561 | 562 | # If debugging is enabled, log prints its parameters to stderr on a line. 563 | log() { 564 | debug italic printf >/dev/stderr '%s\n' "$*" 565 | } 566 | 567 | # interactive tests whether our stdin is an interactive tty or a file/pipe. 568 | interactive() { 569 | # test if file descriptor 0 (stdin) is a tty 570 | [ -t 0 ] 571 | } 572 | 573 | # bool defines a boolean setter / getter initialized to false. 574 | # Usage: 575 | # bool name 576 | # name [true|false] 577 | # if name; then... if ! name; then... name && ... 578 | bool() { 579 | _bool_var_name=_bool_$1 580 | # Yeah, I'm judging me too. I'm trying this on for size, to see if 581 | # localized complexity helps simplify the rest. 582 | # Anyway, this creates a new global function using $1 as its name, 583 | # acting per the description of `bool` above. 584 | eval " 585 | $1() { 586 | case \$# in 587 | 0) \$$_bool_var_name ;; 588 | 1) [ \"\$1\" = true ] && $_bool_var_name=true || $_bool_var_name=false ;; 589 | esac 590 | } 591 | $_bool_var_name=false 592 | " 593 | } 594 | 595 | main "$@" 596 | --------------------------------------------------------------------------------