diff options
| author | typebrook <typebrook@gmail.com> | 2020-02-01 12:29:59 +0800 |
|---|---|---|
| committer | typebrook <typebrook@gmail.com> | 2020-02-01 12:29:59 +0800 |
| commit | cf5e6f9dc5196994aed64dfcba23f47419fc1888 (patch) | |
| tree | 8b2e031aede91672eb80790b58e731795a7a811d | |
| parent | a2f1012084591cdf1872f02828a922b239d4dd97 (diff) | |
update
| -rwxr-xr-x | gist | 511 |
1 files changed, 293 insertions, 218 deletions
| @@ -15,13 +15,9 @@ | |||
| 15 | # | 15 | # |
| 16 | # * list your gists with format: [number] [url] [file_num] [comment_num] [short description] | 16 | # * list your gists with format: [number] [url] [file_num] [comment_num] [short description] |
| 17 | # gist [star | s] | 17 | # gist [star | s] |
| 18 | # | ||
| 19 | # * clone gist repos which are not in local | ||
| 20 | # * pull master branch if a local repo is behind its remote | ||
| 21 | # gist (sync | S) | ||
| 22 | # | 18 | # |
| 23 | # * Go to local gist repo | 19 | # * show the path of local gist repo and files |
| 24 | # . gist <number_of_gist_in_list> | 20 | # gist <index_of_gist> |
| 25 | # | 21 | # |
| 26 | # * create a new gist with files | 22 | # * create a new gist with files |
| 27 | # gist (new | n) [-d | --desc "<gist-description>"] <files>... | 23 | # gist (new | n) [-d | --desc "<gist-description>"] <files>... |
| @@ -30,13 +26,13 @@ | |||
| 30 | # gist (new | n) [-d | --desc "<gist-description>"] [-f | --file <file>] < <file-with-content> | 26 | # gist (new | n) [-d | --desc "<gist-description>"] [-f | --file <file>] < <file-with-content> |
| 31 | # | 27 | # |
| 32 | # * show the detail of a gist | 28 | # * show the detail of a gist |
| 33 | # gist (detail | d) <gist_index> | 29 | # gist (detail | d) <index_of_gist> |
| 34 | # | 30 | # |
| 35 | # * edit a gist description | 31 | # * edit a gist description |
| 36 | # gist (edit | e) <gist_index> | 32 | # gist (edit | e) <index_of_gist> |
| 37 | # | 33 | # |
| 38 | # * delete a gist | 34 | # * delete a gist |
| 39 | # gist (delete | D) <gist_index>... | 35 | # gist (delete | D) <index_of_gist>... |
| 40 | # | 36 | # |
| 41 | # * clean removed gists in local | 37 | # * clean removed gists in local |
| 42 | # gist (clean | C) | 38 | # gist (clean | C) |
| @@ -46,109 +42,172 @@ | |||
| 46 | # It is your business to do git commit and git push | 42 | # It is your business to do git commit and git push |
| 47 | # | 43 | # |
| 48 | # * configuration | 44 | # * configuration |
| 49 | # gist (config | c) [token <value>] [user <value>] [folder <value>] [auto-sync false] | 45 | # gist (config | c) [token|user|folder|auto-sync|EDITOR|action [value]] |
| 50 | # | 46 | # |
| 51 | # * show this help message | 47 | # * show this help message |
| 52 | # gist (help | h) | 48 | # gist (help | h) |
| 53 | 49 | ||
| 54 | # TODO error handling, unit test | ||
| 55 | # TODO parallel branch works with wget and other stuff | 50 | # TODO parallel branch works with wget and other stuff |
| 56 | # TODO completion | 51 | # TODO new command "user" to fetch other user's gists |
| 57 | # TODO grep mode for description, file content | 52 | # TODO grep mode for description, file content |
| 53 | # TODO push github.com (may need new token) | ||
| 54 | # TODO description for current directory | ||
| 55 | # TODO error handling, unit test | ||
| 56 | # TODO test on mac and remote machine | ||
| 57 | # TODO completion | ||
| 58 | 58 | ||
| 59 | # Validate settings. | 59 | # Shell configuration |
| 60 | config=~/.config/gistrc | ||
| 61 | set -eo pipefail | ||
| 62 | [ "$TRACE" ] && set -x | 60 | [ "$TRACE" ] && set -x |
| 63 | 61 | ||
| 64 | ## parse JSON from STDIN with string of commands | 62 | GITHUB_API=https://api.github.com |
| 65 | AccessJsonElement() { | 63 | CONFIG=~/.config/gist.conf; mkdir -p ~/.config |
| 66 | PYTHONIOENCODING=utf-8 python -c "from __future__ import print_function; import sys, json; raw = json.load(sys.stdin); $1" | 64 | configuredClient="" |
| 67 | return "$?" | 65 | |
| 66 | # handle configuration cases | ||
| 67 | _configure() { | ||
| 68 | local target="" | ||
| 69 | [[ -z "$@" ]] && (${EDITOR:-vi} $CONFIG) && return 0 | ||
| 70 | if [[ $1 == 'token' ]]; then | ||
| 71 | [[ ${#2} -eq 40 ]] && target=$1=$2 \ | ||
| 72 | || echo -e Invalid token format, it is not 40 chars '\n' > /dev/tty | ||
| 73 | elif [[ $1 == 'auto_sync' ]]; then | ||
| 74 | [[ $2 == 'false' ]] && target=$1=$2 \ | ||
| 75 | || target=$1=true | ||
| 76 | elif [[ $1 == 'folder' ]]; then | ||
| 77 | [[ -n "$2" ]] && target=$1=$2 \ | ||
| 78 | || target=$1=~/gist | ||
| 79 | elif [[ $1 == 'user' ]]; then | ||
| 80 | target=$1=$2 | ||
| 81 | fi | ||
| 82 | |||
| 83 | umask 0077 && touch $CONFIG | ||
| 84 | [[ "$target" =~ [^=]$ ]] && sed -i "/^$1=/ d" $CONFIG && echo $target >> $CONFIG | ||
| 85 | cat $CONFIG | ||
| 68 | } | 86 | } |
| 69 | 87 | ||
| 70 | _auth() { | 88 | # prompt for username |
| 71 | echo 'Hi fellow! To access your gists, I need your Github username and a personal token with scope which allows "gist"!' | 89 | _ask_username() { |
| 90 | while [[ ! $user =~ ^[[:alnum:]]+$ ]]; do | ||
| 91 | [[ -n $user ]] && echo "Not a valid username" | ||
| 72 | read -p "Github username: " user < /dev/tty | 92 | read -p "Github username: " user < /dev/tty |
| 73 | if [[ $user =~ ^[[:alnum:]]+$ ]]; then | 93 | done |
| 74 | mkdir -p ~/.config | 94 | _configure user $user |
| 75 | umask 0077 | 95 | } |
| 76 | echo user=$user > $config | ||
| 77 | else | ||
| 78 | echo "Not a valid username" | ||
| 79 | return 0 | ||
| 80 | fi | ||
| 81 | |||
| 82 | echo -n "Create a new token from web browser? [Y/n] " | ||
| 83 | read answer < /dev/tty | ||
| 84 | if [[ ! $answer =~ ^(N|n|No|NO|no)$ ]]; then | ||
| 85 | python -mwebbrowser https://github.com/settings/tokens/new\?scopes\=gist; | ||
| 86 | fi | ||
| 87 | 96 | ||
| 88 | read -p "Paste your token here: " new_token < /dev/tty | 97 | # prompt for toekn |
| 89 | [[ $new_token =~ ^[[:alnum:]]{40}$ ]] \ | 98 | # TODO token check, ref: https://developer.github.com/v3/apps/oauth_applications/#check-a-token |
| 90 | && echo token=$new_token >> $config \ | 99 | _ask_token() { |
| 91 | || echo "Not a valid token" | 100 | echo -n "Create a new token from web browser? [Y/n] " |
| 101 | read answer < /dev/tty | ||
| 102 | if [[ ! $answer =~ ^(N|n|No|NO|no)$ ]]; then | ||
| 103 | python -mwebbrowser https://github.com/settings/tokens/new\?scopes\=gist | ||
| 104 | fi | ||
| 105 | |||
| 106 | while [[ ! $token =~ ^[[:alnum:]]{40}$ ]]; do | ||
| 107 | [[ -n $token ]] && echo "Not a valid token" | ||
| 108 | trap 'echo; return 1' INT | ||
| 109 | read -p "Paste your token here (Ctrl-C to skip): " token < /dev/tty | ||
| 110 | done | ||
| 111 | _configure token $token | ||
| 92 | } | 112 | } |
| 93 | 113 | ||
| 94 | case "$1" in | 114 | _validate_config(){ |
| 95 | config | c) ;; | 115 | source $CONFIG 2> /dev/null || true |
| 96 | *) | 116 | if [[ ! -e $CONFIG || -z $user ]]; then |
| 97 | while ! source $config 2> /dev/null || [[ -z "$token" ]] || [[ -z "$user" ]]; do | 117 | echo 'Hi fellow! To access your gists, I need your Github username' |
| 98 | _auth | 118 | echo "Also a personal token with scope which allows "gist"!'" |
| 99 | done;; | 119 | echo |
| 100 | esac | 120 | _ask_username |
| 121 | _ask_token | ||
| 122 | elif [[ -z $token && $1 =~ ^(n|new|e|edit|D|delete)$ ]]; then | ||
| 123 | if ! (_ask_token); then | ||
| 124 | echo 'To create/edit/delete a gist, a token is needed' | ||
| 125 | return 1 | ||
| 126 | fi | ||
| 127 | elif [[ -z $token && $1 =~ ^(u|update)$ && $2 =~ ^(s|star) ]]; then | ||
| 128 | if ! (_ask_token); then | ||
| 129 | echo 'To get user starred gists, a token is needed' | ||
| 130 | return 1 | ||
| 131 | fi | ||
| 132 | fi | ||
| 133 | } | ||
| 134 | |||
| 135 | # load configuration | ||
| 136 | _apply_config() { | ||
| 137 | source $CONFIG && _validate_config | ||
| 138 | |||
| 139 | AUTH_HEADER="Authorization: token $token" | ||
| 140 | [[ -z "$action" ]] && action="${EDITOR:-vi} *" | ||
| 141 | [[ -z "$folder" ]] && folder=~/gist && mkdir -p $folder | ||
| 142 | INDEX=$folder/index | ||
| 143 | } | ||
| 101 | 144 | ||
| 102 | github_api=https://api.github.com | 145 | _apply_config "$@" || exit 1 |
| 103 | auth_header="Authorization: token $token" | 146 | |
| 147 | ## This function determines which http get tool the system has installed and returns an error if there isnt one | ||
| 148 | getConfiguredClient() { | ||
| 149 | if command -v curl &>/dev/null; then | ||
| 150 | configuredClient="curl" | ||
| 151 | elif command -v wget &>/dev/null; then | ||
| 152 | configuredClient="wget" | ||
| 153 | elif command -v http &>/dev/null; then | ||
| 154 | configuredClient="httpie" | ||
| 155 | elif command -v fetch &>/dev/null; then | ||
| 156 | configuredClient="fetch" | ||
| 157 | else | ||
| 158 | echo "Error: This tool requires either curl, wget, httpie or fetch to be installed." >&2 | ||
| 159 | return 1 | ||
| 160 | fi | ||
| 161 | } | ||
| 104 | 162 | ||
| 105 | [[ -z "$folder" ]] && folder=~/gist | 163 | ## Allows to call the users configured client without if statements everywhere |
| 106 | mkdir -p $folder | 164 | httpGet() { |
| 107 | index=$folder/index | 165 | local header="" |
| 108 | starred=$folder/starred | 166 | case "$configuredClient" in |
| 167 | curl) [[ -n $token ]] && header="--header Authorization: token $token"; curl -A curl -s $header "$@" ;; | ||
| 168 | wget) [[ -n $token ]] && header="--header Authorization: token $token"; wget -qO- $header "$@" ;; | ||
| 169 | httpie) [[ -n $token ]] && header="Authorization:token $token"; http -b GET "$@" "$header";; | ||
| 170 | fetch) fetch -q "$@" ;; | ||
| 171 | esac | ||
| 172 | } | ||
| 109 | 173 | ||
| 110 | # Show the list of gist, but not updated time | 174 | # Show the list of gist, but not updated time |
| 111 | # TODO a way to show files | 175 | # TODO a way to show files |
| 112 | # TODO show git status outdated | 176 | # TODO show git status outdated |
| 113 | _show_list() { | 177 | _show_list() { |
| 114 | if [[ ! -e "$1" ]]; then | 178 | if [[ ! -e $INDEX ]]; then |
| 115 | echo No local file found for last update | 179 | echo 'No local file found for last update, please run command:' |
| 116 | echo Please run command: | 180 | echo ' gist update' |
| 117 | echo " gist update" | 181 | return 0 |
| 118 | exit 0 | 182 | fi |
| 119 | fi | 183 | local filter="" |
| 120 | cat $1 \ | 184 | if [[ $1 == "s" ]]; then |
| 121 | | while read line_num link file_url_array file_num extra description; do | 185 | filter='/^[^s]/ d' |
| 122 | local repo=$folder/$(echo $link | sed 's#.*/##') | 186 | else |
| 123 | local occupy=0 | 187 | filter='/^s/ d' |
| 124 | 188 | fi | |
| 125 | # if repo is not yet cloned, show green message "Not cloned yet" | 189 | cat $INDEX \ |
| 126 | [[ ! -d $repo ]] && extra="\e[32m[Not cloned yet]\e[0m" && occupy=17 | 190 | | while read index link blob_code file_num extra description; do |
| 127 | 191 | local repo=$folder/$(echo $link | sed 's#.*/##') | |
| 128 | # if there are some changes in git index or working directory, show blue message "working" | 192 | local occupy=0 |
| 129 | [[ -n $(cd $repo && git status --short) ]] 2>/dev/null && extra="\e[36m[working]\e[0m" && occupy=10 | 193 | |
| 130 | # if there is a commit not yet push, show red message "ahead" | 194 | # if repo is not yet cloned, show green message "Not cloned yet" |
| 131 | [[ -n $(cd $repo && git cherry) ]] 2>/dev/null && extra="\e[31m[ahead]\e[0m" && occupy=8 | 195 | [[ ! -d $repo ]] && extra="\e[32m[Not cloned yet]\e[0m" && occupy=16 |
| 132 | 196 | # if there are some changes in git index or working directory, show blue message "working" | |
| 133 | echo -e $line_num $link $file_num $extra $(echo $description | cut -c -$(( 60 - $occupy )) ) | 197 | [[ -n $(cd $repo && git status --short) ]] 2>/dev/null && extra="\e[36m[working]\e[0m" && occupy=9 |
| 134 | done | 198 | # 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 | ||
| 200 | |||
| 201 | echo -e $index $link $file_num $extra $(echo $description | cut -c -$(( 60 -$occupy -1 )) ) | ||
| 202 | done \ | ||
| 203 | | sed "$filter" | ||
| 135 | } | 204 | } |
| 136 | 205 | ||
| 137 | # get the list of gists | 206 | # parse JSON from STDIN with string of commands |
| 138 | _update() { | 207 | AccessJsonElement() { |
| 139 | echo "fetching from api.github.com..." | 208 | PYTHONIOENCODING=utf-8 \ |
| 140 | echo | 209 | python -c "from __future__ import print_function; import sys, json; raw = json.load(sys.stdin); $1" 2> /dev/null |
| 141 | local list_file=$index | 210 | return "$?" |
| 142 | local route="users/$user/gists" | ||
| 143 | local mark="" | ||
| 144 | [[ "$1" =~ ^(star|s)$ ]] && list_file=$starred && route="gists/starred" && mark="s" | ||
| 145 | |||
| 146 | curl -s -H "$auth_header" $github_api/$route \ | ||
| 147 | | _parse_response | nl -s' ' | sed -E "s/^ */$mark/" > $list_file \ | ||
| 148 | && _show_list $list_file \ | ||
| 149 | || echo Fail to update gists | ||
| 150 | |||
| 151 | if [[ $auto_sync != "false" ]]; then (_sync_repos $1 > /dev/null 2>&1 &); fi | ||
| 152 | } | 211 | } |
| 153 | 212 | ||
| 154 | # equal to: jq '.[] | "\(.html_url) \([.files[] | .raw_url]) \(.files | keys | length) \(.comments) \(.description)"' | 213 | # equal to: jq '.[] | "\(.html_url) \([.files[] | .raw_url]) \(.files | keys | length) \(.comments) \(.description)"' |
| @@ -157,6 +216,7 @@ _handle_gists() { | |||
| 157 | for gist in raw: | 216 | for gist in raw: |
| 158 | print(gist["html_url"], end=" ") | 217 | print(gist["html_url"], end=" ") |
| 159 | print([file["raw_url"] for file in gist["files"].values()], end=" ") | 218 | print([file["raw_url"] for file in gist["files"].values()], end=" ") |
| 219 | print(gist["public"], end=" ") | ||
| 160 | print(len(gist["files"]), end=" ") | 220 | print(len(gist["files"]), end=" ") |
| 161 | print(gist["comments"], end=" ") | 221 | print(gist["comments"], end=" ") |
| 162 | print(gist["description"]) | 222 | print(gist["description"]) |
| @@ -164,83 +224,110 @@ for gist in raw: | |||
| 164 | } | 224 | } |
| 165 | 225 | ||
| 166 | # TODO check if a user create a very first gist | 226 | # TODO check if a user create a very first gist |
| 227 | # parse response from gists require | ||
| 167 | _parse_response() { | 228 | _parse_response() { |
| 168 | AccessJsonElement "$(_handle_gists)" \ | 229 | AccessJsonElement "$(_handle_gists)" \ |
| 169 | | tac | sed 's/, /,/g'\ | 230 | | tac | sed 's/, /,/g' | nl -s' ' \ |
| 170 | | while read link file_url_array file_num comment_num description; do | 231 | | while read index link file_url_array public file_num comment_num description; do |
| 171 | local blob_code=$(echo $file_url_array | tr ',' '\n' | sed -E 's#.*raw/(.*)/.*#\1#' | sort | cut -c -7 | paste -sd '-') | 232 | local blob_code=$(echo $file_url_array | tr ',' '\n' | sed -E 's#.*raw/(.*)/.*#\1#' | sort | cut -c -7 | paste -sd '-') |
| 172 | echo $link $blob_code $file_num $comment_num $description | tr -d '"' | 233 | [[ $public == 'False' ]] && local mark=p |
| 173 | done | 234 | [[ -n $1 ]] && local index=$1 |
| 235 | echo $mark$index $link $blob_code $file_num $comment_num $description | tr -d '"' | ||
| 236 | done | ||
| 237 | } | ||
| 238 | |||
| 239 | # get latest list of gists from Github API | ||
| 240 | _update() { | ||
| 241 | echo "fetching $user's gists from $GITHUB_API..." | ||
| 242 | echo | ||
| 243 | local route="users/$user/gists" | ||
| 244 | local mark="" | ||
| 245 | local filter='/^[^s]/ d' | ||
| 246 | if [[ "$1" =~ ^(star|s)$ ]];then | ||
| 247 | route="gists/starred" | ||
| 248 | mark="s" | ||
| 249 | filter='/^[s]/ d' | ||
| 250 | fi | ||
| 251 | |||
| 252 | local response=$(httpGet $GITHUB_API/$route) | ||
| 253 | false && echo Failed to update gists && return 1 | ||
| 254 | sed -i "$filter" $INDEX | ||
| 255 | echo $response | _parse_response >> $INDEX | ||
| 256 | _show_list $mark | ||
| 257 | |||
| 258 | if [[ $auto_sync != "false" ]]; then (_sync_repos $1 > /dev/null 2>&1 &); fi | ||
| 174 | } | 259 | } |
| 175 | 260 | ||
| 261 | # update local git repos | ||
| 176 | _sync_repos() { | 262 | _sync_repos() { |
| 177 | local list_file=$index | 263 | # clone repos which are not in the local |
| 178 | [[ "$1" =~ ^(star|s)$ ]] && list_file=$starred && route="gists/starred" | 264 | comm -13 <(find $folder -maxdepth 1 -type d | sed '1d; s#.*/##' | sort) \ |
| 179 | 265 | <(cat $INDEX | cut -d' ' -f2 | sed 's#.*/##' | sort) \ | |
| 180 | # clone repos which are not in the local | 266 | | xargs -I{} --max-procs 8 git clone git@github.com:{}.git $folder/{} |
| 181 | comm -13 <(find $folder -maxdepth 1 -type d | sed '1d; s#.*/##' | sort) \ | 267 | |
| 182 | <(cat $list_file | cut -d' ' -f2 | sed 's#.*/##' | sort) \ | 268 | # pull if remote repo has different blob objects |
| 183 | | xargs -I{} --max-procs 8 git clone git@github.com:{}.git $folder/{} | 269 | cat $INDEX | cut -d' ' -f2,3 \ |
| 184 | 270 | | while read url blob_code_remote; do | |
| 185 | # pull if remote repo has different blob objects | 271 | local repo=$folder/$(echo $url | sed 's#.*/##') |
| 186 | cat $index | cut -d' ' -f2,3 \ | 272 | local blob_code_local=$(cd $repo && git ls-tree master | cut -d' ' -f3 | cut -c-7 | sort | paste -sd '-') |
| 187 | | while read url blob_code_remote; do | 273 | cd $repo \ |
| 188 | local repo=$folder/$(echo $url | sed 's#.*/##') | 274 | && [[ $blob_code_local != $blob_code_remote ]] \ |
| 189 | local blob_code_local=$(cd $repo && git ls-tree master | cut -d' ' -f3 | cut -c-7 | sort | paste -sd '-') | 275 | && [[ $(git rev-parse origin/master) == $(git rev-parse master) ]] \ |
| 190 | cd $repo \ | 276 | && git pull |
| 191 | && [[ $blob_code_local != $blob_code_remote ]] \ | 277 | done |
| 192 | &&[[ $(git rev-parse origin/master) == $(git rev-parse master) ]] \ | 278 | echo Everything is fine! |
| 193 | && git pull | ||
| 194 | done | ||
| 195 | echo Everything is fine! | ||
| 196 | } | 279 | } |
| 197 | 280 | ||
| 198 | # get gist id from index files | 281 | # get gist id from index files |
| 199 | _gist_id() { | 282 | _gist_id() { |
| 200 | GIST_ID=$( (grep -hs '' $index $starred || true) | sed -n "/^$1 / p" | cut -d' ' -f2 | sed -E 's#.*/##') | 283 | GIST_ID=$( (grep -hs '' $INDEX || true) | sed -n "/^$1 / p" | cut -d' ' -f2 | sed -E 's#.*/##') |
| 201 | if [[ -z "$GIST_ID" ]]; then | 284 | if [[ -z "$GIST_ID" ]]; then |
| 202 | echo -e "Not a valid index: \e[31m$1\e[0m" | 285 | echo -e "Not a valid index: \e[31m$1\e[0m" |
| 203 | echo Use the index number in the first column instead: | 286 | echo Use the index in the first column instead: |
| 204 | echo | 287 | echo |
| 205 | _show_list "$index" | 288 | _show_list |
| 206 | exit 1 | 289 | return 1 |
| 207 | fi | 290 | fi |
| 208 | } | 291 | } |
| 209 | 292 | ||
| 210 | _goto_gist() { | 293 | _goto_gist() { |
| 211 | _gist_id $1 | 294 | _gist_id $1 || return 1 |
| 212 | 295 | ||
| 213 | if [[ ! -d $folder/$GIST_ID ]]; then | 296 | if [[ ! -d $folder/$GIST_ID ]]; then |
| 214 | echo 'Cloning gist as repo...' | 297 | echo 'Cloning gist as repo...' |
| 215 | git clone git@github.com:$GIST_ID.git $folder/$GIST_ID \ | 298 | git clone git@github.com:$GIST_ID.git $folder/$GIST_ID |
| 216 | && echo 'Repo is cloned' \ | 299 | |
| 217 | || echo 'Failed to clone the gist' | 300 | if [[ $? -eq 0 ]]; then |
| 301 | echo 'Repo is cloned' > /dev/tty | ||
| 302 | else | ||
| 303 | echo 'Failed to clone the gist' > /dev/tty | ||
| 304 | return 1 | ||
| 218 | fi | 305 | fi |
| 306 | fi | ||
| 219 | 307 | ||
| 220 | echo This gist is at $folder/$GIST_ID | 308 | (cd $folder/$GIST_ID && eval "$action") |
| 221 | echo -e "You can run the following command to jump to this directory: \n" | 309 | echo $folder/$GIST_ID |
| 222 | echo -e " \e[32m. gist $1\e[0m\n" | ||
| 223 | cd $folder/$GIST_ID && ls && tig --all 2> /dev/null | ||
| 224 | } | 310 | } |
| 225 | 311 | ||
| 226 | _delete_gist() { | 312 | _delete_gist() { |
| 227 | for i in "$@"; do | 313 | for i in "$@"; do |
| 228 | _gist_id "$i" | 314 | _gist_id "$i" |
| 229 | curl -X DELETE -s -H "$auth_header" $github_api/gists/$GIST_ID \ | 315 | curl -X DELETE -s -H "$AUTH_HEADER" $GITHUB_API/gists/$GIST_ID \ |
| 230 | && echo "$i" deleted \ | 316 | && echo "$i" deleted \ |
| 231 | && sed -i -E "/^$i / d" $index | 317 | && sed -i -E "/^$i / d" $INDEX |
| 232 | done | 318 | done |
| 233 | } | 319 | } |
| 234 | 320 | ||
| 235 | # remove repos which are not in user gists anymore | 321 | # remove repos which are not in user gists anymore |
| 236 | _clean_repos() { | 322 | _clean_repos() { |
| 237 | comm -23 <(find $folder -maxdepth 1 -type d | sed '1d; s#.*/##' | sort) \ | 323 | comm -23 <(find $folder -maxdepth 1 -type d | sed '1d; s#.*/##' | sort) \ |
| 238 | <(cat $index $starred 2> /dev/null | cut -d' ' -f2 | sed 's#.*/##' | sort) \ | 324 | <(cat $INDEX 2> /dev/null | cut -d' ' -f2 | sed 's#.*/##' | sort) \ |
| 239 | | while read dir; do | 325 | | while read dir; do |
| 240 | mv $folder/$dir /tmp && echo move $folder/$dir to /tmp | 326 | mv $folder/$dir /tmp && echo move $folder/$dir to /tmp |
| 241 | done | 327 | done |
| 242 | } | 328 | } |
| 243 | 329 | ||
| 330 | # parse JSON from gist detail | ||
| 244 | _handle_gist() { | 331 | _handle_gist() { |
| 245 | echo ' | 332 | echo ' |
| 246 | print("site:", raw["html_url"]) | 333 | print("site:", raw["html_url"]) |
| @@ -269,104 +356,92 @@ for comment in raw: | |||
| 269 | 356 | ||
| 270 | # TODO format with simple text | 357 | # TODO format with simple text |
| 271 | _show_detail() { | 358 | _show_detail() { |
| 272 | _gist_id $1 | 359 | _gist_id $1 |
| 273 | curl -s $github_api/gists/$GIST_ID \ | 360 | httpGet $GITHUB_API/gists/$GIST_ID \ |
| 274 | | AccessJsonElement "$(_handle_gist)" | 361 | | AccessJsonElement "$(_handle_gist)" |
| 275 | 362 | ||
| 276 | curl -s $github_api/gists/$GIST_ID/comments \ | 363 | httpGet $GITHUB_API/gists/$GIST_ID/comments \ |
| 277 | | AccessJsonElement "$(_handle_comment)" | 364 | | AccessJsonElement "$(_handle_comment)" |
| 278 | } | 365 | } |
| 279 | 366 | ||
| 280 | # FIXME put file before parameters | 367 | # set filename/description/permission for a new gist |
| 281 | _set_gist() { | 368 | _set_gist() { |
| 282 | while [[ "$1" =~ ^- && "$1" != "--" ]]; do case $1 in | 369 | public=true |
| 283 | -d | --desc) | 370 | while [[ -n "$@" ]]; do case $1 in |
| 284 | description="$2" | 371 | -d | --desc) |
| 285 | shift; shift;; | 372 | description="$2" |
| 286 | -f | --file) | 373 | shift; shift;; |
| 287 | filename="$2" | 374 | -f | --file) |
| 288 | shift; shift;; | 375 | filename="$2" |
| 289 | esac | 376 | shift; shift;; |
| 290 | done | 377 | -p) |
| 291 | if [[ "$1" == '--' ]]; then shift; fi | 378 | public=false |
| 292 | files="$@" | 379 | shift;; |
| 293 | ls $files > /dev/null || exit 1 | 380 | *) |
| 381 | files="$1 $files" | ||
| 382 | shift;; | ||
| 383 | esac | ||
| 384 | done | ||
| 385 | ls $files > /dev/null || return 1 | ||
| 294 | } | 386 | } |
| 295 | 387 | ||
| 388 | # Let user type the content of gist before setting filename | ||
| 296 | _new_file() { | 389 | _new_file() { |
| 297 | [[ -t 0 ]] && echo "Type a gist. <Ctrl-C> to cancel, <Ctrl-D> when done" > /dev/tty | 390 | [[ -t 0 ]] && echo "Type a gist. <Ctrl-C> to cancel, <Ctrl-D> when done" > /dev/tty |
| 298 | tmp_file=$(mktemp) | 391 | local tmp_file=$(mktemp) |
| 299 | cat > $tmp_file | 392 | cat > $tmp_file |
| 300 | echo -e '\n' > /dev/tty | 393 | echo -e '\n' > /dev/tty |
| 301 | [[ -z "$1" ]] && read -p 'Type file name: ' filename < /dev/tty | 394 | [[ -z "$1" ]] && read -p 'Type file name: ' filename < /dev/tty |
| 302 | mv $tmp_file /tmp/$filename | 395 | mv $tmp_file /tmp/$filename |
| 303 | echo /tmp/$filename | 396 | echo /tmp/$filename |
| 304 | } | 397 | } |
| 305 | 398 | ||
| 306 | # create a new gist with files | 399 | # create a new gist with files |
| 307 | # TODO support secret gist | ||
| 308 | _create_gist() { | 400 | _create_gist() { |
| 309 | _set_gist "$@" | 401 | _set_gist "$@" || return 1 |
| 310 | [[ -z "$files" ]] && files=$(_new_file $filename) | 402 | [[ -z "$files" ]] && files=$(_new_file $filename) |
| 311 | [[ -z "$description" ]] && read -p 'Type description: ' description < /dev/tty | 403 | [[ -z "$description" ]] && read -p 'Type description: ' description < /dev/tty |
| 312 | 404 | ||
| 313 | for file in $files; do | 405 | local index=$(( $(sed '/^s/ d' $INDEX | wc -l) +1 )) |
| 314 | echo "\"$(basename $file)\": {\"content\": \"$(sed '$ !s/$/\\n/' $file)\"}," | 406 | echo 'Creating a new gist...' |
| 315 | done | tr -d '\n' | sed 's/^/{/; s/,$/}/' \ | 407 | for file in $files; do |
| 316 | | echo "{ \"public\": true, \"files\": $(cat -), \"description\": \"$description\"}" \ | 408 | echo "\"$(basename $file)\": {\"content\": \"$(sed '$ !s/$/\\n/' $file)\"}," |
| 317 | | curl -s -H "$auth_header" --data @- $github_api/gists \ | 409 | done | tr -d '\n' | sed 's/^/{/; s/,$/}/' \ |
| 318 | | sed '1 s/^/[/; $ s/$/]/' \ | 410 | | echo "{ \"public\": $public, \"files\": $(cat -), \"description\": \"$description\"}" \ |
| 319 | | _parse_response \ | 411 | | curl -s -H "$AUTH_HEADER" --data @- $GITHUB_API/gists \ |
| 320 | | sed -E "s/^/$(( $(wc -l $index | cut -d' ' -f1) + 1 )) /" >> $index \ | 412 | | tee jojo \ |
| 321 | && echo -e '\nGist created' \ | 413 | | sed '1 s/^/[/; $ s/$/]/' \ |
| 322 | || echo 'Fail to create gist' | 414 | | _parse_response $index >> $INDEX |
| 323 | 415 | ||
| 324 | _show_list $index | tail -1 | 416 | if [[ $? -eq 0 ]]; then |
| 417 | echo 'Gist is created' | ||
| 418 | _show_list | tail -1 | ||
| 419 | else | ||
| 420 | echo 'Failed to create gist' | ||
| 421 | fi | ||
| 325 | } | 422 | } |
| 326 | 423 | ||
| 327 | # update description of a gist | 424 | # update description of a gist |
| 328 | _edit_gist() { | 425 | _edit_gist() { |
| 329 | _gist_id $1 | 426 | _gist_id $1 |
| 330 | |||
| 331 | echo -n 'Type new description: ' | ||
| 332 | read DESC < /dev/tty | ||
| 333 | echo "{ \"description\": \"$DESC\" }" \ | ||
| 334 | | curl -X PATCH -H "$auth_header" --data @- $github_api/gists/$GIST_ID > /dev/null \ | ||
| 335 | && _update | ||
| 336 | } | ||
| 337 | |||
| 338 | _help_message() { | ||
| 339 | sed -E -n ' /^$/ q; 8,$ s/^#//p' $0 | ||
| 340 | } | ||
| 341 | 427 | ||
| 342 | _cases() { | 428 | echo -n 'Type new description: ' |
| 343 | if [[ $1 == 'token' ]]; then | 429 | read DESC < /dev/tty |
| 344 | [[ ${#2} -eq 40 ]] && echo $1=$2 \ | 430 | echo "{ \"description\": \"$DESC\" }" \ |
| 345 | || echo -e Invalid token format, it is not 40 chars '\n' > /dev/tty | 431 | | curl -X PATCH -H "$AUTH_HEADER" --data @- $GITHUB_API/gists/$GIST_ID > /dev/null \ |
| 346 | elif [[ $1 == 'auto_sync' ]]; then | 432 | && _update |
| 347 | [[ $2 == 'false' ]] && echo $1=$2 \ | ||
| 348 | || echo $1=true | ||
| 349 | elif [[ $1 == 'folder' ]]; then | ||
| 350 | [[ -n "$2" ]] && echo $1=$2 \ | ||
| 351 | || echo $1=~/gist | ||
| 352 | elif [[ $1 == 'user' ]]; then | ||
| 353 | echo $1=$2 | ||
| 354 | fi | ||
| 355 | } | 433 | } |
| 356 | 434 | ||
| 357 | _configure() { | 435 | usage() { |
| 358 | [[ -z "$@" ]] && (vim $config) && exit 0 | 436 | sed -E -n ' /^$/ q; 8,$ s/^#//p' $0 |
| 359 | target=$(_cases "$@") | ||
| 360 | |||
| 361 | [[ "$target" =~ [^=]$ ]] && sed -i "/^$1=/ d" $config && echo $target >> $config | ||
| 362 | cat $config | ||
| 363 | } | 437 | } |
| 364 | 438 | ||
| 439 | getConfiguredClient | ||
| 365 | case "$1" in | 440 | case "$1" in |
| 366 | "") | 441 | "") |
| 367 | _show_list $index ;; | 442 | _show_list ;; |
| 368 | star | s) | 443 | star | s) |
| 369 | _show_list $starred ;; | 444 | _show_list s ;; |
| 370 | update | u) | 445 | update | u) |
| 371 | _update "$2" ;; | 446 | _update "$2" ;; |
| 372 | new | n) | 447 | new | n) |
| @@ -388,7 +463,7 @@ case "$1" in | |||
| 388 | shift | 463 | shift |
| 389 | _configure "$@" ;; | 464 | _configure "$@" ;; |
| 390 | help | h) | 465 | help | h) |
| 391 | _help_message ;; | 466 | usage ;; |
| 392 | *) | 467 | *) |
| 393 | _goto_gist "$1" ;; | 468 | _goto_gist "$1" ;; |
| 394 | esac | 469 | esac |