1#!/bin/sh
2#
3# [X] open section
4# [X] one shot mode
5# [X] usage info
6# [X] dependencies check
7# [X] help
8# [X] yank/y/copy/c
9# [X] Y/C
10# [X] eof problem
11# [X] more
12# [X] stealth mode
13#
14# here are several examples for the stealth mode:
15#
16# zip lists
17# list permutation
18# random list element
19# reverse a list
20# read json from file
21# append string to a file
22# run process in background
23# count words in text counter
24# group elements list
25
26__CHTSH_VERSION=4
27__CHTSH_DATETIME="2018-07-08 22:26:46 +0200"
28
29export LESSSECURE=1
30STEALTH_MAX_SELECTION_LENGTH=5
31
32case `uname -s` in
33 Darwin) is_macos=yes ;;
34 *) is_macos=no ;;
35esac
36
37# for KSH93
38if echo $KSH_VERSION | grep -q ' 93' && ! local foo 2>/dev/null; then
39 alias local=typeset
40fi
41
42get_query_options()
43{
44 local query="$*"
45 if [ -n "$CHTSH_QUERY_OPTIONS" ]; then
46 case $query in
47 *\?*) query="$query&${CHTSH_QUERY_OPTIONS}";;
48 *) query="$query?${CHTSH_QUERY_OPTIONS}";;
49 esac
50 fi
51 printf "%s" "$query"
52}
53
54do_query()
55{
56 local query="$*"
57 local b_opts=
58 local uri="${CHTSH_URL}/\"\$(get_query_options $query)\""
59
60 if [ -e "$HOME/.cht.sh/id" ]; then
61 b_opts="-b \"\$HOME/.cht.sh/id\""
62 fi
63
64 eval curl $b_opts -s $uri > "$TMP1"
65
66 if [ -z "$lines" ] || [ "$(wc -l "$TMP1" | awk '{print $1}')" -lt "$lines" ]; then
67 cat "$TMP1"
68 else
69 ${PAGER:-$defpager} "$TMP1"
70 fi
71}
72
73prepare_query()
74{
75 local section="$1"; shift
76 local input="$1"; shift
77 local arguments="$1"
78
79 local query
80 if [ -z "$section" ] || [ x"${input}" != x"${input#/}" ]; then
81 query=$(printf %s "$input" | sed 's@ @/@; s@ @+@g')
82 else
83 query=$(printf %s "$section/$input" | sed 's@ @+@g')
84 fi
85
86 [ -n "$arguments" ] && arguments="?$arguments"
87 printf %s "$query$arguments"
88}
89
90get_list_of_sections()
91{
92 curl -s "${CHTSH_URL}"/:list | grep -v '/.*/' | grep '/$' | xargs
93}
94
95gen_random_str()
96(
97 len=$1
98 if command -v openssl >/dev/null; then
99 openssl rand -base64 $(($len*3/4)) | awk -v ORS='' //
100 else
101 rdev=/dev/urandom
102 for d in /dev/{srandom,random,arandom}; do
103 test -r $d && rdev=$d
104 done
105 if command -v hexdump >/dev/null; then
106 hexdump -vn $(($len/2)) -e '1/1 "%02X" 1 ""' $rdev
107 elif command -v xxd >/dev/null; then
108 xxd -l $(($len/2)) -ps $dev | awk -v ORS='' //
109 else
110 cd /tmp
111 s=
112 while [ $(echo "$s" | wc -c) -lt $len ]; do
113 s="$s$(mktemp -u XXXXXXXXXX)"
114 done
115 printf %.${len}s "$s"
116 fi
117 fi
118)
119
120if [ -z "$CHTSH_CONF" ]; then
121 CHTSH_CONF="$HOME"/.cht.sh/cht.sh.conf
122fi
123
124if [ -e "$CHTSH_CONF" ]; then
125 # shellcheck disable=SC1090,SC2002
126 . "$CHTSH_CONF"
127fi
128
129[ -z "$CHTSH_URL" ] && CHTSH_URL=https://cht.sh
130
131# any better test not involving either OS matching or actual query?
132if [ `uname -s` = OpenBSD ] && [ -x /usr/bin/ftp ]; then
133 curl() {
134 local opt args="-o -"
135 while getopts "b:s" opt; do
136 case $opt in
137 b) args="$args -c $OPTARG";;
138 s) args="$args -M -V";;
139 *) echo "internal error: unsupported cURL option '$opt'" >&2; exit 1;;
140 esac
141 done
142 shift $(($OPTIND - 1))
143 /usr/bin/ftp $args "$@"
144 }
145else
146 command -v curl >/dev/null || { echo 'DEPENDENCY: install "curl" to use cht.sh' >&2; exit 1; }
147 _CURL=$(command -v curl)
148 if [ x"$CHTSH_CURL_OPTIONS" != x ]; then
149 curl() {
150 $_CURL ${CHTSH_CURL_OPTIONS} "$@"
151 }
152 fi
153fi
154
155
156if [ "$1" = --read ]; then
157 read -r a || a=exit
158 printf "%s\n" "$a"
159 exit 0
160elif [ x"$1" = x--help ] || [ -z "$1" ]; then
161 cat <<EOF
162Usage:
163
164 ${0##*/} --help show this help
165 ${0##*/} --shell [LANG] shell mode (open LANG if specified)
166 ${0##*/} QUERY process QUERY and exit
167EOF
168 exit 0
169elif [ x"$1" = x--shell ]; then
170 shell_mode=yes
171 shift
172fi
173
174prompt="cht.sh"
175opts=""
176input=""
177for o; do
178 if [ x"$o" != x"${o#-}" ]; then
179 opts="${opts}${o#-}"
180 else
181 input="$input $o"
182 fi
183done
184query=$(echo "$input" | sed 's@ *$@@; s@^ *@@; s@ @/@; s@ @+@g')
185
186if [ "$shell_mode" != yes ]; then
187 curl -s "${CHTSH_URL}"/"$(get_query_options "$query")"
188 exit 0
189else
190 new_section="$1"
191 valid_sections=$(get_list_of_sections)
192 valid=no; for q in $valid_sections; do [ "$q" = "$new_section/" ] && { valid=yes; break; }; done
193
194 if [ "$valid" = yes ]; then
195 section="$new_section"
196 this_query="$(echo "$input" | sed 's@ *[^ ]* *@@')"
197 this_prompt="\033[0;32mcht.sh/$section>\033[0m "
198 else
199 this_query="$input"
200 this_prompt="\033[0;32mcht.sh>\033[0m "
201 fi
202 if [ -n "$this_query" ] && [ -z "$CHEATSH_RESTART" ]; then
203 printf "$this_prompt$this_query\n"
204 curl -s "${CHTSH_URL}"/"$(get_query_options "$query")"
205 fi
206fi
207
208if [ "$is_macos" != yes ]; then
209 command -v xsel >/dev/null || echo 'DEPENDENCY: please install "xsel" for "copy"' >&2
210fi
211command -v rlwrap >/dev/null || { echo 'DEPENDENCY: install "rlwrap" to use cht.sh in the shell mode' >&2; exit 1; }
212
213mkdir -p "$HOME/.cht.sh/"
214lines=$(tput lines)
215
216if command -v less >/dev/null; then
217 defpager="less -R"
218elif command -v more >/dev/null; then
219 defpager=more
220else
221 defpager=cat
222fi
223
224cmd_cd() {
225 if [ $# -eq 0 ]; then
226 section=""
227 else
228 new_section=$(echo "$input" | sed 's/cd *//; s@/*$@@; s@^/*@@')
229 if [ -z "$new_section" ] || [ ".." = "$new_section" ]; then
230 section=""
231 else
232 valid_sections=$(get_list_of_sections)
233 valid=no; for q in $valid_sections; do [ "$q" = "$new_section/" ] && { valid=yes; break; }; done
234 if [ "$valid" = no ]; then
235 echo "Invalid section: $new_section"
236 echo "Valid sections:"
237 echo $valid_sections | xargs printf "%-10s\n" | tr ' ' . | xargs -n 10 | sed 's/\./ /g; s/^/ /'
238 else
239 section="$new_section"
240 fi
241 fi
242 fi
243}
244
245cmd_copy() {
246 if [ -z "$DISPLAY" ]; then
247 echo copy: supported only in the Desktop version
248 elif [ -z "$input" ]; then
249 echo copy: Make at least one query first.
250 else
251 curl -s "${CHTSH_URL}"/"$(get_query_options "$query"?T)" > "$TMP1"
252 if [ "$is_macos" != yes ]; then
253 xsel -bi < "$TMP1"
254 else
255 cat "$TMP1" | pbcopy
256 fi
257 echo "copy: $(wc -l "$TMP1" | awk '{print $1}') lines copied to the selection"
258 fi
259}
260
261cmd_ccopy() {
262 if [ -z "$DISPLAY" ]; then
263 echo copy: supported only in the Desktop version
264 elif [ -z "$input" ]; then
265 echo copy: Make at least one query first.
266 else
267 curl -s "${CHTSH_URL}"/"$(get_query_options "$query"?TQ)" > "$TMP1"
268 if [ "$is_macos" != yes ]; then
269 xsel -bi < "$TMP1"
270 else
271 cat "$TMP1" | pbcopy
272 fi
273 echo "copy: $(wc -l "$TMP1" | awk '{print $1}') lines copied to the selection"
274 fi
275}
276
277cmd_exit() {
278 exit 0
279}
280
281cmd_help() {
282 cat <<EOF
283help - show this help
284hush - do not show the 'help' string at start anymore
285cd LANG - change the language context
286copy - copy the last answer in the clipboard (aliases: yank, y, c)
287ccopy - copy the last answer w/o comments (cut comments; aliases: cc, Y, C)
288exit - exit the cheat shell (aliases: quit, ^D)
289id [ID] - set/show an unique session id ("reset" to reset, "remove" to remove)
290stealth - stealth mode (automatic queries for selected text)
291update - self update (only if the scriptfile is writeable)
292version - show current cht.sh version
293/:help - service help
294QUERY - space ceparated query staring (examples are below)
295 cht.sh> python zip list
296 cht.sh/python> zip list
297 cht.sh/go> /python zip list
298EOF
299}
300
301cmd_hush() {
302 mkdir -p $HOME/.cht.sh/ && touch $HOME/.cht.sh/.hushlogin && echo "Initial 'use help' message was disabled"
303}
304
305cmd_id() {
306 id_file="$HOME/.cht.sh/id"
307
308 if [ id = "$input" ]; then
309 new_id=""
310 else
311 new_id=$(echo "$input" | sed 's/id *//; s/ *$//; s/ /+/g')
312 fi
313 if [ "$new_id" = remove ]; then
314 if [ -e "$id_file" ]; then
315 rm -f -- "$id_file" && echo "id is removed"
316 else
317 echo "id was not set, so you can't remove it"
318 fi
319 return
320 fi
321 if [ -n "$new_id" ] && [ reset != "$new_id" ] && [ $(/bin/echo -n "$new_id" | wc -c) -lt 16 ]; then
322 echo "ERROR: $new_id: Too short id. Minimal id length is 16. Use 'id reset' for a random id"
323 return
324 fi
325 if [ -z "$new_id" ]; then
326 # if new_id is not specified check if we have some id already
327 # if yes, just show it
328 # if not, generate a new id
329 if [ -e "$id_file" ]; then
330 echo $(awk '$6 == "id" {print $NF}' <"$id_file" | tail -n 1)
331 return
332 else
333 new_id=reset
334 fi
335 fi
336 if [ "$new_id" = reset ]; then
337 new_id=$(gen_random_str 12)
338 else
339 echo WARNING: if someone gueses your id, he can read your cht.sh search history
340 fi
341 if [ -e "$id_file" ] && grep -q '\tid\t[^\t][^\t]*$' "$id_file" 2> /dev/null; then
342 sed -i 's/\tid\t[^\t][^\t]*$/ id '"$new_id"'/' "$id_file"
343 else
344 if ! [ -e "$id_file" ]; then
345 printf '#\n\n' > "$id_file"
346 fi
347 printf ".cht.sh\tTRUE\t/\tTRUE\t0\tid\t$new_id\n" >> "$id_file"
348 fi
349 echo "$new_id"
350}
351
352cmd_query() {
353 query=$(prepare_query "$section" "$input")
354 do_query "$query"
355}
356
357cmd_stealth() {
358 if [ "$input" != stealth ]; then
359 arguments=$(echo "$input" | sed 's/stealth //; s/ /\&/')
360 fi
361 trap break INT
362 if [ "$is_macos" = yes ]; then
363 past=$(pbpaste)
364 else
365 past=$(xsel -o)
366 fi
367 printf "\033[0;31mstealth:\033[0m you are in the stealth mode; select any text in any window for a query\n"
368 printf "\033[0;31mstealth:\033[0m selections longer than $STEALTH_MAX_SELECTION_LENGTH words are ignored\n"
369 if [ -n "$arguments" ]; then
370 printf "\033[0;31mstealth:\033[0m query arguments: ?$arguments\n"
371 fi
372 printf "\033[0;31mstealth:\033[0m use ^C to leave this mode\n"
373 while true; do
374 if [ "$is_macos" = yes ]; then
375 current=$(pbpaste)
376 else
377 current=$(xsel -o)
378 fi
379 if [ "$past" != "$current" ]; then
380 past=$current
381 current_text="$(echo $current | tr -c '[a-zA-Z0-9]' ' ')"
382 if [ $(echo $current_text | wc -w) -gt "$STEALTH_MAX_SELECTION_LENGTH" ]; then
383 echo "\033[0;31mstealth:\033[0m selection length is longer than $STEALTH_MAX_SELECTION_LENGTH words; ignoring"
384 continue
385 else
386 printf "\n\033[0;31mstealth: \033[7m $current_text\033[0m\n"
387 query=$(prepare_query "$section" "$current_text" "$arguments")
388 do_query "$query"
389 fi
390 fi
391 sleep 1;
392 done
393 trap - INT
394}
395
396cmd_update() {
397 [ -w "$0" ] || { echo "The script is readonly; please update manually: curl -s "${CHTSH_URL}"/:bash | sudo tee $0"; return; }
398 TMP2=$(mktemp /tmp/cht.sh.XXXXXXXXXXXXX)
399 curl -s "${CHTSH_URL}"/:cht.sh > "$TMP2"
400 if ! cmp "$0" "$TMP2" > /dev/null 2>&1; then
401 if grep -q ^__CHTSH_VERSION= "$TMP2"; then
402 # section was vaildated by us already
403 args="--shell $section"
404 cp "$TMP2" "$0" && echo "Updated. Restarting..." && rm "$TMP2" && CHEATSH_RESTART=1 exec "$0" $args
405 else
406 echo "Something went wrong. Please update manually"
407 fi
408 else
409 echo "cht.sh is up to date. No update needed"
410 fi
411 rm -f "$TMP2" > /dev/null 2>&1
412}
413
414cmd_version() {
415 insttime=$(ls -l -- "$0" | sed 's/ */ /g' | cut -d ' ' -f 6-8)
416 echo "cht.sh version $__CHTSH_VERSION of $__CHTSH_DATETIME; installed at: $insttime"
417 TMP2=$(mktemp /tmp/cht.sh.XXXXXXXXXXXXX)
418 if curl -s "${CHTSH_URL}"/:cht.sh > "$TMP2"; then
419 if ! cmp "$0" "$TMP2" > /dev/null 2>&1; then
420 echo "Update needed (type 'update' for that)".
421 else
422 echo "Up to date. No update needed"
423 fi
424 fi
425 rm -f "$TMP2" > /dev/null 2>&1
426}
427
428TMP1=$(mktemp /tmp/cht.sh.XXXXXXXXXXXXX)
429trap 'rm -f $TMP1 $TMP2' EXIT
430trap 'true' INT
431
432if ! [ -e "$HOME/.cht.sh/.hushlogin" ] && [ -z "$this_query" ]; then
433 echo "type 'help' for the cht.sh shell help"
434fi
435
436while true; do
437 if [ "$section" != "" ]; then
438 full_prompt="$prompt/$section> "
439 else
440 full_prompt="$prompt> "
441 fi
442
443 input=$(
444 rlwrap -H "$HOME/.cht.sh/history" -pgreen -C cht.sh -S "$full_prompt" sh "$0" --read | sed 's/ *#.*//'
445 )
446
447 cmd_name=${input%% *}
448 cmd_args=${input#* }
449 case $cmd_name in
450 "") continue;; # skip empty input lines
451 '?'|h|help) cmd_name=help;;
452 hush) cmd_name=hush;;
453 cd) cmd_name=cd;;
454 exit|quit) cmd_name=exit;;
455 copy|yank|c|y) cmd_name=copy;;
456 ccopy|cc|C|Y) cmd_name=ccopy;;
457 id) cmd_name=id;;
458 stealth) cmd_name=stealth;;
459 update) cmd_name=update;;
460 version) cmd_name=version;;
461 *) cmd_name="query $cmd_name";;
462 esac
463 cmd_$cmd_name $cmd_args
464done