aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authortypebrook <typebrook@gmail.com>2020-02-04 15:10:34 +0800
committertypebrook <typebrook@gmail.com>2020-02-04 15:10:34 +0800
commit0eb1e02b0647f08445b77a8969e62731d2a5bf14 (patch)
treed910bb1e303297c1072529a728f5c97cc301b50c
parentcf5e6f9dc5196994aed64dfcba23f47419fc1888 (diff)
update
-rwxr-xr-xgist185
1 files changed, 101 insertions, 84 deletions
diff --git a/gist b/gist
index ee400fb..e9e5130 100755
--- a/gist
+++ b/gist
@@ -4,60 +4,42 @@
4# License: MIT 4# License: MIT
5# https://gist.github.com/typebrook/b0d2e7e67aa50298fdf8111ae7466b56 5# https://gist.github.com/typebrook/b0d2e7e67aa50298fdf8111ae7466b56
6# 6#
7# gist
8# Description: Host your gists as local cloned git repo
9# Usage: gist [command] [<args>]
7# 10#
8# This script host your gists as local cloned git repo 11# [star | s] list your gists with format below, star for your starred gists:
9# It works under GNU curl, which is easy to get in most cases 12# [index_of_gist] [url] [file_num] [comment_num] [short description]
13# update, u [star | s] update the local list of your gists, star for your starred gists
14# <index_of_gist> show the path of local gist repo and do custom actions
15# new, n [-d | --desc <description>] <files>... create a new gist with files
16# new, n [-d | --desc <description>] [-f | --file <file_name>] create a new gist from STDIN
17# detail, d <index_of_gist> show the detail of a gist
18# edit, e <index_of_gist> edit a gist description
19# delete, D <index_of_gist>... delete a gist
20# clean, C clean removed gists in local
21# config, c [token | user | folder | auto-sync | EDITOR | action [value] ] do configuration
22# user, U <user> get gists from a given Github user
23# help, h show this help message
10# 24#
11# Use the following commands to manage your gists: 25# Example:
12# 26# gist (Show your gists)
13# * update the local list of your gists, star for your starred gists 27# gist 3 (show the repo path of your 3rd gist, and do custom actions)
14# gist (update | u) [star | s]
15#
16# * list your gists with format: [number] [url] [file_num] [comment_num] [short description]
17# gist [star | s]
18# 28#
19# * show the path of local gist repo and files
20# gist <index_of_gist>
21#
22# * create a new gist with files
23# gist (new | n) [-d | --desc "<gist-description>"] <files>...
24#
25# * create a new gist with STDIN
26# gist (new | n) [-d | --desc "<gist-description>"] [-f | --file <file>] < <file-with-content>
27#
28# * show the detail of a gist
29# gist (detail | d) <index_of_gist>
30#
31# * edit a gist description
32# gist (edit | e) <index_of_gist>
33#
34# * delete a gist
35# gist (delete | D) <index_of_gist>...
36#
37# * clean removed gists in local
38# gist (clean | C)
39#
40# * update a gist
41# Since now a gist is a local cloned repo 29# Since now a gist is a local cloned repo
42# It is your business to do git commit and git push 30# It is your business to do git commit and git push
43#
44# * configuration
45# gist (config | c) [token|user|folder|auto-sync|EDITOR|action [value]]
46#
47# * show this help message
48# gist (help | h)
49 31
50# TODO parallel branch works with wget and other stuff
51# TODO new command "user" to fetch other user's gists
52# TODO grep mode for description, file content 32# TODO grep mode for description, file content
53# TODO push github.com (may need new token) 33# TODO push github.com (may need new token)
54# TODO description for current directory 34# TODO description for current directory
55# TODO error handling, unit test 35# TODO unit test
56# TODO test on mac and remote machine 36# TODO test on mac and remote machine
57# TODO completion 37# TODO completion
58 38
59# Shell configuration 39# Shell configuration
40set -o pipefail
60[ "$TRACE" ] && set -x 41[ "$TRACE" ] && set -x
42trap 'rm -f "$http_data" "tmp_file"' EXIT
61 43
62GITHUB_API=https://api.github.com 44GITHUB_API=https://api.github.com
63CONFIG=~/.config/gist.conf; mkdir -p ~/.config 45CONFIG=~/.config/gist.conf; mkdir -p ~/.config
@@ -134,17 +116,17 @@ _validate_config(){
134 116
135# load configuration 117# load configuration
136_apply_config() { 118_apply_config() {
137 source $CONFIG && _validate_config 119 _validate_config "$@" || return 1
138 120
139 AUTH_HEADER="Authorization: token $token" 121 AUTH_HEADER="Authorization: token $token"
140 [[ -z "$action" ]] && action="${EDITOR:-vi} *" 122 [[ -z "$action" ]] && action="${EDITOR:-vi} ."
141 [[ -z "$folder" ]] && folder=~/gist && mkdir -p $folder 123 [[ -z "$folder" ]] && folder=~/gist && mkdir -p $folder
142 INDEX=$folder/index 124 INDEX=$folder/index
143} 125}
144 126
145_apply_config "$@" || exit 1 127_apply_config "$@" || exit 1
146 128
147## This function determines which http get tool the system has installed and returns an error if there isnt one 129# This function determines which http get tool the system has installed and returns an error if there isnt one
148getConfiguredClient() { 130getConfiguredClient() {
149 if command -v curl &>/dev/null; then 131 if command -v curl &>/dev/null; then
150 configuredClient="curl" 132 configuredClient="curl"
@@ -160,13 +142,20 @@ getConfiguredClient() {
160 fi 142 fi
161} 143}
162 144
163## Allows to call the users configured client without if statements everywhere 145# Allows to call the users configured client without if statements everywhere
164httpGet() { 146http_method() {
165 local header="" 147 local METHOD=$1; shift
166 case "$configuredClient" in 148 case "$configuredClient" in
167 curl) [[ -n $token ]] && header="--header Authorization: token $token"; curl -A curl -s $header "$@" ;; 149 curl) [[ -n $token ]] && local extra="--header" local header="Authorization: token $token"
168 wget) [[ -n $token ]] && header="--header Authorization: token $token"; wget -qO- $header "$@" ;; 150 [[ $METHOD =~ (POST|PATCH) ]] && extra2="--data"
169 httpie) [[ -n $token ]] && header="Authorization:token $token"; http -b GET "$@" "$header";; 151 curl -X $METHOD -A curl -s $extra "$header" $extra2 @$http_data "$@" ;;
152 wget) [[ -n $token ]] && local extra="--header" local header="Authorization: token $token"
153 [[ $METHOD =~ (POST|PATCH) ]] && extra2='--body-file'
154 wget --method=$METHOD -qO- $extra "$header" $extra2 $http_data "$@" ;;
155 httpie) [[ -n $token ]] && header="Authorization:token $token"
156 [[ $METHOD =~ (POST|PATCH) ]] && extra2="@$http_data"
157 http -b $METHOD "$@" "$header" $extra2 ;;
158 # TODO add other methods
170 fetch) fetch -q "$@" ;; 159 fetch) fetch -q "$@" ;;
171 esac 160 esac
172} 161}
@@ -180,14 +169,11 @@ _show_list() {
180 echo ' gist update' 169 echo ' gist update'
181 return 0 170 return 0
182 fi 171 fi
183 local filter="" 172 local filter='/^s/ d; /^$/ d'
184 if [[ $1 == "s" ]]; then 173 [[ $1 == "s" ]] && filter='/^[^s]/ d; /^$/ d'
185 filter='/^[^s]/ d' 174
186 else 175 while read index link blob_code file_num extra author description; do
187 filter='/^s/ d' 176 [[ $1 == "s" ]] && local author=$author
188 fi
189 cat $INDEX \
190 | while read index link blob_code file_num extra description; do
191 local repo=$folder/$(echo $link | sed 's#.*/##') 177 local repo=$folder/$(echo $link | sed 's#.*/##')
192 local occupy=0 178 local occupy=0
193 179
@@ -198,27 +184,30 @@ _show_list() {
198 # if there is a commit not yet push, show red message "ahead" 184 # if there is a commit not yet push, show red message "ahead"
199 [[ -n $(cd $repo && git cherry) ]] 2>/dev/null && extra="\e[31m[ahead]\e[0m" && occupy=7 185 [[ -n $(cd $repo && git cherry) ]] 2>/dev/null && extra="\e[31m[ahead]\e[0m" && occupy=7
200 186
201 echo -e $index $link $file_num $extra $(echo $description | cut -c -$(( 60 -$occupy -1 )) ) 187 echo -e $index $link $author $file_num $extra $(echo $description | cut -c -$(( 60 -$occupy -1 )) )
202 done \ 188 done < $INDEX \
203 | sed "$filter" 189 | sed "$filter"
190 echo -e '\nrun "gist help" for more details'
204} 191}
205 192
206# parse JSON from STDIN with string of commands 193# parse JSON from STDIN with string of commands
207AccessJsonElement() { 194AccessJsonElement() {
208 PYTHONIOENCODING=utf-8 \ 195 PYTHONIOENCODING=utf-8 \
209 python -c "from __future__ import print_function; import sys, json; raw = json.load(sys.stdin); $1" 2> /dev/null 196 python -c "from __future__ import print_function; import sys, json; $1"
210 return "$?" 197 return "$?"
211} 198}
212 199
213# equal to: jq '.[] | "\(.html_url) \([.files[] | .raw_url]) \(.files | keys | length) \(.comments) \(.description)"' 200# equal to: jq '.[] | "\(.html_url) \([.files[] | .raw_url]) \(.files | keys | length) \(.comments) \(.description)"'
214_handle_gists() { 201_handle_gists() {
215 echo ' 202 echo '
203raw = json.load(sys.stdin)
216for gist in raw: 204for gist in raw:
217 print(gist["html_url"], end=" ") 205 print(gist["html_url"], end=" ")
218 print([file["raw_url"] for file in gist["files"].values()], end=" ") 206 print([file["raw_url"] for file in gist["files"].values()], end=" ")
219 print(gist["public"], end=" ") 207 print(gist["public"], end=" ")
220 print(len(gist["files"]), end=" ") 208 print(len(gist["files"]), end=" ")
221 print(gist["comments"], end=" ") 209 print(gist["comments"], end=" ")
210 print(gist["owner"]["login"], end=" ")
222 print(gist["description"]) 211 print(gist["description"])
223 ' 212 '
224} 213}
@@ -228,36 +217,48 @@ for gist in raw:
228_parse_response() { 217_parse_response() {
229 AccessJsonElement "$(_handle_gists)" \ 218 AccessJsonElement "$(_handle_gists)" \
230 | tac | sed 's/, /,/g' | nl -s' ' \ 219 | tac | sed 's/, /,/g' | nl -s' ' \
231 | while read index link file_url_array public file_num comment_num description; do 220 | while read index link file_url_array public file_num comment_num author description; do
232 local blob_code=$(echo $file_url_array | tr ',' '\n' | sed -E 's#.*raw/(.*)/.*#\1#' | sort | cut -c -7 | paste -sd '-') 221 local blob_code=$(echo $file_url_array | tr ',' '\n' | sed -E 's#.*raw/(.*)/.*#\1#' | sort | cut -c -7 | paste -sd '-')
233 [[ $public == 'False' ]] && local mark=p 222 [[ $public == 'False' ]] && local mark=p
234 [[ -n $1 ]] && local index=$1 223 [[ -n $1 ]] && local index=$1
235 echo $mark$index $link $blob_code $file_num $comment_num $description | tr -d '"' 224 echo $mark$index $link $blob_code $file_num $comment_num $author $description | tr -d '"'
236 done 225 done
237} 226}
238 227
228# TODO add author, files and date of a gist
239# get latest list of gists from Github API 229# get latest list of gists from Github API
240_update() { 230_update() {
241 echo "fetching $user's gists from $GITHUB_API..." 231 echo "fetching $user's gists from $GITHUB_API..."
242 echo 232 echo
243 local route="users/$user/gists" 233 local route="users/$user/gists"
244 local mark="" 234 local mark=""
245 local filter='/^[^s]/ d' 235 local filter='/^[^s]/ d; /^$/ d'
246 if [[ "$1" =~ ^(star|s)$ ]];then 236 if [[ "$1" =~ ^(star|s)$ ]];then
247 route="gists/starred" 237 route="gists/starred"
248 mark="s" 238 mark="s"
249 filter='/^[s]/ d' 239 filter='/^[s]/ d; /^$/ d'
250 fi 240 fi
251 241
252 local response=$(httpGet $GITHUB_API/$route) 242 result=$(http_method GET $GITHUB_API/$route | _parse_response)
253 false && echo Failed to update gists && return 1 243 [[ -z $result ]] && echo Failed to update gists && return 1
254 sed -i "$filter" $INDEX 244
255 echo $response | _parse_response >> $INDEX 245 sed -i "$filter" $INDEX && echo "$result" >> $INDEX
256 _show_list $mark 246 _show_list $mark
257 247
258 if [[ $auto_sync != "false" ]]; then (_sync_repos $1 > /dev/null 2>&1 &); fi 248 if [[ $auto_sync != "false" ]]; then (_sync_repos $1 > /dev/null 2>&1 &); fi
259} 249}
260 250
251_query_user() {
252 local route="users/$1/gists"
253 result=$(http_method GET $GITHUB_API/$route | _parse_response)
254 [[ -z $result ]] && echo Failed to update gists && return 1
255
256 echo "$result" \
257 | while read index link blob_code file_num extra description; do
258 echo $link $file_num $extra $(echo $description | cut -c -70 )
259 done
260}
261
261# update local git repos 262# update local git repos
262_sync_repos() { 263_sync_repos() {
263 # clone repos which are not in the local 264 # clone repos which are not in the local
@@ -312,7 +313,7 @@ _goto_gist() {
312_delete_gist() { 313_delete_gist() {
313 for i in "$@"; do 314 for i in "$@"; do
314 _gist_id "$i" 315 _gist_id "$i"
315 curl -X DELETE -s -H "$AUTH_HEADER" $GITHUB_API/gists/$GIST_ID \ 316 http_method DELETE $GITHUB_API/gists/$GIST_ID \
316 && echo "$i" deleted \ 317 && echo "$i" deleted \
317 && sed -i -E "/^$i / d" $INDEX 318 && sed -i -E "/^$i / d" $INDEX
318 done 319 done
@@ -330,6 +331,7 @@ _clean_repos() {
330# parse JSON from gist detail 331# parse JSON from gist detail
331_handle_gist() { 332_handle_gist() {
332 echo ' 333 echo '
334raw = json.load(sys.stdin)
333print("site:", raw["html_url"]) 335print("site:", raw["html_url"])
334print("description:", raw["description"]) 336print("description:", raw["description"])
335print("public:", raw["public"]) 337print("public:", raw["public"])
@@ -345,6 +347,7 @@ for file in raw["files"].keys():
345# equal to jq '.[] | {user: .user.login, created_at: .created_at, updated_at: .updated_at, body: .body}' 347# equal to jq '.[] | {user: .user.login, created_at: .created_at, updated_at: .updated_at, body: .body}'
346_handle_comment() { 348_handle_comment() {
347 echo ' 349 echo '
350raw = json.load(sys.stdin);
348for comment in raw: 351for comment in raw:
349 print() 352 print()
350 print("|", "user:", comment["user"]["login"]) 353 print("|", "user:", comment["user"]["login"])
@@ -357,16 +360,16 @@ for comment in raw:
357# TODO format with simple text 360# TODO format with simple text
358_show_detail() { 361_show_detail() {
359 _gist_id $1 362 _gist_id $1
360 httpGet $GITHUB_API/gists/$GIST_ID \ 363 http_method GET $GITHUB_API/gists/$GIST_ID \
361 | AccessJsonElement "$(_handle_gist)" 364 | AccessJsonElement "$(_handle_gist)"
362 365
363 httpGet $GITHUB_API/gists/$GIST_ID/comments \ 366 http_method GET $GITHUB_API/gists/$GIST_ID/comments \
364 | AccessJsonElement "$(_handle_comment)" 367 | AccessJsonElement "$(_handle_comment)"
365} 368}
366 369
367# set filename/description/permission for a new gist 370# set filename/description/permission for a new gist
368_set_gist() { 371_set_gist() {
369 public=true 372 public=True
370 while [[ -n "$@" ]]; do case $1 in 373 while [[ -n "$@" ]]; do case $1 in
371 -d | --desc) 374 -d | --desc)
372 description="$2" 375 description="$2"
@@ -375,7 +378,7 @@ _set_gist() {
375 filename="$2" 378 filename="$2"
376 shift; shift;; 379 shift; shift;;
377 -p) 380 -p)
378 public=false 381 public=False
379 shift;; 382 shift;;
380 *) 383 *)
381 files="$1 $files" 384 files="$1 $files"
@@ -388,7 +391,7 @@ _set_gist() {
388# Let user type the content of gist before setting filename 391# Let user type the content of gist before setting filename
389_new_file() { 392_new_file() {
390 [[ -t 0 ]] && echo "Type a gist. <Ctrl-C> to cancel, <Ctrl-D> when done" > /dev/tty 393 [[ -t 0 ]] && echo "Type a gist. <Ctrl-C> to cancel, <Ctrl-D> when done" > /dev/tty
391 local tmp_file=$(mktemp) 394 tmp_file=$(mktemp)
392 cat > $tmp_file 395 cat > $tmp_file
393 echo -e '\n' > /dev/tty 396 echo -e '\n' > /dev/tty
394 [[ -z "$1" ]] && read -p 'Type file name: ' filename < /dev/tty 397 [[ -z "$1" ]] && read -p 'Type file name: ' filename < /dev/tty
@@ -396,22 +399,33 @@ _new_file() {
396 echo /tmp/$filename 399 echo /tmp/$filename
397} 400}
398 401
402_gist_body(){
403 echo "
404import os.path
405files_json = {}
406files = sys.stdin.readline().split()
407description = sys.stdin.readline().replace('\n','')
408for file in files:
409 with open(file, 'r') as f:
410 files_json[os.path.basename(file)] = {'content': f.read()}
411print(json.dumps({'public': $public, 'files': files_json, 'description': description}))
412 "
413}
414
399# create a new gist with files 415# create a new gist with files
400_create_gist() { 416_create_gist() {
401 _set_gist "$@" || return 1 417 _set_gist "$@" || return 1
402 [[ -z "$files" ]] && files=$(_new_file $filename) 418 [[ -z "$files" ]] && files=$(_new_file $filename)
403 [[ -z "$description" ]] && read -p 'Type description: ' description < /dev/tty 419 [[ -z "$description" ]] && read -p 'Type description: ' description < /dev/tty
404 420
405 local index=$(( $(sed '/^s/ d' $INDEX | wc -l) +1 ))
406 echo 'Creating a new gist...' 421 echo 'Creating a new gist...'
407 for file in $files; do 422 http_data=$(mktemp)
408 echo "\"$(basename $file)\": {\"content\": \"$(sed '$ !s/$/\\n/' $file)\"}," 423
409 done | tr -d '\n' | sed 's/^/{/; s/,$/}/' \ 424 echo -e "$files\n$description" \
410 | echo "{ \"public\": $public, \"files\": $(cat -), \"description\": \"$description\"}" \ 425 | AccessJsonElement "$(_gist_body)" > $http_data \
411 | curl -s -H "$AUTH_HEADER" --data @- $GITHUB_API/gists \ 426 && http_method POST $GITHUB_API/gists \
412 | tee jojo \
413 | sed '1 s/^/[/; $ s/$/]/' \ 427 | sed '1 s/^/[/; $ s/$/]/' \
414 | _parse_response $index >> $INDEX 428 | _parse_response $(( $(sed '/^s/ d' $INDEX | wc -l) +1 )) >> $INDEX
415 429
416 if [[ $? -eq 0 ]]; then 430 if [[ $? -eq 0 ]]; then
417 echo 'Gist is created' 431 echo 'Gist is created'
@@ -428,12 +442,12 @@ _edit_gist() {
428 echo -n 'Type new description: ' 442 echo -n 'Type new description: '
429 read DESC < /dev/tty 443 read DESC < /dev/tty
430 echo "{ \"description\": \"$DESC\" }" \ 444 echo "{ \"description\": \"$DESC\" }" \
431 | curl -X PATCH -H "$AUTH_HEADER" --data @- $GITHUB_API/gists/$GIST_ID > /dev/null \ 445 | http_method PATCH $GITHUB_API/gists/$GIST_ID > /dev/null \
432 && _update 446 && _update
433} 447}
434 448
435usage() { 449usage() {
436 sed -E -n ' /^$/ q; 8,$ s/^#//p' $0 450 sed -E -n ' /^$/ q; 7,$ s/^#//p' $0
437} 451}
438 452
439getConfiguredClient 453getConfiguredClient
@@ -462,6 +476,9 @@ case "$1" in
462 config | c) 476 config | c)
463 shift 477 shift
464 _configure "$@" ;; 478 _configure "$@" ;;
479 user | U)
480 shift
481 _query_user "$@" ;;
465 help | h) 482 help | h)
466 usage ;; 483 usage ;;
467 *) 484 *)