|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868 |
- #!/usr/bin/env sh
- # # A GitHub API client library written in POSIX sh
- #
- # https://github.com/whiteinge/ok.sh
- # BSD licensed.
- #
- # ## Requirements
- #
- # * A POSIX environment (tested against Busybox v1.19.4)
- # * curl (tested against 7.32.0)
- #
- # ## Optional requirements
- #
- # * jq <http://stedolan.github.io/jq/> (tested against 1.3)
- # If jq is not installed commands will output raw JSON; if jq is installed
- # the output will be formatted and filtered for use with other shell tools.
- #
- # ## Setup
- #
- # Authentication credentials are read from a `$HOME/.netrc` file on UNIX
- # machines or a `_netrc` file in `%HOME%` for UNIX environments under Windows.
- # [Generate the token on GitHub](https://github.com/settings/tokens) under
- # "Account Settings -> Applications".
- # Restrict permissions on that file with `chmod 600 ~/.netrc`!
- #
- # machine api.github.com
- # login <username>
- # password <token>
- #
- # machine uploads.github.com
- # login <username>
- # password <token>
- #
- # Or set an environment `GITHUB_TOKEN=token`
- #
- # ## Configuration
- #
- # The following environment variables may be set to customize ${NAME}.
- #
- # * OK_SH_URL=${OK_SH_URL}
- # Base URL for GitHub or GitHub Enterprise.
- # * OK_SH_ACCEPT=${OK_SH_ACCEPT}
- # The 'Accept' header to send with each request.
- # * OK_SH_JQ_BIN=${OK_SH_JQ_BIN}
- # The name of the jq binary, if installed.
- # * OK_SH_VERBOSE=${OK_SH_VERBOSE}
- # The debug logging verbosity level. Same as the verbose flag.
- # * OK_SH_RATE_LIMIT=${OK_SH_RATE_LIMIT}
- # Output current GitHub rate limit information to stderr.
- # * OK_SH_DESTRUCTIVE=${OK_SH_DESTRUCTIVE}
- # Allow destructive operations without prompting for confirmation.
- # * OK_SH_MARKDOWN=${OK_SH_MARKDOWN}
- # Output some text in Markdown format.
-
- # shellcheck disable=SC2039,SC2220
-
- NAME=$(basename "$0")
- export NAME
- export VERSION='0.7.0'
-
- export OK_SH_URL=${OK_SH_URL:-'https://api.github.com'}
- export OK_SH_ACCEPT=${OK_SH_ACCEPT:-'application/vnd.github.v3+json'}
- export OK_SH_JQ_BIN="${OK_SH_JQ_BIN:-jq}"
- export OK_SH_VERBOSE="${OK_SH_VERBOSE:-0}"
- export OK_SH_RATE_LIMIT="${OK_SH_RATE_LIMIT:-0}"
- export OK_SH_DESTRUCTIVE="${OK_SH_DESTRUCTIVE:-0}"
- export OK_SH_MARKDOWN="${OK_SH_MARKDOWN:-0}"
-
- # Detect if jq is installed.
- command -v "$OK_SH_JQ_BIN" 1>/dev/null 2>/dev/null
- NO_JQ=$?
-
- # Customizable logging output.
- exec 4>/dev/null
- exec 5>/dev/null
- exec 6>/dev/null
- export LINFO=4 # Info-level log messages.
- export LDEBUG=5 # Debug-level log messages.
- export LSUMMARY=6 # Summary output.
-
- # Generate a carriage return so we can match on it.
- # Using a variable because these are tough to specify in a portable way.
- crlf=$(printf '\r\n')
-
- # ## Main
- # Generic functions not necessarily specific to working with GitHub.
-
- # ### Help
- # Functions for fetching and formatting help text.
-
- _cols() {
- sort | awk '
- { w[NR] = $0 }
- END {
- cols = 3
- per_col = sprintf("%.f", NR / cols + 0.5) # Round up if decimal.
-
- for (i = 1; i < per_col + 1; i += 1) {
- for (j = 0; j < cols; j += 1) {
- printf("%-24s", w[i + per_col * j])
- }
- printf("\n")
- }
- }
- '
- }
- _links() { awk '{ print "* [" $0 "](#" $0 ")" }'; }
- _funcsfmt() { if [ "$OK_SH_MARKDOWN" -eq 0 ]; then _cols; else _links; fi; }
-
- help() {
- # Output the help text for a command
- #
- # Usage:
- #
- # help commandname
- #
- # Positional arguments
- #
- local fname="$1"
- # Function name to search for; if omitted searches whole file.
-
- # Short-circuit if only producing help for a single function.
- if [ $# -gt 0 ]; then
- awk -v fname="^$fname\\\(\\\) \\\{$" '$0 ~ fname, /^}/ { print }' "$0" \
- | _helptext
- return
- fi
-
- _helptext < "$0"
- printf '\n'
- help __main
- printf '\n'
-
- printf '## Table of Contents\n'
- printf '\n### Utility and request/response commands\n\n'
- _all_funcs public=0 | _funcsfmt
- printf '\n### GitHub commands\n\n'
- _all_funcs private=0 | _funcsfmt
- printf '\n## Commands\n\n'
-
- for cmd in $(_all_funcs public=0); do
- printf '### %s\n\n' "$cmd"
- help "$cmd"
- printf '\n'
- done
-
- for cmd in $(_all_funcs private=0); do
- printf '### %s\n\n' "$cmd"
- help "$cmd"
- printf '\n'
- done
- }
-
- _all_funcs() {
- # List all functions found in the current file in the order they appear
- #
- # Keyword arguments
- #
- local public=1
- # `0` do not output public functions.
- local private=1
- # `0` do not output private functions.
-
- for arg in "$@"; do
- case $arg in
- (public=*) public="${arg#*=}";;
- (private=*) private="${arg#*=}";;
- esac
- done
-
- awk -v public="$public" -v private="$private" '
- $1 !~ /^__/ && /^[a-zA-Z0-9_]+\s*\(\)/ {
- sub(/\(\)$/, "", $1)
- if (!public && substr($1, 1, 1) != "_") next
- if (!private && substr($1, 1, 1) == "_") next
- print $1
- }
- ' "$0"
- }
-
- __main() {
- # ## Usage
- #
- # `${NAME} [<flags>] (command [<arg>, <name=value>...])`
- #
- # ${NAME} -h # Short, usage help text.
- # ${NAME} help # All help text. Warning: long!
- # ${NAME} help command # Command-specific help text.
- # ${NAME} command # Run a command with and without args.
- # ${NAME} command foo bar baz=Baz qux='Qux arg here'
- #
- # Flag | Description
- # ---- | -----------
- # -V | Show version.
- # -h | Show this screen.
- # -j | Output raw JSON; don't process with jq.
- # -q | Quiet; don't print to stdout.
- # -r | Print current GitHub API rate limit to stderr.
- # -v | Logging output; specify multiple times: info, debug, trace.
- # -x | Enable xtrace debug logging.
- # -y | Answer 'yes' to any prompts.
- #
- # Flags _must_ be the first argument to `${NAME}`, before `command`.
-
- local cmd
- local ret
- local opt
- local OPTARG
- local OPTIND
- local quiet=0
- local temp_dir
- temp_dir="${TMPDIR-/tmp}/${NAME}.${$}.$(awk 'BEGIN {srand(); printf "%d\n", rand() * 10^10}')"
- local summary_fifo="${temp_dir}/oksh_summary.fifo"
-
- # shellcheck disable=SC2154
- trap '
- excode=$?; trap - EXIT;
- exec 4>&-
- exec 5>&-
- exec 6>&-
- rm -rf '"$temp_dir"'
- exit $excode
- ' INT TERM EXIT
-
- while getopts Vhjqrvxy opt; do
- case $opt in
- V) printf 'Version: %s\n' $VERSION
- exit;;
- h) help __main
- printf '\nAvailable commands:\n\n'
- _all_funcs private=0 | _cols
- printf '\n'
- exit;;
- j) NO_JQ=1;;
- q) quiet=1;;
- r) OK_SH_RATE_LIMIT=1;;
- v) OK_SH_VERBOSE=$(( OK_SH_VERBOSE + 1 ));;
- x) set -x;;
- y) OK_SH_DESTRUCTIVE=1;;
- esac
- done
- shift $(( OPTIND - 1 ))
-
- if [ -z "$1" ] ; then
- printf 'No command given. Available commands:\n\n%s\n' \
- "$(_all_funcs private=0 | _cols)" 1>&2
- exit 1
- fi
-
- [ $OK_SH_VERBOSE -gt 0 ] && exec 4>&2
- [ $OK_SH_VERBOSE -gt 1 ] && exec 5>&2
- if [ $quiet -eq 1 ]; then
- exec 1>/dev/null 2>/dev/null
- fi
-
- if [ "$OK_SH_RATE_LIMIT" -eq 1 ] ; then
- mkdir -m 700 "$temp_dir" || {
- printf 'failed to create temp_dir\n' >&2; exit 1;
- }
- mkfifo "$summary_fifo"
- # Hold the fifo open so it will buffer input until emptied.
- exec 6<>"$summary_fifo"
- fi
-
- # Run the command.
- cmd="$1" && shift
- _log debug "Running command ${cmd}."
- "$cmd" "$@"
- ret=$?
- _log debug "Command ${cmd} exited with ${?}."
-
- # Output any summary messages.
- if [ "$OK_SH_RATE_LIMIT" -eq 1 ] ; then
- cat "$summary_fifo" 1>&2 &
- exec 6>&-
- fi
-
- exit $ret
- }
-
- _log() {
- # A lightweight logging system based on file descriptors
- #
- # Usage:
- #
- # _log debug 'Starting the combobulator!'
- #
- # Positional arguments
- #
- local level="${1:?Level is required.}"
- # The level for a given log message. (info or debug)
- local message="${2:?Message is required.}"
- # The log message.
-
- shift 2
-
- local lname
-
- case "$level" in
- info) lname='INFO'; level=$LINFO ;;
- debug) lname='DEBUG'; level=$LDEBUG ;;
- *) printf 'Invalid logging level: %s\n' "$level" ;;
- esac
-
- printf '%s %s: %s\n' "$NAME" "$lname" "$message" 1>&$level
- }
-
- _helptext() {
- # Extract contiguous lines of comments and function params as help text
- #
- # Indentation will be ignored. She-bangs will be ignored. Local variable
- # declarations and their default values can also be pulled in as
- # documentation. Exits upon encountering the first blank line.
- #
- # Exported environment variables can be used for string interpolation in
- # the extracted commented text.
- #
- # Input
- #
- # * (stdin)
- # The text of a function body to parse.
-
- awk '
- NR != 1 && /^\s*#/ {
- line=$0
- while(match(line, "[$]{[^}]*}")) {
- var=substr(line, RSTART+2, RLENGTH -3)
- gsub("[$]{"var"}", ENVIRON[var], line)
- }
- gsub(/^\s*#\s?/, "", line)
- print line
- }
- /^\s*local/ {
- sub(/^\s*local /, "")
- sub(/\$\{/, "$", $0)
- sub(/:.*}/, "", $0)
- print "* `" $0 "`\n"
- }
- !NF { exit }'
- }
-
- # ### Request-response
- # Functions for making HTTP requests and processing HTTP responses.
-
- _format_json() {
- # Create formatted JSON from name=value pairs
- #
- # Usage:
- # ```
- # ok.sh _format_json foo=Foo bar=123 baz=true qux=Qux=Qux quux='Multi-line
- # string' quuz=\'5.20170918\' \
- # corge="$(ok.sh _format_json grault=Grault)" \
- # garply="$(ok.sh _format_json -a waldo true 3)"
- # ```
- #
- # Return:
- # ```
- # {
- # "garply": [
- # "waldo",
- # true,
- # 3
- # ],
- # "foo": "Foo",
- # "corge": {
- # "grault": "Grault"
- # },
- # "baz": true,
- # "qux": "Qux=Qux",
- # "quux": "Multi-line\nstring",
- # "quuz": "5.20170918",
- # "bar": 123
- # }
- # ```
- #
- # Tries not to quote numbers, booleans, nulls, or nested structures.
- # Note, nested structures must be quoted since the output contains spaces.
- #
- # The `-a` option will create an array instead of an object. This option
- # must come directly after the _format_json command and before any
- # operands. E.g., `_format_json -a foo bar baz`.
- #
- # If jq is installed it will also validate the output.
- #
- # Positional arguments
- #
- # * $1 - $9
- #
- # Each positional arg must be in the format of `name=value` which will be
- # added to a single, flat JSON object.
-
- local opt
- local OPTIND
- local is_array=0
- local use_env=1
- while getopts a opt; do
- case $opt in
- a) is_array=1; unset use_env;;
- esac
- done
- shift $(( OPTIND - 1 ))
-
- _log debug "Formatting ${#} parameters as JSON."
-
- env -i -- ${use_env+"$@"} awk -v is_array="$is_array" '
- function isnum(x){ return (x == x + 0) }
- function isnull(x){ return (x == "null" ) }
- function isbool(x){ if (x == "true" || x == "false") return 1 }
- function isnested(x) {
- if (substr(x, 0, 1) == "[" || substr(x, 0, 1) == "{") {
- return 1
- }
- }
- function castOrQuote(val) {
- if (!isbool(val) && !isnum(val) && !isnull(val) && !isnested(val)) {
- sub(/^('\''|")/, "", val) # Remove surrounding quotes
- sub(/('\''|")$/, "", val)
-
- gsub(/"/, "\\\"", val) # Escape double-quotes.
- gsub(/\n/, "\\n", val) # Replace newlines with \n text.
- val = "\"" val "\""
- return val
- } else {
- return val
- }
- }
-
- BEGIN {
- printf("%s", is_array ? "[" : "{")
-
- for (i = 1; i < length(ARGV); i += 1) {
- arg = ARGV[i]
-
- if (is_array == 1) {
- val = castOrQuote(arg)
- printf("%s%s", sep, val)
- } else {
- name = substr(arg, 0, index(arg, "=") - 1)
- val = castOrQuote(ENVIRON[name])
- printf("%s\"%s\": %s", sep, name, val)
- }
-
- sep = ", "
- ARGV[i] = ""
- }
- printf("%s", is_array ? "]" : "}")
- }' "$@"
- }
-
- _format_urlencode() {
- # URL encode and join name=value pairs
- #
- # Usage:
- # ```
- # _format_urlencode foo='Foo Foo' bar='<Bar>&/Bar/'
- # ```
- #
- # Return:
- # ```
- # foo=Foo%20Foo&bar=%3CBar%3E%26%2FBar%2F
- # ```
- #
- # Ignores pairs if the value begins with an underscore.
-
- _log debug "Formatting ${#} parameters as urlencoded"
-
- env -i -- "$@" awk '
- function escape(str, c, i, len, res) {
- len = length(str)
- res = ""
- for (i = 1; i <= len; i += 1) {
- c = substr(str, i, 1);
- if (c ~ /[0-9A-Za-z]/)
- res = res c
- else
- res = res "%" sprintf("%02X", ord[c])
- }
- return res
- }
-
- BEGIN {
- for (i = 0; i <= 255; i += 1) ord[sprintf("%c", i)] = i;
-
- for (j = 1; j < length(ARGV); j += 1) {
- arg = ARGV[j]
- name = substr(arg, 0, index(arg, "=") - 1)
- if (substr(name, 1, 1) == "_") continue
- val = ENVIRON[name]
-
- printf("%s%s=%s", sep, name, escape(val))
- sep = "&"
- ARGV[j] = ""
- }
- }' "$@"
- }
-
- _filter_json() {
- # Filter JSON input using jq; outputs raw JSON if jq is not installed
- #
- # Usage:
- #
- # printf '[{"foo": "One"}, {"foo": "Two"}]' | \
- # ok.sh _filter_json '.[] | "\(.foo)"'
- #
- # * (stdin)
- # JSON input.
- local _filter="$1"
- # A string of jq filters to apply to the input stream.
-
- _log debug 'Filtering JSON.'
-
- if [ $NO_JQ -ne 0 ] ; then
- _log debug 'Bypassing jq processing.'
- cat
- return
- fi
-
- "${OK_SH_JQ_BIN}" -c -r "${_filter}" || printf 'jq parse error; invalid JSON.\n' 1>&2
- }
-
- _get_mime_type() {
- # Guess the mime type for a file based on the file extension
- #
- # Usage:
- #
- # local mime_type
- # _get_mime_type "foo.tar"; printf 'mime is: %s' "$mime_type"
- #
- # Sets the global variable `mime_type` with the result. (If this function
- # is called from within a function that has declared a local variable of
- # that name it will update the local copy and not set a global.)
- #
- # Positional arguments
- #
- local filename="${1:?Filename is required.}"
- # The full name of the file, with extension.
-
- # Taken from Apache's mime.types file (public domain).
- case "$filename" in
- *.bz2) mime_type=application/x-bzip2 ;;
- *.exe) mime_type=application/x-msdownload ;;
- *.tar.gz | *.gz | *.tgz) mime_type=application/x-gzip ;;
- *.jpg | *.jpeg | *.jpe | *.jfif) mime_type=image/jpeg ;;
- *.json) mime_type=application/json ;;
- *.pdf) mime_type=application/pdf ;;
- *.png) mime_type=image/png ;;
- *.rpm) mime_type=application/x-rpm ;;
- *.svg | *.svgz) mime_type=image/svg+xml ;;
- *.tar) mime_type=application/x-tar ;;
- *.txt) mime_type=text/plain ;;
- *.yaml) mime_type=application/x-yaml ;;
- *.apk) mime_type=application/vnd.android.package-archive ;;
- *.zip) mime_type=application/zip ;;
- *.jar) mime_type=application/java-archive ;;
- *.war) mime_type=application/zip ;;
- esac
-
- _log debug "Guessed mime type of '${mime_type}' for '${filename}'."
- }
-
- _get_confirm() {
- # Prompt the user for confirmation
- #
- # Usage:
- #
- # local confirm; _get_confirm
- # [ "$confirm" -eq 1 ] && printf 'Good to go!\n'
- #
- # If global confirmation is set via `$OK_SH_DESTRUCTIVE` then the user
- # is not prompted. Assigns the user's confirmation to the `confirm` global
- # variable. (If this function is called within a function that has a local
- # variable of that name, the local variable will be updated instead.)
- #
- # Positional arguments
- #
- local message="${1:-Are you sure?}"
- # The message to prompt the user with.
-
- local answer
-
- if [ "$OK_SH_DESTRUCTIVE" -eq 1 ] ; then
- confirm=$OK_SH_DESTRUCTIVE
- return
- fi
-
- printf '%s ' "$message"
- read -r answer
-
- ! printf '%s\n' "$answer" | grep -Eq "$(locale yesexpr)"
- confirm=$?
- }
-
- _opts_filter() {
- # Extract common jq filter keyword options and assign to vars
- #
- # Usage:
- #
- # local filter
- # _opts_filter "$@"
-
- for arg in "$@"; do
- case $arg in
- (_filter=*) _filter="${arg#*=}";;
- esac
- done
- }
-
- _opts_pagination() {
- # Extract common pagination keyword options and assign to vars
- #
- # Usage:
- #
- # local _follow_next
- # _opts_pagination "$@"
-
- for arg in "$@"; do
- case $arg in
- (_follow_next=*) _follow_next="${arg#*=}";;
- (_follow_next_limit=*) _follow_next_limit="${arg#*=}";;
- esac
- done
- }
-
- _opts_qs() {
- # Extract common query string keyword options and assign to vars
- #
- # Usage:
- #
- # local qs
- # _opts_qs "$@"
- # _get "/some/path${qs}"
-
- local querystring
- querystring=$(_format_urlencode "$@")
- qs="${querystring:+?$querystring}"
- }
-
- _request() {
- # A wrapper around making HTTP requests with curl
- #
- # Usage:
- # ```
- # # Get JSON for all issues:
- # _request /repos/saltstack/salt/issues
- #
- # # Send a POST request; parse response using jq:
- # printf '{"title": "%s", "body": "%s"}\n' "Stuff" "Things" \
- # | _request /some/path | jq -r '.[url]'
- #
- # # Send a PUT request; parse response using jq:
- # printf '{"title": "%s", "body": "%s"}\n' "Stuff" "Things" \
- # | _request /repos/:owner/:repo/issues method=PUT | jq -r '.[url]'
- #
- # # Send a conditional-GET request:
- # _request /users etag=edd3a0d38d8c329d3ccc6575f17a76bb
- # ```
- #
- # Input
- #
- # * (stdin)
- # Data that will be used as the request body.
- #
- # Positional arguments
- #
- local path="${1:?Path is required.}"
- # The URL path for the HTTP request.
- # Must be an absolute path that starts with a `/` or a full URL that
- # starts with http(s). Absolute paths will be append to the value in
- # `$OK_SH_URL`.
- #
- # Keyword arguments
- #
- local method='GET'
- # The method to use for the HTTP request.
- local content_type='application/json'
- # The value of the Content-Type header to use for the request.
- local etag
- # An optional Etag to send as the If-None-Match header.
-
- shift 1
-
- local cmd
- local arg
- local has_stdin
- local trace_curl
-
- case $path in
- (http*) : ;;
- *) path="${OK_SH_URL}${path}" ;;
- esac
-
- for arg in "$@"; do
- case $arg in
- (method=*) method="${arg#*=}";;
- (content_type=*) content_type="${arg#*=}";;
- (etag=*) etag="${arg#*=}";;
- esac
- done
-
- case "$method" in
- POST | PUT | PATCH) has_stdin=1;;
- esac
-
- [ $OK_SH_VERBOSE -eq 3 ] && trace_curl=1
-
- [ "$OK_SH_VERBOSE" -eq 1 ] && set -x
- # shellcheck disable=SC2086
- curl -nsSig \
- -H "Accept: ${OK_SH_ACCEPT}" \
- -H "Content-Type: ${content_type}" \
- ${GITHUB_TOKEN:+-H "Authorization: token ${GITHUB_TOKEN}"} \
- ${etag:+-H "If-None-Match: \"${etag}\""} \
- ${has_stdin:+--data-binary @-} \
- ${trace_curl:+--trace-ascii /dev/stderr} \
- -X "${method}" \
- "${path}"
- set +x
- }
-
- _response() {
- # Process an HTTP response from curl
- #
- # Output only headers of interest followed by the response body. Additional
- # processing is performed on select headers to make them easier to parse
- # using shell tools.
- #
- # Usage:
- # ```
- # # Send a request; output the response and only select response headers:
- # _request /some/path | _response status_code ETag Link_next
- #
- # # Make request using curl; output response with select response headers;
- # # assign response headers to local variables:
- # curl -isS example.com/some/path | _response status_code status_text | {
- # local status_code status_text
- # read -r status_code
- # read -r status_text
- # }
- # ```
- #
- # Header reformatting
- #
- # * HTTP Status
- #
- # The HTTP line is split into separate `http_version`, `status_code`, and
- # `status_text` variables.
- #
- # * ETag
- #
- # The surrounding quotes are removed.
- #
- # * Link
- #
- # Each URL in the Link header is expanded with the URL type appended to
- # the name. E.g., `Link_first`, `Link_last`, `Link_next`.
- #
- # Positional arguments
- #
- # * $1 - $9
- #
- # Each positional arg is the name of an HTTP header. Each header value is
- # output in the same order as each argument; each on a single line. A
- # blank line is output for headers that cannot be found.
-
- local hdr
- local val
- local http_version
- local status_code=100
- local status_text
- local headers output
-
- _log debug 'Processing response.'
-
- while [ "${status_code}" = "100" ]; do
- read -r http_version status_code status_text
- status_text="${status_text%${crlf}}"
- http_version="${http_version#HTTP/}"
-
- _log debug "Response status is: ${status_code} ${status_text}"
-
- if [ "${status_code}" = "100" ]; then
- _log debug "Ignoring response '${status_code} ${status_text}', skipping to real response."
- while IFS=": " read -r hdr val; do
- # Headers stop at the first blank line.
- [ "$hdr" = "$crlf" ] && break
- val="${val%${crlf}}"
- _log debug "Unexpected additional header: ${hdr}: ${val}"
- done
-
- fi
- done
-
- headers="HTTP_VERSION: ${http_version}
- STATUS_CODE: ${status_code}
- STATUS_TEXT: ${status_text}
- "
- while IFS=": " read -r hdr val; do
- # Headers stop at the first blank line.
- [ "$hdr" = "$crlf" ] && break
- val="${val%${crlf}}"
-
- # Headers are case insensitive
- hdr="$(printf '%s' "$hdr" | awk '{print toupper($0)}')"
-
- # Process each header; reformat some to work better with sh tools.
- case "$hdr" in
- # Update the GitHub rate limit trackers.
- X-RATELIMIT-REMAINING)
- printf 'GitHub remaining requests: %s\n' "$val" 1>&$LSUMMARY ;;
- X-RATELIMIT-RESET)
- awk -v gh_reset="$val" 'BEGIN {
- srand(); curtime = srand()
- print "GitHub seconds to reset: " gh_reset - curtime
- }' 1>&$LSUMMARY ;;
-
- # Remove quotes from the etag header.
- ETAG) val="${val#\"}"; val="${val%\"}" ;;
-
- # Split the URLs in the Link header into separate pseudo-headers.
- LINK) headers="${headers}$(printf '%s' "$val" | awk '
- BEGIN { RS=", "; FS="; "; OFS=": " }
- {
- sub(/^rel="/, "", $2); sub(/"$/, "", $2)
- sub(/^ *</, "", $1); sub(/>$/, "", $1)
- print "LINK_" toupper($2), $1
- }')
- " # need trailing newline
- ;;
- esac
-
- headers="${headers}${hdr}: ${val}
- " # need trailing newline
-
- done
-
- # Output requested headers in deterministic order.
- for arg in "$@"; do
- _log debug "Outputting requested header '${arg}'."
- arg="$(printf '%s' "$arg" | awk '{print toupper($0)}')"
- output=$(printf '%s' "$headers" | while IFS=": " read -r hdr val; do
- [ "$hdr" = "$arg" ] && printf '%s' "$val"
- done)
- printf '%s\n' "$output"
- done
-
- # Output the response body.
- cat
- }
-
- _get() {
- # A wrapper around _request() for common GET patterns
- #
- # Will automatically follow 'next' pagination URLs in the Link header.
- #
- # Usage:
- #
- # _get /some/path
- # _get /some/path _follow_next=0
- # _get /some/path _follow_next_limit=200 | jq -c .
- #
- # Positional arguments
- #
- local path="${1:?Path is required.}"
- # The HTTP path or URL to pass to _request().
- #
- # Keyword arguments
- #
- # * _follow_next=1
- #
- # Automatically look for a 'Links' header and follow any 'next' URLs.
- #
- # * _follow_next_limit=50
- #
- # Maximum number of 'next' URLs to follow before stopping.
-
- shift 1
- local status_code
- local status_text
- local next_url
-
- # If the variable is unset or empty set it to a default value. Functions
- # that call this function can pass these parameters in one of two ways:
- # explicitly as a keyword arg or implicitly by setting variables of the same
- # names within the local scope.
- # shellcheck disable=SC2086
- if [ -z ${_follow_next+x} ] || [ -z "${_follow_next}" ]; then
- local _follow_next=1
- fi
- # shellcheck disable=SC2086
- if [ -z ${_follow_next_limit+x} ] || [ -z "${_follow_next_limit}" ]; then
- local _follow_next_limit=50
- fi
-
- _opts_pagination "$@"
-
- _request "$path" | _response status_code status_text Link_next | {
- read -r status_code
- read -r status_text
- read -r next_url
-
- case "$status_code" in
- 20*) : ;;
- 4*) printf 'Client Error: %s %s\n' \
- "$status_code" "$status_text" 1>&2; exit 1 ;;
- 5*) printf 'Server Error: %s %s\n' \
- "$status_code" "$status_text" 1>&2; exit 1 ;;
- esac
-
- # Output response body.
- cat
-
- [ "$_follow_next" -eq 1 ] || return
-
- _log info "Remaining next link follows: ${_follow_next_limit}"
- if [ -n "$next_url" ] && [ $_follow_next_limit -gt 0 ] ; then
- _follow_next_limit=$(( _follow_next_limit - 1 ))
-
- _get "$next_url" "_follow_next_limit=${_follow_next_limit}"
- fi
- }
- }
-
- _post() {
- # A wrapper around _request() for common POST / PUT patterns
- #
- # Usage:
- #
- # _format_json foo=Foo bar=Bar | _post /some/path
- # _format_json foo=Foo bar=Bar | _post /some/path method='PUT'
- # _post /some/path filename=somearchive.tar
- # _post /some/path filename=somearchive.tar mime_type=application/x-tar
- # _post /some/path filename=somearchive.tar \
- # mime_type=$(file -b --mime-type somearchive.tar)
- #
- # Input
- #
- # * (stdin)
- # Optional. See the `filename` argument also.
- # Data that will be used as the request body.
- #
- # Positional arguments
- #
- local path="${1:?Path is required.}"
- # The HTTP path or URL to pass to _request().
- #
- # Keyword arguments
- #
- local method='POST'
- # The method to use for the HTTP request.
- local filename
- # Optional. See the `stdin` option above also.
- # Takes precedence over any data passed as stdin and loads a file off the
- # file system to serve as the request body.
- local mime_type
- # The value of the Content-Type header to use for the request.
- # If the `filename` argument is given this value will be guessed from the
- # file extension. If the `filename` argument is not given (i.e., using
- # stdin) this value defaults to `application/json`. Specifying this
- # argument overrides all other defaults or guesses.
-
- shift 1
-
- for arg in "$@"; do
- case $arg in
- (method=*) method="${arg#*=}";;
- (filename=*) filename="${arg#*=}";;
- (mime_type=*) mime_type="${arg#*=}";;
- esac
- done
-
- # Make either the file or stdin available as fd7.
- if [ -n "$filename" ] ; then
- if [ -r "$filename" ] ; then
- _log debug "Using '${filename}' as POST data."
- [ -n "$mime_type" ] || _get_mime_type "$filename"
- : ${mime_type:?The MIME type could not be guessed.}
- exec 7<"$filename"
- else
- printf 'File could not be found or read.\n' 1>&2
- exit 1
- fi
- else
- _log debug "Using stdin as POST data."
- mime_type='application/json'
- exec 7<&0
- fi
-
- _request "$path" method="$method" content_type="$mime_type" 0<&7 \
- | _response status_code status_text \
- | {
- read -r status_code
- read -r status_text
-
- case "$status_code" in
- 20*) : ;;
- 4*) printf 'Client Error: %s %s\n' \
- "$status_code" "$status_text" 1>&2; exit 1 ;;
- 5*) printf 'Server Error: %s %s\n' \
- "$status_code" "$status_text" 1>&2; exit 1 ;;
- esac
-
- # Output response body.
- cat
- }
- }
-
- _delete() {
- # A wrapper around _request() for common DELETE patterns
- #
- # Usage:
- #
- # _delete '/some/url'
- #
- # Return: 0 for success; 1 for failure.
- #
- # Positional arguments
- #
- local url="${1:?URL is required.}"
- # The URL to send the DELETE request to.
-
- local status_code
-
- _request "${url}" method='DELETE' | _response status_code | {
- read -r status_code
- [ "$status_code" = "204" ]
- exit $?
- }
- }
-
- # ## GitHub
- # Friendly functions for common GitHub tasks.
-
- # ### Authorization
- # Perform authentication and authorization.
-
- show_scopes() {
- # Show the permission scopes for the currently authenticated user
- #
- # Usage:
- #
- # show_scopes
-
- local oauth_scopes
-
- _request '/' | _response X-OAuth-Scopes | {
- read -r oauth_scopes
-
- printf '%s\n' "$oauth_scopes"
-
- # Dump any remaining response body.
- cat >/dev/null
- }
- }
-
- # ### Repository
- # Create, update, delete, list repositories.
-
- org_repos() {
- # List organization repositories
- #
- # Usage:
- #
- # org_repos myorg
- # org_repos myorg type=private per_page=10
- # org_repos myorg _filter='.[] | "\(.name)\t\(.owner.login)"'
- #
- # Positional arguments
- #
- local org="${1:?Org name required.}"
- # Organization GitHub login or id for which to list repos.
- #
- # Keyword arguments
- #
- local _follow_next
- # Automatically look for a 'Links' header and follow any 'next' URLs.
- local _follow_next_limit
- # Maximum number of 'next' URLs to follow before stopping.
- local _filter='.[] | "\(.name)\t\(.ssh_url)"'
- # A jq filter to apply to the return data.
- #
- # Querystring arguments may also be passed as keyword arguments:
- #
- # * `per_page`
- # * `type`
-
- shift 1
- local qs
-
- _opts_pagination "$@"
- _opts_filter "$@"
- _opts_qs "$@"
-
- _get "/orgs/${org}/repos${qs}" | _filter_json "${_filter}"
- }
-
- org_teams() {
- # List teams
- #
- # Usage:
- #
- # org_teams org
- #
- # Positional arguments
- #
- local org="${1:?Org name required.}"
- # Organization GitHub login or id.
- #
- # Keyword arguments
- #
- local _filter='.[] | "\(.name)\t\(.id)\t\(.permission)"'
- # A jq filter to apply to the return data.
-
- shift 1
-
- _opts_filter "$@"
-
- _get "/orgs/${org}/teams" \
- | _filter_json "${_filter}"
- }
-
- org_members() {
- # List organization members
- #
- # Usage:
- #
- # org_members org
- #
- # Positional arguments
- #
- local org="${1:?Org name required.}"
- # Organization GitHub login or id.
- #
- # Keyword arguments
- #
- local _filter='.[] | "\(.login)\t\(.id)"'
- # A jq filter to apply to the return data.
-
- local qs
-
- shift 1
-
- _opts_filter "$@"
- _opts_qs "$@"
-
- _get "/orgs/${org}/members${qs}" | _filter_json "${_filter}"
- }
-
- org_collaborators() {
- # List organization outside collaborators
- #
- # Usage:
- #
- # org_collaborators org
- #
- # Positional arguments
- #
- local org="${1:?Org name required.}"
- # Organization GitHub login or id.
- #
- # Keyword arguments
- #
- local _filter='.[] | "\(.login)\t\(.id)"'
- # A jq filter to apply to the return data.
-
- local qs
-
- shift 1
-
- _opts_filter "$@"
- _opts_qs "$@"
-
- _get "/orgs/${org}/outside_collaborators${qs}" | _filter_json "${_filter}"
- }
-
- org_auditlog() {
- # Interact with the Github Audit Log
- #
- # Usage:
- #
- # org_auditlog org
- #
- # Positional arguments
- #
- local org="${1:?Org name required.}"
- # Organization GitHub login or id.
- #
- # Keyword arguments
- #
- local _filter='.[] | "\(.actor)\t\(.action)"'
- # A jq filter to apply to the return data.
-
- local qs
-
- shift 1
-
- _opts_filter "$@"
- _opts_qs "$@"
-
- _get "/orgs/${org}/audit-log${qs}" | _filter_json "${_filter}"
- }
-
- team_members() {
- # List team members
- #
- # Usage:
- #
- # team_members team_id
- #
- # Positional arguments
- #
- local team_id="${1:?Team id required.}"
- # Team id.
- #
- # Keyword arguments
- #
- local _filter='.[] | "\(.login)\t\(.id)"'
- # A jq filter to apply to the return data.
-
- shift 1
-
- _opts_filter "$@"
-
- _get "/teams/${team_id}/members" \
- | _filter_json "${_filter}"
-
- }
-
- list_repos() {
- # List user repositories
- #
- # Usage:
- #
- # list_repos
- # list_repos user
- #
- # Positional arguments
- #
- local user="$1"
- # Optional GitHub user login or id for which to list repos.
- #
- # Keyword arguments
- #
- local _filter='.[] | "\(.name)\t\(.html_url)"'
- # A jq filter to apply to the return data.
- #
- # Querystring arguments may also be passed as keyword arguments:
- #
- # * `direction`
- # * `per_page`
- # * `sort`
- # * `type`
-
- # User is optional; is this a keyword arg?
- case "$user" in *=*) user='' ;; esac
- if [ -n "$user" ]; then shift 1; fi
-
- local qs
-
- _opts_filter "$@"
- _opts_qs "$@"
-
- if [ -n "$user" ] ; then
- url="/users/${user}/repos"
- else
- url='/user/repos'
- fi
-
- _get "${url}${qs}" | _filter_json "${_filter}"
- }
-
- list_branches() {
- # List branches of a specified repository.
- # ( https://developer.github.com/v3/repos/#list_branches )
- #
- # Usage:
- #
- # list_branches user repo
- #
- # Positional arguments
- #
- # GitHub user login or id for which to list branches
- # Name of the repo for which to list branches
- #
- local user="${1:?User name required.}"
- local repo="${2:?Repo name required.}"
- shift 2
- #
- # Keyword arguments
- #
- local _filter='.[] | "\(.name)"'
- # A jq filter to apply to the return data.
- #
- # Querystring arguments may also be passed as keyword arguments:
- #
- # * `direction`
- # * `per_page`
- # * `sort`
- # * `type`
-
- local qs
-
- _opts_filter "$@"
- _opts_qs "$@"
-
- url="/repos/${user}/${repo}/branches"
-
- _get "${url}${qs}" | _filter_json "${_filter}"
- }
-
- list_commits() {
- # List commits of a specified repository.
- # ( https://developer.github.com/v3/repos/commits/#list-commits-on-a-repository )
- #
- # Usage:
- #
- # list_commits user repo
- #
- # Positional arguments
- #
- # GitHub user login or id for which to list branches
- # Name of the repo for which to list branches
- #
-
- local user="${1:?User name required.}"
- local repo="${2:?Repo name required.}"
- shift 2
-
- # A jq filter to apply to the return data.
- #
-
- local _filter='.[] | "\(.sha) \(.author.login) \(.commit.author.email) \(.committer.login) \(.commit.committer.email)"'
-
- # Querystring arguments may also be passed as keyword arguments:
- #
- # * `sha`
- # * `path`
- # * `author`
- # * `since` Only commits after this date will be returned. This is a timestamp in ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ.
- # * `until`
-
- local qs
-
- _opts_filter "$@"
- _opts_qs "$@"
-
- url="/repos/${user}/${repo}/commits"
-
- _get "${url}${qs}" | _filter_json "${_filter}"
- }
-
- list_contributors() {
- # List contributors to the specified repository, sorted by the number of commits per contributor in descending order.
- # ( https://developer.github.com/v3/repos/#list-contributors )
- #
- # Usage:
- #
- # list_contributors user repo
- #
- # Positional arguments
- #
- local user="${1:?User name required.}"
- # GitHub user login or id for which to list contributors
- local repo="${2:?Repo name required.}"
- # Name of the repo for which to list contributors
- #
- # Keyword arguments
- #
- local _filter='.[] | "\(.login)\t\(.type)\tType:\(.type)\tContributions:\(.contributions)"'
- # A jq filter to apply to the return data.
- #
- # Querystring arguments may also be passed as keyword arguments:
- #
- # * `direction`
- # * `per_page`
- # * `sort`
- # * `type`
-
- shift 2
-
- local qs
-
- _opts_filter "$@"
- _opts_qs "$@"
-
- url="/repos/${user}/${repo}/contributors"
-
- _get "${url}${qs}" | _filter_json "${_filter}"
- }
-
- list_collaborators() {
- # List collaborators to the specified repository, sorted by the number of commits per collaborator in descending order.
- # ( https://developer.github.com/v3/repos/#list-collaborators )
- #
- # Usage:
- #
- # list_collaborators someuser/somerepo
- #
- # Positional arguments
- # GitHub user login or id for which to list collaborators
- # Name of the repo for which to list collaborators
- #
- local repo="${1:?Repo name required.}"
- #
- # Keyword arguments
- #
- local _filter='.[] | "\(.login)\t\(.type)\tType:\(.type)\tPermissions:\(.permissions)"'
- # A jq filter to apply to the return data.
- #
- # Querystring arguments may also be passed as keyword arguments:
- #
- # * `direction`
- # * `per_page`
- # * `sort`
- # * `type`
-
- shift 1
-
- local qs
-
- _opts_filter "$@"
- _opts_qs "$@"
-
- url="/repos/${repo}/collaborators"
-
- _get "${url}${qs}" | _filter_json "${_filter}"
- }
-
- list_hooks() {
- # List webhooks from the specified repository.
- # ( https://developer.github.com/v3/repos/hooks/#list-hooks )
- #
- # Usage:
- #
- # list_hooks owner/repo
- #
- # Positional arguments
- #
- local repo="${1:?Repo name required.}"
- # Name of the repo for which to list contributors
- # Owner is mandatory, like 'owner/repo'
- #
- local _filter='.[] | "\(.name)\t\(.config.url)"'
- # A jq filter to apply to the return data.
- #
-
- shift 1
-
- _opts_filter "$@"
-
- url="/repos/${repo}/hooks"
-
- _get "${url}" | _filter_json "${_filter}"
- }
-
- list_gists() {
- # List gists for the current authenticated user or a specific user
- #
- # https://developer.github.com/v3/gists/#list-a-users-gists
- #
- # Usage:
- #
- # list_gists
- # list_gists <username>
- #
- # Positional arguments
- #
- local username="$1"
- # An optional user to filter listing
- #
- # Keyword arguments
- #
- local _follow_next
- # Automatically look for a 'Links' header and follow any 'next' URLs.
- local _follow_next_limit
- # Maximum number of 'next' URLs to follow before stopping.
- local _filter='.[] | "\(.id)\t\(.description)"'
- # A jq filter to apply to the return data.
-
- local url
- case "$username" in
- ('') url='/gists';;
- (*=*) url='/gists';;
- (*) url="/users/${username}/gists"; shift 1;;
- esac
-
- _opts_pagination "$@"
- _opts_filter "$@"
-
- _get "${url}" | _filter_json "${_filter}"
- }
-
- public_gists() {
- # List public gists
- #
- # https://developer.github.com/v3/gists/#list-all-public-gists
- #
- # Usage:
- #
- # public_gists
- #
- # Keyword arguments
- #
- local _follow_next
- # Automatically look for a 'Links' header and follow any 'next' URLs.
- local _follow_next_limit
- # Maximum number of 'next' URLs to follow before stopping.
- local _filter='.[] | "\(.id)\t\(.description)"'
- # A jq filter to apply to the return data.
-
- _opts_pagination "$@"
- _opts_filter "$@"
-
- _get '/gists/public' | _filter_json "${_filter}"
- }
-
- gist() {
- # Get a single gist
- #
- # https://developer.github.com/v3/gists/#get-a-single-gist
- #
- # Usage:
- #
- # get_gist
- #
- # Positional arguments
- #
- local gist_id="${1:?Gist ID required.}"
- # ID of gist to fetch.
- #
- # Keyword arguments
- #
- local _filter='.files | keys | join(", ")'
- # A jq filter to apply to the return data.
-
- shift 1
-
- _opts_filter "$@"
-
- _get "/gists/${gist_id}" | _filter_json "${_filter}"
- }
-
- add_collaborator() {
- # Add a collaborator to a repository
- #
- # Usage:
- #
- # add_collaborator someuser/somerepo collaboratoruser permission
- #
- # Positional arguments
- #
- local repo="${1:?Repo name required.}"
- # A GitHub repository.
- local collaborator="${2:?Collaborator name required.}"
- # A new collaborator.
- local permission="${3:?Permission required. One of: push pull admin}"
- # The permission level for this collaborator. One of `push`, `pull`,
- # `admin`. The `pull` and `admin` permissions are valid for organization
- # repos only.
- case $permission in
- push|pull|admin) :;;
- *) printf 'Permission invalid: %s\nMust be one of: push pull admin\n' \
- "$permission" 1>&2; exit 1 ;;
- esac
- #
- # Keyword arguments
- #
- local _filter='"\(.name)\t\(.color)"'
- # A jq filter to apply to the return data.
-
- _opts_filter "$@"
-
- _format_json permission="$permission" \
- | _post "/repos/${repo}/collaborators/${collaborator}" method='PUT' \
- | _filter_json "$_filter"
- }
-
- delete_collaborator() {
- # Delete a collaborator to a repository
- #
- # Usage:
- #
- # delete_collaborator someuser/somerepo collaboratoruser permission
- #
- # Positional arguments
- #
- local repo="${1:?Repo name required.}"
- # A GitHub repository.
- local collaborator="${2:?Collaborator name required.}"
- # A new collaborator.
-
- shift 2
-
- local confirm
-
- _get_confirm 'This will permanently delete the collaborator from this repo. Continue?'
- [ "$confirm" -eq 1 ] || exit 0
-
- _delete "/repos/${repo}/collaborators/${collaborator}"
- exit $?
- }
-
- create_repo() {
- # Create a repository for a user or organization
- #
- # Usage:
- #
- # create_repo foo
- # create_repo bar description='Stuff and things' homepage='example.com'
- # create_repo baz organization=myorg
- #
- # Positional arguments
- #
- local name="${1:?Repo name required.}"
- # Name of the new repo
- #
- # Keyword arguments
- #
- local _filter='"\(.name)\t\(.html_url)"'
- # A jq filter to apply to the return data.
- #
- # POST data may also be passed as keyword arguments:
- #
- # * `auto_init`,
- # * `description`
- # * `gitignore_template`
- # * `has_downloads`
- # * `has_issues`
- # * `has_wiki`,
- # * `homepage`
- # * `organization`
- # * `private`
- # * `team_id`
-
- shift 1
-
- _opts_filter "$@"
-
- local url
- local organization
-
- for arg in "$@"; do
- case $arg in
- (organization=*) organization="${arg#*=}";;
- esac
- done
-
- if [ -n "$organization" ] ; then
- url="/orgs/${organization}/repos"
- else
- url='/user/repos'
- fi
-
- export OK_SH_ACCEPT="application/vnd.github.nebula-preview+json"
- _format_json "name=${name}" "$@" | _post "$url" | _filter_json "${_filter}"
- }
-
- delete_repo() {
- # Delete a repository for a user or organization
- #
- # Usage:
- #
- # delete_repo owner repo
- #
- # The currently authenticated user must have the `delete_repo` scope. View
- # current scopes with the `show_scopes()` function.
- #
- # Positional arguments
- #
- local owner="${1:?Owner name required.}"
- # Name of the new repo
- local repo="${2:?Repo name required.}"
- # Name of the new repo
-
- shift 2
-
- local confirm
-
- _get_confirm 'This will permanently delete a repository! Continue?'
- [ "$confirm" -eq 1 ] || exit 0
-
- _delete "/repos/${owner}/${repo}"
- exit $?
- }
-
- fork_repo() {
- # Fork a repository from a user or organization to own account or organization
- #
- # Usage:
- #
- # fork_repo owner repo
- #
- # Positional arguments
- #
- local owner="${1:?Owner name required.}"
- # Name of existing user or organization
- local repo="${2:?Repo name required.}"
- # Name of the existing repo
- #
- #
- # Keyword arguments
- #
- local _filter='"\(.clone_url)\t\(.ssh_url)"'
- # A jq filter to apply to the return data.
- #
- # POST data may also be passed as keyword arguments:
- #
- # * `organization` (The organization to clone into; default: your personal account)
-
- shift 2
-
- _opts_filter "$@"
-
- _format_json "$@" | _post "/repos/${owner}/${repo}/forks" \
- | _filter_json "${_filter}"
- exit $? # might take a bit time...
- }
-
- # ### Releases
- # Create, update, delete, list releases.
-
- list_releases() {
- # List releases for a repository
- #
- # https://developer.github.com/v3/repos/releases/#list-releases-for-a-repository
- #
- # Usage:
- #
- # list_releases org repo '\(.assets[0].name)\t\(.name.id)'
- #
- # Positional arguments
- #
- local owner="${1:?Owner name required.}"
- # A GitHub user or organization.
- local repo="${2:?Repo name required.}"
- # A GitHub repository.
- #
- # Keyword arguments
- #
- local _filter='.[] | "\(.name)\t\(.tag_name)\t\(.id)\t\(.html_url)"'
- # A jq filter to apply to the return data.
-
- shift 2
-
- _opts_filter "$@"
-
- _get "/repos/${owner}/${repo}/releases" \
- | _filter_json "${_filter}"
- }
-
- release() {
- # Get a release
- #
- # https://developer.github.com/v3/repos/releases/#get-a-single-release
- #
- # Usage:
- #
- # release user repo 1087855
- #
- # Positional arguments
- #
- local owner="${1:?Owner name required.}"
- # A GitHub user or organization.
- local repo="${2:?Repo name required.}"
- # A GitHub repository.
- local release_id="${3:?Release ID required.}"
- # The unique ID of the release; see list_releases.
- #
- # Keyword arguments
- #
- local _filter='"\(.author.login)\t\(.published_at)"'
- # A jq filter to apply to the return data.
-
- shift 3
-
- _opts_filter "$@"
-
- _get "/repos/${owner}/${repo}/releases/${release_id}" \
- | _filter_json "${_filter}"
- }
-
- create_release() {
- # Create a release
- #
- # https://developer.github.com/v3/repos/releases/#create-a-release
- #
- # Usage:
- #
- # create_release org repo v1.2.3
- # create_release user repo v3.2.1 draft=true
- #
- # Positional arguments
- #
- local owner="${1:?Owner name required.}"
- # A GitHub user or organization.
- local repo="${2:?Repo name required.}"
- # A GitHub repository.
- local tag_name="${3:?Tag name required.}"
- # Git tag from which to create release.
- #
- # Keyword arguments
- #
- local _filter='"\(.name)\t\(.id)\t\(.html_url)"'
- # A jq filter to apply to the return data.
- #
- # POST data may also be passed as keyword arguments:
- #
- # * `body`
- # * `draft`
- # * `name`
- # * `prerelease`
- # * `target_commitish`
-
- shift 3
-
- _opts_filter "$@"
-
- _format_json "tag_name=${tag_name}" "$@" \
- | _post "/repos/${owner}/${repo}/releases" \
- | _filter_json "${_filter}"
- }
-
- edit_release() {
- # Edit a release
- #
- # https://developer.github.com/v3/repos/releases/#edit-a-release
- #
- # Usage:
- #
- # edit_release org repo 1087855 name='Foo Bar 1.4.6'
- # edit_release user repo 1087855 draft=false
- #
- # Positional arguments
- #
- local owner="${1:?Owner name required.}"
- # A GitHub user or organization.
- local repo="${2:?Repo name required.}"
- # A GitHub repository.
- local release_id="${3:?Release ID required.}"
- # The unique ID of the release; see list_releases.
- #
- # Keyword arguments
- #
- local _filter='"\(.tag_name)\t\(.name)\t\(.html_url)"'
- # A jq filter to apply to the return data.
- #
- # POST data may also be passed as keyword arguments:
- #
- # * `tag_name`
- # * `body`
- # * `draft`
- # * `name`
- # * `prerelease`
- # * `target_commitish`
-
- shift 3
-
- _opts_filter "$@"
-
- _format_json "$@" \
- | _post "/repos/${owner}/${repo}/releases/${release_id}" method="PATCH" \
- | _filter_json "${_filter}"
- }
-
- delete_release() {
- # Delete a release
- #
- # https://developer.github.com/v3/repos/releases/#delete-a-release
- #
- # Usage:
- #
- # delete_release org repo 1087855
- #
- # Return: 0 for success; 1 for failure.
- #
- # Positional arguments
- #
- local owner="${1:?Owner name required.}"
- # A GitHub user or organization.
- local repo="${2:?Repo name required.}"
- # A GitHub repository.
- local release_id="${3:?Release ID required.}"
- # The unique ID of the release; see list_releases.
-
- shift 3
-
- local confirm
-
- _get_confirm 'This will permanently delete a release. Continue?'
- [ "$confirm" -eq 1 ] || exit 0
-
- _delete "/repos/${owner}/${repo}/releases/${release_id}"
- exit $?
- }
-
- release_assets() {
- # List release assets
- #
- # https://developer.github.com/v3/repos/releases/#list-assets-for-a-release
- #
- # Usage:
- #
- # release_assets user repo 1087855
- #
- # Example of downloading release assets:
- #
- # ok.sh release_assets <user> <repo> <release_id> \
- # _filter='.[] | .browser_download_url' \
- # | xargs -L1 curl -L -O
- #
- # Example of the multi-step process for grabbing the release ID for
- # a specific version, then grabbing the release asset IDs, and then
- # downloading all the release assets (whew!):
- #
- # username='myuser'
- # repo='myrepo'
- # release_tag='v1.2.3'
- # ok.sh list_releases "$myuser" "$myrepo" \
- # | awk -F'\t' -v tag="$release_tag" '$2 == tag { print $3 }' \
- # | xargs -I{} ./ok.sh release_assets "$myuser" "$myrepo" {} \
- # _filter='.[] | .browser_download_url' \
- # | xargs -L1 curl -n -L -O
- #
- # Positional arguments
- #
- local owner="${1:?Owner name required.}"
- # A GitHub user or organization.
- local repo="${2:?Repo name required.}"
- # A GitHub repository.
- local release_id="${3:?Release ID required.}"
- # The unique ID of the release; see list_releases.
- #
- # Keyword arguments
- #
- local _filter='.[] | "\(.id)\t\(.name)\t\(.updated_at)"'
- # A jq filter to apply to the return data.
-
- shift 3
-
- _opts_filter "$@"
-
- _get "/repos/${owner}/${repo}/releases/${release_id}/assets" \
- | _filter_json "$_filter"
- }
-
- upload_asset() {
- # Upload a release asset
- #
- # https://developer.github.com/v3/repos/releases/#upload-a-release-asset
- #
- # Usage:
- #
- # upload_asset https://<upload-url> /path/to/file.zip
- #
- # The upload URL can be gotten from `release()`. There are multiple steps
- # required to upload a file: get the release ID, get the upload URL, parse
- # the upload URL, then finally upload the file. For example:
- #
- # ```sh
- # USER="someuser"
- # REPO="somerepo"
- # TAG="1.2.3"
- # FILE_NAME="foo.zip"
- # FILE_PATH="/path/to/foo.zip"
- #
- # # Create a release then upload a file:
- # ok.sh create_release "$USER" "$REPO" "$TAG" _filter='.upload_url' \
- # | sed 's/{.*$/?name='"$FILE_NAME"'/' \
- # | xargs -I@ ok.sh upload_asset @ "$FILE_PATH"
- #
- # # Find a release by tag then upload a file:
- # ok.sh list_releases "$USER" "$REPO" \
- # | awk -v "tag=$TAG" -F'\t' '$2 == tag { print $3 }' \
- # | xargs -I@ ok.sh release "$USER" "$REPO" @ _filter='.upload_url' \
- # | sed 's/{.*$/?name='"$FILE_NAME"'/' \
- # | xargs -I@ ok.sh upload_asset @ "$FILE_PATH"
- # ```
- #
- # Positional arguments
- #
- local upload_url="${1:?upload_url is required.}"
- # The _parsed_ upload_url returned from GitHub.
- #
- local file_path="${2:?file_path is required.}"
- # A path to the file that should be uploaded.
- #
- # Keyword arguments
- #
- local _filter='"\(.state)\t\(.browser_download_url)"'
- # A jq filter to apply to the return data.
- #
- # Also any other keyword arguments accepted by `_post()`.
-
- shift 2
-
- _opts_filter "$@"
-
- _post "$upload_url" filename="$file_path" "$@" \
- | _filter_json "$_filter"
- }
-
- delete_asset() {
- # Delete a release asset
- #
- # https://docs.github.com/en/rest/reference/releases#delete-a-release-asset
- #
- # Usage:
- #
- # delete_asset user repo 51955388
- #
- # Example of deleting release assets:
- #
- # ok.sh release_assets <user> <repo> <release_id> \
- # _filter='.[] | .id' \
- # | xargs -L1 ./ok.sh delete_asset "$myuser" "$myrepo"
- #
- # Example of the multi-step process for grabbing the release ID for
- # a specific version, then grabbing the release asset IDs, and then
- # deleting all the release assets (whew!):
- #
- # username='myuser'
- # repo='myrepo'
- # release_tag='v1.2.3'
- # ok.sh list_releases "$myuser" "$myrepo" \
- # | awk -F'\t' -v tag="$release_tag" '$2 == tag { print $3 }' \
- # | xargs -I{} ./ok.sh release_assets "$myuser" "$myrepo" {} \
- # _filter='.[] | .id' \
- # | xargs -L1 ./ok.sh -y delete_asset "$myuser" "$myrepo"
- #
- # Positional arguments
- #
- local owner="${1:?Owner name required.}"
- # A GitHub user or organization.
- local repo="${2:?Repo name required.}"
- # A GitHub repository.
- local asset_id="${3:?Release asset ID required.}"
- # The unique ID of the release asset; see release_assets.
-
- shift 3
-
- local confirm
-
- _get_confirm 'This will permanently delete a release asset. Continue?'
- [ "$confirm" -eq 1 ] || exit 0
-
- _delete "/repos/${owner}/${repo}/releases/assets/${asset_id}"
- exit $?
- }
-
- # ### Issues
- # Create, update, edit, delete, list issues and milestones.
-
- list_milestones() {
- # List milestones for a repository
- #
- # Usage:
- #
- # list_milestones someuser/somerepo
- # list_milestones someuser/somerepo state=closed
- #
- # Positional arguments
- #
- local repository="${1:?Repo name required.}"
- # A GitHub repository.
- #
- # Keyword arguments
- #
- local _follow_next
- # Automatically look for a 'Links' header and follow any 'next' URLs.
- local _follow_next_limit
- # Maximum number of 'next' URLs to follow before stopping.
- local _filter='.[] | "\(.number)\t\(.open_issues)/\(.closed_issues)\t\(.title)"'
- # A jq filter to apply to the return data.
- #
- # GitHub querystring arguments may also be passed as keyword arguments:
- #
- # * `direction`
- # * `per_page`
- # * `sort`
- # * `state`
-
- shift 1
- local qs
-
- _opts_pagination "$@"
- _opts_filter "$@"
- _opts_qs "$@"
-
- _get "/repos/${repository}/milestones${qs}" | _filter_json "$_filter"
- }
-
- create_milestone() {
- # Create a milestone for a repository
- #
- # Usage:
- #
- # create_milestone someuser/somerepo MyMilestone
- #
- # create_milestone someuser/somerepo MyMilestone \
- # due_on=2015-06-16T16:54:00Z \
- # description='Long description here
- # that spans multiple lines.'
- #
- # Positional arguments
- #
- local repo="${1:?Repo name required.}"
- # A GitHub repository.
- local title="${2:?Milestone name required.}"
- # A unique title.
- #
- # Keyword arguments
- #
- local _filter='"\(.number)\t\(.html_url)"'
- # A jq filter to apply to the return data.
- #
- # Milestone options may also be passed as keyword arguments:
- #
- # * `description`
- # * `due_on`
- # * `state`
-
- shift 2
-
- _opts_filter "$@"
-
- _format_json title="$title" "$@" \
- | _post "/repos/${repo}/milestones" \
- | _filter_json "$_filter"
- }
-
- list_issue_comments() {
- # List comments of a specified issue.
- # ( https://developer.github.com/v3/issues/comments/#list-issue-comments )
- #
- # Usage:
- #
- # list_issue_comments someuser/somerepo number
- #
- # Positional arguments
- #
- # GitHub owner login or id for which to list branches
- # Name of the repo for which to list branches
- # Issue number
- #
- local repo="${1:?Repo name required.}"
- local number="${2:?Issue number is required.}"
- shift 2
-
- local _follow_next
- # Automatically look for a 'Links' header and follow any 'next' URLs.
- local _follow_next_limit
- # Maximum number of 'next' URLs to follow before stopping.
- local _filter='.[] | "\(.body)"'
- # A jq filter to apply to the return data.
-
- _opts_pagination "$@"
-
- # A jq filter to apply to the return data.
- #
- # Querystring arguments may also be passed as keyword arguments:
- #
- # * `direction`
- # * `sort`
- # * `since`
- local qs
- _opts_filter "$@"
- _opts_qs "$@"
- url="/repos/${repo}/issues/${number}/comments"
- _get "${url}${qs}" | _filter_json "${_filter}"
- }
-
- add_comment() {
- # Add a comment to an issue
- #
- # Usage:
- #
- # add_comment someuser/somerepo 123 'This is a comment'
- #
- # Positional arguments
- #
- local repository="${1:?Repo name required}"
- # A GitHub repository
- local number="${2:?Issue number required}"
- # Issue Number
- local comment="${3:?Comment required}"
- # Comment to be added
- #
- # Keyword arguments
- #
- local _filter='"\(.id)\t\(.html_url)"'
- # A jq filter to apply to the return data.
-
- shift 3
- _opts_filter "$@"
-
- _format_json body="$comment" \
- | _post "/repos/${repository}/issues/${number}/comments" \
- | _filter_json "${_filter}"
- }
-
- list_commit_comments() {
- # List comments of a specified commit.
- # ( https://developer.github.com/v3/repos/comments/#list-commit-comments )
- #
- # Usage:
- #
- # list_commit_comments someuser/somerepo sha
- #
- # Positional arguments
- #
- # GitHub owner login or id for which to list branches
- # Name of the repo for which to list branches
- # Commit SHA
- #
- local repo="${1:?Repo name required.}"
- local sha="${2:?Commit SHA is required.}"
- shift 2
-
- local _follow_next
- # Automatically look for a 'Links' header and follow any 'next' URLs.
- local _follow_next_limit
- # Maximum number of 'next' URLs to follow before stopping.
- local _filter='.[] | "\(.body)"'
- # A jq filter to apply to the return data.
-
- _opts_pagination "$@"
-
- # A jq filter to apply to the return data.
- #
- # Querystring arguments may also be passed as keyword arguments:
- #
- # * `direction`
- # * `sort`
- # * `since`
- local qs
- _opts_filter "$@"
- _opts_qs "$@"
- url="/repos/${repo}/commits/${sha}/comments"
- _get "${url}${qs}" | _filter_json "${_filter}"
- }
-
-
- add_commit_comment() {
- # Add a comment to a commit
- #
- # Usage:
- #
- # add_commit_comment someuser/somerepo 123 'This is a comment'
- #
- # Positional arguments
- #
- local repository="${1:?Repo name required}"
- # A GitHub repository
- local hash="${2:?Commit hash required}"
- # Commit hash
- local comment="${3:?Comment required}"
- # Comment to be added
- #
- # Keyword arguments
- #
- local _filter='"\(.id)\t\(.html_url)"'
- # A jq filter to apply to the return data.
-
- shift 3
- _opts_filter "$@"
-
- _format_json body="$comment" \
- | _post "/repos/${repository}/commits/${hash}/comments" \
- | _filter_json "${_filter}"
- }
-
- close_issue() {
- # Close an issue
- #
- # Usage:
- #
- # close_issue someuser/somerepo 123
- #
- # Positional arguments
- #
- local repository="${1:?Repo name required}"
- # A GitHub repository
- local number="${2:?Issue number required}"
- # Issue Number
- #
- # Keyword arguments
- #
- local _filter='"\(.id)\t\(.state)\t\(.html_url)"'
- # A jq filter to apply to the return data.
- #
- # POST data may also be passed as keyword arguments:
- #
- # * `assignee`
- # * `labels`
- # * `milestone`
-
- shift 2
- _opts_filter "$@"
-
- _format_json state="closed" "$@" \
- | _post "/repos/${repository}/issues/${number}" method='PATCH' \
- | _filter_json "${_filter}"
- }
-
- list_issues() {
- # List issues for the authenticated user or repository
- #
- # Usage:
- #
- # list_issues
- # list_issues someuser/somerepo
- # list_issues <any of the above> state=closed labels=foo,bar
- #
- # Positional arguments
- #
- # user or user/repository
- #
- # Keyword arguments
- #
- local _follow_next
- # Automatically look for a 'Links' header and follow any 'next' URLs.
- local _follow_next_limit
- # Maximum number of 'next' URLs to follow before stopping.
- local _filter='.[] | "\(.number)\t\(.title)"'
- # A jq filter to apply to the return data.
- #
- # GitHub querystring arguments may also be passed as keyword arguments:
- #
- # * `assignee`
- # * `creator`
- # * `direction`
- # * `labels`
- # * `mentioned`
- # * `milestone`
- # * `per_page`
- # * `since`
- # * `sort`
- # * `state`
-
- local url
- local qs
-
- case $1 in
- ('') url='/user/issues' ;;
- (*=*) url='/user/issues' ;;
- (*/*) url="/repos/${1}/issues"; shift 1 ;;
- esac
-
- _opts_pagination "$@"
- _opts_filter "$@"
- _opts_qs "$@"
-
- _get "${url}${qs}" | _filter_json "$_filter"
- }
-
- user_issues() {
- # List all issues across owned and member repositories for the authenticated user
- #
- # Usage:
- #
- # user_issues
- # user_issues since=2015-60-11T00:09:00Z
- #
- # Keyword arguments
- #
- local _follow_next
- # Automatically look for a 'Links' header and follow any 'next' URLs.
- local _follow_next_limit
- # Maximum number of 'next' URLs to follow before stopping.
- local _filter='.[] | "\(.repository.full_name)\t\(.number)\t\(.title)"'
- # A jq filter to apply to the return data.
- #
- # GitHub querystring arguments may also be passed as keyword arguments:
- #
- # * `direction`
- # * `filter`
- # * `labels`
- # * `per_page`
- # * `since`
- # * `sort`
- # * `state`
-
- local qs
-
- _opts_pagination "$@"
- _opts_filter "$@"
- _opts_qs "$@"
-
- _get "/issues${qs}" | _filter_json "$_filter"
- }
-
- create_issue() {
- # Create an issue
- #
- # Usage:
- #
- # create_issue owner repo 'Issue title' body='Add multiline body
- # content here' labels="$(./ok.sh _format_json -a foo bar)"
- #
- # Positional arguments
- #
- local owner="${1:?Owner name required.}"
- # A GitHub repository.
- local repo="${2:?Repo name required.}"
- # A GitHub repository.
- local title="${3:?Issue title required.}"
- # A GitHub repository.
- #
- # Keyword arguments
- #
- local _filter='"\(.id)\t\(.number)\t\(.html_url)"'
- # A jq filter to apply to the return data.
- #
- # Additional issue fields may be passed as keyword arguments:
- #
- # * `body` (string)
- # * `assignee` (string)
- # * `milestone` (integer)
- # * `labels` (array of strings)
- # * `assignees` (array of strings)
-
- shift 3
-
- _opts_filter "$@"
-
- _format_json title="$title" "$@" \
- | _post "/repos/${owner}/${repo}/issues" \
- | _filter_json "$_filter"
- }
-
- org_issues() {
- # List all issues for a given organization for the authenticated user
- #
- # Usage:
- #
- # org_issues someorg
- #
- # Positional arguments
- #
- local org="${1:?Organization name required.}"
- # Organization GitHub login or id.
- #
- # Keyword arguments
- #
- local _follow_next
- # Automatically look for a 'Links' header and follow any 'next' URLs.
- local _follow_next_limit
- # Maximum number of 'next' URLs to follow before stopping.
- local _filter='.[] | "\(.number)\t\(.title)"'
- # A jq filter to apply to the return data.
- #
- # GitHub querystring arguments may also be passed as keyword arguments:
- #
- # * `direction`
- # * `filter`
- # * `labels`
- # * `per_page`
- # * `since`
- # * `sort`
- # * `state`
-
- shift 1
- local qs
-
- _opts_pagination "$@"
- _opts_filter "$@"
- _opts_qs "$@"
-
- _get "/orgs/${org}/issues${qs}" | _filter_json "$_filter"
- }
-
- list_starred() {
- # List starred repositories
- #
- # Usage:
- #
- # list_starred
- # list_starred user
- #
- # Positional arguments
- #
- local user="$1"
- # Optional GitHub user login or id for which to list the starred repos.
- #
- # Keyword arguments
- #
- local _filter='.[] | "\(.name)\t\(.html_url)"'
- # A jq filter to apply to the return data.
- #
- # Querystring arguments may also be passed as keyword arguments:
- #
- # * `direction`
- # * `per_page`
- # * `sort`
- # * `type`
-
- # User is optional; is this a keyword arg?
- case "$user" in *=*) user='' ;; esac
- if [ -n "$user" ]; then shift 1; fi
-
- local qs
-
- _opts_filter "$@"
- _opts_qs "$@"
-
- if [ -n "$user" ] ; then
- url="/users/${user}/starred"
- else
- url='/user/starred'
- fi
-
- _get "${url}${qs}" | _filter_json "${_filter}"
- }
-
- list_my_orgs() {
- # List your organizations
- #
- # Usage:
- #
- # list_my_orgs
- #
- # Keyword arguments
- #
- local _follow_next
- # Automatically look for a 'Links' header and follow any 'next' URLs.
- local _follow_next_limit
- # Maximum number of 'next' URLs to follow before stopping.
- local _filter='.[] | "\(.login)\t\(.id)"'
- # A jq filter to apply to the return data.
-
- local qs
-
- _opts_pagination "$@"
- _opts_filter "$@"
- _opts_qs "$@"
-
- _get "/user/orgs" | _filter_json "$_filter"
- }
-
- list_orgs() {
- # List all organizations
- #
- # Usage:
- #
- # list_orgs
- #
- # Keyword arguments
- #
- local _follow_next
- # Automatically look for a 'Links' header and follow any 'next' URLs.
- local _follow_next_limit
- # Maximum number of 'next' URLs to follow before stopping.
- local _filter='.[] | "\(.login)\t\(.id)"'
- # A jq filter to apply to the return data.
-
- local qs
-
- _opts_pagination "$@"
- _opts_filter "$@"
- _opts_qs "$@"
-
- _get "/organizations" | _filter_json "$_filter"
- }
-
- list_users() {
- # List all users
- #
- # Usage:
- #
- # list_users
- #
- # Keyword arguments
- #
- local _follow_next
- # Automatically look for a 'Links' header and follow any 'next' URLs.
- local _follow_next_limit
- # Maximum number of 'next' URLs to follow before stopping.
- local _filter='.[] | "\(.login)\t\(.id)"'
- # A jq filter to apply to the return data.
-
- local qs
-
- _opts_pagination "$@"
- _opts_filter "$@"
- _opts_qs "$@"
- _get "/users" | _filter_json "$_filter"
- }
-
- labels() {
- # List available labels for a repository
- #
- # Usage:
- #
- # labels someuser/somerepo
- #
- # Positional arguments
- #
- local repo="$1"
- # A GitHub repository.
- #
- # Keyword arguments
- #
- local _follow_next
- # Automatically look for a 'Links' header and follow any 'next' URLs.
- local _follow_next_limit
- # Maximum number of 'next' URLs to follow before stopping.
- local _filter='.[] | "\(.name)\t\(.color)"'
- # A jq filter to apply to the return data.
-
- _opts_pagination "$@"
- _opts_filter "$@"
-
- _get "/repos/${repo}/labels" | _filter_json "$_filter"
- }
-
- add_label() {
- # Add a label to a repository
- #
- # Usage:
- #
- # add_label someuser/somerepo LabelName color
- #
- # Positional arguments
- #
- local repo="${1:?Repo name required.}"
- # A GitHub repository.
- local label="${2:?Label name required.}"
- # A new label.
- local color="${3:?Hex color required.}"
- # A color, in hex, without the leading `#`.
- #
- # Keyword arguments
- #
- local _filter='"\(.name)\t\(.color)"'
- # A jq filter to apply to the return data.
-
- _opts_filter "$@"
-
- _format_json name="$label" color="$color" \
- | _post "/repos/${repo}/labels" \
- | _filter_json "$_filter"
- }
-
- update_label() {
- # Update a label
- #
- # Usage:
- #
- # update_label someuser/somerepo OldLabelName \
- # label=NewLabel color=newcolor
- #
- # Positional arguments
- #
- local repo="${1:?Repo name required.}"
- # A GitHub repository.
- local label="${2:?Label name required.}"
- # The name of the label which will be updated
- #
- # Keyword arguments
- #
- local _filter='"\(.name)\t\(.color)"'
- # A jq filter to apply to the return data.
- #
- # Label options may also be passed as keyword arguments, these will update
- # the existing values:
- #
- # * `color`
- # * `name`
-
- shift 2
-
- _opts_filter "$@"
-
- _format_json "$@" \
- | _post "/repos/${repo}/labels/${label}" method='PATCH' \
- | _filter_json "$_filter"
- }
-
- add_team_repo() {
- # Add a team repository
- #
- # Usage:
- #
- # add_team_repo team_id organization repository_name permission
- #
- # Positional arguments
- #
- local team_id="${1:?Team id required.}"
- # Team id to add repository to
- local organization="${2:?Organization required.}"
- # Organization to add repository to
- local repository_name="${3:?Repository name required.}"
- # Repository name to add
- local permission="${4:?Permission required.}"
- # Permission to grant: pull, push, admin
- #
- local url="/teams/${team_id}/repos/${organization}/${repository_name}"
-
- export OK_SH_ACCEPT="application/vnd.github.ironman-preview+json"
-
- _format_json "name=${name}" "permission=${permission}" | _post "$url" method='PUT' | _filter_json "${_filter}"
- }
-
- list_pulls() {
- # Lists the pull requests for a repository
- #
- # Usage:
- #
- # list_pulls user repo
- #
- # Positional arguments
- #
- local owner="${1:?Owner required.}"
- # A GitHub owner.
- local repo="${2:?Repo name required.}"
- # A GitHub repository.
- #
- # Keyword arguments
- #
- local _follow_next
- # Automatically look for a 'Links' header and follow any 'next' URLs.
- local _follow_next_limit
- # Maximum number of 'next' URLs to follow before stopping.
- local _filter='.[] | "\(.number)\t\(.user.login)\t\(.head.repo.clone_url)\t\(.head.ref)"'
- # A jq filter to apply to the return data.
-
- _opts_pagination "$@"
- _opts_filter "$@"
-
- _get "/repos/${owner}/${repo}/pulls" | _filter_json "$_filter"
- }
-
- create_pull_request() {
- # Create a pull request for a repository
- #
- # Usage:
- #
- # create_pull_request someuser/somerepo title head base
- #
- # create_pull_request someuser/somerepo title head base body='Description here.'
- #
- # Positional arguments
- #
- local repo="${1:?Repo name required.}"
- # A GitHub repository.
- local title="${2:?Pull request title required.}"
- # A title.
- local head="${3:?Pull request head required.}"
- # A head.
- local base="${4:?Pull request base required.}"
- # A base.
- #
- # Keyword arguments
- #
- local _filter='"\(.number)\t\(.html_url)"'
- # A jq filter to apply to the return data.
- #
- # Pull request options may also be passed as keyword arguments:
- #
- # * `body`
- # * `maintainer_can_modify`
-
- shift 4
-
- _opts_filter "$@"
-
- _format_json title="$title" head="$head" base="$base" "$@" \
- | _post "/repos/${repo}/pulls" \
- | _filter_json "$_filter"
- }
-
- update_pull_request() {
- # Update a pull request for a repository
- #
- # Usage:
- #
- # update_pull_request someuser/somerepo number title='New title' body='New body'
- #
- # Positional arguments
- #
- local repo="${1:?Repo name required.}"
- # A GitHub repository.
- local number="${2:?Pull request number required.}"
- # A pull request number.
- #
- # Keyword arguments
- #
- local _filter='"\(.number)\t\(.html_url)"'
- # A jq filter to apply to the return data.
- #
- # Pull request options may also be passed as keyword arguments:
- #
- # * `base`
- # * `body`
- # * `maintainer_can_modify`
- # * `state` (either open or closed)
- # * `title`
-
- shift 2
-
- _opts_filter "$@"
-
- _format_json "$@" \
- | _post "/repos/${repo}/pulls/${number}" method='PATCH' \
- | _filter_json "$_filter"
- }
-
- transfer_repo() {
- # Transfer a repository to a user or organization
- #
- # Usage:
- #
- # transfer_repo owner repo new_owner
- # transfer_repo owner repo new_owner team_ids='[ 12, 345 ]'
- #
- # Positional arguments
- #
- local owner="${1:?Owner name required.}"
- # Name of the current owner
- #
- local repo="${2:?Repo name required.}"
- # Name of the current repo
- #
- local new_owner="${3:?New owner name required.}"
- # Name of the new owner
- #
- # Keyword arguments
- #
- local _filter='"\(.name)"'
- # A jq filter to apply to the return data.
- #
- # POST data may also be passed as keyword arguments:
- #
- # * `team_ids`
-
- shift 3
-
- _opts_filter "$@"
-
- export OK_SH_ACCEPT='application/vnd.github.nightshade-preview+json'
- _format_json "new_owner=${new_owner}" "$@" | _post "/repos/${owner}/${repo}/transfer" | _filter_json "${_filter}"
- }
-
- archive_repo() {
- # Archive a repo
- #
- # Usage:
- #
- # archive_repo owner/repo
- #
- # Positional arguments
- #
- local repo="${1:?Repo name required.}"
- # A GitHub repository.
- #
- local _filter='"\(.name)\t\(.html_url)"'
- # A jq filter to apply to the return data.
- #
-
- shift 1
-
- _opts_filter "$@"
-
- _format_json "archived=true" \
- | _post "/repos/${repo}" method='PATCH' \
- | _filter_json "$_filter"
- }
-
- __main "$@"
|