#!/bin/bash # shellcheck disable=SC1117,SC2001 # # [X] open section # [X] one shot mode # [X] usage info # [X] dependencies check # [X] help # [X] yank/y/copy/c # [X] Y/C # [X] eof problem # [X] more # [X] stealth mode # # here are several examples for the stealth mode: # # zip lists # list permutation # random list element # reverse a list # read json from file # append string to a file # run process in background # count words in text counter # group elements list __CHTSH_VERSION=5 __CHTSH_DATETIME="2019-05-11 07:29:46 +0200" # cht.sh configuration loading # # configuration is stored in ~/.cht.sh/ (can be overridden by CHTSH_HOME) # CHTSH_HOME=${CHTSH:-"$HOME"/.cht.sh} [ -z "$CHTSH_CONF" ] && CHTSH_CONF=$CHTSH_HOME/cht.sh.conf # shellcheck disable=SC1090,SC2002 [ -e "$CHTSH_CONF" ] && source "$CHTSH_CONF" [ -z "$CHTSH_URL" ] && CHTSH_URL=https://cht.sh # currently we support only two modes: # * lite = access the server using curl # * auto = try standalone usage first CHTSH_MODE="$(cat "$CHTSH_HOME"/mode 2> /dev/null)" [ "$CHTSH_MODE" != lite ] && CHTSH_MODE=auto CHEATSH_INSTALLATION="$(cat "$CHTSH_HOME/standalone" 2> /dev/null)" export LESSSECURE=1 STEALTH_MAX_SELECTION_LENGTH=5 case "$(uname -s)" in Darwin) is_macos=yes ;; *) is_macos=no ;; esac # for KSH93 # shellcheck disable=SC2034,SC2039,SC2168 if echo "$KSH_VERSION" | grep -q ' 93' && ! local foo 2>/dev/null; then alias local=typeset fi fatal() { echo "ERROR: $*" >&2 exit 1 } _say_what_i_do() { [ -n "$LOG" ] && echo "$(date '+[%Y-%m-%d %H:%M%S]') $*" >> "$LOG" local this_prompt="\033[0;1;4;32m>>\033[0m" printf "\n${this_prompt}%s\033[0m\n" " $* " } cheatsh_standalone_install() { # the function installs cheat.sh with the upstream repositories # in the standalone mode local installdir; installdir="$1" local default_installdir="$HOME/.cheat.sh" if [ "$installdir" = help ]; then cat </dev/null || \ { echo "DEPENDENCY: \"$dep\" is needed to install cheat.sh in the standalone mode" >&2; _exit_code=1; } done [ "$_exit_code" -ne 0 ] && return "$_exit_code" while true; do echo -n "Where should cheat.sh be installed [$default_installdir]? "; read -r installdir [ -n "$installdir" ] || installdir="$default_installdir" if [ "$installdir" = y ] \ || [ "$installdir" = Y ] \ || [ "$(echo "$installdir" | tr "[:upper:]" "[:lower:]")" = yes ] then echo Please enter the directory name echo If it was the directory name already, please prepend it with \"./\": "./$installdir" else break fi done if [ -e "$installdir" ]; then echo "ERROR: Installation directory [$installdir] exists already" echo "Please remove it first before continuing" return 1 fi if ! mkdir -p "$installdir"; then echo "ERROR: Could not create the installation directory \"$installdir\"" echo "ERROR: Please check the permissions and start the script again" return 1 fi local space_needed=700 local space_available; space_available=$(("$(df -k "$installdir" | awk '{print $4}' | tail -1)"/1024)) if [ "$space_available" -lt "$space_needed" ]; then echo "ERROR: Installation directory has no enough space (needed: ${space_needed}M, available: ${space_available}M" echo "ERROR: Please clean up and start the script again" rmdir "$installdir" return 1 fi _say_what_i_do Cloning cheat.sh locally local url=https://github.com/chubin/cheat.sh rmdir "$installdir" git clone "$url" "$installdir" || fatal Could not clone "$url" with git into "$installdir" cd "$installdir" || fatal "Cannot cd into $installdir" git checkout offline_usage # after the repository cloned, we may have the log directory # and we can write our installation log into it mkdir -p "$installdir/log/" LOG="$installdir/log/install.log" # we use tee everywhere so we should set -o pipefail set -o pipefail _say_what_i_do Creating virtual environment python2 "$(command -v virtualenv)" ve \ || fatal Could not create virtual environment with "python2 $(command -v virtualenv) ve" _say_what_i_do Installing python requirements into the virtual environment ve/bin/pip install -r requirements.txt > "$LOG" \ || { echo "ERROR:" echo "---" tail -n 10 "$LOG" echo "---" echo "See $LOG for more" fatal Could not install python dependecies into the virtual environment } echo "$(ve/bin/pip freeze | wc -l) dependencies were successfully installed" _say_what_i_do Fetching the upstream cheat sheets repositories ve/bin/python lib/fetch.py fetch-all | tee -a "$LOG" _say_what_i_do Running self-tests ( cd tests || exit if CHEATSH_TEST_STANDALONE=YES \ CHEATSH_TEST_SKIP_ONLINE=NO \ CHEATSH_TEST_SHOW_DETAILS=NO \ PYTHON=../ve/bin/python bash run-tests.sh | tee -a "$LOG" then printf "\033[0;32m%s\033[0m\n" "SUCCESS" else printf "\033[0;31m%s\033[0m\n" "FAILED" echo "Some tests were failed. Run the tests manually for further investigation:" echo " cd $PWD; bash tests/run-tests.sh)" fi ) mkdir -p "$CHTSH_HOME" echo "$installdir" > "$CHTSH_HOME/standalone" echo auto > "$CHTSH_HOME/mode" _say_what_i_do Done local v1; v1=$(printf "\033[0;1;32m") local v2; v2=$(printf "\033[0m") cat < "$CHTSH_HOME/mode" echo "Configured mode: $mode" fi else echo "Uknown mode: $mode" echo Suported modes: echo " auto use the standalone installation first" echo " lite use the cheat sheets server directly" fi } get_query_options() { local query="$*" if [ -n "$CHTSH_QUERY_OPTIONS" ]; then case $query in *\?*) query="$query&${CHTSH_QUERY_OPTIONS}";; *) query="$query?${CHTSH_QUERY_OPTIONS}";; esac fi printf "%s" "$query" } do_query() { local query="$*" local b_opts= local uri="${CHTSH_URL}/\"\$(get_query_options $query)\"" if [ -e "$HOME/.cht.sh/id" ]; then b_opts="-b \"\$HOME/.cht.sh/id\"" fi eval curl "$b_opts" -s "$uri" > "$TMP1" if [ -z "$lines" ] || [ "$(wc -l "$TMP1" | awk '{print $1}')" -lt "$lines" ]; then cat "$TMP1" else ${PAGER:-$defpager} "$TMP1" fi } prepare_query() { local section="$1"; shift local input="$1"; shift local arguments="$1" local query if [ -z "$section" ] || [ x"${input}" != x"${input#/}" ]; then query=$(printf %s "$input" | sed 's@ @/@; s@ @+@g') else query=$(printf %s "$section/$input" | sed 's@ @+@g') fi [ -n "$arguments" ] && arguments="?$arguments" printf %s "$query$arguments" } get_list_of_sections() { curl -s "${CHTSH_URL}"/:list | grep -v '/.*/' | grep '/$' | xargs } gen_random_str() ( len=$1 if command -v openssl >/dev/null; then openssl rand -base64 $((len*3/4)) | awk -v ORS='' // else rdev=/dev/urandom for d in /dev/{srandom,random,arandom}; do test -r "$d" && rdev=$d done if command -v hexdump >/dev/null; then hexdump -vn $((len/2)) -e '1/1 "%02X" 1 ""' "$rdev" elif command -v xxd >/dev/null; then xxd -l $((len/2)) -ps "$rdev" | awk -v ORS='' // else cd /tmp || { echo Cannot cd into /tmp >&2; exit 1; } s= # shellcheck disable=SC2000 while [ "$(echo "$s" | wc -c)" -lt "$len" ]; do s="$s$(mktemp -u XXXXXXXXXX)" done printf "%.${len}s" "$s" fi fi ) if [ "$CHTSH_MODE" = auto ] && [ -d "$CHEATSH_INSTALLATION" ]; then curl() { # ignoring all options # currently the standalone.py does not support them anyway local opt while getopts "b:s" opt; do : done shift $((OPTIND - 1)) local url; url="$1"; shift "$CHEATSH_INSTALLATION/ve/bin/python" "$CHEATSH_INSTALLATION/lib/standalone.py" "${url#"$CHTSH_URL"}" "$@" } elif [ "$(uname -s)" = OpenBSD ] && [ -x /usr/bin/ftp ]; then # any better test not involving either OS matching or actual query? curl() { local opt args="-o -" while getopts "b:s" opt; do case $opt in b) args="$args -c $OPTARG";; s) args="$args -M -V";; *) echo "internal error: unsupported cURL option '$opt'" >&2; exit 1;; esac done shift $((OPTIND - 1)) /usr/bin/ftp "$args" "$@" } else command -v curl >/dev/null || { echo 'DEPENDENCY: install "curl" to use cht.sh' >&2; exit 1; } _CURL=$(command -v curl) if [ x"$CHTSH_CURL_OPTIONS" != x ]; then curl() { $_CURL "${CHTSH_CURL_OPTIONS}" "$@" } fi fi if [ "$1" = --read ]; then read -r a || a="exit" printf "%s\n" "$a" exit 0 elif [ x"$1" = x--help ] || [ -z "$1" ]; then n=${0##*/} s=$(echo "$n" | sed "s/./ /"g) cat </dev/null || echo 'DEPENDENCY: please install "xsel" for "copy"' >&2 fi command -v rlwrap >/dev/null || { echo 'DEPENDENCY: install "rlwrap" to use cht.sh in the shell mode' >&2; exit 1; } mkdir -p "$HOME/.cht.sh/" lines=$(tput lines) if command -v less >/dev/null; then defpager="less -R" elif command -v more >/dev/null; then defpager="more" else defpager="cat" fi cmd_cd() { if [ $# -eq 0 ]; then section="" else new_section=$(echo "$input" | sed 's/cd *//; s@/*$@@; s@^/*@@') if [ -z "$new_section" ] || [ ".." = "$new_section" ]; then section="" else valid_sections=$(get_list_of_sections) valid=no; for q in $valid_sections; do [ "$q" = "$new_section/" ] && { valid=yes; break; }; done if [ "$valid" = no ]; then echo "Invalid section: $new_section" echo "Valid sections:" echo "$valid_sections" \ | xargs printf "%-10s\n" \ | tr ' ' . \ | xargs -n 10 \ | sed 's/\./ /g; s/^/ /' else section="$new_section" fi fi fi } cmd_copy() { if [ -z "$DISPLAY" ]; then echo copy: supported only in the Desktop version elif [ -z "$input" ]; then echo copy: Make at least one query first. else curl -s "${CHTSH_URL}"/"$(get_query_options "$query"?T)" > "$TMP1" if [ "$is_macos" != yes ]; then xsel -bi < "$TMP1" else pbcopy < "$TMP1" fi echo "copy: $(wc -l "$TMP1" | awk '{print $1}') lines copied to the selection" fi } cmd_ccopy() { if [ -z "$DISPLAY" ]; then echo copy: supported only in the Desktop version elif [ -z "$input" ]; then echo copy: Make at least one query first. else curl -s "${CHTSH_URL}"/"$(get_query_options "$query"?TQ)" > "$TMP1" if [ "$is_macos" != yes ]; then xsel -bi < "$TMP1" else pbcopy < "$TMP1" fi echo "copy: $(wc -l "$TMP1" | awk '{print $1}') lines copied to the selection" fi } cmd_exit() { exit 0 } cmd_help() { cat < python zip list cht.sh/python> zip list cht.sh/go> /python zip list EOF } cmd_hush() { mkdir -p "$HOME/.cht.sh/" && touch "$HOME/.cht.sh/.hushlogin" && echo "Initial 'use help' message was disabled" } cmd_id() { id_file="$HOME/.cht.sh/id" if [ id = "$input" ]; then new_id="" else new_id=$(echo "$input" | sed 's/id *//; s/ *$//; s/ /+/g') fi if [ "$new_id" = remove ]; then if [ -e "$id_file" ]; then rm -f -- "$id_file" && echo "id is removed" else echo "id was not set, so you can't remove it" fi return fi if [ -n "$new_id" ] && [ reset != "$new_id" ] && [ "$(/bin/echo -n "$new_id" | wc -c)" -lt 16 ]; then echo "ERROR: $new_id: Too short id. Minimal id length is 16. Use 'id reset' for a random id" return fi if [ -z "$new_id" ]; then # if new_id is not specified check if we have some id already # if yes, just show it # if not, generate a new id if [ -e "$id_file" ]; then awk '$6 == "id" {print $NF}' <"$id_file" | tail -n 1 return else new_id=reset fi fi if [ "$new_id" = reset ]; then new_id=$(gen_random_str 12) else echo WARNING: if someone gueses your id, he can read your cht.sh search history fi if [ -e "$id_file" ] && grep -q '\tid\t[^\t][^\t]*$' "$id_file" 2> /dev/null; then sed -i 's/\tid\t[^\t][^\t]*$/ id '"$new_id"'/' "$id_file" else if ! [ -e "$id_file" ]; then printf '#\n\n' > "$id_file" fi printf ".cht.sh\tTRUE\t/\tTRUE\t0\tid\t$new_id\n" >> "$id_file" fi echo "$new_id" } cmd_query() { query=$(prepare_query "$section" "$input") do_query "$query" } cmd_stealth() { if [ "$input" != stealth ]; then arguments=$(echo "$input" | sed 's/stealth //; s/ /\&/') fi trap break INT if [ "$is_macos" = yes ]; then past=$(pbpaste) else past=$(xsel -o) fi printf "\033[0;31mstealth:\033[0m you are in the stealth mode; select any text in any window for a query\n" printf "\033[0;31mstealth:\033[0m selections longer than $STEALTH_MAX_SELECTION_LENGTH words are ignored\n" if [ -n "$arguments" ]; then printf "\033[0;31mstealth:\033[0m query arguments: ?$arguments\n" fi printf "\033[0;31mstealth:\033[0m use ^C to leave this mode\n" while true; do if [ "$is_macos" = yes ]; then current=$(pbpaste) else current=$(xsel -o) fi if [ "$past" != "$current" ]; then past=$current current_text="$(echo $current | tr -c '[a-zA-Z0-9]' ' ')" if [ "$(echo "$current_text" | wc -w)" -gt "$STEALTH_MAX_SELECTION_LENGTH" ]; then echo "\033[0;31mstealth:\033[0m selection length is longer than $STEALTH_MAX_SELECTION_LENGTH words; ignoring" continue else printf "\n\033[0;31mstealth: \033[7m $current_text\033[0m\n" query=$(prepare_query "$section" "$current_text" "$arguments") do_query "$query" fi fi sleep 1; done trap - INT } cmd_update() { [ -w "$0" ] || { echo "The script is readonly; please update manually: curl -s ${CHTSH_URL}/:cht.sh | sudo tee $0"; return; } TMP2=$(mktemp /tmp/cht.sh.XXXXXXXXXXXXX) curl -s "${CHTSH_URL}"/:cht.sh > "$TMP2" if ! cmp "$0" "$TMP2" > /dev/null 2>&1; then if grep -q ^__CHTSH_VERSION= "$TMP2"; then # section was vaildated by us already args=(--shell "$section") cp "$TMP2" "$0" && echo "Updated. Restarting..." && rm "$TMP2" && CHEATSH_RESTART=1 exec "$0" "${args[@]}" else echo "Something went wrong. Please update manually" fi else echo "cht.sh is up to date. No update needed" fi rm -f "$TMP2" > /dev/null 2>&1 } cmd_version() { insttime=$(ls -l -- "$0" | sed 's/ */ /g' | cut -d ' ' -f 6-8) echo "cht.sh version $__CHTSH_VERSION of $__CHTSH_DATETIME; installed at: $insttime" TMP2=$(mktemp /tmp/cht.sh.XXXXXXXXXXXXX) if curl -s "${CHTSH_URL}"/:cht.sh > "$TMP2"; then if ! cmp "$0" "$TMP2" > /dev/null 2>&1; then echo "Update needed (type 'update' for that)". else echo "Up to date. No update needed" fi fi rm -f "$TMP2" > /dev/null 2>&1 } TMP1=$(mktemp /tmp/cht.sh.XXXXXXXXXXXXX) trap 'rm -f $TMP1 $TMP2' EXIT trap 'true' INT if ! [ -e "$HOME/.cht.sh/.hushlogin" ] && [ -z "$this_query" ]; then echo "type 'help' for the cht.sh shell help" fi while true; do if [ "$section" != "" ]; then full_prompt="$prompt/$section> " else full_prompt="$prompt> " fi input=$( rlwrap -H "$HOME/.cht.sh/history" -pgreen -C cht.sh -S "$full_prompt" sh "$0" --read | sed 's/ *#.*//' ) cmd_name=${input%% *} cmd_args=${input#* } case $cmd_name in "") continue;; # skip empty input lines '?'|h|help) cmd_name=help;; hush) cmd_name=hush;; cd) cmd_name="cd";; exit|quit) cmd_name="exit";; copy|yank|c|y) cmd_name=copy;; ccopy|cc|C|Y) cmd_name=ccopy;; id) cmd_name=id;; stealth) cmd_name=stealth;; update) cmd_name=update;; version) cmd_name=version;; *) cmd_name="query $cmd_name";; esac "cmd_$cmd_name" $cmd_args done