diff options
| author | typebrook <typebrook@gmail.com> | 2020-01-20 11:53:00 +0800 |
|---|---|---|
| committer | typebrook <typebrook@gmail.com> | 2020-01-20 11:53:00 +0800 |
| commit | 456d5d41eeca37a595980ec3ba8e9411e0dfb63b (patch) | |
| tree | 9c20dd885e2ff9d5402611ae91c7341826b579c1 | |
| parent | 0078e7e9e1e1ab46833cb108df11dd4f4aa8c4b9 (diff) | |
update
| -rwxr-xr-x | gist | 221 |
1 files changed, 149 insertions, 72 deletions
| @@ -5,7 +5,7 @@ | |||
| 5 | # https://gist.github.com/typebrook/b0d2e7e67aa50298fdf8111ae7466b56 | 5 | # https://gist.github.com/typebrook/b0d2e7e67aa50298fdf8111ae7466b56 |
| 6 | # | 6 | # |
| 7 | # | 7 | # |
| 8 | # This script host your gists as local Github repo | 8 | # This script host your gists as local cloned git repo |
| 9 | # It works under GNU with jq and curl, both are easy to get in most cases | 9 | # It works under GNU with jq and curl, both are easy to get in most cases |
| 10 | # | 10 | # |
| 11 | # Use the following commands to manage your gists: | 11 | # Use the following commands to manage your gists: |
| @@ -18,31 +18,42 @@ | |||
| 18 | # | 18 | # |
| 19 | # * clone gist repos which are not in local | 19 | # * clone gist repos which are not in local |
| 20 | # * pull master branch if a local repo is behind its remote | 20 | # * pull master branch if a local repo is behind its remote |
| 21 | # gist [sync | s] | 21 | # gist (sync | S) |
| 22 | # | 22 | # |
| 23 | # * Go to local gist repo | 23 | # * Go to local gist repo |
| 24 | # gist <number_of_gist_in_list> | 24 | # . gist <number_of_gist_in_list> |
| 25 | # | 25 | # |
| 26 | # * create a new gist with a file and description | 26 | # * create a new gist with files |
| 27 | # gist [create | c] <filename> "<description>" | 27 | # gist (new | n) [-d | --desc "<gist-description>"] <files>... |
| 28 | # | ||
| 29 | # * create a new gist with STDIN | ||
| 30 | # gist (new | n) [-d | --desc "<gist-description>"] [-f | --file <file>] < <file-with-content> | ||
| 28 | # | 31 | # |
| 29 | # * show the detail of a gist | 32 | # * show the detail of a gist |
| 30 | # gist [detail | d] <number_of_gist_in_list> | 33 | # gist (detail | d) <gist_index> |
| 31 | # | 34 | # |
| 32 | # * edit a gist description | 35 | # * edit a gist description |
| 33 | # gist [edit | e] <number_of_gist_in_list> | 36 | # gist (edit | e) <gist_index> |
| 34 | # | 37 | # |
| 35 | # * delete a gist | 38 | # * delete a gist |
| 36 | # gist [delete | D] <number_of_gist_in_list> | 39 | # gist (delete | D) <gist_index>... |
| 37 | # | 40 | # |
| 38 | # * clean removed gists in local | 41 | # * clean removed gists in local |
| 39 | # gist [clean | C] | 42 | # gist (clean | C) |
| 40 | 43 | # | |
| 44 | # * update a gist | ||
| 45 | # Since now a gist is a local cloned repo | ||
| 46 | # It is your business to do git commit and git push | ||
| 47 | # | ||
| 41 | # * show this help message | 48 | # * show this help message |
| 42 | # gist [help | h] | 49 | # gist (help | h) |
| 43 | 50 | ||
| 44 | # define your environmemnts here | 51 | # define your environmemnts here |
| 45 | # TODO support auth prompt | 52 | # TODO support auth prompt, remove personal info here |
| 53 | # TODO error handling, unit test | ||
| 54 | # TODO parallel branch works with json parsing on python | ||
| 55 | # TODO parallel branch works with wget and other stuff | ||
| 56 | # completion | ||
| 46 | #------------------- | 57 | #------------------- |
| 47 | github_api_token=$(cat $SETTING_DIR/tokens/github) | 58 | github_api_token=$(cat $SETTING_DIR/tokens/github) |
| 48 | user=typebrook | 59 | user=typebrook |
| @@ -53,32 +64,68 @@ github_api=https://api.github.com | |||
| 53 | auth_header="Authorization: token $github_api_token" | 64 | auth_header="Authorization: token $github_api_token" |
| 54 | mkdir -p $folder | 65 | mkdir -p $folder |
| 55 | index=$folder/index | 66 | index=$folder/index |
| 67 | starred=$folder/starred | ||
| 56 | 68 | ||
| 57 | # Validate settings. | 69 | # Validate settings. |
| 58 | [ "$TRACE" ] && set -x | 70 | [ "$TRACE" ] && set -x |
| 59 | 71 | ||
| 60 | # Show the list of gist, but not updated time | 72 | # Show the list of gist, but not updated time |
| 73 | # TODO show git status outdated | ||
| 61 | _show_list() { | 74 | _show_list() { |
| 62 | cat $index | cut -d' ' -f1-2,4- | 75 | if [[ ! -e $1 ]]; then |
| 76 | echo No local file found for last update | ||
| 77 | echo Please run command: | ||
| 78 | echo " gist update" | ||
| 79 | exit 0 | ||
| 80 | fi | ||
| 81 | cat $1 |\ | ||
| 82 | while read line_num link file_url_array file_num extra description; do | ||
| 83 | repo=$folder/$(echo $link | sed 's#.*/##') | ||
| 84 | |||
| 85 | # if repo is not yet cloned, show green message "Sync Now" | ||
| 86 | cd $repo 2>/dev/null || extra="\e[32m[Sync Now]\e[0m" | ||
| 87 | # if there are some changes in git index or working directory, show blue message "working" | ||
| 88 | [[ -n $(git status --short) ]] 2>/dev/null && extra="\e[36m[working]\e[0m" | ||
| 89 | # if there is a commit not yet push, show red message "ahead" | ||
| 90 | [[ -n $(git cherry) ]] 2>/dev/null && extra="\e[31m[ahead]\e[0m" | ||
| 91 | |||
| 92 | echo -e $line_num $link $file_num $extra $(echo $description | cut -c -60) | ||
| 93 | done | ||
| 63 | } | 94 | } |
| 64 | 95 | ||
| 65 | # get the list of gists | 96 | # get the list of gists |
| 66 | # TODO support secret gist | 97 | # TODO support secret gist |
| 67 | _update() { | 98 | _update() { |
| 68 | curl -s -H "$auth_header" $github_api/users/$user/gists |\ | 99 | echo "fetching from api.github.com..." |
| 100 | echo | ||
| 101 | list_file=$index | ||
| 102 | route="users/$user/gists" | ||
| 103 | mark="" | ||
| 104 | [[ "$1" =~ ^(star|s)$ ]] && list_file=$starred && route="gists/starred" && mark="s" | ||
| 105 | |||
| 106 | curl -s -H "$auth_header" $github_api/$route |\ | ||
| 107 | _parse_response | nl -s' ' | sed -E "s/^ */$mark/" > $list_file && \ | ||
| 108 | _show_list $list_file | ||
| 109 | (_sync_repos $1 > /dev/null 2>&1 &) | ||
| 110 | } | ||
| 111 | |||
| 112 | # TODO check if a user create a very first gist | ||
| 113 | _parse_response() { | ||
| 69 | jq '.[] | "\(.html_url) \([.files[] | .raw_url]) \(.files | keys | length) \(.comments) \(.description)"' |\ | 114 | jq '.[] | "\(.html_url) \([.files[] | .raw_url]) \(.files | keys | length) \(.comments) \(.description)"' |\ |
| 70 | tac | nl |\ | 115 | tac |\ |
| 71 | while read line_num link file_url_array file_num comment_num description; do | 116 | while read link file_url_array file_num comment_num description; do |
| 72 | blob_code=$(echo $file_url_array | jq -r '.[]' | sed -r 's#.*raw/(.*)/.*#\1#' | sort | cut -c -7 | paste -sd '-') | 117 | blob_code=$(echo $file_url_array | jq -r '.[]' | sed -E 's#.*raw/(.*)/.*#\1#' | sort | cut -c -7 | paste -sd '-') |
| 73 | echo $line_num $link $blob_code $file_num $comment_num $(echo $description | cut -c -70) | tr -d '"' | 118 | echo $link $blob_code $file_num $comment_num $description | tr -d '"' |
| 74 | done > $index && \ | 119 | done |
| 75 | _show_list | ||
| 76 | } | 120 | } |
| 77 | 121 | ||
| 78 | _sync_repos() { | 122 | _sync_repos() { |
| 123 | list_file=$index | ||
| 124 | [[ "$1" == "--star" ]] && list_file=$starred && route="gists/starred" | ||
| 125 | |||
| 79 | # clone repos which are not in the local | 126 | # clone repos which are not in the local |
| 80 | comm -13 <(find $folder -maxdepth 1 -type d | sed '1d; s#.*/##' | sort) \ | 127 | comm -13 <(find $folder -maxdepth 1 -type d | sed '1d; s#.*/##' | sort) \ |
| 81 | <(cat $index | cut -d' ' -f2 | sed 's#.*/##' | sort) |\ | 128 | <(cat $list_file | cut -d' ' -f2 | sed 's#.*/##' | sort) |\ |
| 82 | xargs -I{} git clone git@github.com:{}.git $folder/{} | 129 | xargs -I{} git clone git@github.com:{}.git $folder/{} |
| 83 | 130 | ||
| 84 | # pull if remote repo has different blob objects | 131 | # pull if remote repo has different blob objects |
| @@ -96,27 +143,32 @@ _sync_repos() { | |||
| 96 | } | 143 | } |
| 97 | 144 | ||
| 98 | _gist_id() { | 145 | _gist_id() { |
| 99 | cat $index | sed -n "$1"p | cut -d' ' -f2 | sed -r 's#.*/##' | 146 | GIST_ID=$(cat $index $starred | sed -n "/^$1 / p" | cut -d' ' -f2 | sed -E 's#.*/##') |
| 100 | } | 147 | if [[ -z $GIST_ID ]]; then |
| 101 | 148 | echo -e "Not a valid index: \e[31m$1\e[0m" | |
| 102 | _goto_gist() { | 149 | echo Use the index number in the first column instead: |
| 103 | gist_num=$(wc -l $index | cut -d' ' -f1) | ||
| 104 | if [[ ! "$1" =~ [0-9]+ ]] || (( $1 > $gist_num )); then | ||
| 105 | echo Not a valid gist number: $1 | ||
| 106 | echo Use the number in the first column instead: | ||
| 107 | echo | 150 | echo |
| 108 | _show_list | 151 | _show_list "$index $starred" |
| 109 | return 0 | 152 | exit 1 |
| 110 | fi | 153 | fi |
| 154 | } | ||
| 111 | 155 | ||
| 112 | GIST_ID=$(_gist_id $1) | 156 | # FIXME error handling |
| 157 | _goto_gist() { | ||
| 158 | _gist_id $1 | ||
| 113 | echo This gist is at $folder/$GIST_ID | 159 | echo This gist is at $folder/$GIST_ID |
| 114 | cd $folder/$GIST_ID && tig --all 2> /dev/null | 160 | echo -e "You can run the following command to jump to this directory: \n" |
| 161 | echo -e " \e[32m. gist $1\e[0m" | ||
| 162 | echo | ||
| 163 | cd $folder/$GIST_ID && ls && tig --all 2> /dev/null | ||
| 115 | } | 164 | } |
| 116 | 165 | ||
| 117 | _delete_gist() { | 166 | _delete_gist() { |
| 118 | GIST_ID=$(_gist_id $1) | 167 | for i in "$@"; do |
| 119 | curl -X DELETE -s -H "$auth_header" $github_api/gists/$GIST_ID && \ | 168 | _gist_id "$i" |
| 169 | curl -X DELETE -s -H "$auth_header" $github_api/gists/$GIST_ID && \ | ||
| 170 | echo "$i" deleted | ||
| 171 | done | ||
| 120 | _update | 172 | _update |
| 121 | } | 173 | } |
| 122 | 174 | ||
| @@ -129,78 +181,103 @@ _clean_repos() { | |||
| 129 | done | 181 | done |
| 130 | } | 182 | } |
| 131 | 183 | ||
| 132 | # TODO star count | 184 | # TODO format with simple text |
| 133 | _show_detail() { | 185 | _show_detail() { |
| 134 | GIST_ID=$(_gist_id $1) | 186 | _gist_id $1 |
| 135 | curl -s -H "$auth_header" $github_api/gists/$GIST_ID |\ | 187 | curl -s -H "$auth_header" $github_api/gists/$GIST_ID |\ |
| 136 | jq '{site: .html_url, description: .description, API: .url, created_at: .created_at, updated_at: .updated_at, files: (.files | keys)}' | 188 | jq '{site: .html_url, description: .description, public: .public, API: .url, created_at: .created_at, updated_at: .updated_at, files: (.files | keys)}' |
| 137 | 189 | ||
| 138 | curl -s -H "$auth_header" $github_api/gists/$GIST_ID/comments |\ | 190 | curl -s -H "$auth_header" $github_api/gists/$GIST_ID/comments |\ |
| 139 | jq '.[] | {user: .user.login, created_at: .created_at, updated_at: .updated_at, body: .body}' | 191 | jq '.[] | {user: .user.login, created_at: .created_at, updated_at: .updated_at, body: .body}' |
| 140 | } | 192 | } |
| 141 | 193 | ||
| 194 | _new_file() { | ||
| 195 | [[ -t 0 ]] && echo "Type a gist. <Ctrl-C> to cancel, <Ctrl-D> when done" > /dev/tty | ||
| 196 | tmp_file=$(mktemp) | ||
| 197 | cat > $tmp_file | ||
| 198 | echo | ||
| 199 | [[ -z "$1" ]] && echo -en '\nType file name: ' > /dev/tty && read filename | ||
| 200 | mv $tmp_file /tmp/$filename | ||
| 201 | echo /tmp/$filename | ||
| 202 | } | ||
| 203 | |||
| 204 | _set_gist() { | ||
| 205 | while [[ "$1" =~ ^- && ! "$1" == "--" ]]; do case $1 in | ||
| 206 | -d | --desc) | ||
| 207 | description="$2" | ||
| 208 | shift; shift;; | ||
| 209 | -f | --file) | ||
| 210 | filename="$2" | ||
| 211 | shift; shift;; | ||
| 212 | esac | ||
| 213 | done | ||
| 214 | if [[ "$1" == '--' ]]; then shift; fi | ||
| 215 | files=$@ | ||
| 216 | } | ||
| 217 | |||
| 142 | # create a new gist with files | 218 | # create a new gist with files |
| 219 | # FIXME error handling if gist is not created | ||
| 143 | _create_gist() { | 220 | _create_gist() { |
| 144 | echo -n 'description: ' | 221 | _set_gist "$@" |
| 145 | read DESC | 222 | [[ -z $files ]] && files=$(_new_file $filename) |
| 223 | [[ -z $description ]] && echo -en '\nDescription: ' && read description | ||
| 146 | 224 | ||
| 147 | echo $@ | tr " " "\n" |\ | 225 | for file in $files; do |
| 148 | while read file; do | ||
| 149 | FILE=$(basename $file) | 226 | FILE=$(basename $file) |
| 150 | jq --arg FILE "$FILE" '. as $content | { ($FILE): {content: $content} }' -Rs $file | 227 | jq --arg FILE "$FILE" '. as $content | { ($FILE): {content: $content} }' -Rs $file |
| 151 | done |\ | 228 | done |\ |
| 152 | jq --slurp --arg DESC "$DESC" '{ | 229 | jq --slurp --arg DESC "$description" '{ |
| 153 | public: true, | 230 | public: true, |
| 154 | files: add, | 231 | files: add, |
| 155 | description: ($DESC) | 232 | description: ($DESC) |
| 156 | }' |\ | 233 | }' |\ |
| 157 | curl -s -H "$auth_header" --data @- $github_api/gists > /dev/null && \ | 234 | curl -H "$auth_header" --data @- $github_api/gists |\ |
| 158 | _update && _sync_repos | 235 | sed '1 s/^/[/; $ s/$/]/' |\ |
| 236 | _parse_response |\ | ||
| 237 | sed -E "s/^/$(( $(wc -l $index | cut -d' ' -f1) + 1 )) /" >> $index && \ | ||
| 238 | echo Gist created | ||
| 239 | echo | ||
| 240 | _show_list $index | tail -1 | ||
| 159 | } | 241 | } |
| 160 | 242 | ||
| 161 | # update description of a gist | 243 | # update description of a gist |
| 162 | _edit_gist() { | 244 | _edit_gist() { |
| 163 | GIST_ID=$(_gist_id $1) | 245 | _gist_id $1 |
| 164 | 246 | ||
| 165 | jq -n --arg DESC "$2" '{ description: ($DESC) }' |\ | 247 | echo -n 'Type new description: ' |
| 248 | read DESC | ||
| 249 | jq -n --arg DESC "$DESC" '{ description: ($DESC) }' |\ | ||
| 166 | curl -X PATCH -H "$auth_header" --data @- $github_api/gists/$GIST_ID > /dev/null && \ | 250 | curl -X PATCH -H "$auth_header" --data @- $github_api/gists/$GIST_ID > /dev/null && \ |
| 167 | _update | 251 | _update |
| 168 | } | 252 | } |
| 169 | 253 | ||
| 170 | _help_message() { | 254 | _help_message() { |
| 171 | sed -r -n ' /^$/ q; 8,$ s/^#//p' $0 | 255 | sed -E -n ' /^$/ q; 8,$ s/^#//p' $0 |
| 172 | } | 256 | } |
| 173 | 257 | ||
| 174 | case "$1" in | 258 | case "$1" in |
| 175 | "") | 259 | "") |
| 176 | _show_list | 260 | _show_list $index ;; |
| 177 | ;; | 261 | star | s) |
| 178 | create | c) | 262 | _show_list $starred ;; |
| 179 | shift; | ||
| 180 | _create_gist $@ | ||
| 181 | ;; | ||
| 182 | edit | e) | ||
| 183 | _edit_gist "$2" "$3" | ||
| 184 | ;; | ||
| 185 | update | u) | 263 | update | u) |
| 186 | _update | 264 | _update "$2" ;; |
| 187 | ;; | 265 | new | n) |
| 188 | sync | s) | 266 | shift |
| 189 | _sync_repos | 267 | _create_gist "$@" ;; |
| 190 | ;; | 268 | edit | e) |
| 269 | _edit_gist "$2" ;; | ||
| 270 | sync | S) | ||
| 271 | _sync_repos ;; | ||
| 191 | detail | d) | 272 | detail | d) |
| 192 | _show_detail "$2" | 273 | _show_detail "$2" ;; |
| 193 | ;; | ||
| 194 | delete | D) | 274 | delete | D) |
| 195 | _delete_gist "$2" | 275 | shift |
| 196 | ;; | 276 | _delete_gist "$@" ;; |
| 197 | clean | C) | 277 | clean | C) |
| 198 | _clean_repos | 278 | _clean_repos ;; |
| 199 | ;; | ||
| 200 | help | h) | 279 | help | h) |
| 201 | _help_message | 280 | _help_message ;; |
| 202 | ;; | ||
| 203 | *) | 281 | *) |
| 204 | _goto_gist "$1" | 282 | _goto_gist "$1" ;; |
| 205 | ;; | ||
| 206 | esac | 283 | esac |