https://github.com/whiteinge/ok.shBSDlicensed.
RequirementsAPOSIXenvironment(testedagainstBusyboxv1.19.4)curl(testedagainst7.32.0)Optionalrequirementsjqhttps://stedolan.github.io/jq/(testedagainst1.3)IfjqisnotinstalledcommandswilloutputrawJSON;ifjqisinstalledtheoutputwillbeformattedandfilteredforusewithothershelltools.SetupAuthenticationcredentialsarereadfroma$HOME/.netrcfileonUNIXmachinesora_netrcfilein%HOME%forUNIXenvironmentsunderWindows.GeneratethetokenonGitHubunder"AccountSettings->Applications".Restrictpermissionsonthatfilewithchmod600~/.netrc!
machineapi.github.comlogin<username>password<token>machineuploads.github.comlogin<username>password<token>OrsetanenvironmentGITHUB_TOKEN=token
ConfigurationThefollowingenvironmentvariablesmaybesettocustomizeok.sh.
OK_SH_URL=https://api.github.comBaseURLforGitHuborGitHubEnterprise.OK_SH_ACCEPT=application/vnd.github.v3+jsonThe'Accept'headertosendwitheachrequest.OK_SH_JQ_BIN=jqThenameofthejqbinary,ifinstalled.OK_SH_VERBOSE=0Thedebugloggingverbositylevel.Sameastheverboseflag.OK_SH_RATE_LIMIT=0OutputcurrentGitHubratelimitinformationtostderr.OK_SH_DESTRUCTIVE=0Allowdestructiveoperationswithoutpromptingforconfirmation.OK_SH_MARKDOWN=1OutputsometextinMarkdownformat.Usageok.sh[<flags>](command[<arg>,<name=value>...])
ok.sh-h#Short,usagehelptext.ok.shhelp#Allhelptext.Warning:long!ok.shhelpcommand#Command-specifichelptext.ok.shcommand#Runacommandwithandwithoutargs.ok.shcommandfoobarbaz=Bazqux='Quxarghere'FlagDescription-VShowversion.-hShowthisscreen.-jOutputrawJSON;don'tprocesswithjq.-qQuiet;don'tprinttostdout.-rPrintcurrentGitHubAPIratelimittostderr.-vLoggingoutput;specifymultipletimes:info,debug,trace.-xEnablextracedebuglogging.-yAnswer'yes'toanyprompts.Flagsmustbethefirstargumenttook.sh,beforecommand.
TableofContentsUtilityandrequest/responsecommands_all_funcs_log_helptext_format_json_format_urlencode_filter_json_get_mime_type_get_confirm_opts_filter_opts_pagination_opts_qs_request_response_get_post_deleteGitHubcommandshelpshow_scopesorg_reposorg_teamsorg_membersorg_collaboratorsorg_auditlogteam_memberslist_reposlist_brancheslist_commitslist_contributorslist_collaboratorslist_hookslist_gistspublic_gistsgistadd_collaboratordelete_collaboratorcreate_repodelete_repofork_repolist_releasesreleasecreate_releaseedit_releasedelete_releaserelease_assetsupload_assetlist_milestonescreate_milestonelist_issue_commentsadd_commentlist_commit_commentsadd_commit_commentclose_issuelist_issuesuser_issuescreate_issueorg_issueslist_starredlist_my_orgslist_orgslist_userslabelsadd_labelupdate_labeladd_team_repolist_pullscreate_pull_requestupdate_pull_requesttransfer_repoarchive_repoCommands_all_funcsListallfunctionsfoundinthecurrentfileintheordertheyappear
Keywordarguments
public=1
0donotoutputpublicfunctions.
private=1
0donotoutputprivatefunctions.
_logAlightweightloggingsystembasedonfiledescriptors
Usage:
_logdebug'Startingthecombobulator!'Positionalarguments
level="$1"
Thelevelforagivenlogmessage.(infoordebug)
message="$2"
Thelogmessage.
_helptextExtractcontiguouslinesofcommentsandfunctionparamsashelptext
Indentationwillbeignored.She-bangswillbeignored.Localvariabledeclarationsandtheirdefaultvaluescanalsobepulledinasdocumentation.Exitsuponencounteringthefirstblankline.
Exportedenvironmentvariablescanbeusedforstringinterpolationintheextractedcommentedtext.
Input
(stdin)Thetextofafunctionbodytoparse._format_jsonCreateformattedJSONfromname=valuepairs
Usage:
ok.sh_format_jsonfoo=Foobar=123baz=truequx=Qux=Quxquux='Multi-linestring'quuz=\'5.20170918\'\corge="$(ok.sh_format_jsongrault=Grault)"\garply="$(ok.sh_format_json-awaldotrue3)"Return:
{"garply":["waldo",true,3],"foo":"Foo","corge":{"grault":"Grault"},"baz":true,"qux":"Qux=Qux","quux":"Multi-line\nstring","quuz":"5.20170918","bar":123}Triesnottoquotenumbers,booleans,nulls,ornestedstructures.Note,nestedstructuresmustbequotedsincetheoutputcontainsspaces.
The-aoptionwillcreateanarrayinsteadofanobject.Thisoptionmustcomedirectlyafterthe_format_jsoncommandandbeforeanyoperands.E.g.,_format_json-afoobarbaz.
Ifjqisinstalleditwillalsovalidatetheoutput.
Positionalarguments
$1-$9
Eachpositionalargmustbeintheformatofname=valuewhichwillbeaddedtoasingle,flatJSONobject.
_format_urlencodeURLencodeandjoinname=valuepairs
Usage:
_format_urlencodefoo='FooFoo'bar='<Bar>&/Bar/'Return:
foo=Foo%20Foo&bar=%3CBar%3E%26%2FBar%2FIgnorespairsifthevaluebeginswithanunderscore.
_filter_jsonFilterJSONinputusingjq;outputsrawJSONifjqisnotinstalled
Usage:
printf'[{"foo":"One"},{"foo":"Two"}]'|\ok.sh_filter_json'.[]|"\(.foo)"'(stdin)JSONinput.
_filter="$1"
Astringofjqfilterstoapplytotheinputstream.
_get_mime_typeGuessthemimetypeforafilebasedonthefileextension
Usage:
localmime_type_get_mime_type"foo.tar";printf'mimeis:%s'"$mime_type"Setstheglobalvariablemime_typewiththeresult.(Ifthisfunctioniscalledfromwithinafunctionthathasdeclaredalocalvariableofthatnameitwillupdatethelocalcopyandnotsetaglobal.)
Positionalarguments
filename="$1"
Thefullnameofthefile,withextension.
_get_confirmPrompttheuserforconfirmation
Usage:
localconfirm;_get_confirm["$confirm"-eq1]&&printf'Goodtogo!\n'Ifglobalconfirmationissetvia$OK_SH_DESTRUCTIVEthentheuserisnotprompted.Assignstheuser'sconfirmationtotheconfirmglobalvariable.(Ifthisfunctioniscalledwithinafunctionthathasalocalvariableofthatname,thelocalvariablewillbeupdatedinstead.)
Positionalarguments
message="$1"
Themessagetoprompttheuserwith.
_opts_filterExtractcommonjqfilterkeywordoptionsandassigntovars
Usage:
localfilter_opts_filter"$@"_opts_paginationExtractcommonpaginationkeywordoptionsandassigntovars
Usage:
local_follow_next_opts_pagination"$@"_opts_qsExtractcommonquerystringkeywordoptionsandassigntovars
Usage:
localqs_opts_qs"$@"_get"/some/path"_requestAwrapperaroundmakingHTTPrequestswithcurl
Usage:
#GetJSONforallissues:_request/repos/saltstack/salt/issues#SendaPOSTrequest;parseresponseusingjq:printf'{"title":"%s","body":"%s"}\n'"Stuff""Things"\|_request/some/path|jq-r'.[url]'#SendaPUTrequest;parseresponseusingjq:printf'{"title":"%s","body":"%s"}\n'"Stuff""Things"\|_request/repos/:owner/:repo/issuesmethod=PUT|jq-r'.[url]'#Sendaconditional-GETrequest:_request/usersetag=edd3a0d38d8c329d3ccc6575f17a76bbInput
(stdin)Datathatwillbeusedastherequestbody.Positionalarguments
path="$1"
TheURLpathfortheHTTPrequest.Mustbeanabsolutepaththatstartswitha/orafullURLthatstartswithhttp(s).Absolutepathswillbeappendtothevaluein$OK_SH_URL.
Keywordarguments
method='GET'
ThemethodtousefortheHTTPrequest.
content_type='application/json'
ThevalueoftheContent-Typeheadertousefortherequest.
etag
AnoptionalEtagtosendastheIf-None-Matchheader.
_responseProcessanHTTPresponsefromcurl
Outputonlyheadersofinterestfollowedbytheresponsebody.Additionalprocessingisperformedonselectheaderstomakethemeasiertoparseusingshelltools.
Usage:
#Sendarequest;outputtheresponseandonlyselectresponseheaders:_request/some/path|_responsestatus_codeETagLink_next#Makerequestusingcurl;outputresponsewithselectresponseheaders;#assignresponseheaderstolocalvariables:curl-isSexample.com/some/path|_responsestatus_codestatus_text|{localstatus_codestatus_textread-rstatus_coderead-rstatus_text}Headerreformatting
HTTPStatus
TheHTTPlineissplitintoseparatehttp_version,status_code,andstatus_textvariables.
ETag
Thesurroundingquotesareremoved.
Link
EachURLintheLinkheaderisexpandedwiththeURLtypeappendedtothename.E.g.,Link_first,Link_last,Link_next.
Positionalarguments
$1-$9
EachpositionalargisthenameofanHTTPheader.Eachheadervalueisoutputinthesameorderaseachargument;eachonasingleline.Ablanklineisoutputforheadersthatcannotbefound.
_getAwrapperaround_request()forcommonGETpatterns
Willautomaticallyfollow'next'paginationURLsintheLinkheader.
Usage:
_get/some/path_get/some/path_follow_next=0_get/some/path_follow_next_limit=200|jq-c.Positionalarguments
path="$1"
TheHTTPpathorURLtopassto_request().
Keywordarguments
_follow_next=1
Automaticallylookfora'Links'headerandfollowany'next'URLs.
_follow_next_limit=50
Maximumnumberof'next'URLstofollowbeforestopping.
_postAwrapperaround_request()forcommonPOST/PUTpatterns
Usage:
_format_jsonfoo=Foobar=Bar|_post/some/path_format_jsonfoo=Foobar=Bar|_post/some/pathmethod='PUT'_post/some/pathfilename=somearchive.tar_post/some/pathfilename=somearchive.tarmime_type=application/x-tar_post/some/pathfilename=somearchive.tar\mime_type=$(file-b--mime-typesomearchive.tar)Input
(stdin)Optional.Seethefilenameargumentalso.Datathatwillbeusedastherequestbody.Positionalarguments
path="$1"
TheHTTPpathorURLtopassto_request().
Keywordarguments
method='POST'
ThemethodtousefortheHTTPrequest.
filename
Optional.Seethestdinoptionabovealso.Takesprecedenceoveranydatapassedasstdinandloadsafileoffthefilesystemtoserveastherequestbody.
mime_type
ThevalueoftheContent-Typeheadertousefortherequest.Ifthefilenameargumentisgiventhisvaluewillbeguessedfromthefileextension.Ifthefilenameargumentisnotgiven(i.e.,usingstdin)thisvaluedefaultstoapplication/json.Specifyingthisargumentoverridesallotherdefaultsorguesses.
_deleteAwrapperaround_request()forcommonDELETEpatterns
Usage:
_delete'/some/url'Return:0forsuccess;1forfailure.
Positionalarguments
url="$1"
TheURLtosendtheDELETErequestto.
helpOutputthehelptextforacommand
Usage:
helpcommandnamePositionalarguments
fname="$1"
Functionnametosearchfor;ifomittedsearcheswholefile.
show_scopesShowthepermissionscopesforthecurrentlyauthenticateduser
Usage:
show_scopesorg_reposListorganizationrepositories
Usage:
org_reposmyorgorg_reposmyorgtype=privateper_page=10org_reposmyorg_filter='.[]|"\(.name)\t\(.owner.login)"'Positionalarguments
org="$1"
OrganizationGitHubloginoridforwhichtolistrepos.
Keywordarguments
_follow_next
Automaticallylookfora'Links'headerandfollowany'next'URLs.
_follow_next_limit
Maximumnumberof'next'URLstofollowbeforestopping.
_filter='.[]|"\(.name)\t\(.ssh_url)"'
Ajqfiltertoapplytothereturndata.
Querystringargumentsmayalsobepassedaskeywordarguments:
per_pagetypeorg_teamsListteams
Usage:
org_teamsorgPositionalarguments
org="$1"
OrganizationGitHubloginorid.
Keywordarguments
_filter='.[]|"\(.name)\t\(.id)\t\(.permission)"'
Ajqfiltertoapplytothereturndata.
org_membersListorganizationmembers
Usage:
org_membersorgPositionalarguments
org="$1"
OrganizationGitHubloginorid.
Keywordarguments
_filter='.[]|"\(.login)\t\(.id)"'
Ajqfiltertoapplytothereturndata.
org_collaboratorsListorganizationoutsidecollaborators
Usage:
org_collaboratorsorgPositionalarguments
org="$1"
OrganizationGitHubloginorid.
Keywordarguments
_filter='.[]|"\(.login)\t\(.id)"'
Ajqfiltertoapplytothereturndata.
org_auditlogInteractwiththeGithubAuditLog
Usage:
org_auditlogorgPositionalarguments
org="$1"
OrganizationGitHubloginorid.
Keywordarguments
_filter='.[]|"\(.actor)\t\(.action)"'
Ajqfiltertoapplytothereturndata.
team_membersListteammembers
Usage:
team_membersteam_idPositionalarguments
team_id="$1"
Teamid.
Keywordarguments
_filter='.[]|"\(.login)\t\(.id)"'
Ajqfiltertoapplytothereturndata.
list_reposListuserrepositories
Usage:
list_reposlist_reposuserPositionalarguments
user="$1"
OptionalGitHubuserloginoridforwhichtolistrepos.
Keywordarguments
_filter='.[]|"\(.name)\t\(.html_url)"'
Ajqfiltertoapplytothereturndata.
Querystringargumentsmayalsobepassedaskeywordarguments:
directionper_pagesorttypelist_branchesListbranchesofaspecifiedrepository.(https://developer.github.com/v3/repos/#list_branches)
Usage:
list_branchesuserrepoPositionalarguments
GitHubuserloginoridforwhichtolistbranchesNameoftherepoforwhichtolistbranches
user="$1"
repo="$2"
Keywordarguments
_filter='.[]|"\(.name)"'
Ajqfiltertoapplytothereturndata.
Querystringargumentsmayalsobepassedaskeywordarguments:
directionper_pagesorttypelist_commitsListcommitsofaspecifiedrepository.(https://developer.github.com/v3/repos/commits/#list-commits-on-a-repository)
Usage:
list_commitsuserrepoPositionalarguments
GitHubuserloginoridforwhichtolistbranchesNameoftherepoforwhichtolistbranches
list_contributorsListcontributorstothespecifiedrepository,sortedbythenumberofcommitspercontributorindescendingorder.(https://developer.github.com/v3/repos/#list-contributors)
Usage:
list_contributorsuserrepoPositionalarguments
user="$1"
GitHubuserloginoridforwhichtolistcontributors
repo="$2"
Nameoftherepoforwhichtolistcontributors
Keywordarguments
_filter='.[]|"\(.login)\t\(.type)\tType:\(.type)\tContributions:\(.contributions)"'
Ajqfiltertoapplytothereturndata.
Querystringargumentsmayalsobepassedaskeywordarguments:
directionper_pagesorttypelist_collaboratorsListcollaboratorstothespecifiedrepository,sortedbythenumberofcommitspercollaboratorindescendingorder.(https://developer.github.com/v3/repos/#list-collaborators)
Usage:
list_collaboratorssomeuser/somerepoPositionalargumentsGitHubuserloginoridforwhichtolistcollaboratorsNameoftherepoforwhichtolistcollaborators
repo="$1"Keywordarguments
_filter='.[]|"\(.login)\t\(.type)\tType:\(.type)\tPermissions:\(.permissions)"'
Ajqfiltertoapplytothereturndata.
Querystringargumentsmayalsobepassedaskeywordarguments:
directionper_pagesorttypelist_hooksListwebhooksfromthespecifiedrepository.(https://developer.github.com/v3/repos/hooks/#list-hooks)
Usage:
list_hooksowner/repoPositionalarguments
repo="$1"
NameoftherepoforwhichtolistcontributorsOwnerismandatory,like'owner/repo'
_filter='.[]|"\(.name)\t\(.config.url)"'
Ajqfiltertoapplytothereturndata.
list_gistsListgistsforthecurrentauthenticateduseroraspecificuser
https://developer.github.com/v3/gists/#list-a-users-gists
Usage:
list_gistslist_gists<username>Positionalarguments
username="$1"
Anoptionalusertofilterlisting
Keywordarguments
_follow_next
Automaticallylookfora'Links'headerandfollowany'next'URLs.
_follow_next_limit
Maximumnumberof'next'URLstofollowbeforestopping.
_filter='.[]|"\(.id)\t\(.description)"'
Ajqfiltertoapplytothereturndata.
public_gistsListpublicgists
https://developer.github.com/v3/gists/#list-all-public-gists
Usage:
public_gistsKeywordarguments
_follow_next
Automaticallylookfora'Links'headerandfollowany'next'URLs.
_follow_next_limit
Maximumnumberof'next'URLstofollowbeforestopping.
_filter='.[]|"\(.id)\t\(.description)"'
Ajqfiltertoapplytothereturndata.
gistGetasinglegist
https://developer.github.com/v3/gists/#get-a-single-gist
Usage:
get_gistPositionalarguments
gist_id="$1"
IDofgisttofetch.
Keywordarguments
_filter='.files|keys|join(",")'
Ajqfiltertoapplytothereturndata.
add_collaboratorAddacollaboratortoarepository
Usage:
add_collaboratorsomeuser/somerepocollaboratoruserpermissionPositionalarguments
repo="$1"
AGitHubrepository.
collaborator="$2"
Anewcollaborator.
permission="$3"
Thepermissionlevelforthiscollaborator.Oneofpush,pull,admin.Thepullandadminpermissionsarevalidfororganizationreposonly.
Keywordarguments
_filter='"\(.name)\t\(.color)"'
Ajqfiltertoapplytothereturndata.
delete_collaboratorDeleteacollaboratortoarepository
Usage:
delete_collaboratorsomeuser/somerepocollaboratoruserpermissionPositionalarguments
repo="$1"
AGitHubrepository.
collaborator="$2"
Anewcollaborator.
create_repoCreatearepositoryforauserororganization
Usage:
create_repofoocreate_repobardescription='Stuffandthings'homepage='example.com'create_repobazorganization=myorgPositionalarguments
name="$1"
Nameofthenewrepo
Keywordarguments
_filter='"\(.name)\t\(.html_url)"'
Ajqfiltertoapplytothereturndata.
POSTdatamayalsobepassedaskeywordarguments:
auto_init,descriptiongitignore_templatehas_downloadshas_issueshas_wiki,homepageorganizationprivateteam_iddelete_repoDeletearepositoryforauserororganization
Usage:
delete_repoownerrepoThecurrentlyauthenticatedusermusthavethedelete_reposcope.Viewcurrentscopeswiththeshow_scopes()function.
Positionalarguments
owner="$1"
Nameofthenewrepo
repo="$2"
Nameofthenewrepo
评论