From 09e6061d8c5fda8df473f9a605432c8fd4ca6eab Mon Sep 17 00:00:00 2001 From: Bryan Austin Date: Fri, 22 Jun 2018 16:27:50 -0700 Subject: [PATCH 01/21] Re-work calendar view As discussed in issue #223 (which has pictures that better explain the changes here), this change: - Removes the date from individual events, instead centering a title at the start of each day with the date (which uses a new configurable color, `wtf.mods.gcal.colors.day`) - Consolidates 3 lines per event down to 2, moving timestamp to front of each event - Makes the time-until-event text turn red when under 30 minutes (wasn't discussed in the issue but was another thing I added locally for this, feel free to discard if unwanted) New format is: ``` Monday, Jun 25 x 13:00 Super Cool Meeting Title 2h Event location x 14:00 Also Super Cool Meeting 3h Event location Tuesday, Jun 26 ... ``` --- gcal/widget.go | 63 ++++++++++++++++++++++++++++++++------------------ wtf/utils.go | 2 ++ 2 files changed, 43 insertions(+), 22 deletions(-) diff --git a/gcal/widget.go b/gcal/widget.go index edca17a6..b66ac960 100644 --- a/gcal/widget.go +++ b/gcal/widget.go @@ -93,16 +93,27 @@ func (widget *Widget) contentFrom(events *calendar.Events) string { for _, event := range events.Items { conflict := widget.conflicts(event, events) - str = str + fmt.Sprintf( - "%s %s[%s]%s[white]\n %s[%s]%s %s[white]\n\n", - widget.dayDivider(event, prevEvent), - widget.responseIcon(event), - widget.titleColor(event), - widget.eventSummary(event, conflict), - widget.location(event), + dayDivider := widget.dayDivider(event, prevEvent) + responseIcon := widget.responseIcon(event) + timestamp := fmt.Sprintf("[%s]%s", widget.descriptionColor(event), - widget.eventTimestamp(event), - widget.until(event), + widget.eventTimestamp(event)) + title := fmt.Sprintf("[%s]%s", + widget.titleColor(event), + widget.eventSummary(event, conflict)) + until := widget.until(event) + + lineOne := fmt.Sprintf( + "%s %s %s %s %s[white]", + dayDivider, + responseIcon, + timestamp, + title, + until, + ) + str = str + fmt.Sprintf("%s%s\n\n", + lineOne, + widget.location(event), // prefixes newline if non-empty ) prevEvent = event @@ -112,13 +123,17 @@ func (widget *Widget) contentFrom(events *calendar.Events) string { } func (widget *Widget) dayDivider(event, prevEvent *calendar.Event) string { + var prevStartTime time.Time if prevEvent != nil { - prevStartTime, _ := time.Parse(time.RFC3339, prevEvent.Start.DateTime) - currStartTime, _ := time.Parse(time.RFC3339, event.Start.DateTime) + prevStartTime, _ = time.Parse(time.RFC3339, prevEvent.Start.DateTime) + } + currStartTime, _ := time.Parse(time.RFC3339, event.Start.DateTime) - if currStartTime.Day() != prevStartTime.Day() { - return "\n" - } + if currStartTime.Day() != prevStartTime.Day() { + _, _, width, _ := widget.View.GetInnerRect() + return fmt.Sprintf("[%s]", wtf.Config.UString("wtf.mods.gcal.colors.day", "forestgreen")) + + wtf.CenterText(currStartTime.Format(wtf.FullDateFormat), width) + + "\n" } return "" @@ -158,7 +173,7 @@ func (widget *Widget) eventTimestamp(event *calendar.Event) string { return startTime.Format(wtf.FriendlyDateFormat) } else { startTime, _ := time.Parse(time.RFC3339, event.Start.DateTime) - return startTime.Format(wtf.FriendlyDateTimeFormat) + return startTime.Format(wtf.MinimumTimeFormat) } } @@ -208,7 +223,7 @@ func (widget *Widget) location(event *calendar.Event) string { } return fmt.Sprintf( - "[%s]%s\n ", + "\n [%s]%s", widget.descriptionColor(event), event.Location, ) @@ -232,15 +247,15 @@ func (widget *Widget) responseIcon(event *calendar.Event) string { switch response { case "accepted": - icon = icon + "✔︎ " + icon = icon + "✔︎" case "declined": - icon = icon + "✘ " + icon = icon + "✘" case "needsAction": - icon = icon + "? " + icon = icon + "?" case "tentative": - icon = icon + "~ " + icon = icon + "~" default: - icon = icon + "" + icon = icon + " " } return icon @@ -268,15 +283,19 @@ func (widget *Widget) until(event *calendar.Event) string { untilStr := "" + var color = "[lightblue]" if days > 0 { untilStr = fmt.Sprintf("%dd", days) } else if hours > 0 { untilStr = fmt.Sprintf("%dh", hours) } else { untilStr = fmt.Sprintf("%dm", mins) + if mins < 30 { + color = "[red]" + } } - return "[lightblue]" + untilStr + "[white]" + return color + untilStr + "[white]" } func updateLoop(widget *Widget) { diff --git a/wtf/utils.go b/wtf/utils.go index c65a925b..fc1ac57d 100644 --- a/wtf/utils.go +++ b/wtf/utils.go @@ -13,6 +13,8 @@ import ( const SimpleDateFormat = "Jan 2" const SimpleTimeFormat = "15:04 MST" +const MinimumTimeFormat = "15:04" +const FullDateFormat = "Monday, Jan 2" const FriendlyDateFormat = "Mon, Jan 2" const FriendlyDateTimeFormat = "Mon, Jan 2, 15:04" const TimestampFormat = "2006-01-02T15:04:05-0700" From b976a1b84763f5bc3dabf5b708560b50548ec5ee Mon Sep 17 00:00:00 2001 From: Anand Sudhir Prayaga Date: Wed, 27 Jun 2018 15:59:50 +0200 Subject: [PATCH 02/21] Add gerrit widget --- Gopkg.lock | 32 +- Gopkg.toml | 4 + README.md | 1 + _site/content/posts/modules/gerrit.md | 90 ++ _site/static/imgs/modules/gerrit.png | Bin 0 -> 32611 bytes .../hyde-hyde/layouts/partials/sidebar.html | 1 + gerrit/display.go | 73 ++ gerrit/gerrit_repo.go | 100 ++ gerrit/widget.go | 180 ++++ .../go/compute/metadata/metadata.go | 456 +++++---- vendor/github.com/adlio/trello/label.go | 14 + .../andygrunwald/go-gerrit/.gitignore | 1 + .../andygrunwald/go-gerrit/.travis.yml | 17 + .../andygrunwald/go-gerrit/CHANGELOG.md | 105 +++ .../github.com/andygrunwald/go-gerrit/LICENSE | 22 + .../andygrunwald/go-gerrit/Makefile | 26 + .../andygrunwald/go-gerrit/README.md | 270 ++++++ .../andygrunwald/go-gerrit/access.go | 74 ++ .../andygrunwald/go-gerrit/accounts.go | 874 ++++++++++++++++++ .../andygrunwald/go-gerrit/authentication.go | 187 ++++ .../andygrunwald/go-gerrit/changes.go | 768 +++++++++++++++ .../andygrunwald/go-gerrit/changes_edit.go | 231 +++++ .../go-gerrit/changes_reviewer.go | 163 ++++ .../go-gerrit/changes_revision.go | 651 +++++++++++++ .../andygrunwald/go-gerrit/config.go | 529 +++++++++++ .../github.com/andygrunwald/go-gerrit/doc.go | 68 ++ .../andygrunwald/go-gerrit/events.go | 166 ++++ .../andygrunwald/go-gerrit/gerrit.go | 564 +++++++++++ .../andygrunwald/go-gerrit/gometalinter.json | 24 + .../andygrunwald/go-gerrit/groups.go | 360 ++++++++ .../andygrunwald/go-gerrit/groups_include.go | 117 +++ .../andygrunwald/go-gerrit/groups_member.go | 133 +++ .../andygrunwald/go-gerrit/plugins.go | 131 +++ .../andygrunwald/go-gerrit/projects.go | 465 ++++++++++ .../andygrunwald/go-gerrit/projects_branch.go | 157 ++++ .../go-gerrit/projects_childproject.go | 66 ++ .../andygrunwald/go-gerrit/projects_commit.go | 36 + .../go-gerrit/projects_dashboard.go | 108 +++ .../andygrunwald/go-gerrit/projects_tag.go | 60 ++ .../andygrunwald/go-gerrit/types.go | 88 ++ .../google/go-github/github/event_types.go | 1 + vendor/github.com/olebedev/config/doc.go | 8 +- vendor/github.com/rivo/tview/README.md | 3 + vendor/github.com/rivo/tview/doc.go | 10 +- vendor/github.com/rivo/tview/table.go | 1 - vendor/github.com/rivo/tview/textview.go | 90 +- vendor/github.com/rivo/tview/treeview.go | 665 +++++++++++++ vendor/github.com/rivo/tview/util.go | 7 +- .../xanzy/go-gitlab/award_emojis.go | 62 +- .../github.com/yfronto/newrelic/.travis.yml | 14 +- .../yfronto/newrelic/alert_events.go | 2 +- vendor/golang.org/x/oauth2/internal/token.go | 1 + vendor/golang.org/x/oauth2/oauth2.go | 10 +- wtf.go | 3 + 54 files changed, 8021 insertions(+), 268 deletions(-) create mode 100644 _site/content/posts/modules/gerrit.md create mode 100644 _site/static/imgs/modules/gerrit.png create mode 100644 gerrit/display.go create mode 100644 gerrit/gerrit_repo.go create mode 100644 gerrit/widget.go create mode 100644 vendor/github.com/andygrunwald/go-gerrit/.gitignore create mode 100644 vendor/github.com/andygrunwald/go-gerrit/.travis.yml create mode 100644 vendor/github.com/andygrunwald/go-gerrit/CHANGELOG.md create mode 100644 vendor/github.com/andygrunwald/go-gerrit/LICENSE create mode 100644 vendor/github.com/andygrunwald/go-gerrit/Makefile create mode 100644 vendor/github.com/andygrunwald/go-gerrit/README.md create mode 100644 vendor/github.com/andygrunwald/go-gerrit/access.go create mode 100644 vendor/github.com/andygrunwald/go-gerrit/accounts.go create mode 100644 vendor/github.com/andygrunwald/go-gerrit/authentication.go create mode 100644 vendor/github.com/andygrunwald/go-gerrit/changes.go create mode 100644 vendor/github.com/andygrunwald/go-gerrit/changes_edit.go create mode 100644 vendor/github.com/andygrunwald/go-gerrit/changes_reviewer.go create mode 100644 vendor/github.com/andygrunwald/go-gerrit/changes_revision.go create mode 100644 vendor/github.com/andygrunwald/go-gerrit/config.go create mode 100644 vendor/github.com/andygrunwald/go-gerrit/doc.go create mode 100644 vendor/github.com/andygrunwald/go-gerrit/events.go create mode 100644 vendor/github.com/andygrunwald/go-gerrit/gerrit.go create mode 100644 vendor/github.com/andygrunwald/go-gerrit/gometalinter.json create mode 100644 vendor/github.com/andygrunwald/go-gerrit/groups.go create mode 100644 vendor/github.com/andygrunwald/go-gerrit/groups_include.go create mode 100644 vendor/github.com/andygrunwald/go-gerrit/groups_member.go create mode 100644 vendor/github.com/andygrunwald/go-gerrit/plugins.go create mode 100644 vendor/github.com/andygrunwald/go-gerrit/projects.go create mode 100644 vendor/github.com/andygrunwald/go-gerrit/projects_branch.go create mode 100644 vendor/github.com/andygrunwald/go-gerrit/projects_childproject.go create mode 100644 vendor/github.com/andygrunwald/go-gerrit/projects_commit.go create mode 100644 vendor/github.com/andygrunwald/go-gerrit/projects_dashboard.go create mode 100644 vendor/github.com/andygrunwald/go-gerrit/projects_tag.go create mode 100644 vendor/github.com/andygrunwald/go-gerrit/types.go create mode 100644 vendor/github.com/rivo/tview/treeview.go diff --git a/Gopkg.lock b/Gopkg.lock index eb2427ac..c429c1fe 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -4,14 +4,20 @@ [[projects]] name = "cloud.google.com/go" packages = ["compute/metadata"] - revision = "0fd7230b2a7505833d5f69b75cbd6c9582401479" - version = "v0.23.0" + revision = "777200caa7fb8936aed0f12b1fd79af64cc83ec9" + version = "v0.24.0" [[projects]] branch = "master" name = "github.com/adlio/trello" packages = ["."] - revision = "05dcd358e32866f2353c4f49077346a0eb585436" + revision = "8a458717123e328d9103a3bf075e64bc1ec961f8" + +[[projects]] + branch = "master" + name = "github.com/andygrunwald/go-gerrit" + packages = ["."] + revision = "5632c7fad122548dfabeb3011e98d3b0a08a89d7" [[projects]] branch = "master" @@ -50,7 +56,7 @@ branch = "master" name = "github.com/google/go-github" packages = ["github"] - revision = "a83ae98ad5d09188c49d6056edb60ec9bdf202bd" + revision = "60d040d2dafa18fa3e86cbf22fbc3208ef9ef1e0" [[projects]] branch = "master" @@ -80,7 +86,7 @@ branch = "master" name = "github.com/olebedev/config" packages = ["."] - revision = "9a10d05a33a8b9e828f20491e21e8927dec35f72" + revision = "ed90d2035b8114c30b9cb65e7d52e10a7148f8c6" [[projects]] name = "github.com/pkg/errors" @@ -104,7 +110,7 @@ branch = "master" name = "github.com/rivo/tview" packages = ["."] - revision = "e643d10b365df4caec5ed32e4c1103e185af9079" + revision = "306abd9cb98c97417ab6c58aa0400b2e5daac88b" [[projects]] name = "github.com/stretchr/testify" @@ -113,16 +119,16 @@ version = "v1.2.2" [[projects]] - branch = "master" name = "github.com/xanzy/go-gitlab" packages = ["."] - revision = "1c1cfedc5a8ffe122b1981e8b0822fe22f461643" + revision = "5c6e84fea386746fd31ff46da2253f6b7ed7dce2" + version = "v0.10.6" [[projects]] branch = "master" name = "github.com/yfronto/newrelic" packages = ["."] - revision = "f7fa0c6f30ac3d86360c73726cfe9dd526a63d21" + revision = "7c9c2852e8f9e69a80bff4f4f1fe4cdd15eeba19" [[projects]] branch = "master" @@ -131,7 +137,7 @@ "context", "context/ctxhttp" ] - revision = "db08ff08e8622530d9ed3a0e8ac279f6d4c02196" + revision = "afe8f62b1d6bbd81f31868121a50b06d8188e1f9" [[projects]] branch = "master" @@ -143,7 +149,7 @@ "jws", "jwt" ] - revision = "113ce6928c4638e14fd5eba69b9e6ec899d5dd83" + revision = "ef147856a6ddbb60760db74283d2424e98c87bff" [[projects]] name = "golang.org/x/text" @@ -167,7 +173,7 @@ "googleapi/internal/uritemplates", "sheets/v4" ] - revision = "2eea9ba0a3d94f6ab46508083e299a00bbbc65f6" + revision = "3639d6d93f377f39a1de765fa4ef37b3c7ca8bd9" [[projects]] name = "google.golang.org/appengine" @@ -195,6 +201,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "147a44acfec399fa7d09cafbc213c1c2c222fd6140bc87b35e942d85891995d9" + inputs-digest = "a7a00554f9040d7617458773eafa64b82f9502eace145152cb50eb082800e936" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 66ae74ea..6221a4d7 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -45,6 +45,10 @@ name = "github.com/xanzy/go-gitlab" branch = "master" +[[constraint]] + name = "github.com/andygrunwald/go-gerrit" + branch = "master" + [[constraint]] name = "github.com/jessevdk/go-flags" version = "1.4.0" diff --git a/README.md b/README.md index cf71889f..8fbdf9a6 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,7 @@ Many thanks to all these developers. * [calendar](https://google.golang.org/api/calendar/v3) * [config](https://github.com/olebedev/config) +* [go-gerrit](https://github.com/andygrunwald/go-gerrit) * [go-github](https://github.com/google/go-github) * [goreleaser](https://github.com/goreleaser/goreleaser) * [newrelic](https://github.com/yfronto/newrelic) diff --git a/_site/content/posts/modules/gerrit.md b/_site/content/posts/modules/gerrit.md new file mode 100644 index 00000000..8c7e752c --- /dev/null +++ b/_site/content/posts/modules/gerrit.md @@ -0,0 +1,90 @@ +--- +title: "Gerrit" +date: 2018-06-27T15:55:42-07:00 +draft: false +--- + +gerrit screenshot + +Displays information about your projects hosted on Gerrit: + +#### Open Incoming Reviews + +All open reviews that are requesting your approval. + +#### My Outgoing Reviews + +All open reviews created by you. + +## Source Code + +```bash +wtf/gerrit/ +``` + +## Required ENV Variables + +Key: `WTF_GERRIT_PASSWORD`
+Action: Your Gerrit HTTP Password. + +## Keyboard Commands + +Key: `/`
+Action: Open/close the widget's help window. + +Key: `h`
+Action: Show the previous project. + +Key: `l`
+Action: Show the next project. + +Key: `←`
+Action: Show the previous project. + +Key: `→`
+Action: Show the next project. + +## Configuration + +```yaml +gerrit: + enabled: true + position: + top: 2 + left: 3 + height: 2 + width: 2 + refreshInterval: 300 + projects: + - org/test-project" + - dotfiles + username: "myname" +``` + +### Attributes + +`enabled`
+Determines whether or not this module is executed and if its data displayed onscreen.
+Values: `true`, `false`. + +`position`
+Defines where in the grid this module's widget will be displayed.
+ +`refreshInterval`
+How often, in seconds, this module will update its data.
+Values: A positive integer, `0..n`. + +`domain`
+_Optional_. Your Gerrit corporate domain.
+Values: A valid URI. + +`projects`
+A list of Gerrit project names to fetch data for.
+ +`username`
+Your Gerrit username.
+ +`verifyServerCertificate`
+_Optional_
+Determines whether or not the server's certificate chain and host name are verified.
+Values: `true`, `false`. diff --git a/_site/static/imgs/modules/gerrit.png b/_site/static/imgs/modules/gerrit.png new file mode 100644 index 0000000000000000000000000000000000000000..6084dc1d29c9de0924cefb4d74c60b48011a843c GIT binary patch literal 32611 zcmeFYWl&sA*Djpk!3h?eKmx%-aCdii3C_Uat^tC32p%B#AcGG+xCIR`xFtac7ziHx zBe%WJ{XX^mJHJkys$ILfW_EY4Uh7)dTB~>OXmwRN987Y|M~@!iD9B4|K6-=-EJ8#d!r>65?8Sm>P6hwTrTB3tPx2~_nvWu zGZag9MKOQ?_A0IwC+z)B3vCanxe7V^Yk8-{4U&!w+82VZMJU$N?D8xcZW3svj7a(K zlMd$L`8w7@&E-Av>lP(%cg1G}Y#V0%MDHg2=GrdWyb)TqbE5Z8w*sKofpb^j>g{{s z3B$wL7WCkA+9R4LNss=xx)$;07Z=N|7l!Xy?$b2|U)`r1Y6|K*UYh?#{rfRt zBqJio3I5UH3vs01my0ci6=xfSEmW5}T1)MlZ*vuN%vl&n&u3^5QH;E;&jQz@aQGNI5rK264A4 zAvnKg#_jJ%P}H9Ghj`yI2tI>EY;eYRKm0LeCEjD=25USZN&RMQ%%W~uPp%bP zDmjK_TaSAJ=Nn-n$k>>~<}8L|kYwp2x5Bq5`c-O1@Nec~E3GYg=NIR5rJVXtpVB^l zN*mIa_DBX{vgEOBfz<20$BlMS|M_-6*jNnTCr5ZVL^XtCG zCksI0h9x6~PCbpiBSV6etOFK-Aqf{Rl#S1TjMe2LLA>+9T~mFHMnAic(Tkc93s2py zs6_6?Fki<<(UZRFQs=egZ8e{vOS~dmvFqHAK^OG!$+c$gs}R1<6J)sAjj>{4R18DSB85V-4#%8>k40M#IP z_SN1ze1s>}P#PR#1AIIt&K@a?D80RH?>whPtTtz!XT%8STb}%-a_Yx1hUZv+svq@1 zT$-z8E;tmf1TQNM61kn+{5W-q1(&+-z1F+}Gfr)vOzi!1E&v5yFb0VQ?k}fLb>95U zA>Med$Wp*dLz3DLylYME9KJIY;#X9ATII*p4k-0MKl*7RUThtj6n@+0iC2ZWl@FTH zyRp)U4<*q`-V-r~%{J`LKOa83W(S;fEE+YPPvbc+Z@`E4UVG`I{g$j5>jBoQ;qe1> zDG#9Q(jf3TxfaU{LZo-3@_1xO&F~y zTjT=w+onqIS1jTAPBL-v{Y)yD?VNEkLz+uRvLSBZj ztX%0fz(UEO(ZB1lh6HR8Re-IzLr<5!;hd)wdrv6*=TFP1VT&t|ZS3uj;$OEK)^Cl_ z;ag+S)7}-DBOS35Ig;w%4)^$}-2-0hl>X#1W%}2g#ZAk~)wcuFa4xlgmw`ei@`pk9 zgVTe88mr`N&<~~xW=fqvrm;iC!=Py;c;emsMXxFpH++v`d%sL1MjogjlJ?4xph~7| zOCB7xm_q01j_DTt!%rKcs4o()-&^f2&9;HLbPc4?5Q;mPg;w#Xv-Ic&lV}LdYgG`K ziF`3IQzOy_FW2uEuW(%3T{u~57ujY^F-T9eFUb3#p)y zgT&!|198^oVv~tN=Lri3=L8J!Ru+Q~+u0Bw01d z-lf)wg;@kUuHrd3e@G>aJ1Hi43#5k>q#;f%6@SBzDTkyNt_}(LZ2C-?_r~%pa zqA}TGV=rrBYLG?f3oBhxOvtg*MWG*rfKO2Lf^&=Ob z`d=4P{(!Yt=&I)L%GzB>EYJ3q-{ci@Dh+!mg+?oP zK4+zOrS`WU_5KVAltM@h07eR)L0keBM{}K+$Q?9&rbwIsI zl^`ij-Pu)?22(!Okp?OVt)^68!?zO8hy5M?f9#djH7}tNs3GJmR#EVYS@%H&FABUu zg7UBJ++j1SqT@)qQweOh^SE&zPA&a!Rf94PD#6K1#}j5!dsEsnj;a^L(A_#+(F*$> z-AM9KD$1v+{dSC+4lk3!3ikVKgdQ6>Rl*06H7b61iLDG5c)ieUdt_m%_{Yj!MjyaV zU5{q#`J1Z0d10KI!MTXqyflyAYkeq4aqHG_W18SgH`e&@)H>T8CnxW9uw3&@GcX7*{|um zKvt3W_Wfz)Hy&4`&KuK-C!GOE$JbN0!6(vJH#9;+Wfq>ho_^a*b9%|SThyA6U7HE} zfivy(hcHD&@^W(F$}sP0b@A96KMwrv0n{2Wzdi^fpq?DZe#EPxu829(dwO8*hDasb z^9%$6;>DB`d?QD!aP%~`k2B`IV?8V%(^&lUVKn%!#HI%0am=EO-I3v+fr438?C9vU zqbWzp8K1g3U|k8`6udXLE@ZcK0V`pFr?RXmbgr~lc*Xo0P0aMm;hX&EJ!|?9S{@>y z{~AN2NpWxRgE!H1R5S*ep*s3P)e>MaU)f6O_u2rP?zzs!4ubf_GC)jJ0ZL{pdE$J_ zYk&%?f3BN~#H?0I>@%HYEV+70Xd1=+B-(N`m zVey(*zr~I|^7}*V$4~Viha7}xY(g6@yI!fRja8pdeq3dy(6))5Qj9LIn-zh67lHVN3kwCSPG`6?yOA_pZbQR791_b0 zS$1^WQGe$dluAfMkiohI1N%Q!RmfmIxmbzLGu9ksxL{jBV5lO}$Ze)@IfJQx=c+~T z!%-<~JCeKDT048M@R6tEb1CzZUo5kYXRy;Pbh+?7z~S*OX`o7yH1G`Xc=T10YcN8h z)%Qf#cLwjDq^|(w6@c{Rz}|0E|IR^VP%OynqL$ozh^U)4{I$MUu6NrH0+W@KbVUsB zHa#qa&{}R<2`^JA%93PUS;(8r{Czf)4(f5rCe_h|3|fk~wgVv#(E(}v~Bigf%aT% z_Xs&Hy(^csR}$E}&-~oqTj~Dsj0`FP&Vp@^ad4y z=}+h^^G9$oXZf?l|byZ}V|9nt^w%&hj69UeuI4j5pWt zR-g5+CI3&ikpCN}ntz6Lzmh!_OLJ2_SaGs)SYmdQch7q}_>x&!qYZ^S=>6Zy|I@{O zdzr{XFsYIgsv-Jsl`G+Y2ALp{i~U_{{Ld3L)W7vwNTrYY-+F!dFd+Nmc$(+`WhZ?+ zBo;yl>BJ`$8c}~Uzh57<9liOW0k?*JH^UY1@RF*{#c8I%gQ=PBwY2WK-D@^2w4gt| z_8)N{8sEkV_2WSz?$2*GFSXz;Bk9v}rce!ltLwJ%)z1YjlkZnU;Y}rK2xFzr8@&nw zNJYn5jx(BQ@_#R#42cCFA_T9^>&T#n9$8#vDpjj8_m<1-lppeyJ#CNruO8oqo-9jf z_%n!{)Z-OJw&~oNUTx%=D4NZ7B0i~181v^)ix#(xmX*AT^w>l_NIx_>cmkiae^?^I z?W&)Jr|zFP^dA`YrU{veUescGc&PvMS4DLR>DEqPOY#ht?KBtM?`pY%&uRjp>bKF; zKlyR9$G- z6&wY9OQo+cYxd&_Y@v7Aj`>P`aU3@dub2?a%XK;sxCpA-zE~KynD`ljSYl?ZZH&m`i1>E>sssx@o( zQ)wh>k@drbvAkNYTyp!vfHybdv^41vwjzJUh<|WlPKZI71yU)a-Z(7@mjQ~XTvE-w zy?1zmGDoy+Ed%D8Rc-az^6)zjP)$FO`@om~*5Gdv{D%?vaZzT~VdwodvO(#JVh0eb z1>5W&Wg~<6{RVRXr4icS&=IKf6NSQeH^1BWmtH5UD{)W@G?(oskL3m4X$sh3YDM}N zc&*-QimLe9@&z6YS9oQ)`8GMVrUhMAR}CNXf^-as|M$TB3@Ey^0Idf_ho5#&UzA+m zLh9g~D~H7##^nVbznmFGyuJtcoi+r0ChkP6c~93*n5n{h`cHS_Nv9`_0cO2i+d%@J zmpyQg`_YS4>J$=Np8rx+7)GTu%n2V)H~hZ=&i=b5B;_HA{WI1451hWhf1vz2xqqPb zPh%PKzy*hkC6?X)d!}~}e=MN?xf}GK_XER++`=0;UHk2S80GJoh#>pO|NW~s+ix5{ z6!3Ze4A2Y8H_abRF9!~bCWMbtPZ2^PTQ}%a^?8A6ik;WVr#sHZt%PdAb++x7IW+S3 zKk`IAQVXVsnEg>vDdu$sH23@dRd-7wk05vE{#AP)tn>XikehU(c$|>>4=8>&LIhBB z83EP8Ijd)Vdb{i6oh*S@^@+uiRRK)5BY5{$-?5e~=>co0qPK_CN_fS;^TzEdit!aP zjlDqm+U0>;6xosbAizSKP)?O4vva(z#`fYmOdy3r} z(0y^%f4kualtE~-6INAt-l!_Ova`}C1}~PFOd#~8&>4m2{PqSKC~QUl1>FaVh-3h? z6(~)=?>uVQZ-OSR-rgmaCi3)buH^l?J*{C)%= z*VO!!8bWoYjV!wUp$$>dd=aqCs;=7eY&Jlta1-=Cd+oC7v`fjXj9^1EcY~l;_0q#;lOG$gW*!pgaEoOXv0P`sEvZ&@vR1 zuDcuqTjjyC*DClB%TI`wGis(%zEZWK>YIAL620+2#@lla;bcul9)Y z<^8IRG7bhJh=pp;~c>Q-fvaY&G#9v(|g(T=_g_x_p_+cCfC` zPr&C0srwq*7Q0>dvj(HUvxJx|UZm@Bydt&xzO|yY29e&nZPLA%&7}-2k8LURsEbOj;IJ+4WtA2^G_q(g9K+;)_!STjS_a|cC0w8q{`e_ zP#tM-txfu8i&T}2@-l{``tu^*q>jM$j;ZG_g6VE|IlioJ-l(qMBT`R05>+=rf|LNfWybd1d8Zo|bIvRdG)mzHknqF6!9@roHLd0^w}K9`WVD0az0HuUHJH<>NH*NP&wE@ ziQQpJm=*0`WU*|GqDv1@dC{Ih-FXb~d%Ladx8H*dpY&k6JvLi0>1k|S%RC*O5KW@) zxcj59ShZHc>^D(5VkkIAt6339$@Tpia{BXtYL~;|%jN;XhE?ES<5Rv2NsU^RV{hjP z-1Q(F6aK5(S=i1~Q>#`=a(M&CA%LXp`7?6tM=gHTjQ>#D*3h}E5EMZ2uVIoNJ=G5Z zA!3Q*y*i#~n#H;Z1d0S6U#VVw3w+2e-4(n#(?xyZnpbWt0q&<&MO;d`fNjCHwV9mL z?vtQSTMm;}mFFTxF!uB1oYSH36L?su+RRlNY@c*`Jv~c%$rrx#veicr>LF4bsg_s1 zT*Xj!;Y5#S8FM&74iN&7YcdHG~if!;shxEBuY@Z0}|Fx*)oZmU}Aj-F&7mc>~h=79q#7LvvI&+p(EOTf9heD`7Joa1Y3Z7zK1R&ra z*-g$wT7iO&9DznvzGYFOY-^Cyk@TvPywT)im{KY=VF09Fb&D^}G>92TW@p*8M!d+& zLhMy05-=Ox6qs-0UJD)Kx-#>%NpCr{t^)GZH3`IS{ZWcZQirs7_&)xZ#FTu$3*TP( zg$xi-Q(5RoA&aI}-X87oQb}di3xsbB2)H}xf6!N_{M7ns*)2d}J0V#-z8Uv|LyZ7( zvZvAS?D49oc4&8*fICnP0Si(ed8)P2X5Cmfw)JMS1#!!fE07X&JW#>2{`n(g3$l%c zSVn9D>Ay0~M^CUZC__NNzDZgY4`p@Ses#TOl%poc*oPTZLgKY$~*C8>jdUX8_=;WR8j!G(5|c$rWdvAivLI~{(SZmU+Du7)V#xLGLv zC|LHTB|&YsC%!^%ppxKyhtf>Z##C8>6Rn?GOUfGXmI-S)T;GU zhu>9zVtQ3Ep9sIw{$Ww9?aeQL*T2sKsFpHLtfe1J_=67zE@;+LwWzTEGrOxP`;FR{ zioD-bMOWq;;}B(3LhsQHEmmZJ&egWn^i+u4MPK-!-)GqKgR(Jor$QR-5@u~PWiydm zaX$}y1KDH3bC@b3xYT>v9j@&MpE`NSdKe!x;c=y1?w@C?Oo8ZFQptI%&+)I=&2NRo zLICkHtUQR_&soJPMU<44(nJQPU7~E%!+YYD;PwKqy$~|s15zh7F;Q>9VwRClK>>z+ zQ?RgttcK+hE|smhT**pAThDYNf>`(&fs>jE`zFmfGFUgz%qe}?L7+V(&wyvfmH>R1ECiQpP7J;ksoMy#xskepO3Qn*FFFx!WUHpK;)l7Y!2c9S49{2m1- zj#=;!OLfS}`nox=KpF0vpgDIH2!w~5KTD20Q_lSO6nE@2v$5ZYnivC8!OiV3?f1Ju ziZbn6hZowgwCc0YTUNJl|GSP=CQ#C=OooajAzDCTd3IvJ?pdte(Ro?y4wsd{u>dKA z)@m>}e~clp-)e(WOcLnTX;0LmA?|%kBywyi>E(7Wk#9o9ej|q@r_g1yCUf;V4s6}} zbEnWFCl^(EHcjVE8yosZFh>O|!QmfU;&M{&3eN4Cz4Hv;!_K_~J?ZYf>7Wd;uZ&PU znc^dy?wC0C4bDOOngYdoxP_uNhNKcD_nR0NBpwEJn`gdl(j<7pctS=?_JR`c%n?PT{o3CpN`{xnt^5R?wL3C7-w9@FSOp; zxoNPMYqF)Ah!)bRTW#KT5K7>ridk-cl`mhmy#7q0TUO!TJ))ZzY1seL@^<`JBnY@! zfJ7=&8MDl4RlftCgT=bLqSf*=R2AJsF6B@p>kK!3BQ%Qr=_QQ_P0aPU6xLwbl$nID zSA;(g91=_x5(k)3iDHKrkX1|t56B!8WqBeE9^as%hM0&-Wz1oR-2}_g2hFrVpiq;o zY%mzHOSvCM=s~=@9Z|)c6O6n3g+IcCD&*J9R`n?IfX3zOHv9c*u-?e~xo_>eT$Ns> znUZ+J+`wP6tm2jeQ}#zizr6s&@QkV0M}Vi_QC_PZE9 zTW`DomTSG3LpvFoz+x~BUW6_9)ldKe( zzOB62gpY1xhNZDX^~ZXJ6)~SdJTQC-tyfq>x-WFH@ua~~6RfiJT{EaAIJ(4>0>H+S zuaXV5^Mc3N22DCOySLgzx^K%qs4KtW5gf(X+hNp7ttD^|RI<_vFFtZP(ZT5iu(twh zt;P4Ydp%pITQf+dd|N>}$aRgGO*+x@F;Q$9#=pp9fa(+GgIUSeo025ZRb+;zhDuu4 zJ>7EI3B2B{IE7;>9S6%o*n$_MYRY9FSH+p<$V=h@hcT+|9oRD9o#e&DqWfP&uNeR` z{PWL1)|4^JCHsY}5kH7!&e|VMXRPIO$QDGgSs(BtF>p6vl-;E=i1ric4On02y=lr+ zLi1O9iG4chwn|EP4Cz+P{vPZbkiNFJ=9$|h+vVqzmT;cO-@5K2nG9<)71RjuVqB&^ zI+X@H#_mL4)J8l6@|wd_nd{1+H!i}g+{+GZ+fN40FS%$qrC^4E&T_Dbo=}#Ov7L-J z%{?&A(MK#5uowK62wo3ZLtq{8nFMxZN4RyS8G8;oi~x2H`Qfs**5ulP!T$R+Ld`0Q zE<1qKKo6I2tmv{?>mAc(p5I|WH39*C8aUQ-wh>Ke@6d+X2L{H=4^PFNKZ!Er_|XM> z#yZU4Cef#}FcX7En$%jE9fkHh)?ZxZCi-dEdb%i#jTP`;DDe>jfDtcZz40I?(>;?7W4toS_BI2F|>VyXc=`Q(1ye-_<58lc8+tJvOs69wjHzLeuH!oVB&Gc+J&tQHi=+ zciJAdK1`Q<=46saPP|;Dnmr{@p{}g`(7oTNvt)0rM2nG zJ)VcYQA_T+%)B0eItcr;z!fK^LuJ=hp@!eT=9Ovd&fOT48kd6q-H{T*QjzMDH&68T+EZb2ihf^LH-H#9hBFm+iz74LvhS3qJ4d0R~{ZwXO` zIcAI1IJ!f$CtiEjlrT|%JI$yN7&N1xU0)ZfHT21k{~ltooHy<179vGHg|kshpFnOr z1dI>uNL=?doLJX+;ZlU+fh)o`P9Yp4%#CyGHjaA__0}@^}5FNCmo|^FeID zSPi4-B0mp3yw{<(`E_R8a*j*L3Ea#@j;E{V%5h{Bk7U-B3PfaSqXZ1(DfS*^q0-4z z4Sya9w{!GZp?BAR4N(iNUC0=fl4?4SshJ?T7K$;0k9;`3cf_GwR= zAn0_sB++j}h28GjmIOi~gJP+>jY-s>R8n5n1`&$_fkH%2UY}O1juYF64sX17*DMxc zF%(L2&*)i#3`=YfQN6#FHMo#dcENqO{yFXWeUD6T^jzKf2KPFmA&{<=Z0_TFJ9VbS zN|E1^I~sGb^-MY^B>KBE!w2J<@^@nz`Q&87X3rIi)&~aMRFc{Ew!LmUN>oRpYIu;) zAs4QBww|hv(u5`NE975qTT;^Y64?{M0kAncz0A_rNK~I(*OMU`x+^6{{xk@j72bZo z+(!v}WoHazS7Q_WahxK}Ts-H)vk6}$(jpR?I5n_NRBBRwzO^Jc|9Ks>cD5uA^-tFD z&~G4Svv#I>ng!ipQf@wv+>c3;`A+DN!|u_MbO+%myZ6O5SR;=)XuWHZVi9aP_Br-# zW&xbc!%nYi&s=MYAPY~8JXx`~`iEMrtTwOS3wn|=-F-l5MR5_{Jp0ji{>0$^#X6xV zG3!EYe}}8jRtH%L%mYLce?x9eXB$O!9tUC+o50Xt7yJ0tPaeqvrG= zH~Fi7F4@5pEd$#yUs6xZ`qSWQKB`wW`GJ$7VrfRX%XU@xgwLey`^`ga^}^Ipo=M|b z8u@mf^r0xI5$C(mVui;TK1ur$q=O)*-`Jdc{3L-&DgUszb}G+JwkvT!!T&m@QU)!nh7ha_c%!F(>LpmD!2-Uw~+RkP0cBE>PUBh|5J+-u1^ zpfr%Glgp7ZyHaDt8OzF z3Q9S4^;{gFngjjvSnzqEH>G{VV?p|_ z1lT9}r(*+V=Hnb{bLbisbYI_~3NAd(ddz6q$=@nSM(OFeLy3H%d@xd&iTxG1r-}C4 zSi(NGw#yalss&?6>`+4R6oG*}(m`ewvhzmJKebVGX?|`btxVKdf;=2!1G zGWvv8xVQ1$@G_4h6cybX5&KfEeq1ly*)i6dFTM8A7wZy!$QhZdZ5< z(l#z|uX|1xhCAD?P04dPMs4todZsg68tj*JBK`e-Cc5w@U1F>HP4f&j_+&7O(=pl5 zv#0m{XW4Y$Z_9Qx(uL%E(veTbk6d@&ky6l*>6Scm{rQDAwSQS8=Ji{TbK^8z#|XBf zD&A|_uv=>3a4wQYg0X3nMOhJUeS3x20jcbbiSBXq33lp$7h}dyKI>BpD@Cm;o@v-9 z8t2z#OumUu@{6|x$3$&{5<6iHpUEdB!pk)@eQX+H1F%EYrMP;eG0&B0o zNJaV5BAla1kV@y;PJ!X>lO@_+fYF(K2Aa`i5iV213QB^^Por(K z6wj}FbW?rZ%`**p@w#&pG8%2rz=D{?<2;{Aoo6w>;w5x))P&%%hBe8jiV$`{-dRf@ z_9LEFCFw@?gR{M$#icbRfB&mCK#fD-F?5%_HK#FV&Lr-0U2aS|rY}XMo%GxCQW*@- zUnAyEZolvfH=&UTet6N}TsR0o%QweoJinwOLKju0p4#8@*Y z7y`v2biLDYEs~(mV~p((uWi<$%JLM-s{;}B=;CKwd%VQErPzAy;rNTc2j=yqV3KEL zHY20ubLOj8ren4I8GNjGRcTP;9m0+#hfk#IjSQJe*1$K@PtwTzE6;%$*@JzRKrHo$ zFocGvji<_xD)~y2sOy&Tg7vfvxI}tWB>xy1Z4fr@3J#x+`Hp5_@HXyKe(YpDaB9T91pW_u5Iu=Oa|JyB=Nky(e{3 z0npSQ=e;UQUDFNDdt{INHxm+oGF&u!*|w=TllUn~f!HX>ntvAfy`r*DOmGjwOpkcp zJA&8QRxnUxm>ajJ0%>|JkI^B;mS6_8A@UdO_S%LuHw#^FF|gmNqL^+jZNeTAM5mrn z%C{osr-0217V$YoP|A4);3P&$feUHaD3TUTL|0Gp*nu=&*I%h<(sI`cTemcSa-) zZ%0Jy=O9-u+pNA1tV!K=fq>izFoH)&eqy9NF^m#SB#ZVF%Ql56m@KNxusw^(*ffuL zFDRYU*=WxKXj>I46HtTf;&SJ7MAm zR3E6r5~A74Qi|`=?}`yG$4L>TBqCEsVel>|ink;oF$A8*W%EZLZ-pb&e`3?;c;-%Z zDYB)MSeA!jXkA$w5~3@QB`}d;)G^HFxq5-HE?lTj?J^g9HA#|mzklUlN%)*l^_oU} zWaj$IEM_c!MU74^Xdqq{D4ILTAd_u5f8V8=(WE|p)pjJwiMCcqs6Ho5P;anfL=j8N z9Pnbj7!%XGXamSFTjiG)8WjNX-23o;;UjsJB;&YxQrR;tSEGt$eo{%2cS00jTP#5? z2L!TV22IiIcvR@%l|?kpE&+aWyO04c_Hyrlmo@J`jJeh*f=qA`dQ?w8WNLrn$;y9` zsX$eFjqs@V&C6i`oWNzRdtQydCWSCID&l$*HwDQ@IzD{KARxW?yw!5locIk#Vw9NL z+=(p9tr3Y7G}7D3tgH6;Im#h#c2z7+>3|Yj3>Zy={4G|*rxj^w*_@@Xy;|XN?DyiZ z@}@Cv$o3B%4R9_asqx$KoXIJYZmbk=Fs)Cbc|+z$R%b~+s2O}QF=>EO6uQTm0Zb(W zyk$(asO?Zgk+t*iSFo8q`r8_+;HNW{z4h>l6Pu3jQO-B*U_B>&deWZboPfzYeJ-OH zim^ydqd-Dw@Dsy$<9A_m8? zqHxg*v9zkOy+QxnjE)Nd0A9r&W%chz6T)*-E;1et-5Uw{YV6(9UJP&?W(-+Lf zHGI<4pH8?qcP5r_DBzW&m&V>TuTo{=gmI@XPJv`-eQ+cJjpK0=u=3ZZ@7!|oMqg<# zc7NvF7tDMq%)<1H-Baze>?c59|8%($&9kDK-J4L!(sVjz0=}qs^M?8EjRBA-+GlQI z?_a!TWa{|tUMWaO!y zjgfT3QuU|>RWO5FvBY1Pr=lxf>yRtUkk!BAdEsZ9XLcnL6oD5zSpw$ycN#E7U#reC z*~7YTOa{16yIg^*{#qkPjfq9j(uVmObU(P=i~|Wj(XbX48qdXI46M#( zzbyDPz+IC6h^3wvdsNW09Jz*nsP8hpg$VsKKPM~OQii;cqHIDVkfho7uA#|8yoeHk z{OSHRz4!%xcJE`lCP3j1B@mZziGglgfO{!!SK3fumLpyz@uWprSrF_&SO+2KVmJwU zL+NhkfRk>V7;5ychg+Ts&+y>ucsvMfq~TEoGIOAqg*>l*+>*GaU?QXkqb8E@rvm~J za}5^CBD&5`qLwlu3Syr<`?OlsntQ$D%9lYF$PB1RM?({eT9nwp^4Ds6s(iKJZ-KDN zh!Bn|db?3HPGJ6MapOHT$1Gg`fZ6JQAf1I=eeZv0Yh+AY;SW5Q{SxE{e z4@vAU33#1{iW3La?F;aX+j&8NlWK@X7@9GK_Jak+(d)49>tWAtWH2>z@_cg2u327F zu~&CVO!=nuRp_<`Qp4-8w|OC&e6j`_nszMFXEdJ}p>|f6pAw%eP2WO7z?dyQ5r2aCK znWVNc4f+9XU9*@eFC7#%WetB&HwpdHgGJf5L0Ach*?a?sA9>w7S(V7oWYv51w&#-|VX}=Kvh0ZhDv5gC z#NpsY-j|9jXn_e4Xp_%y`A=T8Q~8uQkesR+agW) zI{xsk&rHeI(foP?5caI+WLHU`=kNRvQE^kP=vlYR`<(j)-uNg9ygNF@K)6$GCr z63D*Fo?H9PL5^_LVLbYgRA1=xEHEs)N&9CO6l2fJBh-VETAe6xp0-{V>qUSCjmyZQ zez_E#kXvlnK+`;^1}Bo<%YS+|SU?WuRD4T#@i9dcy+9W)df03vn9~p|Gqx5+wGPk+ z>k{GiS9((B2>*zpT-EgN>xy3r6C{E(eqHL@Gd*MmJSV4fNR{C-r<)_f%+v#*bZ3YY zVZVGQg)x|=Sa&1ASV$K^}J;#@8N`cKux8Zr#VB#+m{v_D= zQ;2_5lr2Zez9lsovI+bti5IuLC$+Z*KfYRiZc7KP7r90Szbjqcm$EVCohZZDyLZXq z1+$-+ZK7H+6YRm%WZKDl))hGRlq1~~2szPve>h7Zxn~W zv|nC37RkTYrDPGjs|0eRR-w|8HVNqK5oq-61_yX0hPgkpgvtKaQ`0x`ac&gB@Iq_MzFGak%?GCNt<4MC{L*oiZ zXfM%=xYNtN4O^mb%N%?Ir_Ota#cS))&^LSq+r-mJa?)S{oVO-bBE{zt4J;m88j-(pEmz+d)53aZ!7SVB%=agqJyCE!cvDK2ucBVnel1 z-*d%*UQFjv`FuC`-A&+~GTiJ#kTG2rv#>bK@czRdb1bhs!JOn83FDqvk*UM|G{WcY zgp(Rf5v6V)oZj*|W|7q?TO_KnA8!IH2}n%hIe?ApVG8K1ICGC|pLmk%N{1lWxAnj* zYslpEMHj)Ely6|9wKAAvPaK*9lhH;wA&QO2yV)l%4fA?Pr-mKYQtoM=9xmVmo#!4A z3(`QVXSnd(viuPj&!r^lf__}$EQA>1F=*$5jb1-j$L=QgtwJlW^bwTvZM(cWaF4pA z1&X3G(m1YnN%;e<2e#_jo{HWnnSB4z%KMJ1hm}`f%%v^Vii&6(%3l&LR#YKHvjPqpXlIm_CAt&M&|7M7AG4zd(;*QnOGqSwvl(FmfnMib0l>(!& zJi{I()>m4Z^|A45pqK)*2ADIcVR7e{VH`-vNq^1NmU#~Kjl zA1(1>As`>Y`C%YT@=HRZ+;$|ken#RTV;Zkwp|PuZV*ziR^l-=KF0UBSymp20ED3Ju^Irm-$tVuh#Qcs8D zASvgjZ4)LZvBMpvGwkTGz!<^n40e7zD&A73s==V^C`}B{#4hK3lB7(tzs1WZH(apmfjYDvK)y9%OoRk?N~f^3IMR?p2doF zA}WZ7`5EqYeF5%;;yPXk7D{3GzY{1~^z5b7#eEfJ=zgro{8A9R|K0u=!2t&HPhDjm z{uO+t#*DGS=Ack_T~z)m4q#fEK#wp&_aeY3>xNAwGyPQothQNCTij}!026D3^_|ps z4HcLYvi01d)GLWuohRO;KOuVm?LZjWIQd|NJUESQ3AE`i5}I;HW$=oA z-^g1XtB>ty-l&lMRXc}%SN^EQVn-%*0llCG!YY9#MJr7eJUiPXokJ}1Go5#?rC5+w z17^b>9vN-uS~X;PD8V4jXZ|QuS32|j-Yc)(=F$9#qL;c;O1pKDQ!vvw)!t!U#?CK1 zub5bU$e808cQ?yP0O5*Ec~-pEE_0|^j=Yy{h&NUf`(6)g^6{%x&*vH%72|b=6gqMi zdh-FD&XT+r_6~CVVv;$zTsU>ax2_SO6-9qWuPNQdY$kw`R9Xah(ojVWH}^BICmyC4 zi;Lk)ka7@JNKt8XxPOC%r)*3rv8D94S+t07<->3d{CGni91Ft6XK}?^4U!C%)-^kZ z9(Z)>tsG!tNZR=KNjWZUm_BNDXh4^g2(aXLrN&j`s0$|GIsgYSR9N8%Fyy)YYv3j;a9C~z>tQ+mtVKX zN(fmoY28vxWC*K86E+M>UH5J7Mf*Pk!! zs`o&OvIgz&W(2-I9P}A$#GsBbBo=-;R#7m^_4xIzPaUZLQRqQ!YRM5cJwX+I+1VWG z_uQ3PcyWR51%+<6P3Za7n1{PGEDf67(`Z$QGEVzBGzIA@9_;4I=H0!mdi87v1blxO zE1qnO*V}iY+A6hHSQB3fZc;+bVYUpY`l1$N)f^$a zZX*MTCzt-^p_WL_w<^Z)@J^T(m{?4X+uXd{HD!R(Iop{J#FMCh9rkckft_y5)SS$F zcj2@e5HQ#v;oes^?o_zyr9S179d?Qhl3usQyExuY$On0LSGk8hG30*Ndyycwu=CL} zL`(zk2hE$8)Flx%xwEc?xak(!8}h*O*=*2Q=k(G{!vN$d!~%crVtTkkT1l+Knkh1C ztAvb}`ZlI4;_OTszbw^Mh#4>=U=e2e<}vpa7GkPO>qLf){0$GY5w@y4G34UdqEEN8 ziAP(_KAn}rmnwyNztBHwJI0WJl(S&zi_fG)!uZqw;_XVI-zGQ z2LiKmA3oMTYzms7$lk)AxlRl2q)`d^`ZR)cjtbl>1Z}M@%b-sdC(eUnLEs)G8O!*z zg^rmIe#SXWN%jUdJ5g8nfAs81&+vw+36Cv#wfZh~l!r1$*^h|6hCW8P!y@?R`r} z=~4v*0cir#iy*yA2Px9KkPr~*gpPDV5d;B43B7}mP^2TE^b&dpDWOY?fZ!9)IrrS7 z_x=2g=N;qb(;gW+bM2Ki=Uj8`z4q@wI`d zV8*!>JC_vX!pWj2NgQh$KNE?kL^A61CF+(EkIVi$c0qPeIYI^?v6TQKE6(M^?&cG( z0rC6872huX;*oR5g=j#|G;d?tL2R${(dlOq`&zHEULlq(0vb#Nw2_w5As?(fm$2#Q zQz@i|@P4%Lk+*A_B2*N!r^lL&Hr3pK`)10RvB4`#dUB|-Y5H;N4^d-2+|v1Saa0w>^xS8PnMot*{|VSF|R-2^8$8&AfoEN`)#MlGe)P9>pQvpRL$5mrL~ z)8>K)c2p8dZ@;f_+%JQf2z;#W`uv$VA!gV==IXiP8!^Jb(tND~I`&A-NTIKfxd8C$ z5QER3_*D)h!Ivx$-=t6qR!V5J5Af@bFSa5=iE=)?MWCZ#i2$@fF5vYTVbQ4#d-tVg zSiad>d~Ca>l1<6?TWGB6mIJ!nW@*nk@DRc#<9a=gGP z{3v>^xf%gM_M#2?J!zkA%5>GVwhon@6spexgCkP~Au{&0x~;!1fCRgi)tolc4k)z!-aJp4R zJM+)V8SU>NEg5;~=MJb~@+Uid=2Jg~maXT!>nx2^6d(bja$furj8z4kApn}LkFcrU z#yP|8X=cA79a)Ph0|)mmE@5~}m|{Mkp-aa0$M7L*n~}(>7*lG~*AiKU7sJs={O;i% zV+)NB`I6elKgLomG!_kC>SZ$=J#_y%SVHg!sa1B{e->rZZ}xo1`Vt_1+sg`zknBqap5p&Kd}Pu z(Dbr`TeGF*HA-}w^SPtH10wL7qSt<<_md?T$UX3EBjD5)M+1^ z;~|@@Ik3GFa(&#!EGusRQNerw{I0O!~vTUp>%dE0CL+TPOj~3Y^_)==f1!4lGj`Xj6Ej z8efqF!NT05SBN?1V=f)Sx1&)FnmgH70ff5~_O4lr-75Bq@1K%!8+JDNxgZkL3x2vn zcuu#wKuFf`(yN%$W4HIq`=Jy&7|o`E*Tb!EXF^u$&1E3rq>L`chgWx5KD~2YYH>!@ zienP5woSp*bTguPyF-l4a-2nQJ@25_)4g1F>va18p!=%4?#K7(D{K2ngI6c1 z1qxl$SUDx5d3TjTkn#S*2WOKFJYRhAu#&dk?5%v{0;$ixQQPO$%i0nhav8nOx`i3! zC$oTGa4)L+o*GJkp^W@tL-;gb)(|?i!TCaObj{}VqFtPOYZ(lR>bhQ@<2%JRY&6BzirBg zT(5J?=?CA7!QAM31gHz;L-mz1QBAgu7c(*%KIN2b{2sB>tu`))MD!Tx+<60j29{Im zIJZ0JRic2)ytw=({mhBH6tCJ)CpFfKN?&m@WOrz;yF;n5Ki>l2)!9Z!4&Scq#K}K$ z+^iMV@n!?)nVUwXtGA|m1LrMF(dW@6eDk=Y6EB$8D0vD#&%R+mGibzxV#QvGiN4mA z)_!#0a8r|LI(>^Y4aRfr+c;f(Mz2v8L2B!qZ6gbKQ*^evEkKyV5Qhwti@m5_hfV2c zynE>z_?GDK_cHt~k@;7~ar_jPVkRG-yC%|do>wy3v^J~FmSS%boK1Y^PZzlu1%!4% zl;Z#|U4>J_$nyQ{)7R=b2*Q$L);Upq*IgcO0b|*dTlXa#slw&T!aFrNoNOUA^7pku z{2HplyP%J(Q^072;>7dC>=Hf(;Nt2x4}ym(-g84rd`i6nYDQUbub-dwCh#ZmyS{46 zoS&|@9!rVJiP~~jcTm`5k;S~`Z;LbaScAmOJwQO?Q~|*5rlZhk6NXa5JaQ$Be{FN; zAaqP6mAquiunsu;)6OQ(kpNj;g5nl@ToTUcsV{Qpd-u&$#xmY0s68GnZQ}$Ky81e$ zLIW0_Z~+Rz0G3BKV08_Dgd@8=GY-KM6}eaDyXeLofxXiGifc(l>;6K??=%$f@hp`O zPl=*U2k4U?U{}Bw3G=I>9K5I1_=>C(;^y?Xj-QGiZ5@`L@e=I66q<$6z9_bVAfE+K z;d~Xb2qv%P!xmJXeXfc6KnynDP}=m5CX3d80S>!;d$;4E9uU>{_D;niike{Tfj)4S zVP9U46%hB{I4W3cq(QBcG02#aP+89KWdtwh-PLeWTpD^899j!#JnjcrrxkO9{454n zxCH;z9jZ7NwDZb!i<8pnZYkWL;rlxQe|jHk1YFWKZd)q)3=TRUo}-7Ny{9@dB}-dJ zrq7C=aNW@^b3-FdA6l|)(UmRmIl~MOSAu+|1i;w{zdw^T~g(3tx4nR0S@|KYmbz zH{2@d+g0c-P4-^-Q->TJvbljf>a+sR=y}kMoGa4OwcZ>lec-NBs)E~Z)oE5iqz`4> zBgd-{CYJmKrwb=1i3W@5A!Fq$oY&t9G2pgNT3DS;q)`r1Q`LB5pMGf@@@J@JWS@I9 zOs#VpZE9n06b$UD4XdOOt_(j1qjKNLbBapYKXCFh)gzKNz2YKF4Twt$vh5u1^&mXF z=oT#QCz5V;SjaQ_o)mFPame!?wsn);$yLsZ1ylBYl`b=~UbONs2|@2$tcCP9*m=12 z*?e%S-S6(st8BTk=^~chk@sHMFK(CZKiJppbEXsO7-Nkwl|@O-h<>kAgvrprbU&Z6 zdp+H5bQwRYZMi0}*6~hG=&Gr#)9%N%<^r?^YSU><43dki<>%MV_Tnhyd&`Sg2n5#9o4y(sEK=T$`Le>x2X$CN^>N5# z)~~A|n?Wpxvdo0+ANY6B(ws@fc{}vX27-c#FIn_}ydtnTHrlBcu|O70g~`22(_$|< zZsxB_<7W>E%`{UC<#(_ftMnQPp2nI=M<56_;az)gR-Y6z+xZ2mKc@6x(vjG|cp8r% zu)lm3IC4PMpFBkW-E-A|*aP}5lMpb2F1tzw!={Fm$zU^cJ75#g^5Zs@qQ!GI^-f94 zefw8|R&d5IhO7wFM3)~Lk($z4O)6}=E#5)}`n#6kdXye|I2J&*;HKBWj z(BP_TizGN*WG^ZWXW=8|G@`#`Dtcn2KbU^eKuS$^N{vqm2t?9|_wSC4YceWVjlT-lp@gFlxDA$p+CgcTl7eEm`zBe@$ z%;#eII36WwU03*m0GF`4#`IM4d<*1)-Sxj_mcvg9G(QGmTk^E*a)R|*5Q?gF;cZXd z`p28(IdG}SW*%=!AY^Wb*SxR?gF>}{%K8aovZ7q>lZz|wYqw>8$|!yJbOj1&o(_|A zO<{$|l0m_>zLgU3fwbq$-8bfTEWL%w-JMMW1_OvUnnb?eI_^_F&oJ$hiQr&g>e5#+Ja{b(1Lu%>yG5tE zuReHxt2RRbkd+WUmwC34AUuK%?19C5*qGz_2a#cR6^geVSt1ik3~kJot@ibt_6O)~ z1vr}rzysr>fftwlb@X({g3GQQA8V0S4O-_aqHX~)an8N^Xv=~r1mH)ut5kqCS>ju+ zoJ9jR_-1_)@z~Eg<+TkjeHzV_CPRYA;amsSaGbCGojx~Jj&aDHBVkqTz=*^)Z9?>k z>7!=8+geCN9n?XLsbKA*=9?#MM-vyzydn*Gp!p2R02V@)KvpFHJKxfWG^dC19C?T3 zXYeZgPfy3&I8Ri)Mz>4*YNk;0W^!zU!X)X}SEu2y@t{S!w^s1zE`%r7naEXHa1CCYKIfm|KJI$Kiw<0%7j~oVUIg1Tv%+^k{t)0 z-o|2XO}TRy-kMMQ^qloj&0{(w`w}-QX^0Y0qGlz+HgQ3)l>j*!4csA%)+rts{S~l> zDxN zX&5f?yEm9cR5shGeCA4D*e7^)o{Xc_sZhTk$kmbuVQsKBJ89Q8lol0Y8M?3EE6nX z1XEF6^Nd@0e%L8pJ#sY)1VPS|cM6WAKiK@@lHgu*>d|CeR#_({*P2LI+^Rp^#>KbG zPm`Gb#9V4f@y#qjMXA!k$8%UP$#dg~1K&M5Z+(OmSyxmUSRU;``9riZEg#Or7&2|5 zZskJEf)c;HQ{~_91B%NWclFL0O8NfTdqK6T7y&%`Qt*;z`;7Vx+iuCbTFr^%85>HJ&@P7 zj{8STln$j8s?J!__kwJkegEMLUsaBxFEf(&;%x+oySlK?@5Y*%>5{9Byv^zm>wnLaP&*07UDf5xh) z6*?oycoV(CYfs(^K?M#)DaHGJYZel=lfNRQD@Vk!u8UVM3RoZ`L^b_h$qLuo+zoqt z56iy}X#|4eK}tYk zF3I+VRHOy`dFS(L(+`*jDnHd|OCa||@5JDL@lcokykwMZS zCq7LIgj+gL&}v3Osx@l8G|-fGvLL0Z$4X+jk{b#5)PwKD9LH<~)fVZAZ*RrzCnQ9Q zg#}5$rnYQ+O#E^C`znjIroVGl>-2y6D1XZ-GQ5i4>wZLvQ8t-k&%(ZXz;X`vy z4%9=LU#%S&6z5`1Gp=+cChN5lw@bH6$5>_toGS~~D1|@DZHILO1o$IjwC<WZMvp6 zaK?0!>!7Lo?%M(foInh{ACS+gyl_)(2^D!7$?9cZyf@zYa40&J>HT#=_L znS-w9wu3Fbuya9hbAFy9evgk3&~qlj-hv`it@23&Jz09yvXj5{Ed?kJ8w_!J@5x@h z$~UNi0C;rOS{(~vtCPl$(4Y9Y5AtlhDZ!od&IjP!|U$UV4xh0@; z?~bpPbF0Xe?l!92=x0cE(HT3Z(a`&o1Yu$B$Y%U39Jd+q#lct39ILbV%QLNxKbas$ zQ_&Gz&eU%Dcnk}m=5i2L?RLREey0G%SbKiMfoBZ3KJBE!t^L-|ts|}a?mdsw>kc(~ z80Q;PJ2Tb3rvG@jI7f>R$Na^1S3W*Tbg@%zKhmCc`ex^*x=2I0vmCWB7u2PnAa3;X zc5H!!qbGs^rTXRG<9oa1?_rBfRaC@*)1Eum5QT3aRCHvcgJ|jQ{2b*p6a0MAE@?G2)<;;X_q+{PGkiteLZ)}qF6kw(`~cy z&a8yrjboIUHvRF4$&gEKaZTzi51AE#^??ZIN7Z)(QLz!yodM?gz%4;qxRNttm`q17kBz`l>$X<>7-+c|VaCsZ@vaGvXXSBh-gT zLoV7F|RAiVX}ZiK7+m`i`}>bM2i7(Fni1}fb=2ksDclbt^! zSR5uX>{C3&uSfZAzYtpoIz>QYO>Kz91byLsMiv*XRi^i^0ofPX2+{dEyBSZRI!EzN z#!wPwZybt2Ey`L#Y=ebNDmmef=L=#)=C$~MmxjqgT)~N7E1nI%c2szSa_?zx5JB3|vzhTI-5k}jg;tVHZJ zkawD_G;hty!UQ2r+=Om(n=kzI249b8z&{a6DkrVczG6o;b|D0$qf*$!tAOns60l1so}3>7^3H! zC2ahG@r;)R^95fa(LL7Y!vagKnERg2@ok~>yaQi!&4LGoW!>~$iBo+~?#0%=+(Yyz zbrvZ`Ya?1pxYh~1chRDidTL*-ontRH6Qc6Wn0kh^Sr0HS{{PWa?(7`yY{2J1Wx^53Mim0o` z3KPjFM^XKOC~Nnz_L^sr@0(obczl9iw*2agX6@Y- zp?M`F#ka_&9_M_2rDpShFd}}qXZuwNszpy}b*AG9VDCzPsxKF0hfsNCV)9&i`2JWj zi+92x1q(I_xXFyAXg@D-QCQQS6o=$Fr(KPo&Eq3~@rh?r)Hc>%HqRUV!BVKXO_1fZ zS4w@-!`mKu!a37-d%|){AnXYl3SLbN2?KPsIa2SczJ)!3iAe<alsjr^l%WF;R1&p?Z@Y3|e=943<~F=?NpmRHOI zbl{dvlfG~vu|STeW8k9ndjiMY zX><{#VAX>owL4_I3=Crrsm*U^CI-Bkkk5fwx9Vv zlUE7A8=tWe4^@9aqRJV}=s{l!zxv9kdr-M7?xE8T3XrF#hPO7?gN$97Xz$j8z{|Ub zmnW_Z;yWIJRK6yhF7)->g8hp=61^9M2F=jJALbF)&$;4B!(H#40Wzi|9bE$ux3B$R zQwMQAdm}OVcxiPYO{9XAz$d$^z$ZayhZ`YdWLlOhXnHW(yq}{vlJaC!Hkh2Yc&oVyRe>5OV-#6xTCF9u=Thfm44iei ze1usx4XK3LkwrTW<!`y{eGOGT?3vF`Mm zIa~(*@X+LCDTB!3pkEc|aN%2vsQkiBaRLjH%Yii*&8}XYtYW-qWL7a{Lut1GBe5-f zNXV~NngF!RJvkE`76P2N>mR*C`rOO~zpvfI?eV=b1=kwOgws9tOJ}q;q~v}U4$bSZ zG{+_jJ)T0i|5}SUnDm1PEwAsGh+a9gy}qK2i9TE8(SfS}Wa_Y(5ko#!05xw3nL8oE!(k!XXjvfde4JoIVtnR2B}!^?%|kkJcqt&pmZ;gDEf z&BCm+ju*XVCk=V4aHqg1m>8p6-7|r{Q%jbzCOF=qp~{20IZzMGyH{f8ep3CMW7eW9 z?OnK0O+u%ebLZIXgv$GuQtu?GXW#p0Bip8LAKjYlxcRGjv2~@8!;ld9a6D&pL0~a zV7-Zgh`KE18HwyCb6lrqZ@_u4=X&mAkqtK~4^2(wg*c3_fVs99SCN$PxM@nfs({Oy zH-kznzFhlHF8A8(?24j!GabgiGR2Aaec?>dM8?WV0V-dEVTcIHyWAf{b7^b6PTlj- zW}cfSFT_pPJY&vhRbQMNzFuklZ#whJ47o{i$v&fugt3SgxpvAyEyl|q&Paj*>3*A^ zM?CYlGHqJAwr9){Cyb;lv2xNC*do{lSaFK%!G7d_EQ5TZT_Mc8b6M zO&Jw`T>ckETn;-+8w3BJFnfRY2T1idwwZ(jW49ABQ1&lXvHwQG6;bAvES}OhUjKK$ z|49F8ZtMGMj_nnEOV4>437O z9~-+Ez8VIZNb9=(Su zvwRtEvHEid=b>#nG!_2Ls+5@9`ij;$KR4jXT-t|04C5qcIx)#y*Z% zwq-B52XO|n6hGKPuGfi=b5`eQ=QQgmgJ_z`3dFtif$5aVfwk|(kDeHYh^{p&{n9S3 z&iqlC18$tBlpNbkI{%Hn58x|2AuGTg+j(8Cjv5o}L2o3VuIndGOiyrD9~6*fw~H_4 z9JXJzp6(TDC}PRN13bVtgfG?k`?dor5AUfHqzC-Q$m>`(r?$C}M%D(6{)TRzyMg9`iTDcmsU*3O`s)#H?N=3J}wfsxIQ_3H{qtmyb#DD zojpt-I7?f-ZtLSs6iwJl>Rh{l)4i-i3BRdtVg>W=Up&|B91auPONM4KNoOfn_LZI< zpZ(^C9tTbRhKj~@KSz~rWFdQKqmervwELqkmZS=(3DlkpF-O)`8Ulj6TO)sZ$TTER(UU=^+4G%~WC`N$+oKk#6X|s1E%=UEr zTnE0bWeB4<-C9i9z7#(FI`K_vBda&|U#e99rc5q4WAjkMb&BaTTC7OPpM}>i8$Ey& zlIwK7dJZ${Utm%ZCEuHv$Ztg$u)C3C! zY6%jE8!-9Y*7Qg^0s1c?~BXQdLAmcuc^EIi$m#|6Dcwe zPJQtA!HCnI6LjQ672iI1F9tuy)rKe?gzL1gSnX|y;7Qz`C<1*!GYOh z^zcH=O7u|@^Umd1emvo8-Q02uV#hxfr|B3Lr z&o}>)arhaCy6Fy(oUFSXa@yGi;Qa}D^S=8S=qHzCzRQPI7_ zqOI}L)A7RQt*fsFpSYnic*WLY3qfv~n-Hegp2ME+=z2 zj2myHP7gm#Vv>Q+2afD4_|zbZf9{m_HUwUN|MInCoQOm91I9P>59#A1d|0PI;MA+b z0@Y!t7OkuKGaYdP0^?#OzRxR@a$2*FlJrF~##hk2iy(RQFyi`X5W_tlcD-D97trNd zsTjmQnh$Ev{G3SpcTVC6&8L93QhKFVZWN$9Hn%q-PQL}-xK8U>wkFH?GA$d#;mxQg zT1ezDN%I|=?#CQb%E2=A#e4v~BTv^MBAr=Zv50p#C*`#D0Mi zLHs8xJ zSc;Pu2f|BNc@+-WXY{0#Li{PfFBiN@*@U$ z{7h#VUVSsjdLD3@TF6;#=D*XN?nW6&y8B-e75_3&LyCJ)QeqKxr|xf)m=m%vd`fSR zxwoQ!tKfv%sba{LGonxaC1dv=M*r{q#+n^=?&WrKahhoLYw@&FLpCkwnE5C4)GlL$ z>_*JuW=eb__>!NbV*0xEbgIEzt(u><@HG6;^4apwxL?An4snZzF8;e_H?IEIiLFMi zKN?VG%a@MLb22_2bMgP9%UE~GvCAppTUJ>?M~iNSZZ|!Xr&i;M#dW`qmsVlNyUrR=>Z-Re>pKiev%1bPc zgHV`G1pi7qHKiVASPZCO{JVZ*bi>BvN-fWNB?qgVHlPB!Z({jZ-!CrcubYN(=AU@I zx>2n4v2%Xt@*MEB&-@icddpW_1gP^j2tP}0g*0|fkitI;=3QZkZ|g&umFV@ij8*8m z*6*{rV~|2aKYV?xr?USli+m{?!(n2DJ?F?_%z#}Lie3Nk73cb&t@XuVc51+#^Cl9v z;!N9@8^*=kp*j}Bzk;4Z15cK!)@zF-mxc#mqlfu28Zk7Vy3rsk;8ZBB&42@l0voJ-qkwpIW;Ao)s|%6$F?P_{@nQ z?a$)=cX45T8&fxH$gQjXQEB|64xb9gRQvw*I2!*Sie9)FgY3Eg78U!C_}P`m#GLe8 zXyG5~E&q^LFbTsLygT}X;(t2K*#pcffoZ=(|35GLpU1qv4c>P8N%*&8Bz5@zmc~l- zw&wny7oYaq;QtF+TU7AfF;d=tsQLZTw~o7*rA&HAZ{i={{$s`Rzr*z(LHB=I8ts3F p>%YS6f1~R^v&#R^li|@X9DSt!(cx=X|67=ky0W%Xm4bEH{{d<02)O_N literal 0 HcmV?d00001 diff --git a/_site/themes/hyde-hyde/layouts/partials/sidebar.html b/_site/themes/hyde-hyde/layouts/partials/sidebar.html index e51d9f1b..3bb31b75 100644 --- a/_site/themes/hyde-hyde/layouts/partials/sidebar.html +++ b/_site/themes/hyde-hyde/layouts/partials/sidebar.html @@ -29,6 +29,7 @@ + diff --git a/gerrit/display.go b/gerrit/display.go new file mode 100644 index 00000000..f7ef3a49 --- /dev/null +++ b/gerrit/display.go @@ -0,0 +1,73 @@ +package gerrit + +import ( + "fmt" + + "github.com/senorprogrammer/wtf/wtf" +) + +func (widget *Widget) display() { + + project := widget.currentGerritProject() + if project == nil { + fmt.Fprintf(widget.View, "%s", " Gerrit project data is unavailable (1)") + return + } + + widget.View.SetTitle(fmt.Sprintf("%s- %s", widget.Name, widget.title(project))) + + str := wtf.SigilStr(len(widget.GerritProjects), widget.Idx, widget.View) + "\n" + str = str + " [red]Stats[white]\n" + str = str + widget.displayStats(project) + str = str + "\n" + str = str + " [red]Open Incoming Reviews[white]\n" + str = str + widget.displayMyIncomingReviews(project, wtf.Config.UString("wtf.mods.gerrit.username")) + str = str + "\n" + str = str + " [red]My Outgoing Reviews[white]\n" + str = str + widget.displayMyOutgoingReviews(project, wtf.Config.UString("wtf.mods.gerrit.username")) + + widget.View.SetText(str) +} + +func (widget *Widget) displayMyOutgoingReviews(project *GerritProject, username string) string { + ors := project.myOutgoingReviews(username) + + if len(ors) == 0 { + return " [grey]none[white]\n" + } + + str := "" + for _, r := range ors { + str = str + fmt.Sprintf(" [green]%4s[white] %s\n", r.ChangeID, r.Subject) + } + + return str +} + +func (widget *Widget) displayMyIncomingReviews(project *GerritProject, username string) string { + irs := project.myIncomingReviews(username) + + if len(irs) == 0 { + return " [grey]none[white]\n" + } + + str := "" + for _, r := range irs { + str = str + fmt.Sprintf(" [green]%4s[white] %s\n", r.ChangeID, r.Subject) + } + + return str +} + +func (widget *Widget) displayStats(project *GerritProject) string { + str := fmt.Sprintf( + " Reviews: %d\n", + project.ReviewCount(), + ) + + return str +} + +func (widget *Widget) title(project *GerritProject) string { + return fmt.Sprintf("[green]%s [white]", project.Path) +} diff --git a/gerrit/gerrit_repo.go b/gerrit/gerrit_repo.go new file mode 100644 index 00000000..0e6b6e78 --- /dev/null +++ b/gerrit/gerrit_repo.go @@ -0,0 +1,100 @@ +package gerrit + +import ( + glb "github.com/andygrunwald/go-gerrit" +) + +type GerritProject struct { + gerrit *glb.Client + Path string + + Changes *[]glb.ChangeInfo +} + +func NewGerritProject(path string, gerrit *glb.Client) *GerritProject { + project := GerritProject{ + gerrit: gerrit, + Path: path, + } + + return &project +} + +// Refresh reloads the gerrit data via the Gerrit API +func (project *GerritProject) Refresh() { + project.Changes, _ = project.loadChanges() +} + +/* -------------------- Counts -------------------- */ + +func (project *GerritProject) IssueCount() int { + if project.Changes == nil { + return 0 + } + + return len(*project.Changes) +} + +func (project *GerritProject) ReviewCount() int { + if project.Changes == nil { + return 0 + } + + return len(*project.Changes) +} + +/* -------------------- Unexported Functions -------------------- */ + +// myOutgoingReviews returns a list of my outgoing reviews created by username on this project +func (project *GerritProject) myOutgoingReviews(username string) []glb.ChangeInfo { + changes := []glb.ChangeInfo{} + + if project.Changes == nil { + return changes + } + + for _, change := range *project.Changes { + user := change.Owner + + if user.Username == username { + changes = append(changes, change) + } + } + + return changes +} + +// myIncomingReviews returns a list of merge requests for which username has been requested to ChangeInfo +func (project *GerritProject) myIncomingReviews(username string) []glb.ChangeInfo { + changes := []glb.ChangeInfo{} + + if project.Changes == nil { + return changes + } + + for _, change := range *project.Changes { + reviewers := change.Reviewers + + for _, reviewer := range reviewers["REVIEWER"] { + if reviewer.Username == username { + changes = append(changes, change) + } + } + } + + return changes +} + +func (project *GerritProject) loadChanges() (*[]glb.ChangeInfo, error) { + opt := &glb.QueryChangeOptions{} + opt.Query = []string{"(projects:" + project.Path + "+ is:open + owner:self) " + " OR " + + "(projects:" + project.Path + " + is:open + ((reviewer:self + -owner:self + -star:ignore) + OR + assignee:self))"} + opt.AdditionalFields = []string{"DETAILED_LABELS", "DETAILED_ACCOUNTS"} + changes, _, err := project.gerrit.Changes.QueryChanges(opt) + + if err != nil { + return nil, err + } + + return changes, err +} diff --git a/gerrit/widget.go b/gerrit/widget.go new file mode 100644 index 00000000..af506257 --- /dev/null +++ b/gerrit/widget.go @@ -0,0 +1,180 @@ +package gerrit + +import ( + "crypto/tls" + "fmt" + glb "github.com/andygrunwald/go-gerrit" + "github.com/gdamore/tcell" + "github.com/rivo/tview" + "github.com/senorprogrammer/wtf/wtf" + "net/http" + "os" + "regexp" +) + +const HelpText = ` + Keyboard commands for Gerrit: + + /: Show/hide this help window + h: Previous project + l: Next project + r: Refresh the data + + arrow left: Previous project + arrow right: Next project +` + +type Widget struct { + wtf.TextWidget + + app *tview.Application + pages *tview.Pages + + gerrit *glb.Client + + GerritProjects []*GerritProject + Idx int +} + +var ( + GerritURLPattern = regexp.MustCompile(`^(http|https)://(.*)$`) +) + +func NewWidget(app *tview.Application, pages *tview.Pages) *Widget { + baseURL := wtf.Config.UString("wtf.mods.gerrit.domain") + username := wtf.Config.UString("wtf.mods.gerrit.username") + password := os.Getenv("WTF_GERRIT_PASSWORD") + verifyServerCertificate := wtf.Config.UBool("wtf.mods.gerrit.verifyServerCertificate", true) + + httpClient := &http.Client{Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: !verifyServerCertificate, + }, + }, + } + + gerritUrl := baseURL + submatches := GerritURLPattern.FindAllStringSubmatch(baseURL, -1) + + if len(submatches) > 0 && len(submatches[0]) > 2 { + submatch := submatches[0] + gerritUrl = fmt.Sprintf( + "%s://%s:%s@%s", submatch[1], username, password, submatch[2]) + } + + gerrit, err := glb.NewClient(gerritUrl, httpClient) + if err != nil { + panic(err) + } + + widget := Widget{ + TextWidget: wtf.NewTextWidget(" Gerrit ", "gerrit", true), + + app: app, + pages: pages, + + gerrit: gerrit, + + Idx: 0, + } + + widget.GerritProjects = widget.buildProjectCollection(wtf.Config.UList("wtf.mods.gerrit.projects")) + + widget.View.SetInputCapture(widget.keyboardIntercept) + + return &widget +} + +/* -------------------- Exported Functions -------------------- */ + +func (widget *Widget) Refresh() { + for _, project := range widget.GerritProjects { + project.Refresh() + } + + widget.UpdateRefreshedAt() + widget.display() +} + +func (widget *Widget) Next() { + widget.Idx = widget.Idx + 1 + if widget.Idx == len(widget.GerritProjects) { + widget.Idx = 0 + } + + widget.display() +} + +func (widget *Widget) Prev() { + widget.Idx = widget.Idx - 1 + if widget.Idx < 0 { + widget.Idx = len(widget.GerritProjects) - 1 + } + + widget.display() +} + +/* -------------------- Unexported Functions -------------------- */ + +func (widget *Widget) buildProjectCollection(projectData []interface{}) []*GerritProject { + gerritProjects := []*GerritProject{} + + for _, name := range projectData { + project := NewGerritProject(name.(string), widget.gerrit) + gerritProjects = append(gerritProjects, project) + } + + return gerritProjects +} + +func (widget *Widget) currentGerritProject() *GerritProject { + if len(widget.GerritProjects) == 0 { + return nil + } + + if widget.Idx < 0 || widget.Idx >= len(widget.GerritProjects) { + return nil + } + + return widget.GerritProjects[widget.Idx] +} + +func (widget *Widget) keyboardIntercept(event *tcell.EventKey) *tcell.EventKey { + switch string(event.Rune()) { + case "/": + widget.showHelp() + return nil + case "h": + widget.Prev() + return nil + case "l": + widget.Next() + return nil + case "r": + widget.Refresh() + return nil + } + + switch event.Key() { + case tcell.KeyLeft: + widget.Prev() + return nil + case tcell.KeyRight: + widget.Next() + return nil + default: + return event + } +} + +func (widget *Widget) showHelp() { + closeFunc := func() { + widget.pages.RemovePage("help") + widget.app.SetFocus(widget.View) + } + + modal := wtf.NewBillboardModal(HelpText, closeFunc) + + widget.pages.AddPage("help", modal, false, true) + widget.app.SetFocus(modal) +} diff --git a/vendor/cloud.google.com/go/compute/metadata/metadata.go b/vendor/cloud.google.com/go/compute/metadata/metadata.go index e708c031..9d0660be 100644 --- a/vendor/cloud.google.com/go/compute/metadata/metadata.go +++ b/vendor/cloud.google.com/go/compute/metadata/metadata.go @@ -1,4 +1,4 @@ -// Copyright 2014 Google Inc. All Rights Reserved. +// Copyright 2014 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -64,7 +64,7 @@ var ( ) var ( - metaClient = &http.Client{ + defaultClient = &Client{hc: &http.Client{ Transport: &http.Transport{ Dial: (&net.Dialer{ Timeout: 2 * time.Second, @@ -72,15 +72,15 @@ var ( }).Dial, ResponseHeaderTimeout: 2 * time.Second, }, - } - subscribeClient = &http.Client{ + }} + subscribeClient = &Client{hc: &http.Client{ Transport: &http.Transport{ Dial: (&net.Dialer{ Timeout: 2 * time.Second, KeepAlive: 30 * time.Second, }).Dial, }, - } + }} ) // NotDefinedError is returned when requested metadata is not defined. @@ -95,74 +95,16 @@ func (suffix NotDefinedError) Error() string { return fmt.Sprintf("metadata: GCE metadata %q not defined", string(suffix)) } -// Get returns a value from the metadata service. -// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/". -// -// If the GCE_METADATA_HOST environment variable is not defined, a default of -// 169.254.169.254 will be used instead. -// -// If the requested metadata is not defined, the returned error will -// be of type NotDefinedError. -func Get(suffix string) (string, error) { - val, _, err := getETag(metaClient, suffix) - return val, err -} - -// getETag returns a value from the metadata service as well as the associated -// ETag using the provided client. This func is otherwise equivalent to Get. -func getETag(client *http.Client, suffix string) (value, etag string, err error) { - // Using a fixed IP makes it very difficult to spoof the metadata service in - // a container, which is an important use-case for local testing of cloud - // deployments. To enable spoofing of the metadata service, the environment - // variable GCE_METADATA_HOST is first inspected to decide where metadata - // requests shall go. - host := os.Getenv(metadataHostEnv) - if host == "" { - // Using 169.254.169.254 instead of "metadata" here because Go - // binaries built with the "netgo" tag and without cgo won't - // know the search suffix for "metadata" is - // ".google.internal", and this IP address is documented as - // being stable anyway. - host = metadataIP - } - url := "http://" + host + "/computeMetadata/v1/" + suffix - req, _ := http.NewRequest("GET", url, nil) - req.Header.Set("Metadata-Flavor", "Google") - req.Header.Set("User-Agent", userAgent) - res, err := client.Do(req) - if err != nil { - return "", "", err - } - defer res.Body.Close() - if res.StatusCode == http.StatusNotFound { - return "", "", NotDefinedError(suffix) - } - if res.StatusCode != 200 { - return "", "", fmt.Errorf("status code %d trying to fetch %s", res.StatusCode, url) - } - all, err := ioutil.ReadAll(res.Body) - if err != nil { - return "", "", err - } - return string(all), res.Header.Get("Etag"), nil -} - -func getTrimmed(suffix string) (s string, err error) { - s, err = Get(suffix) - s = strings.TrimSpace(s) - return -} - -func (c *cachedValue) get() (v string, err error) { +func (c *cachedValue) get(cl *Client) (v string, err error) { defer c.mu.Unlock() c.mu.Lock() if c.v != "" { return c.v, nil } if c.trim { - v, err = getTrimmed(c.k) + v, err = cl.getTrimmed(c.k) } else { - v, err = Get(c.k) + v, err = cl.Get(c.k) } if err == nil { c.v = v @@ -201,7 +143,7 @@ func testOnGCE() bool { go func() { req, _ := http.NewRequest("GET", "http://"+metadataIP, nil) req.Header.Set("User-Agent", userAgent) - res, err := ctxhttp.Do(ctx, metaClient, req) + res, err := ctxhttp.Do(ctx, defaultClient.hc, req) if err != nil { resc <- false return @@ -266,6 +208,255 @@ func systemInfoSuggestsGCE() bool { return name == "Google" || name == "Google Compute Engine" } +// Subscribe calls Client.Subscribe on a client designed for subscribing (one with no +// ResponseHeaderTimeout). +func Subscribe(suffix string, fn func(v string, ok bool) error) error { + return subscribeClient.Subscribe(suffix, fn) +} + +// Get calls Client.Get on the default client. +func Get(suffix string) (string, error) { return defaultClient.Get(suffix) } + +// ProjectID returns the current instance's project ID string. +func ProjectID() (string, error) { return defaultClient.ProjectID() } + +// NumericProjectID returns the current instance's numeric project ID. +func NumericProjectID() (string, error) { return defaultClient.NumericProjectID() } + +// InternalIP returns the instance's primary internal IP address. +func InternalIP() (string, error) { return defaultClient.InternalIP() } + +// ExternalIP returns the instance's primary external (public) IP address. +func ExternalIP() (string, error) { return defaultClient.ExternalIP() } + +// Hostname returns the instance's hostname. This will be of the form +// ".c..internal". +func Hostname() (string, error) { return defaultClient.Hostname() } + +// InstanceTags returns the list of user-defined instance tags, +// assigned when initially creating a GCE instance. +func InstanceTags() ([]string, error) { return defaultClient.InstanceTags() } + +// InstanceID returns the current VM's numeric instance ID. +func InstanceID() (string, error) { return defaultClient.InstanceID() } + +// InstanceName returns the current VM's instance ID string. +func InstanceName() (string, error) { return defaultClient.InstanceName() } + +// Zone returns the current VM's zone, such as "us-central1-b". +func Zone() (string, error) { return defaultClient.Zone() } + +// InstanceAttributes calls Client.InstanceAttributes on the default client. +func InstanceAttributes() ([]string, error) { return defaultClient.InstanceAttributes() } + +// ProjectAttributes calls Client.ProjectAttributes on the default client. +func ProjectAttributes() ([]string, error) { return defaultClient.ProjectAttributes() } + +// InstanceAttributeValue calls Client.InstanceAttributeValue on the default client. +func InstanceAttributeValue(attr string) (string, error) { + return defaultClient.InstanceAttributeValue(attr) +} + +// ProjectAttributeValue calls Client.ProjectAttributeValue on the default client. +func ProjectAttributeValue(attr string) (string, error) { + return defaultClient.ProjectAttributeValue(attr) +} + +// Scopes calls Client.Scopes on the default client. +func Scopes(serviceAccount string) ([]string, error) { return defaultClient.Scopes(serviceAccount) } + +func strsContains(ss []string, s string) bool { + for _, v := range ss { + if v == s { + return true + } + } + return false +} + +// A Client provides metadata. +type Client struct { + hc *http.Client +} + +// NewClient returns a Client that can be used to fetch metadata. All HTTP requests +// will use the given http.Client instead of the default client. +func NewClient(c *http.Client) *Client { + return &Client{hc: c} +} + +// getETag returns a value from the metadata service as well as the associated ETag. +// This func is otherwise equivalent to Get. +func (c *Client) getETag(suffix string) (value, etag string, err error) { + // Using a fixed IP makes it very difficult to spoof the metadata service in + // a container, which is an important use-case for local testing of cloud + // deployments. To enable spoofing of the metadata service, the environment + // variable GCE_METADATA_HOST is first inspected to decide where metadata + // requests shall go. + host := os.Getenv(metadataHostEnv) + if host == "" { + // Using 169.254.169.254 instead of "metadata" here because Go + // binaries built with the "netgo" tag and without cgo won't + // know the search suffix for "metadata" is + // ".google.internal", and this IP address is documented as + // being stable anyway. + host = metadataIP + } + url := "http://" + host + "/computeMetadata/v1/" + suffix + req, _ := http.NewRequest("GET", url, nil) + req.Header.Set("Metadata-Flavor", "Google") + req.Header.Set("User-Agent", userAgent) + res, err := c.hc.Do(req) + if err != nil { + return "", "", err + } + defer res.Body.Close() + if res.StatusCode == http.StatusNotFound { + return "", "", NotDefinedError(suffix) + } + if res.StatusCode != 200 { + return "", "", fmt.Errorf("status code %d trying to fetch %s", res.StatusCode, url) + } + all, err := ioutil.ReadAll(res.Body) + if err != nil { + return "", "", err + } + return string(all), res.Header.Get("Etag"), nil +} + +// Get returns a value from the metadata service. +// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/". +// +// If the GCE_METADATA_HOST environment variable is not defined, a default of +// 169.254.169.254 will be used instead. +// +// If the requested metadata is not defined, the returned error will +// be of type NotDefinedError. +func (c *Client) Get(suffix string) (string, error) { + val, _, err := c.getETag(suffix) + return val, err +} + +func (c *Client) getTrimmed(suffix string) (s string, err error) { + s, err = c.Get(suffix) + s = strings.TrimSpace(s) + return +} + +func (c *Client) lines(suffix string) ([]string, error) { + j, err := c.Get(suffix) + if err != nil { + return nil, err + } + s := strings.Split(strings.TrimSpace(j), "\n") + for i := range s { + s[i] = strings.TrimSpace(s[i]) + } + return s, nil +} + +// ProjectID returns the current instance's project ID string. +func (c *Client) ProjectID() (string, error) { return projID.get(c) } + +// NumericProjectID returns the current instance's numeric project ID. +func (c *Client) NumericProjectID() (string, error) { return projNum.get(c) } + +// InstanceID returns the current VM's numeric instance ID. +func (c *Client) InstanceID() (string, error) { return instID.get(c) } + +// InternalIP returns the instance's primary internal IP address. +func (c *Client) InternalIP() (string, error) { + return c.getTrimmed("instance/network-interfaces/0/ip") +} + +// ExternalIP returns the instance's primary external (public) IP address. +func (c *Client) ExternalIP() (string, error) { + return c.getTrimmed("instance/network-interfaces/0/access-configs/0/external-ip") +} + +// Hostname returns the instance's hostname. This will be of the form +// ".c..internal". +func (c *Client) Hostname() (string, error) { + return c.getTrimmed("instance/hostname") +} + +// InstanceTags returns the list of user-defined instance tags, +// assigned when initially creating a GCE instance. +func (c *Client) InstanceTags() ([]string, error) { + var s []string + j, err := c.Get("instance/tags") + if err != nil { + return nil, err + } + if err := json.NewDecoder(strings.NewReader(j)).Decode(&s); err != nil { + return nil, err + } + return s, nil +} + +// InstanceName returns the current VM's instance ID string. +func (c *Client) InstanceName() (string, error) { + host, err := c.Hostname() + if err != nil { + return "", err + } + return strings.Split(host, ".")[0], nil +} + +// Zone returns the current VM's zone, such as "us-central1-b". +func (c *Client) Zone() (string, error) { + zone, err := c.getTrimmed("instance/zone") + // zone is of the form "projects//zones/". + if err != nil { + return "", err + } + return zone[strings.LastIndex(zone, "/")+1:], nil +} + +// InstanceAttributes returns the list of user-defined attributes, +// assigned when initially creating a GCE VM instance. The value of an +// attribute can be obtained with InstanceAttributeValue. +func (c *Client) InstanceAttributes() ([]string, error) { return c.lines("instance/attributes/") } + +// ProjectAttributes returns the list of user-defined attributes +// applying to the project as a whole, not just this VM. The value of +// an attribute can be obtained with ProjectAttributeValue. +func (c *Client) ProjectAttributes() ([]string, error) { return c.lines("project/attributes/") } + +// InstanceAttributeValue returns the value of the provided VM +// instance attribute. +// +// If the requested attribute is not defined, the returned error will +// be of type NotDefinedError. +// +// InstanceAttributeValue may return ("", nil) if the attribute was +// defined to be the empty string. +func (c *Client) InstanceAttributeValue(attr string) (string, error) { + return c.Get("instance/attributes/" + attr) +} + +// ProjectAttributeValue returns the value of the provided +// project attribute. +// +// If the requested attribute is not defined, the returned error will +// be of type NotDefinedError. +// +// ProjectAttributeValue may return ("", nil) if the attribute was +// defined to be the empty string. +func (c *Client) ProjectAttributeValue(attr string) (string, error) { + return c.Get("project/attributes/" + attr) +} + +// Scopes returns the service account scopes for the given account. +// The account may be empty or the string "default" to use the instance's +// main account. +func (c *Client) Scopes(serviceAccount string) ([]string, error) { + if serviceAccount == "" { + serviceAccount = "default" + } + return c.lines("instance/service-accounts/" + serviceAccount + "/scopes") +} + // Subscribe subscribes to a value from the metadata service. // The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/". // The suffix may contain query parameters. @@ -275,11 +466,11 @@ func systemInfoSuggestsGCE() bool { // and ok false. Subscribe blocks until fn returns a non-nil error or the value // is deleted. Subscribe returns the error value returned from the last call to // fn, which may be nil when ok == false. -func Subscribe(suffix string, fn func(v string, ok bool) error) error { +func (c *Client) Subscribe(suffix string, fn func(v string, ok bool) error) error { const failedSubscribeSleep = time.Second * 5 // First check to see if the metadata value exists at all. - val, lastETag, err := getETag(subscribeClient, suffix) + val, lastETag, err := c.getETag(suffix) if err != nil { return err } @@ -295,7 +486,7 @@ func Subscribe(suffix string, fn func(v string, ok bool) error) error { suffix += "?wait_for_change=true&last_etag=" } for { - val, etag, err := getETag(subscribeClient, suffix+url.QueryEscape(lastETag)) + val, etag, err := c.getETag(suffix + url.QueryEscape(lastETag)) if err != nil { if _, deleted := err.(NotDefinedError); !deleted { time.Sleep(failedSubscribeSleep) @@ -310,128 +501,3 @@ func Subscribe(suffix string, fn func(v string, ok bool) error) error { } } } - -// ProjectID returns the current instance's project ID string. -func ProjectID() (string, error) { return projID.get() } - -// NumericProjectID returns the current instance's numeric project ID. -func NumericProjectID() (string, error) { return projNum.get() } - -// InternalIP returns the instance's primary internal IP address. -func InternalIP() (string, error) { - return getTrimmed("instance/network-interfaces/0/ip") -} - -// ExternalIP returns the instance's primary external (public) IP address. -func ExternalIP() (string, error) { - return getTrimmed("instance/network-interfaces/0/access-configs/0/external-ip") -} - -// Hostname returns the instance's hostname. This will be of the form -// ".c..internal". -func Hostname() (string, error) { - return getTrimmed("instance/hostname") -} - -// InstanceTags returns the list of user-defined instance tags, -// assigned when initially creating a GCE instance. -func InstanceTags() ([]string, error) { - var s []string - j, err := Get("instance/tags") - if err != nil { - return nil, err - } - if err := json.NewDecoder(strings.NewReader(j)).Decode(&s); err != nil { - return nil, err - } - return s, nil -} - -// InstanceID returns the current VM's numeric instance ID. -func InstanceID() (string, error) { - return instID.get() -} - -// InstanceName returns the current VM's instance ID string. -func InstanceName() (string, error) { - host, err := Hostname() - if err != nil { - return "", err - } - return strings.Split(host, ".")[0], nil -} - -// Zone returns the current VM's zone, such as "us-central1-b". -func Zone() (string, error) { - zone, err := getTrimmed("instance/zone") - // zone is of the form "projects//zones/". - if err != nil { - return "", err - } - return zone[strings.LastIndex(zone, "/")+1:], nil -} - -// InstanceAttributes returns the list of user-defined attributes, -// assigned when initially creating a GCE VM instance. The value of an -// attribute can be obtained with InstanceAttributeValue. -func InstanceAttributes() ([]string, error) { return lines("instance/attributes/") } - -// ProjectAttributes returns the list of user-defined attributes -// applying to the project as a whole, not just this VM. The value of -// an attribute can be obtained with ProjectAttributeValue. -func ProjectAttributes() ([]string, error) { return lines("project/attributes/") } - -func lines(suffix string) ([]string, error) { - j, err := Get(suffix) - if err != nil { - return nil, err - } - s := strings.Split(strings.TrimSpace(j), "\n") - for i := range s { - s[i] = strings.TrimSpace(s[i]) - } - return s, nil -} - -// InstanceAttributeValue returns the value of the provided VM -// instance attribute. -// -// If the requested attribute is not defined, the returned error will -// be of type NotDefinedError. -// -// InstanceAttributeValue may return ("", nil) if the attribute was -// defined to be the empty string. -func InstanceAttributeValue(attr string) (string, error) { - return Get("instance/attributes/" + attr) -} - -// ProjectAttributeValue returns the value of the provided -// project attribute. -// -// If the requested attribute is not defined, the returned error will -// be of type NotDefinedError. -// -// ProjectAttributeValue may return ("", nil) if the attribute was -// defined to be the empty string. -func ProjectAttributeValue(attr string) (string, error) { - return Get("project/attributes/" + attr) -} - -// Scopes returns the service account scopes for the given account. -// The account may be empty or the string "default" to use the instance's -// main account. -func Scopes(serviceAccount string) ([]string, error) { - if serviceAccount == "" { - serviceAccount = "default" - } - return lines("instance/service-accounts/" + serviceAccount + "/scopes") -} - -func strsContains(ss []string, s string) bool { - for _, v := range ss { - if v == s { - return true - } - } - return false -} diff --git a/vendor/github.com/adlio/trello/label.go b/vendor/github.com/adlio/trello/label.go index b60faa7a..343542a2 100644 --- a/vendor/github.com/adlio/trello/label.go +++ b/vendor/github.com/adlio/trello/label.go @@ -5,6 +5,8 @@ package trello +import "fmt" + type Label struct { ID string `json:"id"` IDBoard string `json:"idBoard"` @@ -12,3 +14,15 @@ type Label struct { Color string `json:"color"` Uses int `json:"uses"` } + +func (c *Client) GetLabel(labelID string, args Arguments) (label *Label, err error) { + path := fmt.Sprintf("labels/%s", labelID) + err = c.Get(path, args, &label) + return +} + +func (b *Board) GetLabels(args Arguments) (labels []*Label, err error) { + path := fmt.Sprintf("boards/%s/labels", b.ID) + err = b.client.Get(path, args, &labels) + return +} diff --git a/vendor/github.com/andygrunwald/go-gerrit/.gitignore b/vendor/github.com/andygrunwald/go-gerrit/.gitignore new file mode 100644 index 00000000..4c7d39ea --- /dev/null +++ b/vendor/github.com/andygrunwald/go-gerrit/.gitignore @@ -0,0 +1 @@ +/coverage.txt \ No newline at end of file diff --git a/vendor/github.com/andygrunwald/go-gerrit/.travis.yml b/vendor/github.com/andygrunwald/go-gerrit/.travis.yml new file mode 100644 index 00000000..b55e33f2 --- /dev/null +++ b/vendor/github.com/andygrunwald/go-gerrit/.travis.yml @@ -0,0 +1,17 @@ +language: go + +sudo: false + +go: + - "1.10.x" + - "1.9.x" + - "1.8.x" + +before_install: + - make deps + +script: + - make + +after_success: + - bash <(curl -s https://codecov.io/bash) diff --git a/vendor/github.com/andygrunwald/go-gerrit/CHANGELOG.md b/vendor/github.com/andygrunwald/go-gerrit/CHANGELOG.md new file mode 100644 index 00000000..2052a6c5 --- /dev/null +++ b/vendor/github.com/andygrunwald/go-gerrit/CHANGELOG.md @@ -0,0 +1,105 @@ +# Changelog + +This is a high level log of changes, bugfixes, enhancements, etc +that have taken place between releases. Later versions are shown +first. For more complete details see +[the releases on GitHub.](https://github.com/andygrunwald/go-gerrit/releases) + +## Versions + +### Latest + +### 0.5.2 + +* Fix panic in checkAuth() if Gerrit is down #42 +* Implement ListVotes(), DeleteVotes() and add missing tests + +### 0.5.1 + +* Added the `AbandonChange`, `RebaseChange`, `RestoreChange` and + `RevertChange` functions. + +### 0.5.0 + +**WARNING**: This release includes breaking changes. + +* [BREAKING CHANGE] The SetReview function was returning the wrong + entity type. (#40) + +### 0.4.0 + +**WARNING**: This release includes breaking changes. + +* [BREAKING CHANGE] - Added gometalinter to the build and fixed problems + discovered by the linters. + * Comment and error string fixes. + * Numerous lint and styling fixes. + * Ensured error values are being properly checked where appropriate. + * Addition of missing documentation + * Removed filePath parameter from DeleteChangeEdit which was unused and + unnecessary for the request. + * Fixed CherryPickRevision and IncludeGroups functions which didn't pass + along the provided input structs into the request. +* Go 1.5 has been removed from testing on Travis. The linters introduced in + 0.4.0 do not support this version, Go 1.5 is lacking security updates and + most Linux distros have moved beyond Go 1.5 now. +* Add Go 1.9 to the Travis matrix. +* Fixed an issue where urls containing certain characters in the credentials + could cause NewClient() to use an invalid url. Something like `/`, which + Gerrit could use for generated passwords, for example would break url.Parse's + expectations. + +### 0.3.0 + +**WARNING**: This release includes breaking changes. + +* [BREAKING CHANGE] Fix Changes.PublishDraftChange to accept a notify parameter. +* [BREAKING CHANGE] Fix PublishChangeEdit to accept a notify parameter. +* [BREAKING CHANGE] Fix ChangeFileContentInChangeEdit to allow the file content + to be included in the request. +* Fix the url being used by CreateChange +* Fix type serialization of EventInfo.PatchSet.Number so it's consistent. +* Fix Changes.AddReviewer so it passes along the reviewer to the request. +* Simplify and optimize RemoveMagicPrefixLine + +### 0.2.0 + +**WARNING**: This release includes breaking changes. + +* [BREAKING CHANGE] Several bugfixes to GetEvents: + * Update EventInfo to handle the changeKey field and apply + the proper type for the Project field + * Provide a means to ignore marshaling errors + * Update GetEvents() to return the failed lines and remove + the pointer to the return value because it's unnecessary. +* [BREAKING CHANGE] In ec28f77 `ChangeInfo.Labels` has been changed to map + to fix #21. + + +### 0.1.1 + +* Minor fix to SubmitChange to use the `http.StatusConflict` constant + instead of a hard coded value when comparing response codes. +* Updated AccountInfo.AccountID to be omitted of empty (such as when + used in ApprovalInfo). +* + and : in url parameters for queries are no longer escaped. This was + causing `400 Bad Request` to be returned when the + symbol was + included as part of the query. To match behavior with Gerrit's search + handling, the : symbol was also excluded. +* Fixed documentation for NewClient and moved fmt.Errorf call from + inside the function to a `ErrNoInstanceGiven` variable so it's + easier to compare against. +* Updated internal function digestAuthHeader to return exported errors + (ErrWWWAuthenticateHeader*) rather than calling fmt.Errorf. This makes + it easier to test against externally and also fixes a lint issue too. +* Updated NewClient function to handle credentials in the url. +* Added the missing `Submitted` field to `ChangeInfo`. +* Added the missing `URL` field to `ChangeInfo` which is usually included + as part of an event from the events-log plugin. + +### 0.1.0 + +* The first official release +* Implemented digest auth and several fixes for it. +* Ensured Content-Type is included in all requests +* Fixed several internal bugs as well as a few documentation issues diff --git a/vendor/github.com/andygrunwald/go-gerrit/LICENSE b/vendor/github.com/andygrunwald/go-gerrit/LICENSE new file mode 100644 index 00000000..692f6bea --- /dev/null +++ b/vendor/github.com/andygrunwald/go-gerrit/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Andy Grunwald + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/github.com/andygrunwald/go-gerrit/Makefile b/vendor/github.com/andygrunwald/go-gerrit/Makefile new file mode 100644 index 00000000..ca354f43 --- /dev/null +++ b/vendor/github.com/andygrunwald/go-gerrit/Makefile @@ -0,0 +1,26 @@ +.PHONY: fmt vet check-vendor lint check clean test build +PACKAGES = $(shell go list ./...) +PACKAGE_DIRS = $(shell go list -f '{{ .Dir }}' ./...) + +check: test vet lint + +test: + go test -v -race -coverprofile=coverage.txt -covermode=atomic ./... + +vet: + go vet $(PACKAGES) || (go clean $(PACKAGES); go vet $(PACKAGES)) + +lint: + gometalinter --config gometalinter.json ./... + +fmt: + go fmt $(PACKAGES) + goimports -w $(PACKAGE_DIRS) + +deps: + go get -t -v ./... + go get github.com/axw/gocov/gocov + go get golang.org/x/tools/cmd/cover + [ -f $(GOPATH)/bin/gometalinter ] || go get -u github.com/alecthomas/gometalinter + [ -f $(GOPATH)/bin/goimports ] || go get golang.org/x/tools/cmd/goimports + gometalinter --install diff --git a/vendor/github.com/andygrunwald/go-gerrit/README.md b/vendor/github.com/andygrunwald/go-gerrit/README.md new file mode 100644 index 00000000..0341d439 --- /dev/null +++ b/vendor/github.com/andygrunwald/go-gerrit/README.md @@ -0,0 +1,270 @@ +# go-gerrit + +[![GoDoc](https://godoc.org/github.com/andygrunwald/go-gerrit?status.svg)](https://godoc.org/github.com/andygrunwald/go-gerrit) +[![Build Status](https://travis-ci.org/andygrunwald/go-gerrit.svg?branch=master)](https://travis-ci.org/andygrunwald/go-gerrit) +[![Go Report Card](https://goreportcard.com/badge/github.com/andygrunwald/go-gerrit)](https://goreportcard.com/report/github.com/andygrunwald/go-gerrit) +[![codecov](https://codecov.io/gh/andygrunwald/go-gerrit/branch/master/graph/badge.svg)](https://codecov.io/gh/andygrunwald/go-gerrit) + +go-gerrit is a [Go(lang)](https://golang.org/) client library for accessing the [Gerrit Code Review](https://www.gerritcodereview.com/) API. + +![go-gerrit - Go(lang) client/library for Gerrit Code Review](./img/logo.png "go-gerrit - Go(lang) client/library for Gerrit Code Review") + +## Features + +* [Authentication](https://godoc.org/github.com/andygrunwald/go-gerrit#AuthenticationService) (HTTP Basic, HTTP Digest, HTTP Cookie) +* Every API Endpoint like Gerrit + * [/access/](https://godoc.org/github.com/andygrunwald/go-gerrit#AccessService) + * [/accounts/](https://godoc.org/github.com/andygrunwald/go-gerrit#AccountsService) + * [/changes/](https://godoc.org/github.com/andygrunwald/go-gerrit#ChangesService) + * [/config/](https://godoc.org/github.com/andygrunwald/go-gerrit#ConfigService) + * [/groups/](https://godoc.org/github.com/andygrunwald/go-gerrit#GroupsService) + * [/plugins/](https://godoc.org/github.com/andygrunwald/go-gerrit#PluginsService) + * [/projects/](https://godoc.org/github.com/andygrunwald/go-gerrit#ProjectsService) +* Supports optional plugin APIs such as + * events-log - [About](https://gerrit.googlesource.com/plugins/events-log/+/master/src/main/resources/Documentation/about.md), [REST API](https://gerrit.googlesource.com/plugins/events-log/+/master/src/main/resources/Documentation/rest-api-events.md) + + +## Installation + +go-gerrit requires Go version 1.8 or greater. + +It is go gettable ... + +```sh +$ go get github.com/andygrunwald/go-gerrit +``` + +... (optional) to run checks and tests: + +**Tests Only** + +```sh +$ cd $GOPATH/src/github.com/andygrunwald/go-gerrit +$ go test -v +``` + +**Checks, Tests, Linters, etc** + +```sh +$ cd $GOPATH/src/github.com/andygrunwald/go-gerrit +$ make +``` + +## API / Usage + +Please have a look at the [GoDoc documentation](https://godoc.org/github.com/andygrunwald/go-gerrit) for a detailed API description. + +The [Gerrit Code Review - REST API](https://gerrit-review.googlesource.com/Documentation/rest-api.html) was the base document. + +### Authentication + +Gerrit support multiple ways for [authentication](https://gerrit-review.googlesource.com/Documentation/rest-api.html#authentication). + +#### HTTP Basic + +Some Gerrit instances (like [TYPO3](https://review.typo3.org/)) has [auth.gitBasicAuth](https://gerrit-review.googlesource.com/Documentation/config-gerrit.html#auth.gitBasicAuth) activated. +With this you can authenticate with HTTP Basic like this: + +```go +instance := "https://review.typo3.org/" +client, _ := gerrit.NewClient(instance, nil) +client.Authentication.SetBasicAuth("andy.grunwald", "my secrect password") + +self, _, _ := client.Accounts.GetAccount("self") + +fmt.Printf("Username: %s", self.Name) + +// Username: Andy Grunwald +``` + +If you get an `401 Unauthorized`, check your Account Settings and have a look at the `HTTP Password` configuration. + +#### HTTP Digest + +Some Gerrit instances (like [Wikimedia](https://gerrit.wikimedia.org/)) has [Digest access authentication](https://en.wikipedia.org/wiki/Digest_access_authentication) activated. + +```go +instance := "https://gerrit.wikimedia.org/r/" +client, _ := gerrit.NewClient(instance, nil) +client.Authentication.SetDigestAuth("andy.grunwald", "my secrect http password") + +self, resp, err := client.Accounts.GetAccount("self") + +fmt.Printf("Username: %s", self.Name) + +// Username: Andy Grunwald +``` + +If digest auth is not supported by the choosen Gerrit instance, an error like `WWW-Authenticate header type is not Digest` is thrown. + +If you get an `401 Unauthorized`, check your Account Settings and have a look at the `HTTP Password` configuration. + +#### HTTP Cookie + +Some Gerrit instances hosted like the one hosted googlesource.com (e.g. [Go(lang)](https://go-review.googlesource.com/), [Android](https://android-review.googlesource.com/) or [Gerrit](https://gerrit-review.googlesource.com/)) support HTTP Cookie authentication. + +You need the cookie name and the cookie value. +You can get them by click on "Settings > HTTP Password > Obtain Password" in your Gerrit instance. + +There you can receive your values. +The cookie name will be (mostly) `o` (if hosted on googlesource.com). +Your cookie secret will be something like `git-your@email.com=SomeHash...`. + +```go +instance := "https://gerrit-review.googlesource.com/" +client, _ := gerrit.NewClient(instance, nil) +client.Authentication.SetCookieAuth("o", "my-cookie-secret") + +self, _, _ := client.Accounts.GetAccount("self") + +fmt.Printf("Username: %s", self.Name) + +// Username: Andy G. +``` + +### More more more + +In the examples chapter below you will find a few more examples. +If you miss one or got a question how to do something please [open a new issue](https://github.com/andygrunwald/go-gerrit/issues/new) with your question. +We will be happy to answer them. + +## Examples + +Further a few examples how the API can be used. +A few more examples are available in the [GoDoc examples section](https://godoc.org/github.com/andygrunwald/go-gerrit#pkg-examples). + +### Get version of Gerrit instance + +Receive the version of the [Gerrit instance used by the Gerrit team](https://gerrit-review.googlesource.com/) for development: + +```go +package main + +import ( + "fmt" + "github.com/andygrunwald/go-gerrit" +) + +func main() { + instance := "https://gerrit-review.googlesource.com/" + client, err := gerrit.NewClient(instance, nil) + if err != nil { + panic(err) + } + + v, _, err := client.Config.GetVersion() + + fmt.Printf("Version: %s", v) + + // Version: 2.12.2-2512-g0b1bccd +} +``` + +### Get all public projects + +List all projects from [Chromium](https://chromium-review.googlesource.com/): + +```go +package main + +import ( + "fmt" + "github.com/andygrunwald/go-gerrit" +) + +func main() { + instance := "https://chromium-review.googlesource.com/" + client, err := gerrit.NewClient(instance, nil) + if err != nil { + panic(err) + } + + opt := &gerrit.ProjectOptions{ + Description: true, + } + projects, _, err := client.Projects.ListProjects(opt) + for name, p := range *projects { + fmt.Printf("%s - State: %s\n", name, p.State) + } + + // chromiumos/platform/depthcharge - State: ACTIVE + // external/github.com/maruel/subcommands - State: ACTIVE + // external/junit - State: ACTIVE + // ... +} +``` + +### Query changes + +Get some changes of the [kernel/common project](https://android-review.googlesource.com/#/q/project:kernel/common) from the [Android](http://source.android.com/) [Gerrit Review System](https://android-review.googlesource.com/). + +```go +package main + +import ( + "fmt" + "github.com/andygrunwald/go-gerrit" +) + +func main() { + instance := "https://android-review.googlesource.com/" + client, err := gerrit.NewClient(instance, nil) + if err != nil { + panic(err) + } + + opt := &gerrit.QueryChangeOptions{} + opt.Query = []string{"project:kernel/common"} + opt.AdditionalFields = []string{"LABELS"} + changes, _, err := client.Changes.QueryChanges(opt) + + for _, change := range *changes { + fmt.Printf("Project: %s -> %s -> %s%d\n", change.Project, change.Subject, instance, change.Number) + } + + // Project: kernel/common -> android: binder: Fix BR_ERROR usage and change LSM denials to use it. -> https://android-review.googlesource.com/150839 + // Project: kernel/common -> android: binder: fix duplicate error return. -> https://android-review.googlesource.com/155031 + // Project: kernel/common -> dm-verity: Add modes and emit uevent on corrupted blocks -> https://android-review.googlesource.com/169572 + // ... +} +``` + +## FAQ + +### How is the source code organized? + +The source code organisation was inspired by [go-github by Google](https://github.com/google/go-github). + +Every REST API Endpoint (e.g. [/access/](https://gerrit-review.googlesource.com/Documentation/rest-api-access.html) or [/changes/](https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html)) is coupled in a service (e.g. [AccessService in access.go](./access.go) or [ChangesService in changes.go](./changes.go)). +Every service is part of [gerrit.Client](./gerrit.go) as a member variable. + +gerrit.Client can provide basic helper functions to avoid unnecessary code duplications such as building a new request, parse responses and so on. + +Based on this structure implementing a new API functionality is straight forwarded. Here is an example of *ChangeService.DeleteTopic* / [DELETE /changes/{change-id}/topic](https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#delete-topic): + +```go +func (s *ChangesService) DeleteTopic(changeID string) (*Response, error) { + u := fmt.Sprintf("changes/%s/topic", changeID) + return s.client.DeleteRequest(u, nil) +} +``` + +### What about the version compatibility with Gerrit? + +The library was implemented based on the REST API of Gerrit version 2.11.3-1230-gb8336f1 and tested against this version. + +This library might be working with older versions as well. +If you notice an incompatibility [open a new issue](https://github.com/andygrunwald/go-gerrit/issues/new) or try to fix it. +We welcome contribution! + + +### What about adding code to support the REST API of an optional plugin? + +It will depend on the plugin, you are welcome to [open a new issue](https://github.com/andygrunwald/go-gerrit/issues/new) first to propose the idea if you wish. +As an example the addition of support for events-log plugin was supported because the plugin itself is fairly +popular and the structures that the REST API uses could also be used by `gerrit stream-events`. + + +## License + +This project is released under the terms of the [MIT license](http://en.wikipedia.org/wiki/MIT_License). diff --git a/vendor/github.com/andygrunwald/go-gerrit/access.go b/vendor/github.com/andygrunwald/go-gerrit/access.go new file mode 100644 index 00000000..d3237a8c --- /dev/null +++ b/vendor/github.com/andygrunwald/go-gerrit/access.go @@ -0,0 +1,74 @@ +package gerrit + +// AccessService contains Access Right related REST endpoints +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-access.html +type AccessService struct { + client *Client +} + +// AccessSectionInfo describes the access rights that are assigned on a ref. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-access.html#access-section-info +type AccessSectionInfo struct { + Permissions map[string]PermissionInfo `json:"permissions"` +} + +// PermissionInfo entity contains information about an assigned permission. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-access.html#permission-info +type PermissionInfo struct { + Label string `json:"label,omitempty"` + Exclusive bool `json:"exclusive"` + Rules map[string]PermissionRuleInfo `json:"rules"` +} + +// PermissionRuleInfo entity contains information about a permission rule that is assigned to group. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-access.html#permission-rule-info +type PermissionRuleInfo struct { + // TODO Possible values for action: ALLOW, DENY or BLOCK, INTERACTIVE and BATCH + Action string `json:"action"` + Force bool `json:"force"` + Min int `json:"min"` + Max int `json:"max"` +} + +// ProjectAccessInfo entity contains information about the access rights for a project. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-access.html#project-access-info +type ProjectAccessInfo struct { + Revision string `json:"revision"` + InheritsFrom ProjectInfo `json:"inherits_from"` + Local map[string]AccessSectionInfo `json:"local"` + IsOwner bool `json:"is_owner"` + OwnerOf []string `json:"owner_of"` + CanUpload bool `json:"can_upload"` + CanAdd bool `json:"can_add"` + ConfigVisible bool `json:"config_visible"` +} + +// ListAccessRightsOptions specifies the parameters to the AccessService.ListAccessRights. +type ListAccessRightsOptions struct { + // The projects for which the access rights should be returned must be specified as project options. + // The project can be specified multiple times. + Project []string `url:"project,omitempty"` +} + +// ListAccessRights lists the access rights for projects. +// As result a map is returned that maps the project name to ProjectAccessInfo entities. +// The entries in the map are sorted by project name. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-access.html#list-access +func (s *AccessService) ListAccessRights(opt *ListAccessRightsOptions) (*map[string]ProjectAccessInfo, *Response, error) { + u := "access/" + + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + v := new(map[string]ProjectAccessInfo) + resp, err := s.client.Call("GET", u, nil, v) + return v, resp, err +} diff --git a/vendor/github.com/andygrunwald/go-gerrit/accounts.go b/vendor/github.com/andygrunwald/go-gerrit/accounts.go new file mode 100644 index 00000000..3f828635 --- /dev/null +++ b/vendor/github.com/andygrunwald/go-gerrit/accounts.go @@ -0,0 +1,874 @@ +package gerrit + +import ( + "fmt" +) + +// AccountsService contains Account related REST endpoints +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html +type AccountsService struct { + client *Client +} + +// AccountInfo entity contains information about an account. +type AccountInfo struct { + AccountID int `json:"_account_id,omitempty"` + Name string `json:"name,omitempty"` + Email string `json:"email,omitempty"` + Username string `json:"username,omitempty"` + + // Avatars lists avatars of various sizes for the account. + // This field is only populated if the avatars plugin is enabled. + Avatars []struct { + URL string `json:"url,omitempty"` + Height int `json:"height,omitempty"` + } `json:"avatars,omitempty"` +} + +// SSHKeyInfo entity contains information about an SSH key of a user. +type SSHKeyInfo struct { + Seq int `json:"seq"` + SSHPublicKey string `json:"ssh_public_key"` + EncodedKey string `json:"encoded_key"` + Algorithm string `json:"algorithm"` + Comment string `json:"comment,omitempty"` + Valid bool `json:"valid"` +} + +// UsernameInput entity contains information for setting the username for an account. +type UsernameInput struct { + Username string `json:"username"` +} + +// QueryLimitInfo entity contains information about the Query Limit of a user. +type QueryLimitInfo struct { + Min int `json:"min"` + Max int `json:"max"` +} + +// HTTPPasswordInput entity contains information for setting/generating an HTTP password. +type HTTPPasswordInput struct { + Generate bool `json:"generate,omitempty"` + HTTPPassword string `json:"http_password,omitempty"` +} + +// GpgKeysInput entity contains information for adding/deleting GPG keys. +type GpgKeysInput struct { + Add []string `json:"add"` + Delete []string `json:"delete"` +} + +// GpgKeyInfo entity contains information about a GPG public key. +type GpgKeyInfo struct { + ID string `json:"id,omitempty"` + Fingerprint string `json:"fingerprint,omitempty"` + UserIDs []string `json:"user_ids,omitempty"` + Key string `json:"key,omitempty"` +} + +// EmailInput entity contains information for registering a new email address. +type EmailInput struct { + Email string `json:"email"` + Preferred bool `json:"preferred,omitempty"` + NoConfirmation bool `json:"no_confirmation,omitempty"` +} + +// EmailInfo entity contains information about an email address of a user. +type EmailInfo struct { + Email string `json:"email"` + Preferred bool `json:"preferred,omitempty"` + PendingConfirmation bool `json:"pending_confirmation,omitempty"` +} + +// AccountInput entity contains information for the creation of a new account. +type AccountInput struct { + Username string `json:"username,omitempty"` + Name string `json:"name,omitempty"` + Email string `json:"email,omitempty"` + SSHKey string `json:"ssh_key,omitempty"` + HTTPPassword string `json:"http_password,omitempty"` + Groups []string `json:"groups,omitempty"` +} + +// AccountDetailInfo entity contains detailed information about an account. +type AccountDetailInfo struct { + AccountInfo + RegisteredOn Timestamp `json:"registered_on"` +} + +// AccountNameInput entity contains information for setting a name for an account. +type AccountNameInput struct { + Name string `json:"name,omitempty"` +} + +// AccountCapabilityInfo entity contains information about the global capabilities of a user. +type AccountCapabilityInfo struct { + AccessDatabase bool `json:"accessDatabase,omitempty"` + AdministrateServer bool `json:"administrateServer,omitempty"` + CreateAccount bool `json:"createAccount,omitempty"` + CreateGroup bool `json:"createGroup,omitempty"` + CreateProject bool `json:"createProject,omitempty"` + EmailReviewers bool `json:"emailReviewers,omitempty"` + FlushCaches bool `json:"flushCaches,omitempty"` + KillTask bool `json:"killTask,omitempty"` + MaintainServer bool `json:"maintainServer,omitempty"` + Priority string `json:"priority,omitempty"` + QueryLimit QueryLimitInfo `json:"queryLimit"` + RunAs bool `json:"runAs,omitempty"` + RunGC bool `json:"runGC,omitempty"` + StreamEvents bool `json:"streamEvents,omitempty"` + ViewAllAccounts bool `json:"viewAllAccounts,omitempty"` + ViewCaches bool `json:"viewCaches,omitempty"` + ViewConnections bool `json:"viewConnections,omitempty"` + ViewPlugins bool `json:"viewPlugins,omitempty"` + ViewQueue bool `json:"viewQueue,omitempty"` +} + +// DiffPreferencesInfo entity contains information about the diff preferences of a user. +type DiffPreferencesInfo struct { + Context int `json:"context"` + Theme string `json:"theme"` + ExpandAllComments bool `json:"expand_all_comments,omitempty"` + IgnoreWhitespace string `json:"ignore_whitespace"` + IntralineDifference bool `json:"intraline_difference,omitempty"` + LineLength int `json:"line_length"` + ManualReview bool `json:"manual_review,omitempty"` + RetainHeader bool `json:"retain_header,omitempty"` + ShowLineEndings bool `json:"show_line_endings,omitempty"` + ShowTabs bool `json:"show_tabs,omitempty"` + ShowWhitespaceErrors bool `json:"show_whitespace_errors,omitempty"` + SkipDeleted bool `json:"skip_deleted,omitempty"` + SkipUncommented bool `json:"skip_uncommented,omitempty"` + SyntaxHighlighting bool `json:"syntax_highlighting,omitempty"` + HideTopMenu bool `json:"hide_top_menu,omitempty"` + AutoHideDiffTableHeader bool `json:"auto_hide_diff_table_header,omitempty"` + HideLineNumbers bool `json:"hide_line_numbers,omitempty"` + TabSize int `json:"tab_size"` + HideEmptyPane bool `json:"hide_empty_pane,omitempty"` +} + +// DiffPreferencesInput entity contains information for setting the diff preferences of a user. +// Fields which are not set will not be updated. +type DiffPreferencesInput struct { + Context int `json:"context,omitempty"` + ExpandAllComments bool `json:"expand_all_comments,omitempty"` + IgnoreWhitespace string `json:"ignore_whitespace,omitempty"` + IntralineDifference bool `json:"intraline_difference,omitempty"` + LineLength int `json:"line_length,omitempty"` + ManualReview bool `json:"manual_review,omitempty"` + RetainHeader bool `json:"retain_header,omitempty"` + ShowLineEndings bool `json:"show_line_endings,omitempty"` + ShowTabs bool `json:"show_tabs,omitempty"` + ShowWhitespaceErrors bool `json:"show_whitespace_errors,omitempty"` + SkipDeleted bool `json:"skip_deleted,omitempty"` + SkipUncommented bool `json:"skip_uncommented,omitempty"` + SyntaxHighlighting bool `json:"syntax_highlighting,omitempty"` + HideTopMenu bool `json:"hide_top_menu,omitempty"` + AutoHideDiffTableHeader bool `json:"auto_hide_diff_table_header,omitempty"` + HideLineNumbers bool `json:"hide_line_numbers,omitempty"` + TabSize int `json:"tab_size,omitempty"` +} + +// PreferencesInfo entity contains information about a user’s preferences. +type PreferencesInfo struct { + ChangesPerPage int `json:"changes_per_page"` + ShowSiteHeader bool `json:"show_site_header,omitempty"` + UseFlashClipboard bool `json:"use_flash_clipboard,omitempty"` + DownloadScheme string `json:"download_scheme"` + DownloadCommand string `json:"download_command"` + CopySelfOnEmail bool `json:"copy_self_on_email,omitempty"` + DateFormat string `json:"date_format"` + TimeFormat string `json:"time_format"` + RelativeDateInChangeTable bool `json:"relative_date_in_change_table,omitempty"` + SizeBarInChangeTable bool `json:"size_bar_in_change_table,omitempty"` + LegacycidInChangeTable bool `json:"legacycid_in_change_table,omitempty"` + MuteCommonPathPrefixes bool `json:"mute_common_path_prefixes,omitempty"` + ReviewCategoryStrategy string `json:"review_category_strategy"` + DiffView string `json:"diff_view"` + My []TopMenuItemInfo `json:"my"` + URLAliases string `json:"url_aliases,omitempty"` +} + +// PreferencesInput entity contains information for setting the user preferences. +// Fields which are not set will not be updated. +type PreferencesInput struct { + ChangesPerPage int `json:"changes_per_page,omitempty"` + ShowSiteHeader bool `json:"show_site_header,omitempty"` + UseFlashClipboard bool `json:"use_flash_clipboard,omitempty"` + DownloadScheme string `json:"download_scheme,omitempty"` + DownloadCommand string `json:"download_command,omitempty"` + CopySelfOnEmail bool `json:"copy_self_on_email,omitempty"` + DateFormat string `json:"date_format,omitempty"` + TimeFormat string `json:"time_format,omitempty"` + RelativeDateInChangeTable bool `json:"relative_date_in_change_table,omitempty"` + SizeBarInChangeTable bool `json:"size_bar_in_change_table,omitempty"` + LegacycidInChangeTable bool `json:"legacycid_in_change_table,omitempty"` + MuteCommonPathPrefixes bool `json:"mute_common_path_prefixes,omitempty"` + ReviewCategoryStrategy string `json:"review_category_strategy,omitempty"` + DiffView string `json:"diff_view,omitempty"` + My []TopMenuItemInfo `json:"my,omitempty"` + URLAliases string `json:"url_aliases,omitempty"` +} + +// CapabilityOptions specifies the parameters to filter for capabilities. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#list-account-capabilities +type CapabilityOptions struct { + // To filter the set of global capabilities the q parameter can be used. + // Filtering may decrease the response time by avoiding looking at every possible alternative for the caller. + Filter []string `url:"q,omitempty"` +} + +// GetAccount returns an account as an AccountInfo entity. +// If account is "self" the current authenticated account will be returned. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#get-account +func (s *AccountsService) GetAccount(account string) (*AccountInfo, *Response, error) { + u := fmt.Sprintf("accounts/%s", account) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new(AccountInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// GetAccountDetails retrieves the details of an account. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#get-detail +func (s *AccountsService) GetAccountDetails(accountID string) (*AccountDetailInfo, *Response, error) { + u := fmt.Sprintf("accounts/%s/detail", accountID) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new(AccountDetailInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// GetAccountName retrieves the full name of an account. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#get-account-name +func (s *AccountsService) GetAccountName(accountID string) (string, *Response, error) { + u := fmt.Sprintf("accounts/%s/name", accountID) + return getStringResponseWithoutOptions(s.client, u) +} + +// GetUsername retrieves the username of an account. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#get-username +func (s *AccountsService) GetUsername(accountID string) (string, *Response, error) { + u := fmt.Sprintf("accounts/%s/username", accountID) + return getStringResponseWithoutOptions(s.client, u) +} + +// GetHTTPPassword retrieves the HTTP password of an account. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#get-http-password +func (s *AccountsService) GetHTTPPassword(accountID string) (string, *Response, error) { + u := fmt.Sprintf("accounts/%s/password.http", accountID) + return getStringResponseWithoutOptions(s.client, u) +} + +// ListAccountEmails returns the email addresses that are configured for the specified user. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#list-account-emails +func (s *AccountsService) ListAccountEmails(accountID string) (*[]EmailInfo, *Response, error) { + u := fmt.Sprintf("accounts/%s/emails", accountID) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new([]EmailInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// GetAccountEmail retrieves an email address of a user. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#get-account-email +func (s *AccountsService) GetAccountEmail(accountID, emailID string) (*EmailInfo, *Response, error) { + u := fmt.Sprintf("accounts/%s/emails/%s", accountID, emailID) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new(EmailInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// ListSSHKeys returns the SSH keys of an account. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#list-ssh-keys +func (s *AccountsService) ListSSHKeys(accountID string) (*[]SSHKeyInfo, *Response, error) { + u := fmt.Sprintf("accounts/%s/sshkeys", accountID) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new([]SSHKeyInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// GetSSHKey retrieves an SSH key of a user. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#get-ssh-key +func (s *AccountsService) GetSSHKey(accountID, sshKeyID string) (*SSHKeyInfo, *Response, error) { + u := fmt.Sprintf("accounts/%s/sshkeys/%s", accountID, sshKeyID) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new(SSHKeyInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// ListGPGKeys returns the GPG keys of an account. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#list-gpg-keys +func (s *AccountsService) ListGPGKeys(accountID string) (*map[string]GpgKeyInfo, *Response, error) { + u := fmt.Sprintf("accounts/%s/gpgkeys", accountID) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new(map[string]GpgKeyInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// GetGPGKey retrieves a GPG key of a user. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#get-gpg-key +func (s *AccountsService) GetGPGKey(accountID, gpgKeyID string) (*GpgKeyInfo, *Response, error) { + u := fmt.Sprintf("accounts/%s/gpgkeys/%s", accountID, gpgKeyID) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new(GpgKeyInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// ListAccountCapabilities returns the global capabilities that are enabled for the specified user. +// If the global capabilities for the calling user should be listed, self can be used as account-id. +// This can be used by UI tools to discover if administrative features are available to the caller, so they can hide (or show) relevant UI actions. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#list-account-capabilities +func (s *AccountsService) ListAccountCapabilities(accountID string, opt *CapabilityOptions) (*AccountCapabilityInfo, *Response, error) { + u := fmt.Sprintf("accounts/%s/capabilities", accountID) + + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new(AccountCapabilityInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// ListGroups lists all groups that contain the specified user as a member. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#list-groups +func (s *AccountsService) ListGroups(accountID string) (*[]GroupInfo, *Response, error) { + u := fmt.Sprintf("accounts/%s/groups", accountID) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new([]GroupInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// GetUserPreferences retrieves the user’s preferences. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#get-user-preferences +func (s *AccountsService) GetUserPreferences(accountID string) (*PreferencesInfo, *Response, error) { + u := fmt.Sprintf("accounts/%s/preferences", accountID) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new(PreferencesInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// GetDiffPreferences retrieves the diff preferences of a user. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#get-diff-preferences +func (s *AccountsService) GetDiffPreferences(accountID string) (*DiffPreferencesInfo, *Response, error) { + u := fmt.Sprintf("accounts/%s/preferences.diff", accountID) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new(DiffPreferencesInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// GetStarredChanges gets the changes starred by the identified user account. +// This URL endpoint is functionally identical to the changes query GET /changes/?q=is:starred. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#get-starred-changes +func (s *AccountsService) GetStarredChanges(accountID string) (*[]ChangeInfo, *Response, error) { + u := fmt.Sprintf("accounts/%s/starred.changes", accountID) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new([]ChangeInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// SuggestAccount suggests users for a given query q and result limit n. +// If result limit is not passed, then the default 10 is used. +// Returns a list of matching AccountInfo entities. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#query-account +func (s *AccountsService) SuggestAccount(opt *QueryOptions) (*[]AccountInfo, *Response, error) { + u := "accounts/" + + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new([]AccountInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// CreateAccount creates a new account. +// In the request body additional data for the account can be provided as AccountInput. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#create-account +func (s *AccountsService) CreateAccount(username string, input *AccountInput) (*AccountInfo, *Response, error) { + u := fmt.Sprintf("accounts/%s", username) + + req, err := s.client.NewRequest("PUT", u, input) + if err != nil { + return nil, nil, err + } + + v := new(AccountInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// SetAccountName sets the full name of an account. +// The new account name must be provided in the request body inside an AccountNameInput entity. +// +// As response the new account name is returned. +// If the name was deleted the response is “204 No Content”. +// Some realms may not allow to modify the account name. +// In this case the request is rejected with “405 Method Not Allowed”. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#set-account-name +func (s *AccountsService) SetAccountName(accountID string, input *AccountNameInput) (*string, *Response, error) { + u := fmt.Sprintf("accounts/%s/name", accountID) + + // TODO Use here the getStringResponseWithoutOptions (for PUT requests) + + req, err := s.client.NewRequest("PUT", u, input) + if err != nil { + return nil, nil, err + } + + v := new(string) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// DeleteAccountName deletes the name of an account. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#delete-account-name +func (s *AccountsService) DeleteAccountName(accountID string) (*Response, error) { + u := fmt.Sprintf("accounts/%s/name", accountID) + return s.client.DeleteRequest(u, nil) +} + +// DeleteActive sets the account state to inactive. +// If the account was already inactive the response is “404 Not Found”. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#delete-active +func (s *AccountsService) DeleteActive(accountID string) (*Response, error) { + u := fmt.Sprintf("accounts/%s/active", accountID) + return s.client.DeleteRequest(u, nil) +} + +// DeleteHTTPPassword deletes the HTTP password of an account. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#delete-http-password +func (s *AccountsService) DeleteHTTPPassword(accountID string) (*Response, error) { + u := fmt.Sprintf("accounts/%s/password.http", accountID) + return s.client.DeleteRequest(u, nil) +} + +// DeleteAccountEmail deletes an email address of an account. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#delete-account-email +func (s *AccountsService) DeleteAccountEmail(accountID, emailID string) (*Response, error) { + u := fmt.Sprintf("accounts/%s/emails/%s", accountID, emailID) + return s.client.DeleteRequest(u, nil) +} + +// DeleteSSHKey deletes an SSH key of a user. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#delete-ssh-key +func (s *AccountsService) DeleteSSHKey(accountID, sshKeyID string) (*Response, error) { + u := fmt.Sprintf("accounts/%s/sshkeys/%s", accountID, sshKeyID) + return s.client.DeleteRequest(u, nil) +} + +// DeleteGPGKey deletes a GPG key of a user. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#delete-gpg-key +func (s *AccountsService) DeleteGPGKey(accountID, gpgKeyID string) (*Response, error) { + u := fmt.Sprintf("accounts/%s/gpgkeys/%s", accountID, gpgKeyID) + return s.client.DeleteRequest(u, nil) +} + +// SetUsername sets a new username. +// The new username must be provided in the request body inside a UsernameInput entity. +// Once set, the username cannot be changed or deleted. +// If attempted this fails with “405 Method Not Allowed”. +// +// As response the new username is returned. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#set-username +func (s *AccountsService) SetUsername(accountID string, input *UsernameInput) (*string, *Response, error) { + u := fmt.Sprintf("accounts/%s/username", accountID) + + req, err := s.client.NewRequest("PUT", u, input) + if err != nil { + return nil, nil, err + } + + v := new(string) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// GetActive checks if an account is active. +// +// If the account is active the string ok is returned. +// If the account is inactive the response is “204 No Content”. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#get-active +func (s *AccountsService) GetActive(accountID string) (string, *Response, error) { + u := fmt.Sprintf("accounts/%s/active", accountID) + return getStringResponseWithoutOptions(s.client, u) +} + +// SetActive sets the account state to active. +// +// If the account was already active the response is “200 OK”. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#set-active +func (s *AccountsService) SetActive(accountID string) (*Response, error) { + u := fmt.Sprintf("accounts/%s/active", accountID) + + req, err := s.client.NewRequest("PUT", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(req, nil) +} + +// SetHTTPPassword sets/Generates the HTTP password of an account. +// The options for setting/generating the HTTP password must be provided in the request body inside a HTTPPasswordInput entity. +// +// As response the new HTTP password is returned. +// If the HTTP password was deleted the response is “204 No Content”. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#set-http-password +func (s *AccountsService) SetHTTPPassword(accountID string, input *HTTPPasswordInput) (*string, *Response, error) { + u := fmt.Sprintf("accounts/%s/password.http", accountID) + + req, err := s.client.NewRequest("PUT", u, input) + if err != nil { + return nil, nil, err + } + + v := new(string) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// CreateAccountEmail registers a new email address for the user. +// A verification email is sent with a link that needs to be visited to confirm the email address, unless DEVELOPMENT_BECOME_ANY_ACCOUNT is used as authentication type. +// For the development mode email addresses are directly added without confirmation. +// A Gerrit administrator may add an email address without confirmation by setting no_confirmation in the EmailInput. +// In the request body additional data for the email address can be provided as EmailInput. +// +// As response the new email address is returned as EmailInfo entity. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#create-account-email +func (s *AccountsService) CreateAccountEmail(accountID, emailID string, input *EmailInput) (*EmailInfo, *Response, error) { + u := fmt.Sprintf("accounts/%s/emails/%s", accountID, emailID) + + req, err := s.client.NewRequest("PUT", u, input) + if err != nil { + return nil, nil, err + } + + v := new(EmailInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// SetPreferredEmail sets an email address as preferred email address for an account. +// +// If the email address was already the preferred email address of the account the response is “200 OK”. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#set-preferred-email +func (s *AccountsService) SetPreferredEmail(accountID, emailID string) (*Response, error) { + u := fmt.Sprintf("accounts/%s/emails/%s/preferred", accountID, emailID) + + req, err := s.client.NewRequest("PUT", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(req, nil) +} + +// GetAvatarChangeURL retrieves the URL where the user can change the avatar image. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#get-avatar-change-url +func (s *AccountsService) GetAvatarChangeURL(accountID string) (string, *Response, error) { + u := fmt.Sprintf("accounts/%s/avatar.change.url", accountID) + return getStringResponseWithoutOptions(s.client, u) +} + +// AddGPGKeys Add or delete one or more GPG keys for a user. +// The changes must be provided in the request body as a GpgKeysInput entity. +// Each new GPG key is provided in ASCII armored format, and must contain a self-signed certification matching a registered email or other identity of the user. +// +// As a response, the modified GPG keys are returned as a map of GpgKeyInfo entities, keyed by ID. Deleted keys are represented by an empty object. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#add-delete-gpg-keys +func (s *AccountsService) AddGPGKeys(accountID string, input *GpgKeysInput) (*map[string]GpgKeyInfo, *Response, error) { + u := fmt.Sprintf("accounts/%s/gpgkeys", accountID) + + req, err := s.client.NewRequest("POST", u, input) + if err != nil { + return nil, nil, err + } + + v := new(map[string]GpgKeyInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// CheckAccountCapability checks if a user has a certain global capability. +// +// If the user has the global capability the string ok is returned. +// If the user doesn’t have the global capability the response is “404 Not Found”. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#check-account-capability +func (s *AccountsService) CheckAccountCapability(accountID, capabilityID string) (string, *Response, error) { + u := fmt.Sprintf("accounts/%s/capabilities/%s", accountID, capabilityID) + return getStringResponseWithoutOptions(s.client, u) +} + +// SetUserPreferences sets the user’s preferences. +// The new preferences must be provided in the request body as a PreferencesInput entity. +// +// As result the new preferences of the user are returned as a PreferencesInfo entity. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#set-user-preferences +func (s *AccountsService) SetUserPreferences(accountID string, input *PreferencesInput) (*PreferencesInfo, *Response, error) { + u := fmt.Sprintf("accounts/%s/preferences", accountID) + + req, err := s.client.NewRequest("PUT", u, input) + if err != nil { + return nil, nil, err + } + + v := new(PreferencesInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// SetDiffPreferences sets the diff preferences of a user. +// The new diff preferences must be provided in the request body as a DiffPreferencesInput entity. +// +// As result the new diff preferences of the user are returned as a DiffPreferencesInfo entity. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#set-diff-preferences +func (s *AccountsService) SetDiffPreferences(accountID string, input *DiffPreferencesInput) (*DiffPreferencesInfo, *Response, error) { + u := fmt.Sprintf("accounts/%s/preferences.diff", accountID) + + req, err := s.client.NewRequest("PUT", u, input) + if err != nil { + return nil, nil, err + } + + v := new(DiffPreferencesInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// StarChange star a change. +// Starred changes are returned for the search query is:starred or starredby:USER and automatically notify the user whenever updates are made to the change. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#star-change +func (s *AccountsService) StarChange(accountID, changeID string) (*Response, error) { + u := fmt.Sprintf("accounts/%s/starred.changes/%s", accountID, changeID) + + req, err := s.client.NewRequest("PUT", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// UnstarChange nstar a change. +// Removes the starred flag, stopping notifications. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#unstar-change +func (s *AccountsService) UnstarChange(accountID, changeID string) (*Response, error) { + u := fmt.Sprintf("accounts/%s/starred.changes/%s", accountID, changeID) + return s.client.DeleteRequest(u, nil) +} + +/* +Missing Account Endpoints: + Add SSH Key + Get Avatar +*/ diff --git a/vendor/github.com/andygrunwald/go-gerrit/authentication.go b/vendor/github.com/andygrunwald/go-gerrit/authentication.go new file mode 100644 index 00000000..7cb3795c --- /dev/null +++ b/vendor/github.com/andygrunwald/go-gerrit/authentication.go @@ -0,0 +1,187 @@ +package gerrit + +import ( + "crypto/md5" // nolint: gas + "crypto/rand" + "encoding/base64" + "errors" + "fmt" + "io" + "net/http" + "strings" +) + +var ( + // ErrWWWAuthenticateHeaderMissing is returned by digestAuthHeader when the WWW-Authenticate header is missing + ErrWWWAuthenticateHeaderMissing = errors.New("WWW-Authenticate header is missing") + + // ErrWWWAuthenticateHeaderInvalid is returned by digestAuthHeader when the WWW-Authenticate invalid + ErrWWWAuthenticateHeaderInvalid = errors.New("WWW-Authenticate header is invalid") + + // ErrWWWAuthenticateHeaderNotDigest is returned by digestAuthHeader when the WWW-Authenticate header is not 'Digest' + ErrWWWAuthenticateHeaderNotDigest = errors.New("WWW-Authenticate header type is not Digest") +) + +const ( + // HTTP Basic Authentication + authTypeBasic = 1 + // HTTP Digest Authentication + authTypeDigest = 2 + // HTTP Cookie Authentication + authTypeCookie = 3 +) + +// AuthenticationService contains Authentication related functions. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api.html#authentication +type AuthenticationService struct { + client *Client + + // Storage for authentication + // Username or name of cookie + name string + // Password or value of cookie + secret string + authType int +} + +// SetBasicAuth sets basic parameters for HTTP Basic auth +func (s *AuthenticationService) SetBasicAuth(username, password string) { + s.name = username + s.secret = password + s.authType = authTypeBasic +} + +// SetDigestAuth sets digest parameters for HTTP Digest auth. +func (s *AuthenticationService) SetDigestAuth(username, password string) { + s.name = username + s.secret = password + s.authType = authTypeDigest +} + +// digestAuthHeader is called by gerrit.Client.Do in the event the server +// returns 401 Unauthorized and authType was set to authTypeDigest. The +// resulting string is used to set the Authorization header before retrying +// the request. +func (s *AuthenticationService) digestAuthHeader(response *http.Response) (string, error) { + authenticateHeader := response.Header.Get("WWW-Authenticate") + if authenticateHeader == "" { + return "", ErrWWWAuthenticateHeaderMissing + } + + split := strings.SplitN(authenticateHeader, " ", 2) + if len(split) != 2 { + return "", ErrWWWAuthenticateHeaderInvalid + } + + if split[0] != "Digest" { + return "", ErrWWWAuthenticateHeaderNotDigest + } + + // Iterate over all the fields from the WWW-Authenticate header + // and create a map of keys and values. + authenticate := map[string]string{} + for _, value := range strings.Split(split[1], ",") { + kv := strings.SplitN(value, "=", 2) + if len(kv) != 2 { + continue + } + + key := strings.Trim(strings.Trim(kv[0], " "), "\"") + value := strings.Trim(strings.Trim(kv[1], " "), "\"") + authenticate[key] = value + } + + // Gerrit usually responds without providing the algorithm. According + // to RFC2617 if no algorithm is provided then the default is to use + // MD5. At the time this code was implemented Gerrit did not appear + // to support other algorithms or provide a means of changing the + // algorithm. + if value, ok := authenticate["algorithm"]; ok { + if value != "MD5" { + return "", fmt.Errorf( + "algorithm not implemented: %s", value) + } + } + + realmHeader := authenticate["realm"] + qopHeader := authenticate["qop"] + nonceHeader := authenticate["nonce"] + + // If the server does not inform us what the uri is supposed + // to be then use the last requests's uri instead. + if _, ok := authenticate["uri"]; !ok { + authenticate["uri"] = response.Request.URL.Path + } + + uriHeader := authenticate["uri"] + + // A1 + h := md5.New() // nolint: gas + A1 := fmt.Sprintf("%s:%s:%s", s.name, realmHeader, s.secret) + if _, err := io.WriteString(h, A1); err != nil { + return "", err + } + HA1 := fmt.Sprintf("%x", h.Sum(nil)) + + // A2 + h = md5.New() // nolint: gas + A2 := fmt.Sprintf("%s:%s", response.Request.Method, uriHeader) + if _, err := io.WriteString(h, A2); err != nil { + return "", err + } + HA2 := fmt.Sprintf("%x", h.Sum(nil)) + + k := make([]byte, 12) + for bytes := 0; bytes < len(k); { + n, err := rand.Read(k[bytes:]) + if err != nil { + return "", fmt.Errorf("cnonce generation failed: %s", err) + } + bytes += n + } + cnonce := base64.StdEncoding.EncodeToString(k) + digest := md5.New() // nolint: gas + if _, err := digest.Write([]byte(strings.Join([]string{HA1, nonceHeader, "00000001", cnonce, qopHeader, HA2}, ":"))); err != nil { + return "", err + } + responseField := fmt.Sprintf("%x", digest.Sum(nil)) + + return fmt.Sprintf( + `Digest username="%s", realm="%s", nonce="%s", uri="%s", cnonce="%s", nc=00000001, qop=%s, response="%s"`, + s.name, realmHeader, nonceHeader, uriHeader, cnonce, qopHeader, responseField), nil +} + +// SetCookieAuth sets basic parameters for HTTP Cookie +func (s *AuthenticationService) SetCookieAuth(name, value string) { + s.name = name + s.secret = value + s.authType = authTypeCookie +} + +// HasBasicAuth checks if the auth type is HTTP Basic auth +func (s *AuthenticationService) HasBasicAuth() bool { + return s.authType == authTypeBasic +} + +// HasDigestAuth checks if the auth type is HTTP Digest based +func (s *AuthenticationService) HasDigestAuth() bool { + return s.authType == authTypeDigest +} + +// HasCookieAuth checks if the auth type is HTTP Cookie based +func (s *AuthenticationService) HasCookieAuth() bool { + return s.authType == authTypeCookie +} + +// HasAuth checks if an auth type is used +func (s *AuthenticationService) HasAuth() bool { + return s.authType > 0 +} + +// ResetAuth resets all former authentification settings +func (s *AuthenticationService) ResetAuth() { + s.name = "" + s.secret = "" + s.authType = 0 +} diff --git a/vendor/github.com/andygrunwald/go-gerrit/changes.go b/vendor/github.com/andygrunwald/go-gerrit/changes.go new file mode 100644 index 00000000..db5f6a72 --- /dev/null +++ b/vendor/github.com/andygrunwald/go-gerrit/changes.go @@ -0,0 +1,768 @@ +package gerrit + +import ( + "errors" + "fmt" + "io/ioutil" + "net/http" +) + +// ChangesService contains Change related REST endpoints +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html +type ChangesService struct { + client *Client +} + +// WebLinkInfo entity describes a link to an external site. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#web-link-info +type WebLinkInfo struct { + Name string `json:"name"` + URL string `json:"url"` + ImageURL string `json:"image_url"` +} + +// GitPersonInfo entity contains information about the author/committer of a commit. +type GitPersonInfo struct { + Name string `json:"name"` + Email string `json:"email"` + Date Timestamp `json:"date"` + TZ int `json:"tz"` +} + +// NotifyInfo entity contains detailed information about who should be +// notified about an update +type NotifyInfo struct { + Accounts []AccountInfo `json:"accounts"` +} + +// AbandonInput entity contains information for abandoning a change. +type AbandonInput struct { + Message string `json:"message,omitempty"` + Notify string `json:"notify"` + NotifyDetails []NotifyInfo `json:"notify_details"` +} + +// ApprovalInfo entity contains information about an approval from a user for a label on a change. +type ApprovalInfo struct { + AccountInfo + Value int `json:"value,omitempty"` + Date string `json:"date,omitempty"` +} + +// ChangeEditInput entity contains information for restoring a path within change edit. +type ChangeEditInput struct { + RestorePath string `json:"restore_path,omitempty"` + OldPath string `json:"old_path,omitempty"` + NewPath string `json:"new_path,omitempty"` +} + +// ChangeEditMessageInput entity contains information for changing the commit message within a change edit. +type ChangeEditMessageInput struct { + Message string `json:"message"` +} + +// ChangeMessageInfo entity contains information about a message attached to a change. +type ChangeMessageInfo struct { + ID string `json:"id"` + Author AccountInfo `json:"author,omitempty"` + Date Timestamp `json:"date"` + Message string `json:"message"` + Tag string `json:"tag,omitempty"` + RevisionNumber int `json:"_revision_number,omitempty"` +} + +// CherryPickInput entity contains information for cherry-picking a change to a new branch. +type CherryPickInput struct { + Message string `json:"message"` + Destination string `json:"destination"` +} + +// CommentRange entity describes the range of an inline comment. +type CommentRange struct { + StartLine int `json:"start_line"` + StartCharacter int `json:"start_character"` + EndLine int `json:"end_line"` + EndCharacter int `json:"end_character"` +} + +// DiffFileMetaInfo entity contains meta information about a file diff +type DiffFileMetaInfo struct { + Name string `json:"name"` + ContentType string `json:"content_type"` + Lines int `json:"lines"` + WebLinks []WebLinkInfo `json:"web_links,omitempty"` +} + +// DiffWebLinkInfo entity describes a link on a diff screen to an external site. +type DiffWebLinkInfo struct { + Name string `json:"name"` + URL string `json:"url"` + ImageURL string `json:"image_url"` + ShowOnSideBySideDiffView bool `json:"show_on_side_by_side_diff_view"` + ShowOnUnifiedDiffView bool `json:"show_on_unified_diff_view"` +} + +// FetchInfo entity contains information about how to fetch a patch set via a certain protocol. +type FetchInfo struct { + URL string `json:"url"` + Ref string `json:"ref"` + Commands map[string]string `json:"commands,omitempty"` +} + +// FixInput entity contains options for fixing commits using the fix change endpoint. +type FixInput struct { + DeletePatchSetIfCommitMissing bool `json:"delete_patch_set_if_commit_missing"` + ExpectMergedAs string `json:"expect_merged_as"` +} + +// GroupBaseInfo entity contains base information about the group. +type GroupBaseInfo struct { + ID int `json:"id"` + Name string `json:"name"` +} + +// IncludedInInfo entity contains information about the branches a change was merged into and tags it was tagged with. +type IncludedInInfo struct { + Branches []string `json:"branches"` + Tags []string `json:"tags"` + External map[string]string `json:"external,omitempty"` +} + +// ProblemInfo entity contains a description of a potential consistency problem with a change. +// These are not related to the code review process, but rather indicate some inconsistency in Gerrit’s database or repository metadata related to the enclosing change. +type ProblemInfo struct { + Message string `json:"message"` + Status string `json:"status,omitempty"` + Outcome string `json:"outcome,omitempty"` +} + +// RebaseInput entity contains information for changing parent when rebasing. +type RebaseInput struct { + Base string `json:"base,omitempty"` +} + +// RestoreInput entity contains information for restoring a change. +type RestoreInput struct { + Message string `json:"message,omitempty"` +} + +// RevertInput entity contains information for reverting a change. +type RevertInput struct { + Message string `json:"message,omitempty"` +} + +// ReviewInfo entity contains information about a review. +type ReviewInfo struct { + Labels map[string]int `json:"labels"` +} + +// ReviewResult entity contains information regarding the updates that were +// made to a review. +type ReviewResult struct { + ReviewInfo + Reviewers map[string]AddReviewerResult `json:"reviewers,omitempty"` + Ready bool `json:"ready,omitempty"` +} + +// TopicInput entity contains information for setting a topic. +type TopicInput struct { + Topic string `json:"topic,omitempty"` +} + +// SubmitRecord entity describes results from a submit_rule. +type SubmitRecord struct { + Status string `json:"status"` + Ok map[string]map[string]AccountInfo `json:"ok,omitempty"` + Reject map[string]map[string]AccountInfo `json:"reject,omitempty"` + Need map[string]interface{} `json:"need,omitempty"` + May map[string]map[string]AccountInfo `json:"may,omitempty"` + Impossible map[string]interface{} `json:"impossible,omitempty"` + ErrorMessage string `json:"error_message,omitempty"` +} + +// SubmitInput entity contains information for submitting a change. +type SubmitInput struct { + WaitForMerge bool `json:"wait_for_merge"` +} + +// SubmitInfo entity contains information about the change status after submitting. +type SubmitInfo struct { + Status string `json:"status"` + OnBehalfOf string `json:"on_behalf_of,omitempty"` +} + +// RuleInput entity contains information to test a Prolog rule. +type RuleInput struct { + Rule string `json:"rule"` + Filters string `json:"filters,omitempty"` +} + +// ReviewerInput entity contains information for adding a reviewer to a change. +type ReviewerInput struct { + Reviewer string `json:"reviewer"` + Confirmed bool `json:"confirmed,omitempty"` +} + +// ReviewInput entity contains information for adding a review to a revision. +type ReviewInput struct { + Message string `json:"message,omitempty"` + Tag string `json:"tag,omitempty"` + Labels map[string]string `json:"labels,omitempty"` + Comments map[string][]CommentInput `json:"comments,omitempty"` + StrictLabels bool `json:"strict_labels,omitempty"` + Drafts string `json:"drafts,omitempty"` + Notify string `json:"notify,omitempty"` + OmitDuplicateComments bool `json:"omit_duplicate_comments,omitempty"` + OnBehalfOf string `json:"on_behalf_of,omitempty"` +} + +// RelatedChangeAndCommitInfo entity contains information about a related change and commit. +type RelatedChangeAndCommitInfo struct { + ChangeID string `json:"change_id,omitempty"` + Commit CommitInfo `json:"commit"` + ChangeNumber int `json:"_change_number,omitempty"` + RevisionNumber int `json:"_revision_number,omitempty"` + CurrentRevisionNumber int `json:"_current_revision_number,omitempty"` + Status string `json:"status,omitempty"` +} + +// DiffContent entity contains information about the content differences in a file. +type DiffContent struct { + A []string `json:"a,omitempty"` + B []string `json:"b,omitempty"` + AB []string `json:"ab,omitempty"` + EditA DiffIntralineInfo `json:"edit_a,omitempty"` + EditB DiffIntralineInfo `json:"edit_b,omitempty"` + Skip int `json:"skip,omitempty"` + Common bool `json:"common,omitempty"` +} + +// CommentInput entity contains information for creating an inline comment. +type CommentInput struct { + ID string `json:"id,omitempty"` + Path string `json:"path,omitempty"` + Side string `json:"side,omitempty"` + Line int `json:"line,omitempty"` + Range *CommentRange `json:"range,omitempty"` + InReplyTo string `json:"in_reply_to,omitempty"` + Updated *Timestamp `json:"updated,omitempty"` + Message string `json:"message,omitempty"` +} + +// DiffIntralineInfo entity contains information about intraline edits in a file. +// +// The information consists of a list of pairs, +// where the skip length is the number of characters between the end of +// the previous edit and the start of this edit, and the mark length is the +// number of edited characters following the skip. The start of the edits +// is from the beginning of the related diff content lines. +// +// Note that the implied newline character at the end of each line +// is included in the length calculation, and thus it is possible for +// the edits to span newlines. +type DiffIntralineInfo [][2]int + +// ChangeInfo entity contains information about a change. +type ChangeInfo struct { + ID string `json:"id"` + URL string `json:"url,omitempty"` + Project string `json:"project"` + Branch string `json:"branch"` + Topic string `json:"topic,omitempty"` + ChangeID string `json:"change_id"` + Subject string `json:"subject"` + Status string `json:"status"` + Created Timestamp `json:"created"` + Updated Timestamp `json:"updated"` + Submitted *Timestamp `json:"submitted,omitempty"` + Starred bool `json:"starred,omitempty"` + Reviewed bool `json:"reviewed,omitempty"` + Mergeable bool `json:"mergeable,omitempty"` + Insertions int `json:"insertions"` + Deletions int `json:"deletions"` + Number int `json:"_number"` + Owner AccountInfo `json:"owner"` + Actions map[string]ActionInfo `json:"actions,omitempty"` + Labels map[string]LabelInfo `json:"labels,omitempty"` + PermittedLabels map[string][]string `json:"permitted_labels,omitempty"` + RemovableReviewers []AccountInfo `json:"removable_reviewers,omitempty"` + Reviewers map[string][]AccountInfo `json:"removable_reviewers,omitempty"` + Messages []ChangeMessageInfo `json:"messages,omitempty"` + CurrentRevision string `json:"current_revision,omitempty"` + Revisions map[string]RevisionInfo `json:"revisions,omitempty"` + MoreChanges bool `json:"_more_changes,omitempty"` + Problems []ProblemInfo `json:"problems,omitempty"` + BaseChange string `json:"base_change,omitempty"` +} + +// LabelInfo entity contains information about a label on a change, always corresponding to the current patch set. +type LabelInfo struct { + Optional bool `json:"optional,omitempty"` + + // Fields set by LABELS + Approved AccountInfo `json:"approved,omitempty"` + Rejected AccountInfo `json:"rejected,omitempty"` + Recommended AccountInfo `json:"recommended,omitempty"` + Disliked AccountInfo `json:"disliked,omitempty"` + Blocking bool `json:"blocking,omitempty"` + Value int `json:"value,omitempty"` + DefaultValue int `json:"default_value,omitempty"` + + // Fields set by DETAILED_LABELS + All []ApprovalInfo `json:"all,omitempty"` + Values map[string]string `json:"values,omitempty"` +} + +// RevisionInfo entity contains information about a patch set. +type RevisionInfo struct { + Draft bool `json:"draft,omitempty"` + Number int `json:"_number"` + Created Timestamp `json:"created"` + Uploader AccountInfo `json:"uploader"` + Ref string `json:"ref"` + Fetch map[string]FetchInfo `json:"fetch"` + Commit CommitInfo `json:"commit,omitempty"` + Files map[string]FileInfo `json:"files,omitempty"` + Actions map[string]ActionInfo `json:"actions,omitempty"` + Reviewed bool `json:"reviewed,omitempty"` + MessageWithFooter string `json:"messageWithFooter,omitempty"` +} + +// CommentInfo entity contains information about an inline comment. +type CommentInfo struct { + PatchSet int `json:"patch_set,omitempty"` + ID string `json:"id"` + Path string `json:"path,omitempty"` + Side string `json:"side,omitempty"` + Line int `json:"line,omitempty"` + Range CommentRange `json:"range,omitempty"` + InReplyTo string `json:"in_reply_to,omitempty"` + Message string `json:"message,omitempty"` + Updated Timestamp `json:"updated"` + Author AccountInfo `json:"author,omitempty"` +} + +// QueryOptions specifies global parameters to query changes / reviewers. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#list-changes +type QueryOptions struct { + // Query parameter + // Clients are allowed to specify more than one query by setting the q parameter multiple times. + // In this case the result is an array of arrays, one per query in the same order the queries were given in. + // + // Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/user-search.html#_search_operators + Query []string `url:"q,omitempty"` + + // The n parameter can be used to limit the returned results. + // If the n query parameter is supplied and additional changes exist that match the query beyond the end, the last change object has a _more_changes: true JSON field set. + Limit int `url:"n,omitempty"` +} + +// QueryChangeOptions specifies the parameters to the ChangesService.QueryChanges. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#list-changes +type QueryChangeOptions struct { + QueryOptions + + // The S or start query parameter can be supplied to skip a number of changes from the list. + Skip int `url:"S,omitempty"` + Start int `url:"start,omitempty"` + + ChangeOptions +} + +// ChangeOptions specifies the parameters for Query changes. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#list-changes +type ChangeOptions struct { + // Additional fields can be obtained by adding o parameters, each option requires more database lookups and slows down the query response time to the client so they are generally disabled by default. + // + // Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#list-changes + AdditionalFields []string `url:"o,omitempty"` +} + +// QueryChanges lists changes visible to the caller. +// The query string must be provided by the q parameter. +// The n parameter can be used to limit the returned results. +// +// The change output is sorted by the last update time, most recently updated to oldest updated. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#list-changes +func (s *ChangesService) QueryChanges(opt *QueryChangeOptions) (*[]ChangeInfo, *Response, error) { + u := "changes/" + + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new([]ChangeInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// GetChange retrieves a change. +// Additional fields can be obtained by adding o parameters, each option requires more database lookups and slows down the query response time to the client so they are generally disabled by default. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-change +func (s *ChangesService) GetChange(changeID string, opt *ChangeOptions) (*ChangeInfo, *Response, error) { + u := fmt.Sprintf("changes/%s", changeID) + return s.getChangeInfoResponse(u, opt) +} + +// GetChangeDetail retrieves a change with labels, detailed labels, detailed accounts, and messages. +// Additional fields can be obtained by adding o parameters, each option requires more database lookups and slows down the query response time to the client so they are generally disabled by default. +// +// This response will contain all votes for each label and include one combined vote. +// The combined label vote is calculated in the following order (from highest to lowest): REJECTED > APPROVED > DISLIKED > RECOMMENDED. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-change-detail +func (s *ChangesService) GetChangeDetail(changeID string, opt *ChangeOptions) (*ChangeInfo, *Response, error) { + u := fmt.Sprintf("changes/%s/detail", changeID) + return s.getChangeInfoResponse(u, opt) +} + +// getChangeInfoResponse retrieved a single ChangeInfo Response for a GET request +func (s *ChangesService) getChangeInfoResponse(u string, opt *ChangeOptions) (*ChangeInfo, *Response, error) { + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new(ChangeInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// GetTopic retrieves the topic of a change. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-topic +func (s *ChangesService) GetTopic(changeID string) (string, *Response, error) { + u := fmt.Sprintf("changes/%s/topic", changeID) + return getStringResponseWithoutOptions(s.client, u) +} + +// ChangesSubmittedTogether returns a list of all changes which are submitted when {submit} is called for this change, including the current change itself. +// An empty list is returned if this change will be submitted by itself (no other changes). +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#submitted_together +func (s *ChangesService) ChangesSubmittedTogether(changeID string) (*[]ChangeInfo, *Response, error) { + u := fmt.Sprintf("changes/%s/submitted_together", changeID) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new([]ChangeInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// GetIncludedIn retrieves the branches and tags in which a change is included. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-included-in +func (s *ChangesService) GetIncludedIn(changeID string) (*IncludedInInfo, *Response, error) { + u := fmt.Sprintf("changes/%s/in", changeID) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new(IncludedInInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// ListChangeComments lists the published comments of all revisions of the change. +// The entries in the map are sorted by file path, and the comments for each path are sorted by patch set number. +// Each comment has the patch_set and author fields set. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#list-change-comments +func (s *ChangesService) ListChangeComments(changeID string) (*map[string][]CommentInfo, *Response, error) { + u := fmt.Sprintf("changes/%s/comments", changeID) + return s.getCommentInfoMapResponse(u) +} + +// ListChangeDrafts lLists the draft comments of all revisions of the change that belong to the calling user. +// The entries in the map are sorted by file path, and the comments for each path are sorted by patch set number. +// Each comment has the patch_set field set, and no author. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#list-change-drafts +func (s *ChangesService) ListChangeDrafts(changeID string) (*map[string][]CommentInfo, *Response, error) { + u := fmt.Sprintf("changes/%s/drafts", changeID) + return s.getCommentInfoMapResponse(u) +} + +// getCommentInfoMapResponse retrieved a map of CommentInfo Response for a GET request +func (s *ChangesService) getCommentInfoMapResponse(u string) (*map[string][]CommentInfo, *Response, error) { + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new(map[string][]CommentInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// CheckChange performs consistency checks on the change, and returns a ChangeInfo entity with the problems field set to a list of ProblemInfo entities. +// Depending on the type of problem, some fields not marked optional may be missing from the result. +// At least id, project, branch, and _number will be present. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#check-change +func (s *ChangesService) CheckChange(changeID string) (*ChangeInfo, *Response, error) { + u := fmt.Sprintf("changes/%s/check", changeID) + return s.getChangeInfoResponse(u, nil) +} + +// getCommentInfoResponse retrieved a CommentInfo Response for a GET request +func (s *ChangesService) getCommentInfoResponse(u string) (*CommentInfo, *Response, error) { + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new(CommentInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// getCommentInfoMapSliceResponse retrieved a map with a slice of CommentInfo Response for a GET request +func (s *ChangesService) getCommentInfoMapSliceResponse(u string) (*map[string][]CommentInfo, *Response, error) { + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new(map[string][]CommentInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// CreateChange creates a new change. +// The change info ChangeInfo entity must be provided in the request body. +// Only the following attributes are honored: project, branch, subject, status and topic. +// The first three attributes are mandatory. +// Valid values for status are: DRAFT and NEW. +// +// As response a ChangeInfo entity is returned that describes the resulting change. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#create-change +func (s *ChangesService) CreateChange(input *ChangeInfo) (*ChangeInfo, *Response, error) { + u := "changes/" + + req, err := s.client.NewRequest("POST", u, input) + if err != nil { + return nil, nil, err + } + + v := new(ChangeInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// SetTopic sets the topic of a change. +// The new topic must be provided in the request body inside a TopicInput entity. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#set-topic +func (s *ChangesService) SetTopic(changeID string, input *TopicInput) (*string, *Response, error) { + u := fmt.Sprintf("changes/%s/topic", changeID) + + req, err := s.client.NewRequest("PUT", u, input) + if err != nil { + return nil, nil, err + } + + v := new(string) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// DeleteTopic deletes the topic of a change. +// The request body does not need to include a TopicInput entity if no review comment is added. +// +// Please note that some proxies prohibit request bodies for DELETE requests. +// In this case, if you want to specify a commit message, use PUT to delete the topic. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#delete-topic +func (s *ChangesService) DeleteTopic(changeID string) (*Response, error) { + u := fmt.Sprintf("changes/%s/topic", changeID) + return s.client.DeleteRequest(u, nil) +} + +// DeleteDraftChange deletes a draft change. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#delete-draft-change +func (s *ChangesService) DeleteDraftChange(changeID string) (*Response, error) { + u := fmt.Sprintf("changes/%s", changeID) + return s.client.DeleteRequest(u, nil) +} + +// PublishDraftChange publishes a draft change. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#publish-draft-change +func (s *ChangesService) PublishDraftChange(changeID, notify string) (*Response, error) { + u := fmt.Sprintf("changes/%s/publish", changeID) + + req, err := s.client.NewRequest("POST", u, map[string]string{ + "notify": notify, + }) + if err != nil { + return nil, err + } + return s.client.Do(req, nil) +} + +// IndexChange adds or updates the change in the secondary index. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#index-change +func (s *ChangesService) IndexChange(changeID string) (*Response, error) { + u := fmt.Sprintf("changes/%s/index", changeID) + + req, err := s.client.NewRequest("POST", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(req, nil) +} + +// FixChange performs consistency checks on the change as with GET /check, and additionally fixes any problems that can be fixed automatically. +// The returned field values reflect any fixes. +// +// Some fixes have options controlling their behavior, which can be set in the FixInput entity body. +// Only the change owner, a project owner, or an administrator may fix changes. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#fix-change +func (s *ChangesService) FixChange(changeID string, input *FixInput) (*ChangeInfo, *Response, error) { + u := fmt.Sprintf("changes/%s/check", changeID) + + req, err := s.client.NewRequest("PUT", u, input) + if err != nil { + return nil, nil, err + } + + v := new(ChangeInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// change is an internal function to consolidate code used by SubmitChange, +// AbandonChange and other similar functions. +func (s *ChangesService) change(tail string, changeID string, input interface{}) (*ChangeInfo, *Response, error) { + u := fmt.Sprintf("changes/%s/%s", changeID, tail) + req, err := s.client.NewRequest("POST", u, input) + if err != nil { + return nil, nil, err + } + + v := new(ChangeInfo) + resp, err := s.client.Do(req, v) + if resp.StatusCode == http.StatusConflict { + body, _ := ioutil.ReadAll(resp.Body) + err = errors.New(string(body[:])) + } + return v, resp, err +} + +// SubmitChange submits a change. +// +// The request body only needs to include a SubmitInput entity if submitting on behalf of another user. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#submit-change +func (s *ChangesService) SubmitChange(changeID string, input *SubmitInput) (*ChangeInfo, *Response, error) { + return s.change("submit", changeID, input) +} + +// AbandonChange abandons a change. +// +// The request body does not need to include a AbandonInput entity if no review +// comment is added. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#abandon-change +func (s *ChangesService) AbandonChange(changeID string, input *AbandonInput) (*ChangeInfo, *Response, error) { + return s.change("abandon", changeID, input) +} + +// RebaseChange rebases a change. +// +// Optionally, the parent revision can be changed to another patch set through +// the RebaseInput entity. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#rebase-change +func (s *ChangesService) RebaseChange(changeID string, input *RebaseInput) (*ChangeInfo, *Response, error) { + return s.change("rebase", changeID, input) +} + +// RestoreChange restores a change. +// +// The request body does not need to include a RestoreInput entity if no review +// comment is added. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#restore-change +func (s *ChangesService) RestoreChange(changeID string, input *RestoreInput) (*ChangeInfo, *Response, error) { + return s.change("restore", changeID, input) +} + +// RevertChange reverts a change. +// +// The request body does not need to include a RevertInput entity if no +// review comment is added. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#revert-change +func (s *ChangesService) RevertChange(changeID string, input *RevertInput) (*ChangeInfo, *Response, error) { + return s.change("revert", changeID, input) +} diff --git a/vendor/github.com/andygrunwald/go-gerrit/changes_edit.go b/vendor/github.com/andygrunwald/go-gerrit/changes_edit.go new file mode 100644 index 00000000..58033e21 --- /dev/null +++ b/vendor/github.com/andygrunwald/go-gerrit/changes_edit.go @@ -0,0 +1,231 @@ +package gerrit + +import ( + "fmt" + "net/url" +) + +// EditInfo entity contains information about a change edit. +type EditInfo struct { + Commit CommitInfo `json:"commit"` + BaseRevision string `json:"baseRevision"` + Fetch map[string]FetchInfo `json:"fetch"` + Files map[string]FileInfo `json:"files,omitempty"` +} + +// EditFileInfo entity contains additional information of a file within a change edit. +type EditFileInfo struct { + WebLinks []WebLinkInfo `json:"web_links,omitempty"` +} + +// ChangeEditDetailOptions specifies the parameters to the ChangesService.GetChangeEditDetails. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-edit-detail +type ChangeEditDetailOptions struct { + // When request parameter list is provided the response also includes the file list. + List bool `url:"list,omitempty"` + // When base request parameter is provided the file list is computed against this base revision. + Base bool `url:"base,omitempty"` + // When request parameter download-commands is provided fetch info map is also included. + DownloadCommands bool `url:"download-commands,omitempty"` +} + +// GetChangeEditDetails retrieves a change edit details. +// As response an EditInfo entity is returned that describes the change edit, or “204 No Content” when change edit doesn’t exist for this change. +// Change edits are stored on special branches and there can be max one edit per user per change. +// Edits aren’t tracked in the database. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-edit-detail +func (s *ChangesService) GetChangeEditDetails(changeID string, opt *ChangeEditDetailOptions) (*EditInfo, *Response, error) { + u := fmt.Sprintf("changes/%s/edit", changeID) + + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new(EditInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// RetrieveMetaDataOfAFileFromChangeEdit retrieves meta data of a file from a change edit. +// Currently only web links are returned. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-edit-meta-data +func (s *ChangesService) RetrieveMetaDataOfAFileFromChangeEdit(changeID, filePath string) (*EditFileInfo, *Response, error) { + u := fmt.Sprintf("changes/%s/edit/%s/meta", changeID, filePath) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new(EditFileInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// RetrieveCommitMessageFromChangeEdit retrieves commit message from change edit. +// The commit message is returned as base64 encoded string. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-edit-message +func (s *ChangesService) RetrieveCommitMessageFromChangeEdit(changeID string) (string, *Response, error) { + u := fmt.Sprintf("changes/%s/edit:message", changeID) + return getStringResponseWithoutOptions(s.client, u) +} + +// ChangeFileContentInChangeEdit put content of a file to a change edit. +// +// When change edit doesn’t exist for this change yet it is created. +// When file content isn’t provided, it is wiped out for that file. +// As response “204 No Content” is returned. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#put-edit-file +func (s *ChangesService) ChangeFileContentInChangeEdit(changeID, filePath, content string) (*Response, error) { + u := fmt.Sprintf("changes/%s/edit/%s", changeID, url.QueryEscape(filePath)) + + req, err := s.client.NewRawPutRequest(u, content) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// ChangeCommitMessageInChangeEdit modify commit message. +// The request body needs to include a ChangeEditMessageInput entity. +// +// If a change edit doesn’t exist for this change yet, it is created. +// As response “204 No Content” is returned. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#put-change-edit-message +func (s *ChangesService) ChangeCommitMessageInChangeEdit(changeID string, input *ChangeEditMessageInput) (*Response, error) { + u := fmt.Sprintf("changes/%s/edit:message", changeID) + + req, err := s.client.NewRequest("PUT", u, input) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// DeleteFileInChangeEdit deletes a file from a change edit. +// This deletes the file from the repository completely. +// This is not the same as reverting or restoring a file to its previous contents. +// +// When change edit doesn’t exist for this change yet it is created. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#delete-edit-file +func (s *ChangesService) DeleteFileInChangeEdit(changeID, filePath string) (*Response, error) { + u := fmt.Sprintf("changes/%s/edit/%s", changeID, filePath) + return s.client.DeleteRequest(u, nil) +} + +// DeleteChangeEdit deletes change edit. +// +// As response “204 No Content” is returned. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#delete-edit +func (s *ChangesService) DeleteChangeEdit(changeID string) (*Response, error) { + u := fmt.Sprintf("changes/%s/edit", changeID) + return s.client.DeleteRequest(u, nil) +} + +// PublishChangeEdit promotes change edit to a regular patch set. +// +// As response “204 No Content” is returned. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#publish-edit +func (s *ChangesService) PublishChangeEdit(changeID, notify string) (*Response, error) { + u := fmt.Sprintf("changes/%s/edit:publish", changeID) + + req, err := s.client.NewRequest("POST", u, map[string]string{ + "notify": notify, + }) + if err != nil { + return nil, err + } + return s.client.Do(req, nil) +} + +// RebaseChangeEdit rebases change edit on top of latest patch set. +// +// When change was rebased on top of latest patch set, response “204 No Content” is returned. +// When change edit is already based on top of the latest patch set, the response “409 Conflict” is returned. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#rebase-edit +func (s *ChangesService) RebaseChangeEdit(changeID string) (*Response, error) { + u := fmt.Sprintf("changes/%s/edit:rebase", changeID) + + req, err := s.client.NewRequest("POST", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// RetrieveFileContentFromChangeEdit retrieves content of a file from a change edit. +// +// The content of the file is returned as text encoded inside base64. +// The Content-Type header will always be text/plain reflecting the outer base64 encoding. +// A Gerrit-specific X-FYI-Content-Type header can be examined to find the server detected content type of the file. +// +// When the specified file was deleted in the change edit “204 No Content” is returned. +// If only the content type is required, callers should use HEAD to avoid downloading the encoded file contents. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-edit-file +func (s *ChangesService) RetrieveFileContentFromChangeEdit(changeID, filePath string) (*string, *Response, error) { + u := fmt.Sprintf("changes/%s/edit/%s", changeID, filePath) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new(string) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// RetrieveFileContentTypeFromChangeEdit retrieves content type of a file from a change edit. +// This is nearly the same as RetrieveFileContentFromChangeEdit. +// But if only the content type is required, callers should use HEAD to avoid downloading the encoded file contents. +// +// For further documentation please have a look at RetrieveFileContentFromChangeEdit. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-edit-file +func (s *ChangesService) RetrieveFileContentTypeFromChangeEdit(changeID, filePath string) (*Response, error) { + u := fmt.Sprintf("changes/%s/edit/%s", changeID, filePath) + + req, err := s.client.NewRequest("HEAD", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +/* +Missing Change Edit Endpoints + Restore file content or rename files in Change Edit +*/ diff --git a/vendor/github.com/andygrunwald/go-gerrit/changes_reviewer.go b/vendor/github.com/andygrunwald/go-gerrit/changes_reviewer.go new file mode 100644 index 00000000..31099d3b --- /dev/null +++ b/vendor/github.com/andygrunwald/go-gerrit/changes_reviewer.go @@ -0,0 +1,163 @@ +package gerrit + +import ( + "fmt" +) + +// ReviewerInfo entity contains information about a reviewer and its votes on a change. +type ReviewerInfo struct { + AccountInfo + Approvals map[string]string `json:"approvals"` +} + +// SuggestedReviewerInfo entity contains information about a reviewer that can be added to a change (an account or a group). +type SuggestedReviewerInfo struct { + Account AccountInfo `json:"account,omitempty"` + Group GroupBaseInfo `json:"group,omitempty"` +} + +// AddReviewerResult entity describes the result of adding a reviewer to a change. +type AddReviewerResult struct { + Input string `json:"input,omitempty"` + Reviewers []ReviewerInfo `json:"reviewers,omitempty"` + CCS []ReviewerInfo `json:"ccs,omitempty"` + Error string `json:"error,omitempty"` + Confirm bool `json:"confirm,omitempty"` +} + +// DeleteVoteInput entity contains options for the deletion of a vote. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#delete-vote-input +type DeleteVoteInput struct { + Label string `json:"label,omitempty"` + Notify string `json:"notify,omitempty"` + NotifyDetails map[string]NotifyInfo `json:"notify_details"` +} + +// ListReviewers lists the reviewers of a change. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#list-reviewers +func (s *ChangesService) ListReviewers(changeID string) (*[]ReviewerInfo, *Response, error) { + u := fmt.Sprintf("changes/%s/reviewers/", changeID) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new([]ReviewerInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// SuggestReviewers suggest the reviewers for a given query q and result limit n. +// If result limit is not passed, then the default 10 is used. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#suggest-reviewers +func (s *ChangesService) SuggestReviewers(changeID string, opt *QueryOptions) (*[]SuggestedReviewerInfo, *Response, error) { + u := fmt.Sprintf("changes/%s/suggest_reviewers", changeID) + + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new([]SuggestedReviewerInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// GetReviewer retrieves a reviewer of a change. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-reviewer +func (s *ChangesService) GetReviewer(changeID, accountID string) (*ReviewerInfo, *Response, error) { + u := fmt.Sprintf("changes/%s/reviewers/%s", changeID, accountID) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new(ReviewerInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// AddReviewer adds one user or all members of one group as reviewer to the change. +// The reviewer to be added to the change must be provided in the request body as a ReviewerInput entity. +// +// As response an AddReviewerResult entity is returned that describes the newly added reviewers. +// If a group is specified, adding the group members as reviewers is an atomic operation. +// This means if an error is returned, none of the members are added as reviewer. +// If a group with many members is added as reviewer a confirmation may be required. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#add-reviewer +func (s *ChangesService) AddReviewer(changeID string, input *ReviewerInput) (*AddReviewerResult, *Response, error) { + u := fmt.Sprintf("changes/%s/reviewers", changeID) + + req, err := s.client.NewRequest("POST", u, input) + if err != nil { + return nil, nil, err + } + + v := new(AddReviewerResult) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// DeleteReviewer deletes a reviewer from a change. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#delete-reviewer +func (s *ChangesService) DeleteReviewer(changeID, accountID string) (*Response, error) { + u := fmt.Sprintf("changes/%s/reviewers/%s", changeID, accountID) + return s.client.DeleteRequest(u, nil) +} + +// ListVotes lists the votes for a specific reviewer of the change. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#list-votes +func (s *ChangesService) ListVotes(changeID string, accountID string) (map[string]int, *Response, error) { + u := fmt.Sprintf("changes/%s/reviewers/%s/votes/", changeID, accountID) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var v map[string]int + resp, err := s.client.Do(req, &v) + if err != nil { + return nil, resp, err + } + return v, resp, err +} + +// DeleteVote deletes a single vote from a change. Note, that even when the +// last vote of a reviewer is removed the reviewer itself is still listed on +// the change. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#delete-vote +func (s *ChangesService) DeleteVote(changeID string, accountID string, label string, input *DeleteVoteInput) (*Response, error) { + u := fmt.Sprintf("changes/%s/reviewers/%s/votes/%s", changeID, accountID, label) + return s.client.DeleteRequest(u, input) +} diff --git a/vendor/github.com/andygrunwald/go-gerrit/changes_revision.go b/vendor/github.com/andygrunwald/go-gerrit/changes_revision.go new file mode 100644 index 00000000..c20c5ac6 --- /dev/null +++ b/vendor/github.com/andygrunwald/go-gerrit/changes_revision.go @@ -0,0 +1,651 @@ +package gerrit + +import ( + "fmt" + "net/url" +) + +// DiffInfo entity contains information about the diff of a file in a revision. +type DiffInfo struct { + MetaA DiffFileMetaInfo `json:"meta_a,omitempty"` + MetaB DiffFileMetaInfo `json:"meta_b,omitempty"` + ChangeType string `json:"change_type"` + IntralineStatus string `json:"intraline_status,omitempty"` + DiffHeader []string `json:"diff_header"` + Content []DiffContent `json:"content"` + WebLinks []DiffWebLinkInfo `json:"web_links,omitempty"` + Binary bool `json:"binary,omitempty"` +} + +// RelatedChangesInfo entity contains information about related changes. +type RelatedChangesInfo struct { + Changes []RelatedChangeAndCommitInfo `json:"changes"` +} + +// FileInfo entity contains information about a file in a patch set. +type FileInfo struct { + Status string `json:"status,omitempty"` + Binary bool `json:"binary,omitempty"` + OldPath string `json:"old_path,omitempty"` + LinesInserted int `json:"lines_inserted,omitempty"` + LinesDeleted int `json:"lines_deleted,omitempty"` + SizeDelta int `json:"size_delta"` + Size int `json:"size"` +} + +// ActionInfo entity describes a REST API call the client can make to manipulate a resource. +// These are frequently implemented by plugins and may be discovered at runtime. +type ActionInfo struct { + Method string `json:"method,omitempty"` + Label string `json:"label,omitempty"` + Title string `json:"title,omitempty"` + Enabled bool `json:"enabled,omitempty"` +} + +// CommitInfo entity contains information about a commit. +type CommitInfo struct { + Commit string `json:"commit,omitempty"` + Parents []CommitInfo `json:"parents"` + Author GitPersonInfo `json:"author"` + Committer GitPersonInfo `json:"committer"` + Subject string `json:"subject"` + Message string `json:"message"` + WebLinks []WebLinkInfo `json:"web_links,omitempty"` +} + +// MergeableInfo entity contains information about the mergeability of a change. +type MergeableInfo struct { + SubmitType string `json:"submit_type"` + Mergeable bool `json:"mergeable"` + MergeableInto []string `json:"mergeable_into,omitempty"` +} + +// DiffOptions specifies the parameters for GetDiff call. +type DiffOptions struct { + // If the intraline parameter is specified, intraline differences are included in the diff. + Intraline bool `url:"intraline,omitempty"` + + // The base parameter can be specified to control the base patch set from which the diff + // should be generated. + Base string `url:"base,omitempty"` + + // The integer-valued request parameter parent can be specified to control the parent commit number + // against which the diff should be generated. This is useful for supporting review of merge commits. + // The value is the 1-based index of the parent’s position in the commit object. + Parent int `url:"parent,omitempty"` + + // If the weblinks-only parameter is specified, only the diff web links are returned. + WeblinksOnly bool `url:"weblinks-only,omitempty"` + + // The ignore-whitespace parameter can be specified to control how whitespace differences are reported in the result. Valid values are NONE, TRAILING, CHANGED or ALL. + IgnoreWhitespace string `url:"ignore-whitespace,omitempty"` + + // The context parameter can be specified to control the number of lines of surrounding context in the diff. + // Valid values are ALL or number of lines. + Context string `url:"context,omitempty"` +} + +// CommitOptions specifies the parameters for GetCommit call. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-commit +type CommitOptions struct { + // Adding query parameter links (for example /changes/.../commit?links) returns a CommitInfo with the additional field web_links. + Weblinks bool `url:"links,omitempty"` +} + +// MergableOptions specifies the parameters for GetMergable call. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-mergeable +type MergableOptions struct { + // If the other-branches parameter is specified, the mergeability will also be checked for all other branches. + OtherBranches bool `url:"other-branches,omitempty"` +} + +// FilesOptions specifies the parameters for ListFiles and ListFilesReviewed calls. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#list-files +type FilesOptions struct { + // The request parameter q changes the response to return a list of all files (modified or unmodified) + // that contain that substring in the path name. This is useful to implement suggestion services + // finding a file by partial name. + Q string `url:"q,omitempty"` + + // The base parameter can be specified to control the base patch set from which the list of files + // should be generated. + // + // Note: This option is undocumented. + Base string `url:"base,omitempty"` + + // The integer-valued request parameter parent changes the response to return a list of the files + // which are different in this commit compared to the given parent commit. This is useful for + // supporting review of merge commits. The value is the 1-based index of the parent’s position + // in the commit object. + Parent int `url:"parent,omitempty"` +} + +// PatchOptions specifies the parameters for GetPatch call. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-patch +type PatchOptions struct { + // Adding query parameter zip (for example /changes/.../patch?zip) returns the patch as a single file inside of a ZIP archive. + // Clients can expand the ZIP to obtain the plain text patch, avoiding the need for a base64 decoding step. + // This option implies download. + Zip bool `url:"zip,omitempty"` + + // Query parameter download (e.g. /changes/.../patch?download) will suggest the browser save the patch as commitsha1.diff.base64, for later processing by command line tools. + Download bool `url:"download,omitempty"` + + // If the path parameter is set, the returned content is a diff of the single file that the path refers to. + Path string `url:"path,omitempty"` +} + +// GetDiff gets the diff of a file from a certain revision. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-diff +func (s *ChangesService) GetDiff(changeID, revisionID, fileID string, opt *DiffOptions) (*DiffInfo, *Response, error) { + u := fmt.Sprintf("changes/%s/revisions/%s/files/%s/diff", changeID, revisionID, url.PathEscape(fileID)) + + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new(DiffInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// GetRelatedChanges retrieves related changes of a revision. +// Related changes are changes that either depend on, or are dependencies of the revision. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-related-changes +func (s *ChangesService) GetRelatedChanges(changeID, revisionID string) (*RelatedChangesInfo, *Response, error) { + u := fmt.Sprintf("changes/%s/revisions/%s/related", changeID, revisionID) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new(RelatedChangesInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// GetDraft retrieves a draft comment of a revision that belongs to the calling user. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-draft +func (s *ChangesService) GetDraft(changeID, revisionID, draftID string) (*CommentInfo, *Response, error) { + u := fmt.Sprintf("changes/%s/revisions/%s/drafts/%s", changeID, revisionID, draftID) + return s.getCommentInfoResponse(u) +} + +// GetComment retrieves a published comment of a revision. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-comment +func (s *ChangesService) GetComment(changeID, revisionID, commentID string) (*CommentInfo, *Response, error) { + u := fmt.Sprintf("changes/%s/revisions/%s//comments/%s", changeID, revisionID, commentID) + return s.getCommentInfoResponse(u) +} + +// GetSubmitType gets the method the server will use to submit (merge) the change. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-submit-type +func (s *ChangesService) GetSubmitType(changeID, revisionID string) (string, *Response, error) { + u := fmt.Sprintf("changes/%s/revisions/%s/submit_type", changeID, revisionID) + return getStringResponseWithoutOptions(s.client, u) +} + +// GetRevisionActions retrieves revision actions of the revision of a change. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-revision-actions +func (s *ChangesService) GetRevisionActions(changeID, revisionID string) (*map[string]ActionInfo, *Response, error) { + u := fmt.Sprintf("changes/%s/revisions/%s/actions", changeID, revisionID) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new(map[string]ActionInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// GetCommit retrieves a parsed commit of a revision. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-commit +func (s *ChangesService) GetCommit(changeID, revisionID string, opt *CommitOptions) (*CommitInfo, *Response, error) { + u := fmt.Sprintf("changes/%s/revisions/%s/commit", changeID, revisionID) + + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new(CommitInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// GetReview retrieves a review of a revision. +// +// As response a ChangeInfo entity with detailed labels and detailed accounts is returned that describes the review of the revision. +// The revision for which the review is retrieved is contained in the revisions field. +// In addition the current_revision field is set if the revision for which the review is retrieved is the current revision of the change. +// Please note that the returned labels are always for the current patch set. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-review +func (s *ChangesService) GetReview(changeID, revisionID string) (*ChangeInfo, *Response, error) { + u := fmt.Sprintf("changes/%s/revisions/%s/review", changeID, revisionID) + return s.getChangeInfoResponse(u, nil) +} + +// GetMergeable gets the method the server will use to submit (merge) the change and an indicator if the change is currently mergeable. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-mergeable +func (s *ChangesService) GetMergeable(changeID, revisionID string, opt *MergableOptions) (*MergeableInfo, *Response, error) { + u := fmt.Sprintf("changes/%s/revisions/%s/mergeable", changeID, revisionID) + + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new(MergeableInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// ListRevisionDrafts lists the draft comments of a revision that belong to the calling user. +// Returns a map of file paths to lists of CommentInfo entries. +// The entries in the map are sorted by file path. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#list-drafts +func (s *ChangesService) ListRevisionDrafts(changeID, revisionID string) (*map[string][]CommentInfo, *Response, error) { + u := fmt.Sprintf("changes/%s/revisions/%s/drafts/", changeID, revisionID) + return s.getCommentInfoMapSliceResponse(u) +} + +// ListRevisionComments lists the published comments of a revision. +// As result a map is returned that maps the file path to a list of CommentInfo entries. +// The entries in the map are sorted by file path and only include file (or inline) comments. +// Use the Get Change Detail endpoint to retrieve the general change message (or comment). +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#list-comments +func (s *ChangesService) ListRevisionComments(changeID, revisionID string) (*map[string][]CommentInfo, *Response, error) { + u := fmt.Sprintf("changes/%s/revisions/%s/comments/", changeID, revisionID) + return s.getCommentInfoMapSliceResponse(u) +} + +// ListFiles lists the files that were modified, added or deleted in a revision. +// As result a map is returned that maps the file path to a list of FileInfo entries. +// The entries in the map are sorted by file path. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#list-files +func (s *ChangesService) ListFiles(changeID, revisionID string, opt *FilesOptions) (map[string]FileInfo, *Response, error) { + u := fmt.Sprintf("changes/%s/revisions/%s/files/", changeID, revisionID) + + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var v map[string]FileInfo + resp, err := s.client.Do(req, &v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// ListFilesReviewed lists the files that were modified, added or deleted in a revision. +// Unlike ListFiles, the response of ListFilesReviewed is a list of the paths the caller +// has marked as reviewed. Clients that also need the FileInfo should make two requests. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#list-files +func (s *ChangesService) ListFilesReviewed(changeID, revisionID string, opt *FilesOptions) ([]string, *Response, error) { + u := fmt.Sprintf("changes/%s/revisions/%s/files/", changeID, revisionID) + + o := struct { + // The request parameter reviewed changes the response to return a list of the paths the caller has marked as reviewed. + Reviewed bool `url:"reviewed,omitempty"` + + FilesOptions + }{ + Reviewed: true, + } + if opt != nil { + o.FilesOptions = *opt + } + u, err := addOptions(u, o) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var v []string + resp, err := s.client.Do(req, &v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// SetReview sets a review on a revision. +// The review must be provided in the request body as a ReviewInput entity. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#set-review +func (s *ChangesService) SetReview(changeID, revisionID string, input *ReviewInput) (*ReviewResult, *Response, error) { + u := fmt.Sprintf("changes/%s/revisions/%s/review", changeID, revisionID) + + req, err := s.client.NewRequest("POST", u, input) + if err != nil { + return nil, nil, err + } + + v := new(ReviewResult) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// PublishDraftRevision publishes a draft revision. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#publish-draft-revision +func (s *ChangesService) PublishDraftRevision(changeID, revisionID string) (*Response, error) { + u := fmt.Sprintf("changes/%s/revisions/%s/publish", changeID, revisionID) + + req, err := s.client.NewRequest("POST", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// DeleteDraftRevision deletes a draft revision. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#delete-draft-revision +func (s *ChangesService) DeleteDraftRevision(changeID, revisionID string) (*Response, error) { + u := fmt.Sprintf("changes/%s/revisions/%s", changeID, revisionID) + return s.client.DeleteRequest(u, nil) +} + +// GetPatch gets the formatted patch for one revision. +// +// The formatted patch is returned as text encoded inside base64. +// Adding query parameter zip (for example /changes/.../patch?zip) returns the patch as a single file inside of a ZIP archive. +// Clients can expand the ZIP to obtain the plain text patch, avoiding the need for a base64 decoding step. +// This option implies download. +// +// Query parameter download (e.g. /changes/.../patch?download) will suggest the browser save the patch as commitsha1.diff.base64, for later processing by command line tools. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-patch +func (s *ChangesService) GetPatch(changeID, revisionID string, opt *PatchOptions) (*string, *Response, error) { + u := fmt.Sprintf("changes/%s/revisions/%s/patch", changeID, revisionID) + + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new(string) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// TestSubmitType tests the submit_type Prolog rule in the project, or the one given. +// +// Request body may be either the Prolog code as text/plain or a RuleInput object. +// The query parameter filters may be set to SKIP to bypass parent project filters while testing a project-specific rule. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#test-submit-type +func (s *ChangesService) TestSubmitType(changeID, revisionID string, input *RuleInput) (*string, *Response, error) { + u := fmt.Sprintf("changes/%s/revisions/%s/test.submit_type", changeID, revisionID) + + req, err := s.client.NewRequest("POST", u, input) + if err != nil { + return nil, nil, err + } + + v := new(string) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// TestSubmitRule tests the submit_rule Prolog rule in the project, or the one given. +// +// Request body may be either the Prolog code as text/plain or a RuleInput object. +// The query parameter filters may be set to SKIP to bypass parent project filters while testing a project-specific rule. +// +// The response is a list of SubmitRecord entries describing the permutations that satisfy the tested submit rule. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#test-submit-rule +func (s *ChangesService) TestSubmitRule(changeID, revisionID string, input *RuleInput) (*[]SubmitRecord, *Response, error) { + u := fmt.Sprintf("changes/%s/revisions/%s/test.submit_rule", changeID, revisionID) + + req, err := s.client.NewRequest("POST", u, input) + if err != nil { + return nil, nil, err + } + + v := new([]SubmitRecord) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// CreateDraft creates a draft comment on a revision. +// The new draft comment must be provided in the request body inside a CommentInput entity. +// +// As response a CommentInfo entity is returned that describes the draft comment. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#create-draft +func (s *ChangesService) CreateDraft(changeID, revisionID string, input *CommentInput) (*CommentInfo, *Response, error) { + u := fmt.Sprintf("changes/%s/revisions/%s/drafts", changeID, revisionID) + + req, err := s.client.NewRequest("PUT", u, input) + if err != nil { + return nil, nil, err + } + + v := new(CommentInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// UpdateDraft updates a draft comment on a revision. +// The new draft comment must be provided in the request body inside a CommentInput entity. +// +// As response a CommentInfo entity is returned that describes the draft comment. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#update-draft +func (s *ChangesService) UpdateDraft(changeID, revisionID, draftID string, input *CommentInput) (*CommentInfo, *Response, error) { + u := fmt.Sprintf("changes/%s/revisions/%s/drafts/%s", changeID, revisionID, draftID) + + req, err := s.client.NewRequest("PUT", u, input) + if err != nil { + return nil, nil, err + } + + v := new(CommentInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// DeleteDraft deletes a draft comment from a revision. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#delete-draft +func (s *ChangesService) DeleteDraft(changeID, revisionID, draftID string) (*Response, error) { + u := fmt.Sprintf("changes/%s/revisions/%s/drafts/%s", changeID, revisionID, draftID) + return s.client.DeleteRequest(u, nil) +} + +// DeleteReviewed deletes the reviewed flag of the calling user from a file of a revision. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#delete-reviewed +func (s *ChangesService) DeleteReviewed(changeID, revisionID, fileID string) (*Response, error) { + u := fmt.Sprintf("changes/%s/revisions/%s/files/%s/reviewed", changeID, revisionID, url.PathEscape(fileID)) + return s.client.DeleteRequest(u, nil) +} + +// GetContent gets the content of a file from a certain revision. +// The content is returned as base64 encoded string. +// The HTTP response Content-Type is always text/plain, reflecting the base64 wrapping. +// A Gerrit-specific X-FYI-Content-Type header is returned describing the server detected content type of the file. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-content +func (s *ChangesService) GetContent(changeID, revisionID, fileID string) (*string, *Response, error) { + u := fmt.Sprintf("changes/%s/revisions/%s/files/%s/content", changeID, revisionID, url.PathEscape(fileID)) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new(string) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// GetContentType gets the content type of a file from a certain revision. +// This is nearly the same as GetContent. +// But if only the content type is required, callers should use HEAD to avoid downloading the encoded file contents. +// +// For further documentation see GetContent. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-content +func (s *ChangesService) GetContentType(changeID, revisionID, fileID string) (*Response, error) { + u := fmt.Sprintf("changes/%s/revisions/%s/files/%s/content", changeID, revisionID, url.PathEscape(fileID)) + + req, err := s.client.NewRequest("HEAD", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// SetReviewed marks a file of a revision as reviewed by the calling user. +// +// If the file was already marked as reviewed by the calling user the response is “200 OK”. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#set-reviewed +func (s *ChangesService) SetReviewed(changeID, revisionID, fileID string) (*Response, error) { + u := fmt.Sprintf("changes/%s/revisions/%s/files/%s/reviewed", changeID, revisionID, url.PathEscape(fileID)) + + req, err := s.client.NewRequest("PUT", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// CherryPickRevision cherry picks a revision to a destination branch. +// The commit message and destination branch must be provided in the request body inside a CherryPickInput entity. +// +// As response a ChangeInfo entity is returned that describes the resulting cherry picked change. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#cherry-pick +func (s *ChangesService) CherryPickRevision(changeID, revisionID string, input *CherryPickInput) (*ChangeInfo, *Response, error) { + u := fmt.Sprintf("changes/%s/revisions/%s/cherrypick", changeID, revisionID) + + req, err := s.client.NewRequest("POST", u, input) + if err != nil { + return nil, nil, err + } + + v := new(ChangeInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +/* +TODO: Missing Revision Endpoints + Rebase Revision + Submit Revision + DownloadContent (https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-safe-content) +*/ diff --git a/vendor/github.com/andygrunwald/go-gerrit/config.go b/vendor/github.com/andygrunwald/go-gerrit/config.go new file mode 100644 index 00000000..088b0fb4 --- /dev/null +++ b/vendor/github.com/andygrunwald/go-gerrit/config.go @@ -0,0 +1,529 @@ +package gerrit + +import ( + "fmt" +) + +// ConfigService contains Config related REST endpoints +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-config.html +type ConfigService struct { + client *Client +} + +// TopMenuItemInfo entity contains information about a menu item in a top menu entry. +type TopMenuItemInfo struct { + URL string `json:"url"` + Name string `json:"name"` + Target string `json:"target"` + ID string `json:"id,omitempty"` +} + +// AuthInfo entity contains information about the authentication configuration of the Gerrit server. +type AuthInfo struct { + Type string `json:"type"` + UseContributorAgreements bool `json:"use_contributor_agreements,omitempty"` + EditableAccountFields []string `json:"editable_account_fields"` + LoginURL string `json:"login_url,omitempty"` + LoginText string `json:"login_text,omitempty"` + SwitchAccountURL string `json:"switch_account_url,omitempty"` + RegisterURL string `json:"register_url,omitempty"` + RegisterText string `json:"register_text,omitempty"` + EditFullNameURL string `json:"edit_full_name_url,omitempty"` + HTTPPasswordURL string `json:"http_password_url,omitempty"` + IsGitBasicAuth bool `json:"is_git_basic_auth,omitempty"` +} + +// CacheInfo entity contains information about a cache. +type CacheInfo struct { + Name string `json:"name,omitempty"` + Type string `json:"type"` + Entries EntriesInfo `json:"entries"` + AverageGet string `json:"average_get,omitempty"` + HitRatio HitRatioInfo `json:"hit_ratio"` +} + +// CacheOperationInput entity contains information about an operation that should be executed on caches. +type CacheOperationInput struct { + Operation string `json:"operation"` + Caches []string `json:"caches,omitempty"` +} + +// ConfigCapabilityInfo entity contains information about a capability.type +type ConfigCapabilityInfo struct { + ID string `json:"id"` + Name string `json:"name"` +} + +// HitRatioInfo entity contains information about the hit ratio of a cache. +type HitRatioInfo struct { + Mem int `json:"mem"` + Disk int `json:"disk,omitempty"` +} + +// EntriesInfo entity contains information about the entries in a cache. +type EntriesInfo struct { + Mem int `json:"mem,omitempty"` + Disk int `json:"disk,omitempty"` + Space string `json:"space,omitempty"` +} + +// UserConfigInfo entity contains information about Gerrit configuration from the user section. +type UserConfigInfo struct { + AnonymousCowardName string `json:"anonymous_coward_name"` +} + +// TopMenuEntryInfo entity contains information about a top menu entry. +type TopMenuEntryInfo struct { + Name string `json:"name"` + Items []TopMenuItemInfo `json:"items"` +} + +// ThreadSummaryInfo entity contains information about the current threads. +type ThreadSummaryInfo struct { + CPUs int `json:"cpus"` + Threads int `json:"threads"` + Counts map[string]map[string]int `json:"counts"` +} + +// TaskSummaryInfo entity contains information about the current tasks. +type TaskSummaryInfo struct { + Total int `json:"total,omitempty"` + Running int `json:"running,omitempty"` + Ready int `json:"ready,omitempty"` + Sleeping int `json:"sleeping,omitempty"` +} + +// TaskInfo entity contains information about a task in a background work queue. +type TaskInfo struct { + ID string `json:"id"` + State string `json:"state"` + StartTime string `json:"start_time"` + Delay int `json:"delay"` + Command string `json:"command"` + RemoteName string `json:"remote_name,omitempty"` + Project string `json:"project,omitempty"` +} + +// SummaryInfo entity contains information about the current state of the server. +type SummaryInfo struct { + TaskSummary TaskSummaryInfo `json:"task_summary"` + MemSummary MemSummaryInfo `json:"mem_summary"` + ThreadSummary ThemeInfo `json:"thread_summary"` + JVMSummary JvmSummaryInfo `json:"jvm_summary,omitempty"` +} + +// SuggestInfo entity contains information about Gerrit configuration from the suggest section. +type SuggestInfo struct { + From int `json:"from"` +} + +// SSHdInfo entity contains information about Gerrit configuration from the sshd section. +type SSHdInfo struct{} + +// ServerInfo entity contains information about the configuration of the Gerrit server. +type ServerInfo struct { + Auth AuthInfo `json:"auth"` + Change ChangeConfigInfo `json:"change"` + Download DownloadInfo `json:"download"` + Gerrit Info `json:"gerrit"` + Gitweb map[string]string `json:"gitweb,omitempty"` + Plugin PluginConfigInfo `json:"plugin"` + Receive ReceiveInfo `json:"receive,omitempty"` + SSHd SSHdInfo `json:"sshd,omitempty"` + Suggest SuggestInfo `json:"suggest"` + URLAliases map[string]string `json:"url_aliases,omitempty"` + User UserConfigInfo `json:"user"` +} + +// ReceiveInfo entity contains information about the configuration of git-receive-pack behavior on the server. +type ReceiveInfo struct { + EnableSignedPush bool `json:"enableSignedPush,omitempty"` +} + +// PluginConfigInfo entity contains information about Gerrit extensions by plugins. +type PluginConfigInfo struct { + // HasAvatars reports whether an avatar provider is registered. + HasAvatars bool `json:"has_avatars,omitempty"` +} + +// MemSummaryInfo entity contains information about the current memory usage. +type MemSummaryInfo struct { + Total string `json:"total"` + Used string `json:"used"` + Free string `json:"free"` + Buffers string `json:"buffers"` + Max string `json:"max"` + OpenFiles int `json:"open_files,omitempty"` +} + +// JvmSummaryInfo entity contains information about the JVM. +type JvmSummaryInfo struct { + VMVendor string `json:"vm_vendor"` + VMName string `json:"vm_name"` + VMVersion string `json:"vm_version"` + OSName string `json:"os_name"` + OSVersion string `json:"os_version"` + OSArch string `json:"os_arch"` + User string `json:"user"` + Host string `json:"host,omitempty"` + CurrentWorkingDirectory string `json:"current_working_directory"` + Site string `json:"site"` +} + +// Info entity contains information about Gerrit configuration from the gerrit section. +type Info struct { + AllProjectsName string `json:"all_projects_name"` + AllUsersName string `json:"all_users_name"` + DocURL string `json:"doc_url,omitempty"` + ReportBugURL string `json:"report_bug_url,omitempty"` + ReportBugText string `json:"report_bug_text,omitempty"` +} + +// GitwebInfo entity contains information about the gitweb configuration. +type GitwebInfo struct { + URL string `json:"url"` + Type GitwebTypeInfo `json:"type"` +} + +// GitwebTypeInfo entity contains information about the gitweb configuration. +type GitwebTypeInfo struct { + Name string `json:"name"` + Revision string `json:"revision,omitempty"` + Project string `json:"project,omitempty"` + Branch string `json:"branch,omitempty"` + RootTree string `json:"root_tree,omitempty"` + File string `json:"file,omitempty"` + FileHistory string `json:"file_history,omitempty"` + PathSeparator string `json:"path_separator"` + LinkDrafts bool `json:"link_drafts,omitempty"` + URLEncode bool `json:"url_encode,omitempty"` +} + +// EmailConfirmationInput entity contains information for confirming an email address. +type EmailConfirmationInput struct { + Token string `json:"token"` +} + +// DownloadSchemeInfo entity contains information about a supported download scheme and its commands. +type DownloadSchemeInfo struct { + URL string `json:"url"` + IsAuthRequired bool `json:"is_auth_required,omitempty"` + IsAuthSupported bool `json:"is_auth_supported,omitempty"` + Commands map[string]string `json:"commands"` + CloneCommands map[string]string `json:"clone_commands"` +} + +// DownloadInfo entity contains information about supported download options. +type DownloadInfo struct { + Schemes map[string]DownloadSchemeInfo `json:"schemes"` + Archives []string `json:"archives"` +} + +// ChangeConfigInfo entity contains information about Gerrit configuration from the change section. +type ChangeConfigInfo struct { + AllowDrafts bool `json:"allow_drafts,omitempty"` + LargeChange int `json:"large_change"` + ReplyLabel string `json:"reply_label"` + ReplyTooltip string `json:"reply_tooltip"` + UpdateDelay int `json:"update_delay"` + SubmitWholeTopic bool `json:"submit_whole_topic"` +} + +// ListCachesOptions specifies the different output formats. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-config.html#list-caches +type ListCachesOptions struct { + // Format specifies the different output formats. + Format string `url:"format,omitempty"` +} + +// SummaryOptions specifies the different options for the GetSummary call. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-config.html#get-summary +type SummaryOptions struct { + // JVM includes a JVM summary. + JVM bool `url:"jvm,omitempty"` + // GC requests a Java garbage collection before computing the information about the Java memory heap. + GC bool `url:"gc,omitempty"` +} + +// GetVersion returns the version of the Gerrit server. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-config.html#get-version +func (s *ConfigService) GetVersion() (string, *Response, error) { + u := "config/server/version" + return getStringResponseWithoutOptions(s.client, u) +} + +// GetServerInfo returns the information about the Gerrit server configuration. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-config.html#get-info +func (s *ConfigService) GetServerInfo() (*ServerInfo, *Response, error) { + u := "config/server/info" + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new(ServerInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// ListCaches lists the caches of the server. Caches defined by plugins are included. +// The caller must be a member of a group that is granted one of the following capabilities: +// * View Caches +// * Maintain Server +// * Administrate Server +// The entries in the map are sorted by cache name. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-config.html#list-caches +func (s *ConfigService) ListCaches(opt *ListCachesOptions) (*map[string]CacheInfo, *Response, error) { + u := "config/server/caches/" + + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new(map[string]CacheInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// GetCache retrieves information about a cache. +// The caller must be a member of a group that is granted one of the following capabilities: +// * View Caches +// * Maintain Server +// * Administrate Server +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-config.html#get-cache +func (s *ConfigService) GetCache(cacheName string) (*CacheInfo, *Response, error) { + u := fmt.Sprintf("config/server/caches/%s", cacheName) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new(CacheInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// GetSummary retrieves a summary of the current server state. +// The caller must be a member of a group that is granted the Administrate Server capability. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-config.html#get-summary +func (s *ConfigService) GetSummary(opt *SummaryOptions) (*SummaryInfo, *Response, error) { + u := "config/server/summary" + + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new(SummaryInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// ListCapabilities lists the capabilities that are available in the system. +// There are two kinds of capabilities: core and plugin-owned capabilities. +// The entries in the map are sorted by capability ID. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-config.html#list-capabilities +func (s *ConfigService) ListCapabilities() (*map[string]ConfigCapabilityInfo, *Response, error) { + u := "config/server/capabilities" + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new(map[string]ConfigCapabilityInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// ListTasks lists the tasks from the background work queues that the Gerrit daemon is currently performing, or will perform in the near future. +// Gerrit contains an internal scheduler, similar to cron, that it uses to queue and dispatch both short and long term tasks. +// Tasks that are completed or canceled exit the queue very quickly once they enter this state, but it can be possible to observe tasks in these states. +// End-users may see a task only if they can also see the project the task is associated with. +// Tasks operating on other projects, or that do not have a specific project, are hidden. +// +// The caller must be a member of a group that is granted one of the following capabilities: +// * View Queue +// * Maintain Server +// * Administrate Server +// +// The entries in the list are sorted by task state, remaining delay and command. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-config.html#list-tasks +func (s *ConfigService) ListTasks() (*[]TaskInfo, *Response, error) { + u := "config/server/tasks" + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new([]TaskInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// GetTask retrieves a task from the background work queue that the Gerrit daemon is currently performing, or will perform in the near future. +// End-users may see a task only if they can also see the project the task is associated with. +// Tasks operating on other projects, or that do not have a specific project, are hidden. +// +// The caller must be a member of a group that is granted one of the following capabilities: +// * View Queue +// * Maintain Server +// * Administrate Server +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-config.html#get-task +func (s *ConfigService) GetTask(taskID string) (*TaskInfo, *Response, error) { + u := fmt.Sprintf("config/server/tasks/%s", taskID) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new(TaskInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// GetTopMenus returns the list of additional top menu entries. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-config.html#get-top-menus +func (s *ConfigService) GetTopMenus() (*[]TopMenuEntryInfo, *Response, error) { + u := "config/server/top-menus" + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new([]TopMenuEntryInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// ConfirmEmail confirms that the user owns an email address. +// The email token must be provided in the request body inside an EmailConfirmationInput entity. +// +// The response is “204 No Content”. +// If the token is invalid or if it’s the token of another user the request fails and the response is “422 Unprocessable Entity”. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-config.html#confirm-email +func (s *ConfigService) ConfirmEmail(input *EmailConfirmationInput) (*Response, error) { + u := "config/server/email.confirm" + + req, err := s.client.NewRequest("PUT", u, input) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// CacheOperations executes a cache operation that is specified in the request body in a CacheOperationInput entity. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-config.html#cache-operations +func (s *ConfigService) CacheOperations(input *CacheOperationInput) (*Response, error) { + u := "config/server/caches/" + + req, err := s.client.NewRequest("POST", u, input) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// FlushCache flushes a cache. +// The caller must be a member of a group that is granted one of the following capabilities: +// +// * Flush Caches (any cache except "web_sessions") +// * Maintain Server (any cache including "web_sessions") +// * Administrate Server (any cache including "web_sessions") +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-config.html#flush-cache +func (s *ConfigService) FlushCache(cacheName string, input *CacheOperationInput) (*Response, error) { + u := fmt.Sprintf("config/server/caches/%s/flush", cacheName) + + req, err := s.client.NewRequest("POST", u, input) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// DeleteTask kills a task from the background work queue that the Gerrit daemon is currently performing, or will perform in the near future. +// The caller must be a member of a group that is granted one of the following capabilities: +// +// * Kill Task +// * Maintain Server +// * Administrate Server +// +// End-users may see a task only if they can also see the project the task is associated with. +// Tasks operating on other projects, or that do not have a specific project, are hidden. +// Members of a group granted one of the following capabilities may view all tasks: +// +// * View Queue +// * Maintain Server +// * Administrate Server +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-config.html#delete-task +func (s *ConfigService) DeleteTask(taskID string) (*Response, error) { + u := fmt.Sprintf("config/server/tasks/%s", taskID) + return s.client.DeleteRequest(u, nil) +} diff --git a/vendor/github.com/andygrunwald/go-gerrit/doc.go b/vendor/github.com/andygrunwald/go-gerrit/doc.go new file mode 100644 index 00000000..e3336ef3 --- /dev/null +++ b/vendor/github.com/andygrunwald/go-gerrit/doc.go @@ -0,0 +1,68 @@ +/* +Package gerrit provides a client for using the Gerrit API. + +Construct a new Gerrit client, then use the various services on the client to +access different parts of the Gerrit API. For example: + + instance := "https://go-review.googlesource.com/" + client, _ := gerrit.NewClient(instance, nil) + + // Get all public projects + projects, _, err := client.Projects.ListProjects(nil) + +Set optional parameters for an API method by passing an Options object. + + // Get all projects with descriptions + opt := &gerrit.ProjectOptions{ + Description: true, + } + projects, _, err := client.Projects.ListProjects(opt) + +The services of a client divide the API into logical chunks and correspond to +the structure of the Gerrit API documentation at +https://gerrit-review.googlesource.com/Documentation/rest-api.html#_endpoints. + +Authentication + +The go-gerrit library supports various methods to support the authentication. +This methods are combined in the AuthenticationService that is available at client.Authentication. + +One way is an authentication via HTTP cookie. +Some Gerrit instances hosted like the one hosted googlesource.com (e.g. https://go-review.googlesource.com/, +https://android-review.googlesource.com/ or https://gerrit-review.googlesource.com/) support HTTP Cookie authentication. + +You need the cookie name and the cookie value. +You can get them by click on "Settings > HTTP Password > Obtain Password" in your Gerrit instance. +There you can receive your values. +The cookie name will be (mostly) "o" (if hosted on googlesource.com). +Your cookie secret will be something like "git-your@email.com=SomeHash...". + + instance := "https://gerrit-review.googlesource.com/" + client, _ := gerrit.NewClient(instance, nil) + client.Authentication.SetCookieAuth("o", "my-cookie-secret") + + self, _, _ := client.Accounts.GetAccount("self") + + fmt.Printf("Username: %s", self.Name) + + // Username: Andy G. + +Some other Gerrit instances (like https://review.typo3.org/) has auth.gitBasicAuth activated. +With this you can authenticate with HTTP Basic like this: + + instance := "https://review.typo3.org/" + client, _ := gerrit.NewClient(instance, nil) + client.Authentication.SetBasicAuth("andy.grunwald", "my secrect password") + + self, _, _ := client.Accounts.GetAccount("self") + + fmt.Printf("Username: %s", self.Name) + + // Username: Andy Grunwald + +Additionally when creating a new client, pass an http.Client that supports further actions for you. +For more information regarding authentication have a look at the Gerrit documentation: +https://gerrit-review.googlesource.com/Documentation/rest-api.html#authentication + +*/ +package gerrit diff --git a/vendor/github.com/andygrunwald/go-gerrit/events.go b/vendor/github.com/andygrunwald/go-gerrit/events.go new file mode 100644 index 00000000..4850ba4b --- /dev/null +++ b/vendor/github.com/andygrunwald/go-gerrit/events.go @@ -0,0 +1,166 @@ +package gerrit + +import ( + "bytes" + "encoding/json" + "io/ioutil" + "net/url" + "time" +) + +// PatchSet contains detailed information about a specific patch set. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/json.html#patchSet +type PatchSet struct { + Number Number `json:"number"` + Revision string `json:"revision"` + Parents []string `json:"parents"` + Ref string `json:"ref"` + Uploader AccountInfo `json:"uploader"` + Author AccountInfo `json:"author"` + CreatedOn int `json:"createdOn"` + IsDraft bool `json:"isDraft"` + Kind string `json:"kind"` +} + +// RefUpdate contains data about a reference update. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/json.html#refUpdate +type RefUpdate struct { + OldRev string `json:"oldRev"` + NewRev string `json:"newRev"` + RefName string `json:"refName"` + Project string `json:"project"` +} + +// EventInfo contains information about an event emitted by Gerrit. This +// structure can be used either when parsing streamed events or when reading +// the output of the events-log plugin. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/cmd-stream-events.html#events +type EventInfo struct { + Type string `json:"type"` + Change ChangeInfo `json:"change,omitempty"` + ChangeKey ChangeInfo `json:"changeKey,omitempty"` + PatchSet PatchSet `json:"patchSet,omitempty"` + EventCreatedOn int `json:"eventCreatedOn,omitempty"` + Reason string `json:"reason,omitempty"` + Abandoner AccountInfo `json:"abandoner,omitempty"` + Restorer AccountInfo `json:"restorer,omitempty"` + Submitter AccountInfo `json:"submitter,omitempty"` + Author AccountInfo `json:"author,omitempty"` + Uploader AccountInfo `json:"uploader,omitempty"` + Approvals []AccountInfo `json:"approvals,omitempty"` + Comment string `json:"comment,omitempty"` + Editor AccountInfo `json:"editor,omitempty"` + Added []string `json:"added,omitempty"` + Removed []string `json:"removed,omitempty"` + Hashtags []string `json:"hashtags,omitempty"` + RefUpdate RefUpdate `json:"refUpdate,omitempty"` + Project ProjectInfo `json:"project,omitempty"` + Reviewer AccountInfo `json:"reviewer,omitempty"` + OldTopic string `json:"oldTopic,omitempty"` + Changer AccountInfo `json:"changer,omitempty"` +} + +// EventsLogService contains functions for querying the API provided +// by the optional events-log plugin. +type EventsLogService struct { + client *Client +} + +// EventsLogOptions contains options for querying events from the events-logs +// plugin. +type EventsLogOptions struct { + From time.Time + To time.Time + + // IgnoreUnmarshalErrors will cause GetEvents to ignore any errors + // that come up when calling json.Unmarshal. This can be useful in + // cases where the events-log plugin was not kept up to date with + // the Gerrit version for some reason. In these cases the events-log + // plugin will return data structs that don't match the EventInfo + // struct which in turn causes issues for json.Unmarshal. + IgnoreUnmarshalErrors bool +} + +// getURL returns the url that should be used in the request. This will vary +// depending on the options provided to GetEvents. +func (events *EventsLogService) getURL(options *EventsLogOptions) (string, error) { + parsed, err := url.Parse("/plugins/events-log/events/") + if err != nil { + return "", err + } + + query := parsed.Query() + + if !options.From.IsZero() { + query.Set("t1", options.From.Format("2006-01-02 15:04:05")) + } + + if !options.To.IsZero() { + query.Set("t2", options.To.Format("2006-01-02 15:04:05")) + } + + encoded := query.Encode() + if len(encoded) > 0 { + parsed.RawQuery = encoded + } + + return parsed.String(), nil +} + +// GetEvents returns a list of events for the given input options. Use of this +// function requires an authenticated user and for the events-log plugin to be +// installed. This function returns the unmarshalled EventInfo structs, response, +// failed lines and errors. Marshaling errors will cause this function to return +// before processing is complete unless you set EventsLogOptions.IgnoreUnmarshalErrors +// to true. This can be useful in cases where the events-log plugin got out of sync +// with the Gerrit version which in turn produced events which can't be transformed +// unmarshalled into EventInfo. +// +// Gerrit API docs: https:///plugins/events-log/Documentation/rest-api-events.html +func (events *EventsLogService) GetEvents(options *EventsLogOptions) ([]EventInfo, *Response, [][]byte, error) { + info := []EventInfo{} + failures := [][]byte{} + requestURL, err := events.getURL(options) + + if err != nil { + return info, nil, failures, err + } + + request, err := events.client.NewRequest("GET", requestURL, nil) + if err != nil { + return info, nil, failures, err + } + + // Perform the request but do not pass in a structure to unpack + // the response into. The format of the response is one EventInfo + // object per line so we need to manually handle the response here. + response, err := events.client.Do(request, nil) + if err != nil { + return info, response, failures, err + } + + body, err := ioutil.ReadAll(response.Body) + defer response.Body.Close() // nolint: errcheck + if err != nil { + return info, response, failures, err + } + + for _, line := range bytes.Split(body, []byte("\n")) { + if len(line) > 0 { + event := EventInfo{} + if err := json.Unmarshal(line, &event); err != nil { // nolint: vetshadow + failures = append(failures, line) + + if !options.IgnoreUnmarshalErrors { + return info, response, failures, err + } + continue + } + info = append(info, event) + } + } + return info, response, failures, err +} diff --git a/vendor/github.com/andygrunwald/go-gerrit/gerrit.go b/vendor/github.com/andygrunwald/go-gerrit/gerrit.go new file mode 100644 index 00000000..ebcbda00 --- /dev/null +++ b/vendor/github.com/andygrunwald/go-gerrit/gerrit.go @@ -0,0 +1,564 @@ +package gerrit + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "reflect" + "regexp" + "strings" + + "github.com/google/go-querystring/query" +) + +// TODO Try to reduce the code duplications of a std API req +// Maybe with http://play.golang.org/p/j-667shCCB +// and https://groups.google.com/forum/#!topic/golang-nuts/D-gIr24k5uY + +// A Client manages communication with the Gerrit API. +type Client struct { + // client is the HTTP client used to communicate with the API. + client *http.Client + + // baseURL is the base URL of the Gerrit instance for API requests. + // It must have a trailing slash. + baseURL *url.URL + + // Gerrit service for authentication. + Authentication *AuthenticationService + + // Services used for talking to different parts of the standard Gerrit API. + Access *AccessService + Accounts *AccountsService + Changes *ChangesService + Config *ConfigService + Groups *GroupsService + Plugins *PluginsService + Projects *ProjectsService + + // Additional services used for talking to non-standard Gerrit APIs. + EventsLog *EventsLogService +} + +// Response is a Gerrit API response. +// This wraps the standard http.Response returned from Gerrit. +type Response struct { + *http.Response +} + +var ( + // ErrNoInstanceGiven is returned by NewClient in the event the + // gerritURL argument was blank. + ErrNoInstanceGiven = errors.New("no Gerrit instance given") + + // ErrUserProvidedWithoutPassword is returned by NewClient + // if a user name is provided without a password. + ErrUserProvidedWithoutPassword = errors.New("a username was provided without a password") + + // ErrAuthenticationFailed is returned by NewClient in the event the provided + // credentials didn't allow us to query account information using digest, basic or cookie + // auth. + ErrAuthenticationFailed = errors.New("failed to authenticate using the provided credentials") + + // ReParseURL is used to parse the url provided to NewClient(). This + // regular expression contains five groups which capture the scheme, + // username, password, hostname and port. If we parse the url with this + // regular expression + ReParseURL = regexp.MustCompile(`^(http|https)://(.+):(.+)@(.+):(\d+)(.*)$`) +) + +// NewClient returns a new Gerrit API client. gerritURL specifies the +// HTTP endpoint of the Gerrit instance. For example, "http://localhost:8080/". +// If gerritURL does not have a trailing slash, one is added automatically. +// If a nil httpClient is provided, http.DefaultClient will be used. +// +// The url may contain credentials, http://admin:secret@localhost:8081/ for +// example. These credentials may either be a user name and password or +// name and value as in the case of cookie based authentication. If the url contains +// credentials then this function will attempt to validate the credentials before +// returning the client. ErrAuthenticationFailed will be returned if the credentials +// cannot be validated. The process of validating the credentials is relatively simple and +// only requires that the provided user have permission to GET /a/accounts/self. +func NewClient(gerritURL string, httpClient *http.Client) (*Client, error) { + if httpClient == nil { + httpClient = http.DefaultClient + } + + endpoint := gerritURL + if endpoint == "" { + return nil, ErrNoInstanceGiven + } + + hasAuth := false + username := "" + password := "" + + // Depending on the contents of the username and password the default + // url.Parse may not work. The below is an example URL that + // would end up being parsed incorrectly with url.Parse: + // http://admin:ZOSOKjgV/kgEkN0bzPJp+oGeJLqpXykqWFJpon/Ckg@localhost:38607 + // So instead of depending on url.Parse we'll try using a regular expression + // first to match a specific pattern. If that ends up working we modify + // the incoming endpoint to remove the username and password so the rest + // of this function will run as expected. + submatches := ReParseURL.FindAllStringSubmatch(endpoint, -1) + if len(submatches) > 0 && len(submatches[0]) > 5 { + submatch := submatches[0] + username = submatch[2] + password = submatch[3] + endpoint = fmt.Sprintf( + "%s://%s:%s%s", submatch[1], submatch[4], submatch[5], submatch[6]) + hasAuth = true + } + + baseURL, err := url.Parse(endpoint) + if err != nil { + return nil, err + } + if !strings.HasSuffix(baseURL.Path, "/") { + baseURL.Path += "/" + } + + // Note, if we retrieved the URL and password using the regular + // expression above then the below code will do nothing. + if baseURL.User != nil { + username = baseURL.User.Username() + parsedPassword, haspassword := baseURL.User.Password() + + // Catches cases like http://user@localhost:8081/ where no password + // was at all. If a blank password is required + if !haspassword { + return nil, ErrUserProvidedWithoutPassword + } + + password = parsedPassword + + // Reconstruct the url but without the username and password. + baseURL, err = url.Parse( + fmt.Sprintf("%s://%s%s", baseURL.Scheme, baseURL.Host, baseURL.RequestURI())) + if err != nil { + return nil, err + } + hasAuth = true + } + + c := &Client{ + client: httpClient, + baseURL: baseURL, + } + c.Authentication = &AuthenticationService{client: c} + c.Access = &AccessService{client: c} + c.Accounts = &AccountsService{client: c} + c.Changes = &ChangesService{client: c} + c.Config = &ConfigService{client: c} + c.Groups = &GroupsService{client: c} + c.Plugins = &PluginsService{client: c} + c.Projects = &ProjectsService{client: c} + c.EventsLog = &EventsLogService{client: c} + + if hasAuth { + // Digest auth (first since that's the default auth type) + c.Authentication.SetDigestAuth(username, password) + if success, err := checkAuth(c); success || err != nil { + return c, err + } + + // Basic auth + c.Authentication.SetBasicAuth(username, password) + if success, err := checkAuth(c); success || err != nil { + return c, err + } + + // Cookie auth + c.Authentication.SetCookieAuth(username, password) + if success, err := checkAuth(c); success || err != nil { + return c, err + } + + // Reset auth in case the consumer needs to do something special. + c.Authentication.ResetAuth() + return c, ErrAuthenticationFailed + } + + return c, nil +} + +// checkAuth is used by NewClient to check if the current credentials are +// valid. If the response is 401 Unauthorized then the error will be discarded. +func checkAuth(client *Client) (bool, error) { + _, response, err := client.Accounts.GetAccount("self") + switch err { + case ErrWWWAuthenticateHeaderMissing: + return false, nil + case ErrWWWAuthenticateHeaderNotDigest: + return false, nil + default: + // Response could be nil if the connection outright failed + // or some other error occurred before we got a response. + if response == nil && err != nil { + return false, err + } + + if err != nil && response.StatusCode == http.StatusUnauthorized { + err = nil + } + return response.StatusCode == http.StatusOK, err + } +} + +// NewRequest creates an API request. +// A relative URL can be provided in urlStr, in which case it is resolved relative to the baseURL of the Client. +// Relative URLs should always be specified without a preceding slash. +// If specified, the value pointed to by body is JSON encoded and included as the request body. +func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Request, error) { + // Build URL for request + u, err := c.buildURLForRequest(urlStr) + if err != nil { + return nil, err + } + + var buf io.ReadWriter + if body != nil { + buf = new(bytes.Buffer) + err = json.NewEncoder(buf).Encode(body) + if err != nil { + return nil, err + } + } + + req, err := http.NewRequest(method, u, buf) + if err != nil { + return nil, err + } + + // Apply Authentication + if err := c.addAuthentication(req); err != nil { + return nil, err + } + + // Request compact JSON + // See https://gerrit-review.googlesource.com/Documentation/rest-api.html#output + req.Header.Add("Accept", "application/json") + req.Header.Add("Content-Type", "application/json") + + // TODO: Add gzip encoding + // Accept-Encoding request header is set to gzip + // See https://gerrit-review.googlesource.com/Documentation/rest-api.html#output + + return req, nil +} + +// NewRawPutRequest creates a raw PUT request and makes no attempt to encode +// or marshal the body. Just passes it straight through. +func (c *Client) NewRawPutRequest(urlStr string, body string) (*http.Request, error) { + // Build URL for request + u, err := c.buildURLForRequest(urlStr) + if err != nil { + return nil, err + } + + buf := bytes.NewBuffer([]byte(body)) + req, err := http.NewRequest("PUT", u, buf) + if err != nil { + return nil, err + } + + // Apply Authentication + if err := c.addAuthentication(req); err != nil { + return nil, err + } + + // Request compact JSON + // See https://gerrit-review.googlesource.com/Documentation/rest-api.html#output + req.Header.Add("Accept", "application/json") + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + + // TODO: Add gzip encoding + // Accept-Encoding request header is set to gzip + // See https://gerrit-review.googlesource.com/Documentation/rest-api.html#output + + return req, nil +} + +// Call is a combine function for Client.NewRequest and Client.Do. +// +// Most API methods are quite the same. +// Get the URL, apply options, make a request, and get the response. +// Without adding special headers or something. +// To avoid a big amount of code duplication you can Client.Call. +// +// method is the HTTP method you want to call. +// u is the URL you want to call. +// body is the HTTP body. +// v is the HTTP response. +// +// For more information read https://github.com/google/go-github/issues/234 +func (c *Client) Call(method, u string, body interface{}, v interface{}) (*Response, error) { + req, err := c.NewRequest(method, u, body) + if err != nil { + return nil, err + } + + resp, err := c.Do(req, v) + if err != nil { + return resp, err + } + + return resp, err +} + +// buildURLForRequest will build the URL (as string) that will be called. +// We need such a utility method, because the URL.Path needs to be escaped (partly). +// +// E.g. if a project is called via "projects/%s" and the project is named "plugin/delete-project" +// there has to be "projects/plugin%25Fdelete-project" instead of "projects/plugin/delete-project". +// The second url will return nothing. +func (c *Client) buildURLForRequest(urlStr string) (string, error) { + // If there is a "/" at the start, remove it. + // TODO: It can be arranged for all callers of buildURLForRequest to never have a "/" prefix, + // which can be ensured via tests. This is how it's done in go-github. + // Then, this run-time check becomes unnecessary and can be removed. + urlStr = strings.TrimPrefix(urlStr, "/") + + // If we are authenticated, let's apply the "a/" prefix, + // but only if it has not already been applied. + if c.Authentication.HasAuth() && !strings.HasPrefix(urlStr, "a/") { + urlStr = "a/" + urlStr + } + + rel, err := url.Parse(urlStr) + if err != nil { + return "", err + } + + return c.baseURL.String() + rel.String(), nil +} + +// Do sends an API request and returns the API response. +// The API response is JSON decoded and stored in the value pointed to by v, +// or returned as an error if an API error has occurred. +// If v implements the io.Writer interface, the raw response body will be written to v, +// without attempting to first decode it. +func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) { + resp, err := c.client.Do(req) + if err != nil { + return nil, err + } + + // Wrap response + response := &Response{Response: resp} + + err = CheckResponse(resp) + if err != nil { + // even though there was an error, we still return the response + // in case the caller wants to inspect it further + return response, err + } + + if v != nil { + defer resp.Body.Close() // nolint: errcheck + if w, ok := v.(io.Writer); ok { + if _, err := io.Copy(w, resp.Body); err != nil { // nolint: vetshadow + return nil, err + } + } else { + var body []byte + body, err = ioutil.ReadAll(resp.Body) + if err != nil { + // even though there was an error, we still return the response + // in case the caller wants to inspect it further + return response, err + } + + body = RemoveMagicPrefixLine(body) + err = json.Unmarshal(body, v) + } + } + return response, err +} + +func (c *Client) addAuthentication(req *http.Request) error { + // Apply HTTP Basic Authentication + if c.Authentication.HasBasicAuth() { + req.SetBasicAuth(c.Authentication.name, c.Authentication.secret) + return nil + } + + // Apply HTTP Cookie + if c.Authentication.HasCookieAuth() { + req.AddCookie(&http.Cookie{ + Name: c.Authentication.name, + Value: c.Authentication.secret, + }) + return nil + } + + // Apply Digest Authentication. If we're using digest based + // authentication we need to make a request, process the + // WWW-Authenticate header, then set the Authorization header on the + // incoming request. We do not need to send a body along because + // the request itself should fail first. + if c.Authentication.HasDigestAuth() { + uri, err := c.buildURLForRequest(req.URL.RequestURI()) + if err != nil { + return err + } + + // WARNING: Don't use c.NewRequest here unless you like + // infinite recursion. + digestRequest, err := http.NewRequest(req.Method, uri, nil) + digestRequest.Header.Set("Accept", "*/*") + digestRequest.Header.Set("Content-Type", "application/json") + if err != nil { + return err + } + + response, err := c.client.Do(digestRequest) + if err != nil { + return err + } + + // When the function exits discard the rest of the + // body and close it. This should cause go to + // reuse the connection. + defer io.Copy(ioutil.Discard, response.Body) // nolint: errcheck + defer response.Body.Close() // nolint: errcheck + + if response.StatusCode == http.StatusUnauthorized { + authorization, err := c.Authentication.digestAuthHeader(response) + if err != nil { + return err + } + req.Header.Set("Authorization", authorization) + } + } + + return nil +} + +// DeleteRequest sends an DELETE API Request to urlStr with optional body. +// It is a shorthand combination for Client.NewRequest with Client.Do. +// +// Relative URLs should always be specified without a preceding slash. +// If specified, the value pointed to by body is JSON encoded and included as the request body. +func (c *Client) DeleteRequest(urlStr string, body interface{}) (*Response, error) { + req, err := c.NewRequest("DELETE", urlStr, body) + if err != nil { + return nil, err + } + + return c.Do(req, nil) +} + +// BaseURL returns the client's Gerrit instance HTTP endpoint. +func (c *Client) BaseURL() url.URL { + return *c.baseURL +} + +// RemoveMagicPrefixLine removes the "magic prefix line" of Gerris JSON +// response if present. The JSON response body starts with a magic prefix line +// that must be stripped before feeding the rest of the response body to a JSON +// parser. The reason for this is to prevent against Cross Site Script +// Inclusion (XSSI) attacks. By default all standard Gerrit APIs include this +// prefix line though some plugins may not. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api.html#output +func RemoveMagicPrefixLine(body []byte) []byte { + if bytes.HasPrefix(body, magicPrefix) { + return body[5:] + } + return body +} + +var magicPrefix = []byte(")]}'\n") + +// CheckResponse checks the API response for errors, and returns them if present. +// A response is considered an error if it has a status code outside the 200 range. +// API error responses are expected to have no response body. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api.html#response-codes +func CheckResponse(r *http.Response) error { + if c := r.StatusCode; 200 <= c && c <= 299 { + return nil + } + + // Some calls require an authentification + // In such cases errors like: + // API call to https://review.typo3.org/accounts/self failed: 403 Forbidden + // will be thrown. + + err := fmt.Errorf("API call to %s failed: %s", r.Request.URL.String(), r.Status) + return err +} + +// queryParameterReplacements are values in a url, specifically the query +// portion of the url, which should not be escaped before being sent to +// Gerrit. Note, Gerrit itself does not escape these values when using the +// search box so we shouldn't escape them either. +var queryParameterReplacements = map[string]string{ + "+": "GOGERRIT_URL_PLACEHOLDER_PLUS", + ":": "GOGERRIT_URL_PLACEHOLDER_COLON"} + +// addOptions adds the parameters in opt as URL query parameters to s. +// opt must be a struct whose fields may contain "url" tags. +func addOptions(s string, opt interface{}) (string, error) { + v := reflect.ValueOf(opt) + if v.Kind() == reflect.Ptr && v.IsNil() { + return s, nil + } + + u, err := url.Parse(s) + if err != nil { + return s, err + } + + qs, err := query.Values(opt) + if err != nil { + return s, err + } + + // If the url contained one or more query parameters (q) then we need + // to do some escaping on these values before Encode() is called. By + // doing so we're ensuring that : and + don't get encoded which means + // they'll be passed along to Gerrit as raw ascii. Without this Gerrit + // could return 400 Bad Request depending on the query parameters. For + // more complete information see this issue on GitHub: + // https://github.com/andygrunwald/go-gerrit/issues/18 + _, hasQuery := qs["q"] + if hasQuery { + values := []string{} + for _, value := range qs["q"] { + for key, replacement := range queryParameterReplacements { + value = strings.Replace(value, key, replacement, -1) + } + values = append(values, value) + } + + qs.Del("q") + for _, value := range values { + qs.Add("q", value) + } + } + encoded := qs.Encode() + + if hasQuery { + for key, replacement := range queryParameterReplacements { + encoded = strings.Replace(encoded, replacement, key, -1) + } + } + + u.RawQuery = encoded + return u.String(), nil +} + +// getStringResponseWithoutOptions retrieved a single string Response for a GET request +func getStringResponseWithoutOptions(client *Client, u string) (string, *Response, error) { + v := new(string) + resp, err := client.Call("GET", u, nil, v) + return *v, resp, err +} diff --git a/vendor/github.com/andygrunwald/go-gerrit/gometalinter.json b/vendor/github.com/andygrunwald/go-gerrit/gometalinter.json new file mode 100644 index 00000000..0f1c951a --- /dev/null +++ b/vendor/github.com/andygrunwald/go-gerrit/gometalinter.json @@ -0,0 +1,24 @@ +{ + "DisableAll": true, + "Enable": [ + "deadcode", + "unconvert", + "golint", + "vetshadow", + "vet", + "unused", + "gofmt", + "goimports", + "goconst", + "errcheck", + "interfacer", + "gas", + "unconvert", + "testify", + "unparam", + "varcheck", + "gotype", + "ineffassign", + "staticcheck" + ] +} diff --git a/vendor/github.com/andygrunwald/go-gerrit/groups.go b/vendor/github.com/andygrunwald/go-gerrit/groups.go new file mode 100644 index 00000000..a83027b3 --- /dev/null +++ b/vendor/github.com/andygrunwald/go-gerrit/groups.go @@ -0,0 +1,360 @@ +package gerrit + +import ( + "fmt" +) + +// GroupsService contains Group related REST endpoints +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-groups.html +type GroupsService struct { + client *Client +} + +// GroupAuditEventInfo entity contains information about an audit event of a group. +type GroupAuditEventInfo struct { + // TODO Member AccountInfo OR GroupInfo `json:"member"` + Type string `json:"type"` + User AccountInfo `json:"user"` + Date Timestamp `json:"date"` +} + +// GroupInfo entity contains information about a group. +// This can be a Gerrit internal group, or an external group that is known to Gerrit. +type GroupInfo struct { + ID string `json:"id"` + Name string `json:"name,omitempty"` + URL string `json:"url,omitempty"` + Options GroupOptionsInfo `json:"options"` + Description string `json:"description,omitempty"` + GroupID int `json:"group_id,omitempty"` + Owner string `json:"owner,omitempty"` + OwnerID string `json:"owner_id,omitempty"` + CreatedOn *Timestamp `json:"created_on,omitempty"` + Members []AccountInfo `json:"members,omitempty"` + Includes []GroupInfo `json:"includes,omitempty"` +} + +// GroupInput entity contains information for the creation of a new internal group. +type GroupInput struct { + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + VisibleToAll bool `json:"visible_to_all,omitempty"` + OwnerID string `json:"owner_id,omitempty"` +} + +// GroupOptionsInfo entity contains options of the group. +type GroupOptionsInfo struct { + VisibleToAll bool `json:"visible_to_all,omitempty"` +} + +// GroupOptionsInput entity contains new options for a group. +type GroupOptionsInput struct { + VisibleToAll bool `json:"visible_to_all,omitempty"` +} + +// GroupsInput entity contains information about groups that should be included into a group or that should be deleted from a group. +type GroupsInput struct { + OneGroup string `json:"_one_group,omitempty"` + Groups []string `json:"groups,omitempty"` +} + +// ListGroupsOptions specifies the different options for the ListGroups call. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-groups.html#list-groups +type ListGroupsOptions struct { + // Group Options + // Options fields can be obtained by adding o parameters, each option requires more lookups and slows down the query response time to the client so they are generally disabled by default. + // Optional fields are: + // INCLUDES: include list of directly included groups. + // MEMBERS: include list of direct group members. + Options []string `url:"o,omitempty"` + + // Check if a group is owned by the calling user + // By setting the option owned and specifying a group to inspect with the option q, it is possible to find out, if this group is owned by the calling user. + // If the group is owned by the calling user, the returned map contains this group. If the calling user doesn’t own this group an empty map is returned. + Owned string `url:"owned,omitempty"` + Group string `url:"q,omitempty"` + + // Group Limit + // The /groups/ URL also accepts a limit integer in the n parameter. This limits the results to show n groups. + Limit int `url:"n,omitempty"` + // The /groups/ URL also accepts a start integer in the S parameter. The results will skip S groups from group list. + Skip int `url:"S,omitempty"` +} + +// ListGroups lists the groups accessible by the caller. +// This is the same as using the ls-groups command over SSH, and accepts the same options as query parameters. +// The entries in the map are sorted by group name. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-groups.html#list-groups +func (s *GroupsService) ListGroups(opt *ListGroupsOptions) (*map[string]GroupInfo, *Response, error) { + u := "groups/" + + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new(map[string]GroupInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// GetGroup retrieves a group. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-groups.html#get-group +func (s *GroupsService) GetGroup(groupID string) (*GroupInfo, *Response, error) { + u := fmt.Sprintf("groups/%s", groupID) + return s.getGroupInfoResponse(u) +} + +// GetGroupDetail retrieves a group with the direct members and the directly included groups. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-groups.html#get-group-detail +func (s *GroupsService) GetGroupDetail(groupID string) (*GroupInfo, *Response, error) { + u := fmt.Sprintf("groups/%s/detail", groupID) + return s.getGroupInfoResponse(u) +} + +// getGroupInfoResponse retrieved a single GroupInfo Response for a GET request +func (s *GroupsService) getGroupInfoResponse(u string) (*GroupInfo, *Response, error) { + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new(GroupInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// GetGroupName retrieves the name of a group. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-groups.html#get-group-name +func (s *GroupsService) GetGroupName(groupID string) (string, *Response, error) { + u := fmt.Sprintf("groups/%s/name", groupID) + return getStringResponseWithoutOptions(s.client, u) +} + +// GetGroupDescription retrieves the description of a group. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-groups.html#get-group-description +func (s *GroupsService) GetGroupDescription(groupID string) (string, *Response, error) { + u := fmt.Sprintf("groups/%s/description", groupID) + return getStringResponseWithoutOptions(s.client, u) +} + +// GetGroupOptions retrieves the options of a group. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-groups.html#get-group-options +func (s *GroupsService) GetGroupOptions(groupID string) (*GroupOptionsInfo, *Response, error) { + u := fmt.Sprintf("groups/%s/options", groupID) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new(GroupOptionsInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// GetGroupOwner retrieves the owner group of a Gerrit internal group. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-groups.html#get-group-owner +func (s *GroupsService) GetGroupOwner(groupID string) (*GroupInfo, *Response, error) { + u := fmt.Sprintf("groups/%s/owner", groupID) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new(GroupInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// GetAuditLog gets the audit log of a Gerrit internal group. +// The returned audit events are sorted by date in reverse order so that the newest audit event comes first. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-groups.html#get-audit-log +func (s *GroupsService) GetAuditLog(groupID string) (*[]GroupAuditEventInfo, *Response, error) { + u := fmt.Sprintf("groups/%s/log.audit", groupID) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new([]GroupAuditEventInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// CreateGroup creates a new Gerrit internal group. +// In the request body additional data for the group can be provided as GroupInput. +// +// As response the GroupInfo entity is returned that describes the created group. +// If the group creation fails because the name is already in use the response is “409 Conflict”. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-groups.html#create-group +func (s *GroupsService) CreateGroup(groupID string, input *GroupInput) (*GroupInfo, *Response, error) { + u := fmt.Sprintf("groups/%s", groupID) + + req, err := s.client.NewRequest("PUT", u, input) + if err != nil { + return nil, nil, err + } + + v := new(GroupInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// RenameGroup renames a Gerrit internal group. +// The new group name must be provided in the request body. +// +// As response the new group name is returned. +// If renaming the group fails because the new name is already in use the response is “409 Conflict”. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-groups.html#rename-group +func (s *GroupsService) RenameGroup(groupID, name string) (*string, *Response, error) { + u := fmt.Sprintf("groups/%s/name", groupID) + input := struct { + Name string `json:"name"` + }{ + Name: name, + } + + req, err := s.client.NewRequest("PUT", u, input) + if err != nil { + return nil, nil, err + } + + v := new(string) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// SetGroupDescription sets the description of a Gerrit internal group. +// The new group description must be provided in the request body. +// +// As response the new group description is returned. +// If the description was deleted the response is “204 No Content”. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-groups.html#set-group-description +func (s *GroupsService) SetGroupDescription(groupID, description string) (*string, *Response, error) { + u := fmt.Sprintf("groups/%s/description", groupID) + input := struct { + Description string `json:"description"` + }{ + Description: description, + } + + req, err := s.client.NewRequest("PUT", u, input) + if err != nil { + return nil, nil, err + } + + v := new(string) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// DeleteGroupDescription deletes the description of a Gerrit internal group. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-groups.html#delete-group-description +func (s *GroupsService) DeleteGroupDescription(groupID string) (*Response, error) { + u := fmt.Sprintf("groups/%s/description'", groupID) + return s.client.DeleteRequest(u, nil) +} + +// SetGroupOptions sets the options of a Gerrit internal group. +// The new group options must be provided in the request body as a GroupOptionsInput entity. +// +// As response the new group options are returned as a GroupOptionsInfo entity. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-groups.html#set-group-options +func (s *GroupsService) SetGroupOptions(groupID string, input *GroupOptionsInput) (*GroupOptionsInfo, *Response, error) { + u := fmt.Sprintf("groups/%s/options", groupID) + + req, err := s.client.NewRequest("PUT", u, input) + if err != nil { + return nil, nil, err + } + + v := new(GroupOptionsInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// SetGroupOwner sets the owner group of a Gerrit internal group. +// The new owner group must be provided in the request body. +// The new owner can be specified by name, by group UUID or by the legacy numeric group ID. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-groups.html#set-group-owner +func (s *GroupsService) SetGroupOwner(groupID, owner string) (*GroupInfo, *Response, error) { + u := fmt.Sprintf("groups/%s/owner", groupID) + input := struct { + Owner string `json:"owner"` + }{ + Owner: owner, + } + + req, err := s.client.NewRequest("PUT", u, input) + if err != nil { + return nil, nil, err + } + + v := new(GroupInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} diff --git a/vendor/github.com/andygrunwald/go-gerrit/groups_include.go b/vendor/github.com/andygrunwald/go-gerrit/groups_include.go new file mode 100644 index 00000000..e8f4afc4 --- /dev/null +++ b/vendor/github.com/andygrunwald/go-gerrit/groups_include.go @@ -0,0 +1,117 @@ +package gerrit + +import ( + "fmt" +) + +// ListIncludedGroups lists the directly included groups of a group. +// The entries in the list are sorted by group name and UUID. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-groups.html#included-groups +func (s *GroupsService) ListIncludedGroups(groupID string) (*[]GroupInfo, *Response, error) { + u := fmt.Sprintf("groups/%s/groups/", groupID) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new([]GroupInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// GetIncludedGroup retrieves an included group. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-groups.html#get-included-group +func (s *GroupsService) GetIncludedGroup(groupID, includeGroupID string) (*GroupInfo, *Response, error) { + u := fmt.Sprintf("groups/%s/groups/%s", groupID, includeGroupID) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new(GroupInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// IncludeGroup includes an internal or external group into a Gerrit internal group. +// External groups must be specified using the UUID. +// +// As response a GroupInfo entity is returned that describes the included group. +// The request also succeeds if the group is already included in this group, but then the HTTP response code is 200 OK. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-groups.html#include-group +func (s *GroupsService) IncludeGroup(groupID, includeGroupID string) (*GroupInfo, *Response, error) { + u := fmt.Sprintf("groups/%s/groups/%s", groupID, includeGroupID) + + req, err := s.client.NewRequest("PUT", u, nil) + if err != nil { + return nil, nil, err + } + + v := new(GroupInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// IncludeGroups includes one or several groups into a Gerrit internal group. +// The groups to be included into the group must be provided in the request body as a GroupsInput entity. +// +// As response a list of GroupInfo entities is returned that describes the groups that were specified in the GroupsInput. +// A GroupInfo entity is returned for each group specified in the input, independently of whether the group was newly included into the group or whether the group was already included in the group. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-groups.html#include-groups +func (s *GroupsService) IncludeGroups(groupID string, input *GroupsInput) (*[]GroupInfo, *Response, error) { + u := fmt.Sprintf("groups/%s/groups", groupID) + + req, err := s.client.NewRequest("POST", u, input) + if err != nil { + return nil, nil, err + } + + v := new([]GroupInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// DeleteIncludedGroup deletes an included group from a Gerrit internal group. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-groups.html#include-group +func (s *GroupsService) DeleteIncludedGroup(groupID, includeGroupID string) (*Response, error) { + u := fmt.Sprintf("groups/%s/groups/%s", groupID, includeGroupID) + return s.client.DeleteRequest(u, nil) +} + +// DeleteIncludedGroups delete one or several included groups from a Gerrit internal group. +// The groups to be deleted from the group must be provided in the request body as a GroupsInput entity. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-groups.html#delete-included-groups +func (s *GroupsService) DeleteIncludedGroups(groupID string, input *GroupsInput) (*Response, error) { + u := fmt.Sprintf("groups/%s/groups.delete", groupID) + + req, err := s.client.NewRequest("POST", u, input) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} diff --git a/vendor/github.com/andygrunwald/go-gerrit/groups_member.go b/vendor/github.com/andygrunwald/go-gerrit/groups_member.go new file mode 100644 index 00000000..45cdf381 --- /dev/null +++ b/vendor/github.com/andygrunwald/go-gerrit/groups_member.go @@ -0,0 +1,133 @@ +package gerrit + +import ( + "fmt" +) + +// ListGroupMembersOptions specifies the different options for the ListGroupMembers call. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-groups.html#group-members +type ListGroupMembersOptions struct { + // To resolve the included groups of a group recursively and to list all members the parameter recursive can be set. + // Members from included external groups and from included groups which are not visible to the calling user are ignored. + Recursive bool `url:"recursive,omitempty"` +} + +// MembersInput entity contains information about accounts that should be added as members to a group or that should be deleted from the group +type MembersInput struct { + OneMember string `json:"_one_member,omitempty"` + Members []string `json:"members,omitempty"` +} + +// ListGroupMembers lists the direct members of a Gerrit internal group. +// The entries in the list are sorted by full name, preferred email and id. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-groups.html#group-members +func (s *GroupsService) ListGroupMembers(groupID string, opt *ListGroupMembersOptions) (*[]AccountInfo, *Response, error) { + u := fmt.Sprintf("groups/%s/members/", groupID) + + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new([]AccountInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// GetGroupMember retrieves a group member. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-groups.html#get-group-member +func (s *GroupsService) GetGroupMember(groupID, accountID string) (*AccountInfo, *Response, error) { + u := fmt.Sprintf("groups/%s/members/%s", groupID, accountID) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new(AccountInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// AddGroupMember adds a user as member to a Gerrit internal group. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-groups.html#add-group-member +func (s *GroupsService) AddGroupMember(groupID, accountID string) (*AccountInfo, *Response, error) { + u := fmt.Sprintf("groups/%s/members/%s", groupID, accountID) + + req, err := s.client.NewRequest("PUT", u, nil) + if err != nil { + return nil, nil, err + } + + v := new(AccountInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// AddGroupMembers adds one or several users to a Gerrit internal group. +// The users to be added to the group must be provided in the request body as a MembersInput entity. +// +// As response a list of detailed AccountInfo entities is returned that describes the group members that were specified in the MembersInput. +// An AccountInfo entity is returned for each user specified in the input, independently of whether the user was newly added to the group or whether the user was already a member of the group. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-groups.html#_add_group_members +func (s *GroupsService) AddGroupMembers(groupID string, input *MembersInput) (*[]AccountInfo, *Response, error) { + u := fmt.Sprintf("groups/%s/members", groupID) + + req, err := s.client.NewRequest("POST", u, input) + if err != nil { + return nil, nil, err + } + + v := new([]AccountInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// DeleteGroupMember deletes a user from a Gerrit internal group. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-groups.html#delete-group-member +func (s *GroupsService) DeleteGroupMember(groupID, accountID string) (*Response, error) { + u := fmt.Sprintf("groups/%s/members/%s'", groupID, accountID) + return s.client.DeleteRequest(u, nil) +} + +// DeleteGroupMembers delete one or several users from a Gerrit internal group. +// The users to be deleted from the group must be provided in the request body as a MembersInput entity. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-groups.html#delete-group-members +func (s *GroupsService) DeleteGroupMembers(groupID string, input *MembersInput) (*Response, error) { + u := fmt.Sprintf("groups/%s/members.delete'", groupID) + + req, err := s.client.NewRequest("POST", u, input) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} diff --git a/vendor/github.com/andygrunwald/go-gerrit/plugins.go b/vendor/github.com/andygrunwald/go-gerrit/plugins.go new file mode 100644 index 00000000..c4460140 --- /dev/null +++ b/vendor/github.com/andygrunwald/go-gerrit/plugins.go @@ -0,0 +1,131 @@ +package gerrit + +import ( + "fmt" +) + +// PluginsService contains Plugin related REST endpoints +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-plugins.html +type PluginsService struct { + client *Client +} + +// PluginInfo entity describes a plugin. +type PluginInfo struct { + ID string `json:"id"` + Version string `json:"version"` + IndexURL string `json:"index_url,omitempty"` + Disabled bool `json:"disabled,omitempty"` +} + +// PluginInput entity describes a plugin that should be installed. +type PluginInput struct { + URL string `json:"url"` +} + +// PluginOptions specifies the different options for the ListPlugins call. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-plugins.html#list-plugins +type PluginOptions struct { + // All enabled that all plugins are returned (enabled and disabled). + All bool `url:"all,omitempty"` +} + +// ListPlugins lists the plugins installed on the Gerrit server. +// Only the enabled plugins are returned unless the all option is specified. +// +// To be allowed to see the installed plugins, a user must be a member of a group that is granted the 'View Plugins' capability or the 'Administrate Server' capability. +// The entries in the map are sorted by plugin ID. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-plugins.html#list-plugins +func (s *PluginsService) ListPlugins(opt *PluginOptions) (*map[string]PluginInfo, *Response, error) { + u := "plugins/" + + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new(map[string]PluginInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// GetPluginStatus retrieves the status of a plugin on the Gerrit server. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-plugins.html#get-plugin-status +func (s *PluginsService) GetPluginStatus(pluginID string) (*PluginInfo, *Response, error) { + u := fmt.Sprintf("plugins/%s/gerrit~status", pluginID) + return s.requestWithPluginInfoResponse("GET", u, nil) +} + +// InstallPlugin installs a new plugin on the Gerrit server. +// If a plugin with the specified name already exists it is overwritten. +// +// Note: if the plugin provides its own name in the MANIFEST file, then the plugin name from the MANIFEST file has precedence over the {plugin-id} above. +// +// The plugin jar can either be sent as binary data in the request body or a URL to the plugin jar must be provided in the request body inside a PluginInput entity. +// +// As response a PluginInfo entity is returned that describes the plugin. +// If an existing plugin was overwritten the response is “200 OK”. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#set-dashboard +func (s *PluginsService) InstallPlugin(pluginID string, input *PluginInput) (*PluginInfo, *Response, error) { + u := fmt.Sprintf("plugins/%s", pluginID) + return s.requestWithPluginInfoResponse("PUT", u, input) +} + +// EnablePlugin enables a plugin on the Gerrit server. +// +// As response a PluginInfo entity is returned that describes the plugin. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-plugins.html#enable-plugin +func (s *PluginsService) EnablePlugin(pluginID string) (*PluginInfo, *Response, error) { + u := fmt.Sprintf("plugins/%s/gerrit~enable", pluginID) + return s.requestWithPluginInfoResponse("POST", u, nil) +} + +// DisablePlugin disables a plugin on the Gerrit server. +// +// As response a PluginInfo entity is returned that describes the plugin. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-plugins.html#disable-plugin +func (s *PluginsService) DisablePlugin(pluginID string) (*PluginInfo, *Response, error) { + u := fmt.Sprintf("plugins/%s/gerrit~disable", pluginID) + return s.requestWithPluginInfoResponse("POST", u, nil) +} + +// ReloadPlugin reloads a plugin on the Gerrit server. +// +// As response a PluginInfo entity is returned that describes the plugin. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-plugins.html#disable-plugin +func (s *PluginsService) ReloadPlugin(pluginID string) (*PluginInfo, *Response, error) { + u := fmt.Sprintf("plugins/%s/gerrit~reload", pluginID) + return s.requestWithPluginInfoResponse("POST", u, nil) +} + +func (s *PluginsService) requestWithPluginInfoResponse(method, u string, input interface{}) (*PluginInfo, *Response, error) { + req, err := s.client.NewRequest(method, u, input) + if err != nil { + return nil, nil, err + } + + v := new(PluginInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} diff --git a/vendor/github.com/andygrunwald/go-gerrit/projects.go b/vendor/github.com/andygrunwald/go-gerrit/projects.go new file mode 100644 index 00000000..5695c6b9 --- /dev/null +++ b/vendor/github.com/andygrunwald/go-gerrit/projects.go @@ -0,0 +1,465 @@ +package gerrit + +import ( + "fmt" + "net/url" +) + +// ProjectsService contains Project related REST endpoints +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html +type ProjectsService struct { + client *Client +} + +// ProjectInfo entity contains information about a project. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#project-info +type ProjectInfo struct { + ID string `json:"id"` + Name string `json:"name"` + Parent string `json:"parent,omitempty"` + Description string `json:"description,omitempty"` + State string `json:"state,omitempty"` + Branches map[string]string `json:"branches,omitempty"` + WebLinks []WebLinkInfo `json:"web_links,omitempty"` +} + +// ProjectInput entity contains information for the creation of a new project. +type ProjectInput struct { + Name string `json:"name,omitempty"` + Parent string `json:"parent,omitempty"` + Description string `json:"description,omitempty"` + PermissionsOnly bool `json:"permissions_only"` + CreateEmptyCommit bool `json:"create_empty_commit"` + SubmitType string `json:"submit_type,omitempty"` + Branches []string `json:"branches,omitempty"` + Owners []string `json:"owners,omitempty"` + UseContributorAgreements string `json:"use_contributor_agreements"` + UseSignedOffBy string `json:"use_signed_off_by"` + CreateNewChangeForAllNotInTarget string `json:"create_new_change_for_all_not_in_target"` + UseContentMerge string `json:"use_content_merge"` + RequireChangeID string `json:"require_change_id"` + MaxObjectSizeLimit string `json:"max_object_size_limit,omitempty"` + PluginConfigValues map[string]map[string]string `json:"plugin_config_values,omitempty"` +} + +// GCInput entity contains information to run the Git garbage collection. +type GCInput struct { + ShowProgress bool `json:"show_progress"` + Aggressive bool `json:"aggressive"` +} + +// HeadInput entity contains information for setting HEAD for a project. +type HeadInput struct { + Ref string `json:"ref"` +} + +// BanInput entity contains information for banning commits in a project. +type BanInput struct { + Commits []string `json:"commits"` + Reason string `json:"reason,omitempty"` +} + +// BanResultInfo entity describes the result of banning commits. +type BanResultInfo struct { + NewlyBanned []string `json:"newly_banned,omitempty"` + AlreadyBanned []string `json:"already_banned,omitempty"` + Ignored []string `json:"ignored,omitempty"` +} + +// ThemeInfo entity describes a theme. +type ThemeInfo struct { + CSS string `type:"css,omitempty"` + Header string `type:"header,omitempty"` + Footer string `type:"footer,omitempty"` +} + +// ReflogEntryInfo entity describes an entry in a reflog. +type ReflogEntryInfo struct { + OldID string `json:"old_id"` + NewID string `json:"new_id"` + Who GitPersonInfo `json:"who"` + Comment string `json:"comment"` +} + +// ProjectParentInput entity contains information for setting a project parent. +type ProjectParentInput struct { + Parent string `json:"parent"` + CommitMessage string `json:"commit_message,omitempty"` +} + +// RepositoryStatisticsInfo entity contains information about statistics of a Git repository. +type RepositoryStatisticsInfo struct { + NumberOfLooseObjects int `json:"number_of_loose_objects"` + NumberOfLooseRefs int `json:"number_of_loose_refs"` + NumberOfPackFiles int `json:"number_of_pack_files"` + NumberOfPackedObjects int `json:"number_of_packed_objects"` + NumberOfPackedRefs int `json:"number_of_packed_refs"` + SizeOfLooseObjects int `json:"size_of_loose_objects"` + SizeOfPackedObjects int `json:"size_of_packed_objects"` +} + +// InheritedBooleanInfo entity represents a boolean value that can also be inherited. +type InheritedBooleanInfo struct { + Value bool `json:"value"` + ConfiguredValue string `json:"configured_value"` + InheritedValue bool `json:"inherited_value,omitempty"` +} + +// MaxObjectSizeLimitInfo entity contains information about the max object size limit of a project. +type MaxObjectSizeLimitInfo struct { + Value string `json:"value,omitempty"` + ConfiguredValue string `json:"configured_value,omitempty"` + InheritedValue string `json:"inherited_value,omitempty"` +} + +// ConfigParameterInfo entity describes a project configuration parameter. +type ConfigParameterInfo struct { + DisplayName string `json:"display_name,omitempty"` + Description string `json:"description,omitempty"` + Warning string `json:"warning,omitempty"` + Type string `json:"type"` + Value string `json:"value,omitempty"` + Values []string `json:"values,omitempty"` + // TODO: 5 fields are missing here, because the documentation seems to be fucked up + // See https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#config-parameter-info +} + +// ProjectDescriptionInput entity contains information for setting a project description. +type ProjectDescriptionInput struct { + Description string `json:"description,omitempty"` + CommitMessage string `json:"commit_message,omitempty"` +} + +// ConfigInfo entity contains information about the effective project configuration. +type ConfigInfo struct { + Description string `json:"description,omitempty"` + UseContributorAgreements InheritedBooleanInfo `json:"use_contributor_agreements,omitempty"` + UseContentMerge InheritedBooleanInfo `json:"use_content_merge,omitempty"` + UseSignedOffBy InheritedBooleanInfo `json:"use_signed_off_by,omitempty"` + CreateNewChangeForAllNotInTarget InheritedBooleanInfo `json:"create_new_change_for_all_not_in_target,omitempty"` + RequireChangeID InheritedBooleanInfo `json:"require_change_id,omitempty"` + EnableSignedPush InheritedBooleanInfo `json:"enable_signed_push,omitempty"` + MaxObjectSizeLimit MaxObjectSizeLimitInfo `json:"max_object_size_limit"` + SubmitType string `json:"submit_type"` + State string `json:"state,omitempty"` + Commentlinks map[string]string `json:"commentlinks"` + Theme ThemeInfo `json:"theme,omitempty"` + PluginConfig map[string]ConfigParameterInfo `json:"plugin_config,omitempty"` + Actions map[string]ActionInfo `json:"actions,omitempty"` +} + +// ConfigInput entity describes a new project configuration. +type ConfigInput struct { + Description string `json:"description,omitempty"` + UseContributorAgreements string `json:"use_contributor_agreements,omitempty"` + UseContentMerge string `json:"use_content_merge,omitempty"` + UseSignedOffBy string `json:"use_signed_off_by,omitempty"` + CreateNewChangeForAllNotInTarget string `json:"create_new_change_for_all_not_in_target,omitempty"` + RequireChangeID string `json:"require_change_id,omitempty"` + MaxObjectSizeLimit MaxObjectSizeLimitInfo `json:"max_object_size_limit,omitempty"` + SubmitType string `json:"submit_type,omitempty"` + State string `json:"state,omitempty"` + PluginConfigValues map[string]map[string]string `json:"plugin_config_values,omitempty"` +} + +// ProjectBaseOptions specifies the really basic options for projects +// and sub functionality (e.g. Tags) +type ProjectBaseOptions struct { + // Limit the number of projects to be included in the results. + Limit int `url:"n,omitempty"` + + // Skip the given number of branches from the beginning of the list. + Skip string `url:"s,omitempty"` +} + +// ProjectOptions specifies the parameters to the ProjectsService.ListProjects. +type ProjectOptions struct { + ProjectBaseOptions + + // Limit the results to the projects having the specified branch and include the sha1 of the branch in the results. + Branch string `url:"b,omitempty"` + + // Include project description in the results. + Description bool `url:"d,omitempty"` + + // Limit the results to those projects that start with the specified prefix. + Prefix string `url:"p,omitempty"` + + // Limit the results to those projects that match the specified regex. + // Boundary matchers '^' and '$' are implicit. + // For example: the regex 'test.*' will match any projects that start with 'test' and regex '.*test' will match any project that end with 'test'. + Regex string `url:"r,omitempty"` + + // Skip the given number of projects from the beginning of the list. + Skip string `url:"S,omitempty"` + + // Limit the results to those projects that match the specified substring. + Substring string `url:"m,omitempty"` + + // Get projects inheritance in a tree-like format. + // This option does not work together with the branch option. + Tree bool `url:"t,omitempty"` + + // Get projects with specified type: ALL, CODE, PERMISSIONS. + Type string `url:"type,omitempty"` +} + +// ListProjects lists the projects accessible by the caller. +// This is the same as using the ls-projects command over SSH, and accepts the same options as query parameters. +// The entries in the map are sorted by project name. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#list-projects +func (s *ProjectsService) ListProjects(opt *ProjectOptions) (*map[string]ProjectInfo, *Response, error) { + u := "projects/" + + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + v := new(map[string]ProjectInfo) + resp, err := s.client.Call("GET", u, nil, v) + return v, resp, err +} + +// GetProject retrieves a project. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#get-project +func (s *ProjectsService) GetProject(projectName string) (*ProjectInfo, *Response, error) { + u := fmt.Sprintf("projects/%s", url.QueryEscape(projectName)) + + v := new(ProjectInfo) + resp, err := s.client.Call("GET", u, nil, v) + return v, resp, err +} + +// CreateProject creates a new project. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#create-project +func (s *ProjectsService) CreateProject(projectName string, input *ProjectInput) (*ProjectInfo, *Response, error) { + u := fmt.Sprintf("projects/%s/", url.QueryEscape(projectName)) + + v := new(ProjectInfo) + resp, err := s.client.Call("PUT", u, input, v) + return v, resp, err +} + +// GetProjectDescription retrieves the description of a project. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#get-project-description +func (s *ProjectsService) GetProjectDescription(projectName string) (string, *Response, error) { + u := fmt.Sprintf("projects/%s/description", url.QueryEscape(projectName)) + + return getStringResponseWithoutOptions(s.client, u) +} + +// GetProjectParent retrieves the name of a project’s parent project. +// For the All-Projects root project an empty string is returned. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#get-project-parent +func (s *ProjectsService) GetProjectParent(projectName string) (string, *Response, error) { + u := fmt.Sprintf("projects/%s/parent", url.QueryEscape(projectName)) + return getStringResponseWithoutOptions(s.client, u) +} + +// GetHEAD retrieves for a project the name of the branch to which HEAD points. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#get-head +func (s *ProjectsService) GetHEAD(projectName string) (string, *Response, error) { + u := fmt.Sprintf("projects/%s/HEAD", url.QueryEscape(projectName)) + return getStringResponseWithoutOptions(s.client, u) +} + +// GetRepositoryStatistics return statistics for the repository of a project. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#get-repository-statistics +func (s *ProjectsService) GetRepositoryStatistics(projectName string) (*RepositoryStatisticsInfo, *Response, error) { + u := fmt.Sprintf("projects/%s/statistics.git", url.QueryEscape(projectName)) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new(RepositoryStatisticsInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// GetConfig gets some configuration information about a project. +// Note that this config info is not simply the contents of project.config; +// it generally contains fields that may have been inherited from parent projects. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#get-config +func (s *ProjectsService) GetConfig(projectName string) (*ConfigInfo, *Response, error) { + u := fmt.Sprintf("projects/%s/config'", url.QueryEscape(projectName)) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new(ConfigInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// SetProjectDescription sets the description of a project. +// The new project description must be provided in the request body inside a ProjectDescriptionInput entity. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#set-project-description +func (s *ProjectsService) SetProjectDescription(projectName string, input *ProjectDescriptionInput) (*string, *Response, error) { + u := fmt.Sprintf("projects/%s/description'", url.QueryEscape(projectName)) + + // TODO Use here the getStringResponseWithoutOptions (for PUT requests) + + req, err := s.client.NewRequest("PUT", u, input) + if err != nil { + return nil, nil, err + } + + v := new(string) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// DeleteProjectDescription deletes the description of a project. +// The request body does not need to include a ProjectDescriptionInput entity if no commit message is specified. +// +// Please note that some proxies prohibit request bodies for DELETE requests. +// In this case, if you want to specify a commit message, use PUT to delete the description. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#delete-project-description +func (s *ProjectsService) DeleteProjectDescription(projectName string) (*Response, error) { + u := fmt.Sprintf("projects/%s/description'", url.QueryEscape(projectName)) + return s.client.DeleteRequest(u, nil) +} + +// BanCommit marks commits as banned for the project. +// If a commit is banned Gerrit rejects every push that includes this commit with contains banned commit ... +// +// Note: +// This REST endpoint only marks the commits as banned, but it does not remove the commits from the history of any central branch. +// This needs to be done manually. +// The commits to be banned must be specified in the request body as a BanInput entity. +// +// The caller must be project owner. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#ban-commit +func (s *ProjectsService) BanCommit(projectName string, input *BanInput) (*BanResultInfo, *Response, error) { + u := fmt.Sprintf("projects/%s/ban'", url.QueryEscape(projectName)) + + req, err := s.client.NewRequest("PUT", u, input) + if err != nil { + return nil, nil, err + } + + v := new(BanResultInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// SetConfig sets the configuration of a project. +// The new configuration must be provided in the request body as a ConfigInput entity. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#set-config +func (s *ProjectsService) SetConfig(projectName string, input *ConfigInput) (*ConfigInfo, *Response, error) { + u := fmt.Sprintf("projects/%s/config'", url.QueryEscape(projectName)) + + req, err := s.client.NewRequest("PUT", u, input) + if err != nil { + return nil, nil, err + } + + v := new(ConfigInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// SetHEAD sets HEAD for a project. +// The new ref to which HEAD should point must be provided in the request body inside a HeadInput entity. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#set-head +func (s *ProjectsService) SetHEAD(projectName string, input *HeadInput) (*string, *Response, error) { + u := fmt.Sprintf("projects/%s/HEAD'", url.QueryEscape(projectName)) + + // TODO Use here the getStringResponseWithoutOptions (for PUT requests) + + req, err := s.client.NewRequest("PUT", u, input) + if err != nil { + return nil, nil, err + } + + v := new(string) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// SetProjectParent sets the parent project for a project. +// The new name of the parent project must be provided in the request body inside a ProjectParentInput entity. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#set-project-parent +func (s *ProjectsService) SetProjectParent(projectName string, input *ProjectParentInput) (*string, *Response, error) { + u := fmt.Sprintf("projects/%s/parent'", url.QueryEscape(projectName)) + + // TODO Use here the getStringResponseWithoutOptions (for PUT requests) + + req, err := s.client.NewRequest("PUT", u, input) + if err != nil { + return nil, nil, err + } + + v := new(string) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// RunGC runs the Git garbage collection for the repository of a project. +// The response is the streamed output of the garbage collection. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#run-gc +func (s *ProjectsService) RunGC(projectName string, input *GCInput) (*Response, error) { + u := fmt.Sprintf("projects/%s/gc'", url.QueryEscape(projectName)) + + req, err := s.client.NewRequest("POST", u, input) + if err != nil { + return nil, err + } + + resp, err := s.client.Do(req, nil) + if err != nil { + return resp, err + } + + return resp, err +} diff --git a/vendor/github.com/andygrunwald/go-gerrit/projects_branch.go b/vendor/github.com/andygrunwald/go-gerrit/projects_branch.go new file mode 100644 index 00000000..66d8ba8c --- /dev/null +++ b/vendor/github.com/andygrunwald/go-gerrit/projects_branch.go @@ -0,0 +1,157 @@ +package gerrit + +import ( + "fmt" + "net/url" +) + +// BranchInfo entity contains information about a branch. +type BranchInfo struct { + Ref string `json:"ref"` + Revision string `json:"revision"` + CanDelete bool `json:"can_delete"` + WebLinks []WebLinkInfo `json:"web_links,omitempty"` +} + +// BranchInput entity contains information for the creation of a new branch. +type BranchInput struct { + Ref string `json:"ref,omitempty"` + Revision string `json:"revision,omitempty"` +} + +// DeleteBranchesInput entity contains information about branches that should be deleted. +type DeleteBranchesInput struct { + Branches []string `json:"DeleteBranchesInput"` +} + +// BranchOptions specifies the parameters to the branch API endpoints. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#branch-options +type BranchOptions struct { + // Limit the number of branches to be included in the results. + Limit int `url:"n,omitempty"` + + // Skip the given number of branches from the beginning of the list. + Skip string `url:"s,omitempty"` + + // Substring limits the results to those projects that match the specified substring. + Substring string `url:"m,omitempty"` + + // Limit the results to those branches that match the specified regex. + // Boundary matchers '^' and '$' are implicit. + // For example: the regex 't*' will match any branches that start with 'test' and regex '*t' will match any branches that end with 'test'. + Regex string `url:"r,omitempty"` +} + +// ListBranches list the branches of a project. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#list-branches +func (s *ProjectsService) ListBranches(projectName string, opt *BranchOptions) (*[]BranchInfo, *Response, error) { + u := fmt.Sprintf("projects/%s/branches/", url.QueryEscape(projectName)) + + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new([]BranchInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// GetBranch retrieves a branch of a project. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#get-branch +func (s *ProjectsService) GetBranch(projectName, branchID string) (*BranchInfo, *Response, error) { + u := fmt.Sprintf("projects/%s/branches/%s", url.QueryEscape(projectName), branchID) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new(BranchInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// GetReflog gets the reflog of a certain branch. +// The caller must be project owner. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#get-reflog +func (s *ProjectsService) GetReflog(projectName, branchID string) (*[]ReflogEntryInfo, *Response, error) { + u := fmt.Sprintf("projects/%s/branches/%s/reflog", url.QueryEscape(projectName), branchID) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new([]ReflogEntryInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// CreateBranch creates a new branch. +// In the request body additional data for the branch can be provided as BranchInput. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#create-branch +func (s *ProjectsService) CreateBranch(projectName, branchID string, input *BranchInput) (*BranchInfo, *Response, error) { + u := fmt.Sprintf("projects/%s/branches/%s", url.QueryEscape(projectName), branchID) + + req, err := s.client.NewRequest("PUT", u, input) + if err != nil { + return nil, nil, err + } + + v := new(BranchInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// DeleteBranch deletes a branch. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#delete-branch +func (s *ProjectsService) DeleteBranch(projectName, branchID string) (*Response, error) { + u := fmt.Sprintf("projects/%s/branches/%s", url.QueryEscape(projectName), branchID) + return s.client.DeleteRequest(u, nil) +} + +// DeleteBranches delete one or more branches. +// If some branches could not be deleted, the response is “409 Conflict” and the error message is contained in the response body. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#delete-branches +func (s *ProjectsService) DeleteBranches(projectName string, input *DeleteBranchesInput) (*Response, error) { + u := fmt.Sprintf("projects/%s/branches:delete", url.QueryEscape(projectName)) + return s.client.DeleteRequest(u, input) +} + +// GetBranchContent gets the content of a file from the HEAD revision of a certain branch. +// The content is returned as base64 encoded string. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#get-content +func (s *ProjectsService) GetBranchContent(projectName, branchID, fileID string) (string, *Response, error) { + u := fmt.Sprintf("projects/%s/branches/%s/files/%s/content", url.QueryEscape(projectName), branchID, fileID) + return getStringResponseWithoutOptions(s.client, u) +} diff --git a/vendor/github.com/andygrunwald/go-gerrit/projects_childproject.go b/vendor/github.com/andygrunwald/go-gerrit/projects_childproject.go new file mode 100644 index 00000000..ef0e100b --- /dev/null +++ b/vendor/github.com/andygrunwald/go-gerrit/projects_childproject.go @@ -0,0 +1,66 @@ +package gerrit + +import ( + "fmt" + "net/url" +) + +// ChildProjectOptions specifies the parameters to the Child Project API endpoints. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#list-child-projects +type ChildProjectOptions struct { + // Recursive resolve the child projects of a project recursively. + // Child projects that are not visible to the calling user are ignored and are not resolved further. + Recursive int `url:"recursive,omitempty"` +} + +// ListChildProjects lists the direct child projects of a project. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#list-child-projects +func (s *ProjectsService) ListChildProjects(projectName string, opt *ChildProjectOptions) (*[]ProjectInfo, *Response, error) { + u := fmt.Sprintf("projects/%s/children/", url.QueryEscape(projectName)) + + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new([]ProjectInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// GetChildProject retrieves a child project. +// If a non-direct child project should be retrieved the parameter recursive must be set. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#get-child-project +func (s *ProjectsService) GetChildProject(projectName, childProjectName string, opt *ChildProjectOptions) (*ProjectInfo, *Response, error) { + u := fmt.Sprintf("projects/%s/children/%s", url.QueryEscape(projectName), url.QueryEscape(childProjectName)) + + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new(ProjectInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} diff --git a/vendor/github.com/andygrunwald/go-gerrit/projects_commit.go b/vendor/github.com/andygrunwald/go-gerrit/projects_commit.go new file mode 100644 index 00000000..a166341b --- /dev/null +++ b/vendor/github.com/andygrunwald/go-gerrit/projects_commit.go @@ -0,0 +1,36 @@ +package gerrit + +import ( + "fmt" + "net/url" +) + +// GetCommit retrieves a commit of a project. +// The commit must be visible to the caller. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#get-commit +func (s *ProjectsService) GetCommit(projectName, commitID string) (*CommitInfo, *Response, error) { + u := fmt.Sprintf("projects/%s/commits/%s", url.QueryEscape(projectName), commitID) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new(CommitInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// GetCommitContent gets the content of a file from the HEAD revision of a certain branch. +// The content is returned as base64 encoded string. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#get-content +func (s *ProjectsService) GetCommitContent(projectName, branchID, fileID string) (string, *Response, error) { + u := fmt.Sprintf("projects/%s/branches/%s/files/%s/content", url.QueryEscape(projectName), branchID, fileID) + return getStringResponseWithoutOptions(s.client, u) +} diff --git a/vendor/github.com/andygrunwald/go-gerrit/projects_dashboard.go b/vendor/github.com/andygrunwald/go-gerrit/projects_dashboard.go new file mode 100644 index 00000000..07c38b58 --- /dev/null +++ b/vendor/github.com/andygrunwald/go-gerrit/projects_dashboard.go @@ -0,0 +1,108 @@ +package gerrit + +import ( + "fmt" + "net/url" +) + +// DashboardSectionInfo entity contains information about a section in a dashboard. +type DashboardSectionInfo struct { + Name string `json:"name"` + Query string `json:"query"` +} + +// DashboardInput entity contains information to create/update a project dashboard. +type DashboardInput struct { + ID string `json:"id,omitempty"` + CommitMessage string `json:"commit_message,omitempty"` +} + +// DashboardInfo entity contains information about a project dashboard. +type DashboardInfo struct { + ID string `json:"id"` + Project string `json:"project"` + DefiningProject string `json:"defining_project"` + Ref string `json:"ref"` + Path string `json:"path"` + Description string `json:"description,omitempty"` + Foreach string `json:"foreach,omitempty"` + URL string `json:"url"` + Default bool `json:"default"` + Title string `json:"title,omitempty"` + Sections []DashboardSectionInfo `json:"sections"` +} + +// ListDashboards list custom dashboards for a project. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#list-dashboards +func (s *ProjectsService) ListDashboards(projectName string) (*[]DashboardInfo, *Response, error) { + u := fmt.Sprintf("projects/%s/dashboards/", url.QueryEscape(projectName)) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new([]DashboardInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// GetDashboard list custom dashboards for a project. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#get-dashboard +func (s *ProjectsService) GetDashboard(projectName, dashboardName string) (*DashboardInfo, *Response, error) { + u := fmt.Sprintf("projects/%s/dashboards/%s", url.QueryEscape(projectName), url.QueryEscape(dashboardName)) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new(DashboardInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// SetDashboard updates/Creates a project dashboard. +// Currently only supported for the default dashboard. +// +// The creation/update information for the dashboard must be provided in the request body as a DashboardInput entity. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#set-dashboard +func (s *ProjectsService) SetDashboard(projectName, dashboardID string, input *DashboardInput) (*DashboardInfo, *Response, error) { + u := fmt.Sprintf("projects/%s/dashboards/%s", url.QueryEscape(projectName), url.QueryEscape(dashboardID)) + + req, err := s.client.NewRequest("PUT", u, input) + if err != nil { + return nil, nil, err + } + + v := new(DashboardInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// DeleteDashboard deletes a project dashboard. +// Currently only supported for the default dashboard. +// +// The request body does not need to include a DashboardInput entity if no commit message is specified. +// Please note that some proxies prohibit request bodies for DELETE requests. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#delete-dashboard +func (s *ProjectsService) DeleteDashboard(projectName, dashboardID string, input *DashboardInput) (*Response, error) { + u := fmt.Sprintf("projects/%s/dashboards/%s", url.QueryEscape(projectName), url.QueryEscape(dashboardID)) + return s.client.DeleteRequest(u, input) +} diff --git a/vendor/github.com/andygrunwald/go-gerrit/projects_tag.go b/vendor/github.com/andygrunwald/go-gerrit/projects_tag.go new file mode 100644 index 00000000..7070d3f7 --- /dev/null +++ b/vendor/github.com/andygrunwald/go-gerrit/projects_tag.go @@ -0,0 +1,60 @@ +package gerrit + +import ( + "fmt" + "net/url" +) + +// TagInfo entity contains information about a tag. +type TagInfo struct { + Ref string `json:"ref"` + Revision string `json:"revision"` + Object string `json:"object"` + Message string `json:"message"` + Tagger GitPersonInfo `json:"tagger"` + Created *Timestamp `json:"created,omitempty"` +} + +// ListTags list the tags of a project. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#list-tags +func (s *ProjectsService) ListTags(projectName string, opt *ProjectBaseOptions) (*[]TagInfo, *Response, error) { + u := fmt.Sprintf("projects/%s/tags/", url.QueryEscape(projectName)) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new([]TagInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} + +// GetTag retrieves a tag of a project. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#get-tag +func (s *ProjectsService) GetTag(projectName, tagName string) (*TagInfo, *Response, error) { + u := fmt.Sprintf("projects/%s/tags/%s", url.QueryEscape(projectName), url.QueryEscape(tagName)) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + v := new(TagInfo) + resp, err := s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + + return v, resp, err +} diff --git a/vendor/github.com/andygrunwald/go-gerrit/types.go b/vendor/github.com/andygrunwald/go-gerrit/types.go new file mode 100644 index 00000000..baf8ee4a --- /dev/null +++ b/vendor/github.com/andygrunwald/go-gerrit/types.go @@ -0,0 +1,88 @@ +package gerrit + +import ( + "encoding/json" + "errors" + "strconv" + "time" +) + +// Timestamp represents an instant in time with nanosecond precision, in UTC time zone. +// It encodes to and from JSON in Gerrit's timestamp format. +// All exported methods of time.Time can be called on Timestamp. +// +// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api.html#timestamp +type Timestamp struct { + // Time is an instant in time. Its time zone must be UTC. + time.Time +} + +// MarshalJSON implements the json.Marshaler interface. +// The time is a quoted string in Gerrit's timestamp format. +// An error is returned if t.Time time zone is not UTC. +func (t Timestamp) MarshalJSON() ([]byte, error) { + if t.Location() != time.UTC { + return nil, errors.New("Timestamp.MarshalJSON: time zone must be UTC") + } + if y := t.Year(); y < 0 || 9999 < y { + // RFC 3339 is clear that years are 4 digits exactly. + // See golang.org/issue/4556#issuecomment-66073163 for more discussion. + return nil, errors.New("Timestamp.MarshalJSON: year outside of range [0,9999]") + } + b := make([]byte, 0, len(timeLayout)+2) + b = append(b, '"') + b = t.AppendFormat(b, timeLayout) + b = append(b, '"') + return b, nil +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +// The time is expected to be a quoted string in Gerrit's timestamp format. +func (t *Timestamp) UnmarshalJSON(b []byte) error { + // Ignore null, like in the main JSON package. + if string(b) == "null" { + return nil + } + var err error + t.Time, err = time.Parse(`"`+timeLayout+`"`, string(b)) + return err +} + +// Gerrit's timestamp layout is like time.RFC3339Nano, but with a space instead +// of the "T", without a timezone (it's always in UTC), and always includes nanoseconds. +// See https://gerrit-review.googlesource.com/Documentation/rest-api.html#timestamp. +const timeLayout = "2006-01-02 15:04:05.000000000" + +// Number is a string representing a number. This type is only used in cases +// where the API being queried may return an inconsistent result. +type Number string + +// String returns the string representing the current number. +func (n *Number) String() string { + return string(*n) +} + +// Int returns the current number as an integer +func (n *Number) Int() (int, error) { + return strconv.Atoi(n.String()) +} + +// UnmarshalJSON will marshal the provided data into the current *Number struct. +func (n *Number) UnmarshalJSON(data []byte) error { + // `data` is a number represented as a string (ex. "5"). + var stringNumber string + if err := json.Unmarshal(data, &stringNumber); err == nil { + *n = Number(stringNumber) + return nil + } + + // `data` is a number represented as an integer (ex. 5). Here + // we're using json.Unmarshal to convert bytes -> number which + // we then convert to our own Number type. + var number int + if err := json.Unmarshal(data, &number); err == nil { + *n = Number(strconv.Itoa(number)) + return nil + } + return errors.New("cannot convert data to number") +} diff --git a/vendor/github.com/google/go-github/github/event_types.go b/vendor/github.com/google/go-github/github/event_types.go index 17c2b102..1b0055c7 100644 --- a/vendor/github.com/google/go-github/github/event_types.go +++ b/vendor/github.com/google/go-github/github/event_types.go @@ -194,6 +194,7 @@ type TeamChange struct { type InstallationEvent struct { // The action that was performed. Can be either "created" or "deleted". Action *string `json:"action,omitempty"` + Repositories []*Repository `json:"repositories,omitempty"` Sender *User `json:"sender,omitempty"` Installation *Installation `json:"installation,omitempty"` } diff --git a/vendor/github.com/olebedev/config/doc.go b/vendor/github.com/olebedev/config/doc.go index a71e76c8..d0ab6062 100644 --- a/vendor/github.com/olebedev/config/doc.go +++ b/vendor/github.com/olebedev/config/doc.go @@ -6,7 +6,7 @@ Package config provides convenient access methods to configuration stored as JSON or YAML. -Let's start with a simple YAML example: +Let's start with a simple YAML file config.yml: development: database: @@ -23,6 +23,12 @@ Let's start with a simple YAML example: We can parse it using ParseYaml(), which will return a *Config instance on success: + file, err := ioutil.ReadFile("config.yml") + if err != nil { + panic(err) + } + yamlString := string(file) + cfg, err := config.ParseYaml(yamlString) An equivalent JSON configuration could be built using ParseJson(): diff --git a/vendor/github.com/rivo/tview/README.md b/vendor/github.com/rivo/tview/README.md index 1ebfc01b..89b866c4 100644 --- a/vendor/github.com/rivo/tview/README.md +++ b/vendor/github.com/rivo/tview/README.md @@ -12,6 +12,7 @@ Among these components are: - __Input forms__ (include __input/password fields__, __drop-down selections__, __checkboxes__, and __buttons__) - Navigable multi-color __text views__ - Sophisticated navigable __table views__ +- Flexible __tree views__ - Selectable __lists__ - __Grid__, __Flexbox__ and __page layouts__ - Modal __message windows__ @@ -64,6 +65,8 @@ Add your issue here on GitHub. Feel free to get in touch if you have any questio (There are no corresponding tags in the project. I only keep such a history in this README.) +- v0.17 (2018-06-20) + - Added `TreeView`. - v0.15 (2018-05-02) - `Flex` and `Grid` don't clear their background per default, thus allowing for custom modals. See the [Wiki](https://github.com/rivo/tview/wiki/Modal) for an example. - v0.14 (2018-04-13) diff --git a/vendor/github.com/rivo/tview/doc.go b/vendor/github.com/rivo/tview/doc.go index 211185f3..1b51a273 100644 --- a/vendor/github.com/rivo/tview/doc.go +++ b/vendor/github.com/rivo/tview/doc.go @@ -7,10 +7,12 @@ Widgets The package implements the following widgets: - - TextView: Scrollable windows that display multi-colored text. Text may also + - TextView: A scrollable window that display multi-colored text. Text may also be highlighted. - - Table: Scrollable display of tabular data. Table cells, rows, or columns may - also be highlighted. + - Table: A scrollable display of tabular data. Table cells, rows, or columns + may also be highlighted. + - TreeView: A scrollable display for hierarchical data. Tree nodes can be + highlighted, collapsed, expanded, and more. - List: A navigable text list with optional keyboard shortcuts. - InputField: One-line input fields to enter text. - DropDown: Drop-down selection fields. @@ -83,7 +85,7 @@ tag is as follows: [::] -Each of the three fields can be left blank and trailing fields can be ommitted. +Each of the three fields can be left blank and trailing fields can be omitted. (Empty square brackets "[]", however, are not considered color tags.) Colors that are not specified will be left unchanged. A field with just a dash ("-") means "reset to default". diff --git a/vendor/github.com/rivo/tview/table.go b/vendor/github.com/rivo/tview/table.go index 10f83995..38f50069 100644 --- a/vendor/github.com/rivo/tview/table.go +++ b/vendor/github.com/rivo/tview/table.go @@ -644,7 +644,6 @@ ColumnLoop: } expWidth := toDistribute * expansion / expansionTotal widths[index] += expWidth - tableWidth += expWidth toDistribute -= expWidth expansionTotal -= expansion } diff --git a/vendor/github.com/rivo/tview/textview.go b/vendor/github.com/rivo/tview/textview.go index d49e7bdd..a88791c3 100644 --- a/vendor/github.com/rivo/tview/textview.go +++ b/vendor/github.com/rivo/tview/textview.go @@ -104,6 +104,10 @@ type TextView struct { // during re-indexing. Set to -1 if there is no current highlight. fromHighlight, toHighlight int + // The screen space column of the highlight in its first line. Set to -1 if + // there is no current highlight. + posHighlight int + // A set of region IDs that are currently highlighted. highlights map[string]struct{} @@ -171,6 +175,7 @@ func NewTextView() *TextView { align: AlignLeft, wrap: true, textColor: Styles.PrimaryTextColor, + regions: false, dynamicColors: false, } } @@ -503,7 +508,7 @@ func (t *TextView) reindexBuffer(width int) { return // Nothing has changed. We can still use the current index. } t.index = nil - t.fromHighlight, t.toHighlight = -1, -1 + t.fromHighlight, t.toHighlight, t.posHighlight = -1, -1, -1 // If there's no space, there's no index. if width < 1 { @@ -522,8 +527,9 @@ func (t *TextView) reindexBuffer(width int) { colorTags [][]string escapeIndices [][]int ) + strippedStr := str if t.dynamicColors { - colorTagIndices, colorTags, escapeIndices, str, _ = decomposeString(str) + colorTagIndices, colorTags, escapeIndices, strippedStr, _ = decomposeString(str) } // Find all regions in this line. Then remove them. @@ -534,14 +540,18 @@ func (t *TextView) reindexBuffer(width int) { if t.regions { regionIndices = regionPattern.FindAllStringIndex(str, -1) regions = regionPattern.FindAllStringSubmatch(str, -1) - str = regionPattern.ReplaceAllString(str, "") - if !t.dynamicColors { - // We haven't detected escape tags yet. Do it now. - escapeIndices = escapePattern.FindAllStringIndex(str, -1) - str = escapePattern.ReplaceAllString(str, "[$1$2]") - } + strippedStr = regionPattern.ReplaceAllString(strippedStr, "") } + // Find all escape tags in this line. Escape them. + if t.dynamicColors || t.regions { + escapeIndices = escapePattern.FindAllStringIndex(str, -1) + strippedStr = escapePattern.ReplaceAllString(strippedStr, "[$1$2]") + } + + // We don't need the original string anymore for now. + str = strippedStr + // Split the line if required. var splitLines []string if t.wrap && len(str) > 0 { @@ -585,15 +595,53 @@ func (t *TextView) reindexBuffer(width int) { // Shift original position with tags. lineLength := len(splitLine) + remainingLength := lineLength + tagEnd := originalPos + totalTagLength := 0 for { - if colorPos < len(colorTagIndices) && colorTagIndices[colorPos][0] <= originalPos+lineLength { + // Which tag comes next? + nextTag := make([][3]int, 0, 3) + if colorPos < len(colorTagIndices) { + nextTag = append(nextTag, [3]int{colorTagIndices[colorPos][0], colorTagIndices[colorPos][1], 0}) // 0 = color tag. + } + if regionPos < len(regionIndices) { + nextTag = append(nextTag, [3]int{regionIndices[regionPos][0], regionIndices[regionPos][1], 1}) // 1 = region tag. + } + if escapePos < len(escapeIndices) { + nextTag = append(nextTag, [3]int{escapeIndices[escapePos][0], escapeIndices[escapePos][1], 2}) // 2 = escape tag. + } + minPos := -1 + tagIndex := -1 + for index, pair := range nextTag { + if minPos < 0 || pair[0] < minPos { + minPos = pair[0] + tagIndex = index + } + } + + // Is the next tag in range? + if tagIndex < 0 || minPos >= tagEnd+remainingLength { + break // No. We're done with this line. + } + + // Advance. + strippedTagStart := nextTag[tagIndex][0] - originalPos - totalTagLength + tagEnd = nextTag[tagIndex][1] + tagLength := tagEnd - nextTag[tagIndex][0] + if nextTag[tagIndex][2] == 2 { + tagLength = 1 + } + totalTagLength += tagLength + remainingLength = lineLength - (tagEnd - originalPos - totalTagLength) + + // Process the tag. + switch nextTag[tagIndex][2] { + case 0: // Process color tags. - originalPos += colorTagIndices[colorPos][1] - colorTagIndices[colorPos][0] foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colorTags[colorPos]) colorPos++ - } else if regionPos < len(regionIndices) && regionIndices[regionPos][0] <= originalPos+lineLength { + case 1: // Process region tags. - originalPos += regionIndices[regionPos][1] - regionIndices[regionPos][0] regionID = regions[regionPos][1] _, highlighted = t.highlights[regionID] @@ -602,23 +650,21 @@ func (t *TextView) reindexBuffer(width int) { line := len(t.index) if t.fromHighlight < 0 { t.fromHighlight, t.toHighlight = line, line + t.posHighlight = runewidth.StringWidth(splitLine[:strippedTagStart]) } else if line > t.toHighlight { t.toHighlight = line } } regionPos++ - } else if escapePos < len(escapeIndices) && escapeIndices[escapePos][0] <= originalPos+lineLength { + case 2: // Process escape tags. - originalPos++ escapePos++ - } else { - break } } // Advance to next line. - originalPos += lineLength + originalPos += lineLength + totalTagLength // Append this line. line.NextPos = originalPos @@ -683,6 +729,16 @@ func (t *TextView) Draw(screen tcell.Screen) { // No, let's move to the start of the highlights. t.lineOffset = t.fromHighlight } + + // If the highlight is too far to the right, move it to the middle. + if t.posHighlight-t.columnOffset > 3*width/4 { + t.columnOffset = t.posHighlight - width/2 + } + + // If the highlight is off-screen on the left, move it on-screen. + if t.posHighlight-t.columnOffset < 0 { + t.columnOffset = t.posHighlight - width/4 + } } t.scrollToHighlights = false diff --git a/vendor/github.com/rivo/tview/treeview.go b/vendor/github.com/rivo/tview/treeview.go new file mode 100644 index 00000000..f419fe5a --- /dev/null +++ b/vendor/github.com/rivo/tview/treeview.go @@ -0,0 +1,665 @@ +package tview + +import ( + "github.com/gdamore/tcell" +) + +// Tree navigation events. +const ( + treeNone int = iota + treeHome + treeEnd + treeUp + treeDown + treePageUp + treePageDown +) + +// TreeNode represents one node in a tree view. +type TreeNode struct { + // The reference object. + reference interface{} + + // This node's child nodes. + children []*TreeNode + + // The item's text. + text string + + // The text color. + color tcell.Color + + // Whether or not this node can be selected. + selectable bool + + // Whether or not this node's children should be displayed. + expanded bool + + // The additional horizontal indent of this node's text. + indent int + + // An optional function which is called when the user selects this node. + selected func() + + // Temporary member variables. + parent *TreeNode // The parent node (nil for the root). + level int // The hierarchy level (0 for the root, 1 for its children, and so on). + graphicsX int // The x-coordinate of the left-most graphics rune. + textX int // The x-coordinate of the first rune of the text. +} + +// NewTreeNode returns a new tree node. +func NewTreeNode(text string) *TreeNode { + return &TreeNode{ + text: text, + color: Styles.PrimaryTextColor, + indent: 2, + expanded: true, + selectable: true, + } +} + +// Walk traverses this node's subtree in depth-first, pre-order (NLR) order and +// calls the provided callback function on each traversed node (which includes +// this node) with the traversed node and its parent node (nil for this node). +// The callback returns whether traversal should continue with the traversed +// node's child nodes (true) or not recurse any deeper (false). +func (n *TreeNode) Walk(callback func(node, parent *TreeNode) bool) *TreeNode { + n.parent = nil + nodes := []*TreeNode{n} + for len(nodes) > 0 { + // Pop the top node and process it. + node := nodes[len(nodes)-1] + nodes = nodes[:len(nodes)-1] + if !callback(node, node.parent) { + // Don't add any children. + continue + } + + // Add children in reverse order. + for index := len(node.children) - 1; index >= 0; index-- { + node.children[index].parent = node + nodes = append(nodes, node.children[index]) + } + } + + return n +} + +// SetReference allows you to store a reference of any type in this node. This +// will allow you to establish a mapping between the TreeView hierarchy and your +// internal tree structure. +func (n *TreeNode) SetReference(reference interface{}) *TreeNode { + n.reference = reference + return n +} + +// GetReference returns this node's reference object. +func (n *TreeNode) GetReference() interface{} { + return n.reference +} + +// SetChildren sets this node's child nodes. +func (n *TreeNode) SetChildren(childNodes []*TreeNode) *TreeNode { + n.children = childNodes + return n +} + +// GetChildren returns this node's children. +func (n *TreeNode) GetChildren() []*TreeNode { + return n.children +} + +// ClearChildren removes all child nodes from this node. +func (n *TreeNode) ClearChildren() *TreeNode { + n.children = nil + return n +} + +// AddChild adds a new child node to this node. +func (n *TreeNode) AddChild(node *TreeNode) *TreeNode { + n.children = append(n.children, node) + return n +} + +// SetSelectable sets a flag indicating whether this node can be selected by +// the user. +func (n *TreeNode) SetSelectable(selectable bool) *TreeNode { + n.selectable = selectable + return n +} + +// SetSelectedFunc sets a function which is called when the user selects this +// node by hitting Enter when it is selected. +func (n *TreeNode) SetSelectedFunc(handler func()) *TreeNode { + n.selected = handler + return n +} + +// SetExpanded sets whether or not this node's child nodes should be displayed. +func (n *TreeNode) SetExpanded(expanded bool) *TreeNode { + n.expanded = expanded + return n +} + +// Expand makes the child nodes of this node appear. +func (n *TreeNode) Expand() *TreeNode { + n.expanded = true + return n +} + +// Collapse makes the child nodes of this node disappear. +func (n *TreeNode) Collapse() *TreeNode { + n.expanded = false + return n +} + +// ExpandAll expands this node and all descendent nodes. +func (n *TreeNode) ExpandAll() *TreeNode { + n.Walk(func(node, parent *TreeNode) bool { + node.expanded = true + return true + }) + return n +} + +// CollapseAll collapses this node and all descendent nodes. +func (n *TreeNode) CollapseAll() *TreeNode { + n.Walk(func(node, parent *TreeNode) bool { + n.expanded = false + return true + }) + return n +} + +// IsExpanded returns whether the child nodes of this node are visible. +func (n *TreeNode) IsExpanded() bool { + return n.expanded +} + +// SetText sets the node's text which is displayed. +func (n *TreeNode) SetText(text string) *TreeNode { + n.text = text + return n +} + +// SetColor sets the node's text color. +func (n *TreeNode) SetColor(color tcell.Color) *TreeNode { + n.color = color + return n +} + +// SetIndent sets an additional indentation for this node's text. A value of 0 +// keeps the text as far left as possible with a minimum of line graphics. Any +// value greater than that moves the text to the right. +func (n *TreeNode) SetIndent(indent int) *TreeNode { + n.indent = indent + return n +} + +// TreeView displays tree structures. A tree consists of nodes (TreeNode +// objects) where each node has zero or more child nodes and exactly one parent +// node (except for the root node which has no parent node). +// +// The SetRoot() function is used to specify the root of the tree. Other nodes +// are added locally to the root node or any of its descendents. See the +// TreeNode documentation for details on node attributes. (You can use +// SetReference() to store a reference to nodes of your own tree structure.) +// +// Nodes can be selected by calling SetCurrentNode(). The user can navigate the +// selection or the tree by using the following keys: +// +// - j, down arrow, right arrow: Move (the selection) down by one node. +// - k, up arrow, left arrow: Move (the selection) up by one node. +// - g, home: Move (the selection) to the top. +// - G, end: Move (the selection) to the bottom. +// - Ctrl-F, page down: Move (the selection) down by one page. +// - Ctrl-B, page up: Move (the selection) up by one page. +// +// Selected nodes can trigger the "selected" callback when the user hits Enter. +// +// The root node corresponds to level 0, its children correspond to level 1, +// their children to level 2, and so on. Per default, the first level that is +// displayed is 0, i.e. the root node. You can call SetTopLevel() to hide +// levels. +// +// If graphics are turned on (see SetGraphics()), lines indicate the tree's +// hierarchy. Alternative (or additionally), you can set different prefixes +// using SetPrefixes() for different levels, for example to display hierarchical +// bullet point lists. +// +// See https://github.com/rivo/tview/wiki/TreeView for an example. +type TreeView struct { + *Box + + // The root node. + root *TreeNode + + // The currently selected node or nil if no node is selected. + currentNode *TreeNode + + // The movement to be performed during the call to Draw(), one of the + // constants defined above. + movement int + + // The top hierarchical level shown. (0 corresponds to the root level.) + topLevel int + + // Strings drawn before the nodes, based on their level. + prefixes []string + + // Vertical scroll offset. + offsetY int + + // If set to true, all node texts will be aligned horizontally. + align bool + + // If set to true, the tree structure is drawn using lines. + graphics bool + + // The color of the lines. + graphicsColor tcell.Color + + // An optional function which is called when the user has navigated to a new + // tree node. + changed func(node *TreeNode) + + // An optional function which is called when a tree item was selected. + selected func(node *TreeNode) +} + +// NewTreeView returns a new tree view. +func NewTreeView() *TreeView { + return &TreeView{ + Box: NewBox(), + graphics: true, + graphicsColor: Styles.GraphicsColor, + } +} + +// SetRoot sets the root node of the tree. +func (t *TreeView) SetRoot(root *TreeNode) *TreeView { + t.root = root + return t +} + +// GetRoot returns the root node of the tree. If no such node was previously +// set, nil is returned. +func (t *TreeView) GetRoot() *TreeNode { + return t.root +} + +// SetCurrentNode sets the currently selected node. Provide nil to clear all +// selections. Selected nodes must be visible and selectable, or else the +// selection will be changed to the top-most selectable and visible node. +// +// This function does NOT trigger the "changed" callback. +func (t *TreeView) SetCurrentNode(node *TreeNode) *TreeView { + t.currentNode = node + return t +} + +// GetCurrentNode returns the currently selected node or nil of no node is +// currently selected. +func (t *TreeView) GetCurrentNode() *TreeNode { + return t.currentNode +} + +// SetTopLevel sets the first tree level that is visible with 0 referring to the +// root, 1 to the root's child nodes, and so on. Nodes above the top level are +// not displayed. +func (t *TreeView) SetTopLevel(topLevel int) *TreeView { + t.topLevel = topLevel + return t +} + +// SetPrefixes defines the strings drawn before the nodes' texts. This is a +// slice of strings where each element corresponds to a node's hierarchy level, +// i.e. 0 for the root, 1 for the root's children, and so on (levels will +// cycle). +// +// For example, to display a hierarchical list with bullet points: +// +// treeView.SetGraphics(false). +// SetPrefixes([]string{"* ", "- ", "x "}) +func (t *TreeView) SetPrefixes(prefixes []string) *TreeView { + t.prefixes = prefixes + return t +} + +// SetAlign controls the horizontal alignment of the node texts. If set to true, +// all texts except that of top-level nodes will be placed in the same column. +// If set to false, they will indent with the hierarchy. +func (t *TreeView) SetAlign(align bool) *TreeView { + t.align = align + return t +} + +// SetGraphics sets a flag which determines whether or not line graphics are +// drawn to illustrate the tree's hierarchy. +func (t *TreeView) SetGraphics(showGraphics bool) *TreeView { + t.graphics = showGraphics + return t +} + +// SetGraphicsColor sets the colors of the lines used to draw the tree structure. +func (t *TreeView) SetGraphicsColor(color tcell.Color) *TreeView { + t.graphicsColor = color + return t +} + +// SetChangedFunc sets the function which is called when the user navigates to +// a new tree node. +func (t *TreeView) SetChangedFunc(handler func(node *TreeNode)) *TreeView { + t.changed = handler + return t +} + +// SetSelectedFunc sets the function which is called when the user selects a +// node by pressing Enter on the current selection. +func (t *TreeView) SetSelectedFunc(handler func(node *TreeNode)) *TreeView { + t.selected = handler + return t +} + +// Draw draws this primitive onto the screen. +func (t *TreeView) Draw(screen tcell.Screen) { + t.Box.Draw(screen) + if t.root == nil { + return + } + x, y, width, height := t.GetInnerRect() + + // Determine visible nodes and their placement. + var graphicsOffset, maxTextX int + var nodes []*TreeNode + selectedIndex := -1 + topLevelGraphicsX := -1 + if t.graphics { + graphicsOffset = 1 + } + t.root.Walk(func(node, parent *TreeNode) bool { + // Set node attributes. + node.parent = parent + if parent == nil { + node.level = 0 + node.graphicsX = 0 + node.textX = 0 + } else { + node.level = parent.level + 1 + node.graphicsX = parent.textX + node.textX = node.graphicsX + graphicsOffset + node.indent + } + if !t.graphics && t.align { + // Without graphics, we align nodes on the first column. + node.textX = 0 + } + if node.level == t.topLevel { + // No graphics for top level nodes. + node.graphicsX = 0 + node.textX = 0 + } + if node.textX > maxTextX { + maxTextX = node.textX + } + if node == t.currentNode && node.selectable { + selectedIndex = len(nodes) + } + + // Maybe we want to skip this level. + if t.topLevel == node.level && (topLevelGraphicsX < 0 || node.graphicsX < topLevelGraphicsX) { + topLevelGraphicsX = node.graphicsX + } + + // Add and recurse (if desired). + if node.level >= t.topLevel { + nodes = append(nodes, node) + } + return node.expanded + }) + + // Post-process positions. + for _, node := range nodes { + // If text must align, we correct the positions. + if t.align && node.level > t.topLevel { + node.textX = maxTextX + } + + // If we skipped levels, shift to the left. + if topLevelGraphicsX > 0 { + node.graphicsX -= topLevelGraphicsX + node.textX -= topLevelGraphicsX + } + } + + // Process selection. (Also trigger events if necessary.) + if selectedIndex >= 0 { + // Move the selection. + newSelectedIndex := selectedIndex + MovementSwitch: + switch t.movement { + case treeUp: + for newSelectedIndex > 0 { + newSelectedIndex-- + if nodes[newSelectedIndex].selectable { + break MovementSwitch + } + } + newSelectedIndex = selectedIndex + case treeDown: + for newSelectedIndex < len(nodes)-1 { + newSelectedIndex++ + if nodes[newSelectedIndex].selectable { + break MovementSwitch + } + } + newSelectedIndex = selectedIndex + case treeHome: + for newSelectedIndex = 0; newSelectedIndex < len(nodes); newSelectedIndex++ { + if nodes[newSelectedIndex].selectable { + break MovementSwitch + } + } + newSelectedIndex = selectedIndex + case treeEnd: + for newSelectedIndex = len(nodes) - 1; newSelectedIndex >= 0; newSelectedIndex-- { + if nodes[newSelectedIndex].selectable { + break MovementSwitch + } + } + newSelectedIndex = selectedIndex + case treePageUp: + if newSelectedIndex+height < len(nodes) { + newSelectedIndex += height + } else { + newSelectedIndex = len(nodes) - 1 + } + for ; newSelectedIndex < len(nodes); newSelectedIndex++ { + if nodes[newSelectedIndex].selectable { + break MovementSwitch + } + } + newSelectedIndex = selectedIndex + case treePageDown: + if newSelectedIndex >= height { + newSelectedIndex -= height + } else { + newSelectedIndex = 0 + } + for ; newSelectedIndex >= 0; newSelectedIndex-- { + if nodes[newSelectedIndex].selectable { + break MovementSwitch + } + } + newSelectedIndex = selectedIndex + } + t.currentNode = nodes[newSelectedIndex] + if newSelectedIndex != selectedIndex { + t.movement = treeNone + if t.changed != nil { + t.changed(t.currentNode) + } + } + selectedIndex = newSelectedIndex + + // Move selection into viewport. + if selectedIndex-t.offsetY >= height { + t.offsetY = selectedIndex - height + 1 + } + if selectedIndex < t.offsetY { + t.offsetY = selectedIndex + } + } else { + // If selection is not visible or selectable, select the first candidate. + if t.currentNode != nil { + for index, node := range nodes { + if node.selectable { + selectedIndex = index + t.currentNode = node + break + } + } + } + if selectedIndex < 0 { + t.currentNode = nil + } + } + + // Scroll the tree. + switch t.movement { + case treeUp: + t.offsetY-- + case treeDown: + t.offsetY++ + case treeHome: + t.offsetY = 0 + case treeEnd: + t.offsetY = len(nodes) + case treePageUp: + t.offsetY -= height + case treePageDown: + t.offsetY += height + } + t.movement = treeNone + + // Fix invalid offsets. + if t.offsetY >= len(nodes)-height { + t.offsetY = len(nodes) - height + } + if t.offsetY < 0 { + t.offsetY = 0 + } + + // Draw the tree. + posY := y + lineStyle := tcell.StyleDefault.Foreground(t.graphicsColor) + for index, node := range nodes { + // Skip invisible parts. + if posY >= y+height+1 { + break + } + if index < t.offsetY { + continue + } + + // Draw the graphics. + if t.graphics { + // Draw ancestor branches. + ancestor := node.parent + for ancestor != nil && ancestor.parent != nil && ancestor.parent.level >= t.topLevel { + if ancestor.graphicsX >= width { + continue + } + + // Draw a branch if this ancestor is not a last child. + if ancestor.parent.children[len(ancestor.parent.children)-1] != ancestor { + if posY-1 >= y && ancestor.textX > ancestor.graphicsX { + PrintJoinedSemigraphics(screen, x+ancestor.graphicsX, posY-1, Borders.Vertical, t.graphicsColor) + } + if posY < y+height { + screen.SetContent(x+ancestor.graphicsX, posY, Borders.Vertical, nil, lineStyle) + } + } + ancestor = ancestor.parent + } + + if node.textX > node.graphicsX && node.graphicsX < width { + // Connect to the node above. + if posY-1 >= y && nodes[index-1].graphicsX <= node.graphicsX && nodes[index-1].textX > node.graphicsX { + PrintJoinedSemigraphics(screen, x+node.graphicsX, posY-1, Borders.TopLeft, t.graphicsColor) + } + + // Join this node. + if posY < y+height { + screen.SetContent(x+node.graphicsX, posY, Borders.BottomLeft, nil, lineStyle) + for pos := node.graphicsX + 1; pos < node.textX && pos < width; pos++ { + screen.SetContent(x+pos, posY, Borders.Horizontal, nil, lineStyle) + } + } + } + } + + // Draw the prefix and the text. + if node.textX < width && posY < y+height { + // Prefix. + var prefixWidth int + if len(t.prefixes) > 0 { + _, prefixWidth = Print(screen, t.prefixes[(node.level-t.topLevel)%len(t.prefixes)], x+node.textX, posY, width-node.textX, AlignLeft, node.color) + } + + // Text. + if node.textX+prefixWidth < width { + style := tcell.StyleDefault.Foreground(node.color) + if index == selectedIndex { + style = tcell.StyleDefault.Background(node.color).Foreground(t.backgroundColor) + } + printWithStyle(screen, node.text, x+node.textX+prefixWidth, posY, width-node.textX-prefixWidth, AlignLeft, style) + } + } + + // Advance. + posY++ + } +} + +// InputHandler returns the handler for this primitive. +func (t *TreeView) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) { + return t.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) { + // Because the tree is flattened into a list only at drawing time, we also + // postpone the (selection) movement to drawing time. + switch key := event.Key(); key { + case tcell.KeyTab, tcell.KeyDown, tcell.KeyRight: + t.movement = treeDown + case tcell.KeyBacktab, tcell.KeyUp, tcell.KeyLeft: + t.movement = treeUp + case tcell.KeyHome: + t.movement = treeHome + case tcell.KeyEnd: + t.movement = treeEnd + case tcell.KeyPgDn, tcell.KeyCtrlF: + t.movement = treePageDown + case tcell.KeyPgUp, tcell.KeyCtrlB: + t.movement = treePageUp + case tcell.KeyRune: + switch event.Rune() { + case 'g': + t.movement = treeHome + case 'G': + t.movement = treeEnd + case 'j': + t.movement = treeDown + case 'k': + t.movement = treeUp + } + case tcell.KeyEnter: + if t.currentNode != nil { + if t.selected != nil { + t.selected(t.currentNode) + } + if t.currentNode.selected != nil { + t.currentNode.selected() + } + } + } + }) +} diff --git a/vendor/github.com/rivo/tview/util.go b/vendor/github.com/rivo/tview/util.go index 7ac8f74f..44c80579 100644 --- a/vendor/github.com/rivo/tview/util.go +++ b/vendor/github.com/rivo/tview/util.go @@ -120,13 +120,12 @@ func overlayStyle(background tcell.Color, defaultStyle tcell.Style, fgColor, bgC defFg, defBg, defAttr := defaultStyle.Decompose() style := defaultStyle.Background(background) - if fgColor == "-" { - style = style.Foreground(defFg) - } else if fgColor != "" { + style = style.Foreground(defFg) + if fgColor != "" { style = style.Foreground(tcell.GetColor(fgColor)) } - if bgColor == "-" { + if bgColor == "-" || bgColor == "" && defBg != tcell.ColorDefault { style = style.Background(defBg) } else if bgColor != "" { style = style.Background(tcell.GetColor(bgColor)) diff --git a/vendor/github.com/xanzy/go-gitlab/award_emojis.go b/vendor/github.com/xanzy/go-gitlab/award_emojis.go index b12a662f..d3356092 100644 --- a/vendor/github.com/xanzy/go-gitlab/award_emojis.go +++ b/vendor/github.com/xanzy/go-gitlab/award_emojis.go @@ -56,18 +56,18 @@ const ( awardSnippets = "snippets" ) -// ListEmojiAwardsOptions represents the available options for listing emoji +// ListAwardEmojiOptions represents the available options for listing emoji // for each resources // // GitLab API docs: // https://docs.gitlab.com/ce/api/award_emoji.html -type ListEmojiAwardsOptions ListOptions +type ListAwardEmojiOptions ListOptions // ListMergeRequestAwardEmoji gets a list of all award emoji on the merge request. // // GitLab API docs: // https://docs.gitlab.com/ce/api/award_emoji.html#list-an-awardable-39-s-award-emoji -func (s *AwardEmojiService) ListMergeRequestAwardEmoji(pid interface{}, mergeRequestIID int, opt *ListEmojiAwardsOptions, options ...OptionFunc) ([]*AwardEmoji, *Response, error) { +func (s *AwardEmojiService) ListMergeRequestAwardEmoji(pid interface{}, mergeRequestIID int, opt *ListAwardEmojiOptions, options ...OptionFunc) ([]*AwardEmoji, *Response, error) { return s.listAwardEmoji(pid, awardMergeRequest, mergeRequestIID, opt, options...) } @@ -75,7 +75,7 @@ func (s *AwardEmojiService) ListMergeRequestAwardEmoji(pid interface{}, mergeReq // // GitLab API docs: // https://docs.gitlab.com/ce/api/award_emoji.html#list-an-awardable-39-s-award-emoji -func (s *AwardEmojiService) ListIssueAwardEmoji(pid interface{}, issueIID int, opt *ListEmojiAwardsOptions, options ...OptionFunc) ([]*AwardEmoji, *Response, error) { +func (s *AwardEmojiService) ListIssueAwardEmoji(pid interface{}, issueIID int, opt *ListAwardEmojiOptions, options ...OptionFunc) ([]*AwardEmoji, *Response, error) { return s.listAwardEmoji(pid, awardIssue, issueIID, opt, options...) } @@ -83,11 +83,11 @@ func (s *AwardEmojiService) ListIssueAwardEmoji(pid interface{}, issueIID int, o // // GitLab API docs: // https://docs.gitlab.com/ce/api/award_emoji.html#list-an-awardable-39-s-award-emoji -func (s *AwardEmojiService) ListSnippetAwardEmoji(pid interface{}, snippetID int, opt *ListEmojiAwardsOptions, options ...OptionFunc) ([]*AwardEmoji, *Response, error) { +func (s *AwardEmojiService) ListSnippetAwardEmoji(pid interface{}, snippetID int, opt *ListAwardEmojiOptions, options ...OptionFunc) ([]*AwardEmoji, *Response, error) { return s.listAwardEmoji(pid, awardSnippets, snippetID, opt, options...) } -func (s *AwardEmojiService) listAwardEmoji(pid interface{}, resource string, resourceID int, opt *ListEmojiAwardsOptions, options ...OptionFunc) ([]*AwardEmoji, *Response, error) { +func (s *AwardEmojiService) listAwardEmoji(pid interface{}, resource string, resourceID int, opt *ListAwardEmojiOptions, options ...OptionFunc) ([]*AwardEmoji, *Response, error) { project, err := parseID(pid) if err != nil { return nil, nil, err @@ -162,43 +162,51 @@ func (s *AwardEmojiService) getAwardEmoji(pid interface{}, resource string, reso return a, resp, err } +// CreateAwardEmojiOptions represents the available options for awarding emoji +// for a resource +// +// GitLab API docs: +// https://docs.gitlab.com/ce/api/award_emoji.html#award-a-new-emoji +type CreateAwardEmojiOptions struct { + Name string `json:"name"` +} + // CreateMergeRequestAwardEmoji get an award emoji from merge request. // // GitLab API docs: // https://docs.gitlab.com/ce/api/award_emoji.html#award-a-new-emoji -func (s *AwardEmojiService) CreateMergeRequestAwardEmoji(pid interface{}, mergeRequestIID, awardID int, options ...OptionFunc) (*AwardEmoji, *Response, error) { - return s.createAwardEmoji(pid, awardMergeRequest, mergeRequestIID, awardID, options...) +func (s *AwardEmojiService) CreateMergeRequestAwardEmoji(pid interface{}, mergeRequestIID int, opt *CreateAwardEmojiOptions, options ...OptionFunc) (*AwardEmoji, *Response, error) { + return s.createAwardEmoji(pid, awardMergeRequest, mergeRequestIID, opt, options...) } // CreateIssueAwardEmoji get an award emoji from issue. // // GitLab API docs: // https://docs.gitlab.com/ce/api/award_emoji.html#award-a-new-emoji -func (s *AwardEmojiService) CreateIssueAwardEmoji(pid interface{}, issueIID, awardID int, options ...OptionFunc) (*AwardEmoji, *Response, error) { - return s.createAwardEmoji(pid, awardIssue, issueIID, awardID, options...) +func (s *AwardEmojiService) CreateIssueAwardEmoji(pid interface{}, issueIID int, opt *CreateAwardEmojiOptions, options ...OptionFunc) (*AwardEmoji, *Response, error) { + return s.createAwardEmoji(pid, awardIssue, issueIID, opt, options...) } // CreateSnippetAwardEmoji get an award emoji from snippet. // // GitLab API docs: // https://docs.gitlab.com/ce/api/award_emoji.html#award-a-new-emoji -func (s *AwardEmojiService) CreateSnippetAwardEmoji(pid interface{}, snippetID, awardID int, options ...OptionFunc) (*AwardEmoji, *Response, error) { - return s.createAwardEmoji(pid, awardSnippets, snippetID, awardID, options...) +func (s *AwardEmojiService) CreateSnippetAwardEmoji(pid interface{}, snippetID int, opt *CreateAwardEmojiOptions, options ...OptionFunc) (*AwardEmoji, *Response, error) { + return s.createAwardEmoji(pid, awardSnippets, snippetID, opt, options...) } -func (s *AwardEmojiService) createAwardEmoji(pid interface{}, resource string, resourceID, awardID int, options ...OptionFunc) (*AwardEmoji, *Response, error) { +func (s *AwardEmojiService) createAwardEmoji(pid interface{}, resource string, resourceID int, opt *CreateAwardEmojiOptions, options ...OptionFunc) (*AwardEmoji, *Response, error) { project, err := parseID(pid) if err != nil { return nil, nil, err } - u := fmt.Sprintf("projects/%s/%s/%d/award_emoji/%d", + u := fmt.Sprintf("projects/%s/%s/%d/award_emoji", url.QueryEscape(project), resource, resourceID, - awardID, ) - req, err := s.client.NewRequest("POST", u, nil, options) + req, err := s.client.NewRequest("POST", u, opt, options) if err != nil { return nil, nil, err } @@ -260,7 +268,7 @@ func (s *AwardEmojiService) deleteAwardEmoji(pid interface{}, resource string, r // // GitLab API docs: // https://docs.gitlab.com/ce/api/award_emoji.html#award-emoji-on-notes -func (s *AwardEmojiService) ListIssuesAwardEmojiOnNote(pid interface{}, issueID, noteID int, opt *ListEmojiAwardsOptions, options ...OptionFunc) ([]*AwardEmoji, *Response, error) { +func (s *AwardEmojiService) ListIssuesAwardEmojiOnNote(pid interface{}, issueID, noteID int, opt *ListAwardEmojiOptions, options ...OptionFunc) ([]*AwardEmoji, *Response, error) { return s.listAwardEmojiOnNote(pid, awardIssue, issueID, noteID, opt, options...) } @@ -269,7 +277,7 @@ func (s *AwardEmojiService) ListIssuesAwardEmojiOnNote(pid interface{}, issueID, // // GitLab API docs: // https://docs.gitlab.com/ce/api/award_emoji.html#award-emoji-on-notes -func (s *AwardEmojiService) ListMergeRequestAwardEmojiOnNote(pid interface{}, mergeRequestIID, noteID int, opt *ListEmojiAwardsOptions, options ...OptionFunc) ([]*AwardEmoji, *Response, error) { +func (s *AwardEmojiService) ListMergeRequestAwardEmojiOnNote(pid interface{}, mergeRequestIID, noteID int, opt *ListAwardEmojiOptions, options ...OptionFunc) ([]*AwardEmoji, *Response, error) { return s.listAwardEmojiOnNote(pid, awardMergeRequest, mergeRequestIID, noteID, opt, options...) } @@ -278,11 +286,11 @@ func (s *AwardEmojiService) ListMergeRequestAwardEmojiOnNote(pid interface{}, me // // GitLab API docs: // https://docs.gitlab.com/ce/api/award_emoji.html#award-emoji-on-notes -func (s *AwardEmojiService) ListSnippetAwardEmojiOnNote(pid interface{}, snippetIID, noteID int, opt *ListEmojiAwardsOptions, options ...OptionFunc) ([]*AwardEmoji, *Response, error) { +func (s *AwardEmojiService) ListSnippetAwardEmojiOnNote(pid interface{}, snippetIID, noteID int, opt *ListAwardEmojiOptions, options ...OptionFunc) ([]*AwardEmoji, *Response, error) { return s.listAwardEmojiOnNote(pid, awardSnippets, snippetIID, noteID, opt, options...) } -func (s *AwardEmojiService) listAwardEmojiOnNote(pid interface{}, resources string, ressourceID, noteID int, opt *ListEmojiAwardsOptions, options ...OptionFunc) ([]*AwardEmoji, *Response, error) { +func (s *AwardEmojiService) listAwardEmojiOnNote(pid interface{}, resources string, ressourceID, noteID int, opt *ListAwardEmojiOptions, options ...OptionFunc) ([]*AwardEmoji, *Response, error) { project, err := parseID(pid) if err != nil { return nil, nil, err @@ -361,8 +369,8 @@ func (s *AwardEmojiService) getSingleNoteAwardEmoji(pid interface{}, ressource s // // GitLab API docs: // https://docs.gitlab.com/ce/api/award_emoji.html#award-emoji-on-notes -func (s *AwardEmojiService) CreateIssuesAwardEmojiOnNote(pid interface{}, issueID, noteID int, options ...OptionFunc) (*AwardEmoji, *Response, error) { - return s.createAwardEmojiOnNote(pid, awardIssue, issueID, noteID, options...) +func (s *AwardEmojiService) CreateIssuesAwardEmojiOnNote(pid interface{}, issueID, noteID int, opt *CreateAwardEmojiOptions, options ...OptionFunc) (*AwardEmoji, *Response, error) { + return s.createAwardEmojiOnNote(pid, awardIssue, issueID, noteID, opt, options...) } // CreateMergeRequestAwardEmojiOnNote gets an award emoji on a note from a @@ -370,23 +378,23 @@ func (s *AwardEmojiService) CreateIssuesAwardEmojiOnNote(pid interface{}, issueI // // GitLab API docs: // https://docs.gitlab.com/ce/api/award_emoji.html#award-emoji-on-notes -func (s *AwardEmojiService) CreateMergeRequestAwardEmojiOnNote(pid interface{}, mergeRequestIID, noteID int, options ...OptionFunc) (*AwardEmoji, *Response, error) { - return s.createAwardEmojiOnNote(pid, awardMergeRequest, mergeRequestIID, noteID, options...) +func (s *AwardEmojiService) CreateMergeRequestAwardEmojiOnNote(pid interface{}, mergeRequestIID, noteID int, opt *CreateAwardEmojiOptions, options ...OptionFunc) (*AwardEmoji, *Response, error) { + return s.createAwardEmojiOnNote(pid, awardMergeRequest, mergeRequestIID, noteID, opt, options...) } // CreateSnippetAwardEmojiOnNote gets an award emoji on a note from a snippet. // // GitLab API docs: // https://docs.gitlab.com/ce/api/award_emoji.html#award-emoji-on-notes -func (s *AwardEmojiService) CreateSnippetAwardEmojiOnNote(pid interface{}, snippetIID, noteID int, options ...OptionFunc) (*AwardEmoji, *Response, error) { - return s.createAwardEmojiOnNote(pid, awardSnippets, snippetIID, noteID, options...) +func (s *AwardEmojiService) CreateSnippetAwardEmojiOnNote(pid interface{}, snippetIID, noteID int, opt *CreateAwardEmojiOptions, options ...OptionFunc) (*AwardEmoji, *Response, error) { + return s.createAwardEmojiOnNote(pid, awardSnippets, snippetIID, noteID, opt, options...) } // CreateAwardEmojiOnNote award emoji on a note. // // GitLab API docs: // https://docs.gitlab.com/ce/api/award_emoji.html#award-a-new-emoji-on-a-note -func (s *AwardEmojiService) createAwardEmojiOnNote(pid interface{}, resource string, resourceID, noteID int, options ...OptionFunc) (*AwardEmoji, *Response, error) { +func (s *AwardEmojiService) createAwardEmojiOnNote(pid interface{}, resource string, resourceID, noteID int, opt *CreateAwardEmojiOptions, options ...OptionFunc) (*AwardEmoji, *Response, error) { project, err := parseID(pid) if err != nil { return nil, nil, err diff --git a/vendor/github.com/yfronto/newrelic/.travis.yml b/vendor/github.com/yfronto/newrelic/.travis.yml index 3c2f32b4..faa70cc6 100644 --- a/vendor/github.com/yfronto/newrelic/.travis.yml +++ b/vendor/github.com/yfronto/newrelic/.travis.yml @@ -1,12 +1,18 @@ language: go -go: - - 1.6 - - 1.7 - - tip +ggo: + - "1.7" + - "1.8" + - "1.9" + - "1.10.x" + - "tip" env: - "PATH=/home/travis/gopath/bin:$PATH" + +install: + - go get -v -t . + script: - go get -u github.com/golang/lint/golint - golint ./... diff --git a/vendor/github.com/yfronto/newrelic/alert_events.go b/vendor/github.com/yfronto/newrelic/alert_events.go index 6291b499..ce3c298a 100644 --- a/vendor/github.com/yfronto/newrelic/alert_events.go +++ b/vendor/github.com/yfronto/newrelic/alert_events.go @@ -10,7 +10,7 @@ type AlertEvent struct { EntityID int `json:"entity_id,omitempty"` Priority string `json:"priority,omitempty"` Description string `json:"description,omitempty"` - Timestamp int `json:"timestamp,omitempty"` + Timestamp int64 `json:"timestamp,omitempty"` IncidentID int `json:"incident_id"` } diff --git a/vendor/golang.org/x/oauth2/internal/token.go b/vendor/golang.org/x/oauth2/internal/token.go index 3954c9b5..999e668e 100644 --- a/vendor/golang.org/x/oauth2/internal/token.go +++ b/vendor/golang.org/x/oauth2/internal/token.go @@ -129,6 +129,7 @@ var brokenAuthHeaderProviders = []string{ "https://log.finalsurge.com/oauth/token", "https://multisport.todaysplan.com.au/rest/oauth/access_token", "https://whats.todaysplan.com.au/rest/oauth/access_token", + "https://stackoverflow.com/oauth/access_token", } // brokenAuthHeaderDomains lists broken providers that issue dynamic endpoints. diff --git a/vendor/golang.org/x/oauth2/oauth2.go b/vendor/golang.org/x/oauth2/oauth2.go index 10299d2e..16775d08 100644 --- a/vendor/golang.org/x/oauth2/oauth2.go +++ b/vendor/golang.org/x/oauth2/oauth2.go @@ -124,6 +124,8 @@ func SetAuthURLParam(key, value string) AuthCodeOption { // // Opts may include AccessTypeOnline or AccessTypeOffline, as well // as ApprovalForce. +// It can also be used to pass the PKCE challange. +// See https://www.oauth.com/oauth2-servers/pkce/ for more info. func (c *Config) AuthCodeURL(state string, opts ...AuthCodeOption) string { var buf bytes.Buffer buf.WriteString(c.Endpoint.AuthURL) @@ -186,7 +188,10 @@ func (c *Config) PasswordCredentialsToken(ctx context.Context, username, passwor // // The code will be in the *http.Request.FormValue("code"). Before // calling Exchange, be sure to validate FormValue("state"). -func (c *Config) Exchange(ctx context.Context, code string) (*Token, error) { +// +// Opts may include the PKCE verifier code if previously used in AuthCodeURL. +// See https://www.oauth.com/oauth2-servers/pkce/ for more info. +func (c *Config) Exchange(ctx context.Context, code string, opts ...AuthCodeOption) (*Token, error) { v := url.Values{ "grant_type": {"authorization_code"}, "code": {code}, @@ -194,6 +199,9 @@ func (c *Config) Exchange(ctx context.Context, code string) (*Token, error) { if c.RedirectURL != "" { v.Set("redirect_uri", c.RedirectURL) } + for _, opt := range opts { + opt.setValue(v) + } return retrieveToken(ctx, c, v) } diff --git a/wtf.go b/wtf.go index 4757265f..79b2a6d8 100644 --- a/wtf.go +++ b/wtf.go @@ -21,6 +21,7 @@ import ( "github.com/senorprogrammer/wtf/cryptoexchanges/cryptolive" "github.com/senorprogrammer/wtf/flags" "github.com/senorprogrammer/wtf/gcal" + "github.com/senorprogrammer/wtf/gerrit" "github.com/senorprogrammer/wtf/git" "github.com/senorprogrammer/wtf/github" "github.com/senorprogrammer/wtf/gitlab" @@ -179,6 +180,8 @@ func addWidget(app *tview.Application, pages *tview.Pages, widgetName string) { Widgets = append(Widgets, cryptolive.NewWidget()) case "gcal": Widgets = append(Widgets, gcal.NewWidget()) + case "gerrit": + Widgets = append(Widgets, gerrit.NewWidget(app, pages)) case "git": Widgets = append(Widgets, git.NewWidget(app, pages)) case "github": From a5aac70647ec300ddd542ac203a84f361602f7d2 Mon Sep 17 00:00:00 2001 From: Anand Sudhir Prayaga Date: Thu, 28 Jun 2018 16:40:24 +0200 Subject: [PATCH 03/21] Add generated docuemntation for gerrit module --- docs/404.html | 1 + docs/categories/index.html | 1 + docs/imgs/modules/gerrit.png | Bin 0 -> 32611 bytes docs/index.html | 1 + docs/index.xml | 20 +- .../posts/configuration/attributes/index.html | 1 + docs/posts/configuration/index.html | 1 + docs/posts/configuration/iterm2/index.html | 1 + docs/posts/glossary/index.html | 1 + docs/posts/index.html | 8 + docs/posts/index.xml | 20 +- docs/posts/installation/index.html | 1 + docs/posts/modules/bamboohr/index.html | 1 + docs/posts/modules/circleci/index.html | 1 + docs/posts/modules/clocks/index.html | 1 + docs/posts/modules/cmdrunner/index.html | 1 + .../cryptocurrencies/bittrex/index.html | 1 + .../cryptocurrencies/blockfolio/index.html | 1 + .../cryptocurrencies/cryptolive/index.html | 57 +++-- docs/posts/modules/gcal/index.html | 1 + docs/posts/modules/gerrit/index.html | 225 ++++++++++++++++++ docs/posts/modules/git/index.html | 1 + docs/posts/modules/github/index.html | 1 + docs/posts/modules/gitlab/index.html | 1 + docs/posts/modules/gspreadsheet/index.html | 1 + docs/posts/modules/index.html | 1 + docs/posts/modules/ipapi/index.html | 1 + docs/posts/modules/ipinfo/index.html | 1 + docs/posts/modules/jenkins/index.html | 1 + docs/posts/modules/jira/index.html | 1 + docs/posts/modules/logger/index.html | 1 + docs/posts/modules/newrelic/index.html | 1 + docs/posts/modules/opsgenie/index.html | 1 + docs/posts/modules/power/index.html | 1 + docs/posts/modules/prettyweather/index.html | 1 + docs/posts/modules/security/index.html | 1 + docs/posts/modules/textfile/index.html | 1 + docs/posts/modules/todo/index.html | 1 + docs/posts/modules/trello/index.html | 1 + docs/posts/modules/weather/index.html | 1 + docs/posts/overview/index.html | 1 + docs/sitemap.xml | 9 +- docs/tags/index.html | 1 + 43 files changed, 349 insertions(+), 26 deletions(-) create mode 100644 docs/imgs/modules/gerrit.png create mode 100644 docs/posts/modules/gerrit/index.html diff --git a/docs/404.html b/docs/404.html index c4dc9e52..2e9ef6b7 100644 --- a/docs/404.html +++ b/docs/404.html @@ -82,6 +82,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/categories/index.html b/docs/categories/index.html index f6ce7abd..1893a8de 100644 --- a/docs/categories/index.html +++ b/docs/categories/index.html @@ -84,6 +84,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/imgs/modules/gerrit.png b/docs/imgs/modules/gerrit.png new file mode 100644 index 0000000000000000000000000000000000000000..6084dc1d29c9de0924cefb4d74c60b48011a843c GIT binary patch literal 32611 zcmeFYWl&sA*Djpk!3h?eKmx%-aCdii3C_Uat^tC32p%B#AcGG+xCIR`xFtac7ziHx zBe%WJ{XX^mJHJkys$ILfW_EY4Uh7)dTB~>OXmwRN987Y|M~@!iD9B4|K6-=-EJ8#d!r>65?8Sm>P6hwTrTB3tPx2~_nvWu zGZag9MKOQ?_A0IwC+z)B3vCanxe7V^Yk8-{4U&!w+82VZMJU$N?D8xcZW3svj7a(K zlMd$L`8w7@&E-Av>lP(%cg1G}Y#V0%MDHg2=GrdWyb)TqbE5Z8w*sKofpb^j>g{{s z3B$wL7WCkA+9R4LNss=xx)$;07Z=N|7l!Xy?$b2|U)`r1Y6|K*UYh?#{rfRt zBqJio3I5UH3vs01my0ci6=xfSEmW5}T1)MlZ*vuN%vl&n&u3^5QH;E;&jQz@aQGNI5rK264A4 zAvnKg#_jJ%P}H9Ghj`yI2tI>EY;eYRKm0LeCEjD=25USZN&RMQ%%W~uPp%bP zDmjK_TaSAJ=Nn-n$k>>~<}8L|kYwp2x5Bq5`c-O1@Nec~E3GYg=NIR5rJVXtpVB^l zN*mIa_DBX{vgEOBfz<20$BlMS|M_-6*jNnTCr5ZVL^XtCG zCksI0h9x6~PCbpiBSV6etOFK-Aqf{Rl#S1TjMe2LLA>+9T~mFHMnAic(Tkc93s2py zs6_6?Fki<<(UZRFQs=egZ8e{vOS~dmvFqHAK^OG!$+c$gs}R1<6J)sAjj>{4R18DSB85V-4#%8>k40M#IP z_SN1ze1s>}P#PR#1AIIt&K@a?D80RH?>whPtTtz!XT%8STb}%-a_Yx1hUZv+svq@1 zT$-z8E;tmf1TQNM61kn+{5W-q1(&+-z1F+}Gfr)vOzi!1E&v5yFb0VQ?k}fLb>95U zA>Med$Wp*dLz3DLylYME9KJIY;#X9ATII*p4k-0MKl*7RUThtj6n@+0iC2ZWl@FTH zyRp)U4<*q`-V-r~%{J`LKOa83W(S;fEE+YPPvbc+Z@`E4UVG`I{g$j5>jBoQ;qe1> zDG#9Q(jf3TxfaU{LZo-3@_1xO&F~y zTjT=w+onqIS1jTAPBL-v{Y)yD?VNEkLz+uRvLSBZj ztX%0fz(UEO(ZB1lh6HR8Re-IzLr<5!;hd)wdrv6*=TFP1VT&t|ZS3uj;$OEK)^Cl_ z;ag+S)7}-DBOS35Ig;w%4)^$}-2-0hl>X#1W%}2g#ZAk~)wcuFa4xlgmw`ei@`pk9 zgVTe88mr`N&<~~xW=fqvrm;iC!=Py;c;emsMXxFpH++v`d%sL1MjogjlJ?4xph~7| zOCB7xm_q01j_DTt!%rKcs4o()-&^f2&9;HLbPc4?5Q;mPg;w#Xv-Ic&lV}LdYgG`K ziF`3IQzOy_FW2uEuW(%3T{u~57ujY^F-T9eFUb3#p)y zgT&!|198^oVv~tN=Lri3=L8J!Ru+Q~+u0Bw01d z-lf)wg;@kUuHrd3e@G>aJ1Hi43#5k>q#;f%6@SBzDTkyNt_}(LZ2C-?_r~%pa zqA}TGV=rrBYLG?f3oBhxOvtg*MWG*rfKO2Lf^&=Ob z`d=4P{(!Yt=&I)L%GzB>EYJ3q-{ci@Dh+!mg+?oP zK4+zOrS`WU_5KVAltM@h07eR)L0keBM{}K+$Q?9&rbwIsI zl^`ij-Pu)?22(!Okp?OVt)^68!?zO8hy5M?f9#djH7}tNs3GJmR#EVYS@%H&FABUu zg7UBJ++j1SqT@)qQweOh^SE&zPA&a!Rf94PD#6K1#}j5!dsEsnj;a^L(A_#+(F*$> z-AM9KD$1v+{dSC+4lk3!3ikVKgdQ6>Rl*06H7b61iLDG5c)ieUdt_m%_{Yj!MjyaV zU5{q#`J1Z0d10KI!MTXqyflyAYkeq4aqHG_W18SgH`e&@)H>T8CnxW9uw3&@GcX7*{|um zKvt3W_Wfz)Hy&4`&KuK-C!GOE$JbN0!6(vJH#9;+Wfq>ho_^a*b9%|SThyA6U7HE} zfivy(hcHD&@^W(F$}sP0b@A96KMwrv0n{2Wzdi^fpq?DZe#EPxu829(dwO8*hDasb z^9%$6;>DB`d?QD!aP%~`k2B`IV?8V%(^&lUVKn%!#HI%0am=EO-I3v+fr438?C9vU zqbWzp8K1g3U|k8`6udXLE@ZcK0V`pFr?RXmbgr~lc*Xo0P0aMm;hX&EJ!|?9S{@>y z{~AN2NpWxRgE!H1R5S*ep*s3P)e>MaU)f6O_u2rP?zzs!4ubf_GC)jJ0ZL{pdE$J_ zYk&%?f3BN~#H?0I>@%HYEV+70Xd1=+B-(N`m zVey(*zr~I|^7}*V$4~Viha7}xY(g6@yI!fRja8pdeq3dy(6))5Qj9LIn-zh67lHVN3kwCSPG`6?yOA_pZbQR791_b0 zS$1^WQGe$dluAfMkiohI1N%Q!RmfmIxmbzLGu9ksxL{jBV5lO}$Ze)@IfJQx=c+~T z!%-<~JCeKDT048M@R6tEb1CzZUo5kYXRy;Pbh+?7z~S*OX`o7yH1G`Xc=T10YcN8h z)%Qf#cLwjDq^|(w6@c{Rz}|0E|IR^VP%OynqL$ozh^U)4{I$MUu6NrH0+W@KbVUsB zHa#qa&{}R<2`^JA%93PUS;(8r{Czf)4(f5rCe_h|3|fk~wgVv#(E(}v~Bigf%aT% z_Xs&Hy(^csR}$E}&-~oqTj~Dsj0`FP&Vp@^ad4y z=}+h^^G9$oXZf?l|byZ}V|9nt^w%&hj69UeuI4j5pWt zR-g5+CI3&ikpCN}ntz6Lzmh!_OLJ2_SaGs)SYmdQch7q}_>x&!qYZ^S=>6Zy|I@{O zdzr{XFsYIgsv-Jsl`G+Y2ALp{i~U_{{Ld3L)W7vwNTrYY-+F!dFd+Nmc$(+`WhZ?+ zBo;yl>BJ`$8c}~Uzh57<9liOW0k?*JH^UY1@RF*{#c8I%gQ=PBwY2WK-D@^2w4gt| z_8)N{8sEkV_2WSz?$2*GFSXz;Bk9v}rce!ltLwJ%)z1YjlkZnU;Y}rK2xFzr8@&nw zNJYn5jx(BQ@_#R#42cCFA_T9^>&T#n9$8#vDpjj8_m<1-lppeyJ#CNruO8oqo-9jf z_%n!{)Z-OJw&~oNUTx%=D4NZ7B0i~181v^)ix#(xmX*AT^w>l_NIx_>cmkiae^?^I z?W&)Jr|zFP^dA`YrU{veUescGc&PvMS4DLR>DEqPOY#ht?KBtM?`pY%&uRjp>bKF; zKlyR9$G- z6&wY9OQo+cYxd&_Y@v7Aj`>P`aU3@dub2?a%XK;sxCpA-zE~KynD`ljSYl?ZZH&m`i1>E>sssx@o( zQ)wh>k@drbvAkNYTyp!vfHybdv^41vwjzJUh<|WlPKZI71yU)a-Z(7@mjQ~XTvE-w zy?1zmGDoy+Ed%D8Rc-az^6)zjP)$FO`@om~*5Gdv{D%?vaZzT~VdwodvO(#JVh0eb z1>5W&Wg~<6{RVRXr4icS&=IKf6NSQeH^1BWmtH5UD{)W@G?(oskL3m4X$sh3YDM}N zc&*-QimLe9@&z6YS9oQ)`8GMVrUhMAR}CNXf^-as|M$TB3@Ey^0Idf_ho5#&UzA+m zLh9g~D~H7##^nVbznmFGyuJtcoi+r0ChkP6c~93*n5n{h`cHS_Nv9`_0cO2i+d%@J zmpyQg`_YS4>J$=Np8rx+7)GTu%n2V)H~hZ=&i=b5B;_HA{WI1451hWhf1vz2xqqPb zPh%PKzy*hkC6?X)d!}~}e=MN?xf}GK_XER++`=0;UHk2S80GJoh#>pO|NW~s+ix5{ z6!3Ze4A2Y8H_abRF9!~bCWMbtPZ2^PTQ}%a^?8A6ik;WVr#sHZt%PdAb++x7IW+S3 zKk`IAQVXVsnEg>vDdu$sH23@dRd-7wk05vE{#AP)tn>XikehU(c$|>>4=8>&LIhBB z83EP8Ijd)Vdb{i6oh*S@^@+uiRRK)5BY5{$-?5e~=>co0qPK_CN_fS;^TzEdit!aP zjlDqm+U0>;6xosbAizSKP)?O4vva(z#`fYmOdy3r} z(0y^%f4kualtE~-6INAt-l!_Ova`}C1}~PFOd#~8&>4m2{PqSKC~QUl1>FaVh-3h? z6(~)=?>uVQZ-OSR-rgmaCi3)buH^l?J*{C)%= z*VO!!8bWoYjV!wUp$$>dd=aqCs;=7eY&Jlta1-=Cd+oC7v`fjXj9^1EcY~l;_0q#;lOG$gW*!pgaEoOXv0P`sEvZ&@vR1 zuDcuqTjjyC*DClB%TI`wGis(%zEZWK>YIAL620+2#@lla;bcul9)Y z<^8IRG7bhJh=pp;~c>Q-fvaY&G#9v(|g(T=_g_x_p_+cCfC` zPr&C0srwq*7Q0>dvj(HUvxJx|UZm@Bydt&xzO|yY29e&nZPLA%&7}-2k8LURsEbOj;IJ+4WtA2^G_q(g9K+;)_!STjS_a|cC0w8q{`e_ zP#tM-txfu8i&T}2@-l{``tu^*q>jM$j;ZG_g6VE|IlioJ-l(qMBT`R05>+=rf|LNfWybd1d8Zo|bIvRdG)mzHknqF6!9@roHLd0^w}K9`WVD0az0HuUHJH<>NH*NP&wE@ ziQQpJm=*0`WU*|GqDv1@dC{Ih-FXb~d%Ladx8H*dpY&k6JvLi0>1k|S%RC*O5KW@) zxcj59ShZHc>^D(5VkkIAt6339$@Tpia{BXtYL~;|%jN;XhE?ES<5Rv2NsU^RV{hjP z-1Q(F6aK5(S=i1~Q>#`=a(M&CA%LXp`7?6tM=gHTjQ>#D*3h}E5EMZ2uVIoNJ=G5Z zA!3Q*y*i#~n#H;Z1d0S6U#VVw3w+2e-4(n#(?xyZnpbWt0q&<&MO;d`fNjCHwV9mL z?vtQSTMm;}mFFTxF!uB1oYSH36L?su+RRlNY@c*`Jv~c%$rrx#veicr>LF4bsg_s1 zT*Xj!;Y5#S8FM&74iN&7YcdHG~if!;shxEBuY@Z0}|Fx*)oZmU}Aj-F&7mc>~h=79q#7LvvI&+p(EOTf9heD`7Joa1Y3Z7zK1R&ra z*-g$wT7iO&9DznvzGYFOY-^Cyk@TvPywT)im{KY=VF09Fb&D^}G>92TW@p*8M!d+& zLhMy05-=Ox6qs-0UJD)Kx-#>%NpCr{t^)GZH3`IS{ZWcZQirs7_&)xZ#FTu$3*TP( zg$xi-Q(5RoA&aI}-X87oQb}di3xsbB2)H}xf6!N_{M7ns*)2d}J0V#-z8Uv|LyZ7( zvZvAS?D49oc4&8*fICnP0Si(ed8)P2X5Cmfw)JMS1#!!fE07X&JW#>2{`n(g3$l%c zSVn9D>Ay0~M^CUZC__NNzDZgY4`p@Ses#TOl%poc*oPTZLgKY$~*C8>jdUX8_=;WR8j!G(5|c$rWdvAivLI~{(SZmU+Du7)V#xLGLv zC|LHTB|&YsC%!^%ppxKyhtf>Z##C8>6Rn?GOUfGXmI-S)T;GU zhu>9zVtQ3Ep9sIw{$Ww9?aeQL*T2sKsFpHLtfe1J_=67zE@;+LwWzTEGrOxP`;FR{ zioD-bMOWq;;}B(3LhsQHEmmZJ&egWn^i+u4MPK-!-)GqKgR(Jor$QR-5@u~PWiydm zaX$}y1KDH3bC@b3xYT>v9j@&MpE`NSdKe!x;c=y1?w@C?Oo8ZFQptI%&+)I=&2NRo zLICkHtUQR_&soJPMU<44(nJQPU7~E%!+YYD;PwKqy$~|s15zh7F;Q>9VwRClK>>z+ zQ?RgttcK+hE|smhT**pAThDYNf>`(&fs>jE`zFmfGFUgz%qe}?L7+V(&wyvfmH>R1ECiQpP7J;ksoMy#xskepO3Qn*FFFx!WUHpK;)l7Y!2c9S49{2m1- zj#=;!OLfS}`nox=KpF0vpgDIH2!w~5KTD20Q_lSO6nE@2v$5ZYnivC8!OiV3?f1Ju ziZbn6hZowgwCc0YTUNJl|GSP=CQ#C=OooajAzDCTd3IvJ?pdte(Ro?y4wsd{u>dKA z)@m>}e~clp-)e(WOcLnTX;0LmA?|%kBywyi>E(7Wk#9o9ej|q@r_g1yCUf;V4s6}} zbEnWFCl^(EHcjVE8yosZFh>O|!QmfU;&M{&3eN4Cz4Hv;!_K_~J?ZYf>7Wd;uZ&PU znc^dy?wC0C4bDOOngYdoxP_uNhNKcD_nR0NBpwEJn`gdl(j<7pctS=?_JR`c%n?PT{o3CpN`{xnt^5R?wL3C7-w9@FSOp; zxoNPMYqF)Ah!)bRTW#KT5K7>ridk-cl`mhmy#7q0TUO!TJ))ZzY1seL@^<`JBnY@! zfJ7=&8MDl4RlftCgT=bLqSf*=R2AJsF6B@p>kK!3BQ%Qr=_QQ_P0aPU6xLwbl$nID zSA;(g91=_x5(k)3iDHKrkX1|t56B!8WqBeE9^as%hM0&-Wz1oR-2}_g2hFrVpiq;o zY%mzHOSvCM=s~=@9Z|)c6O6n3g+IcCD&*J9R`n?IfX3zOHv9c*u-?e~xo_>eT$Ns> znUZ+J+`wP6tm2jeQ}#zizr6s&@QkV0M}Vi_QC_PZE9 zTW`DomTSG3LpvFoz+x~BUW6_9)ldKe( zzOB62gpY1xhNZDX^~ZXJ6)~SdJTQC-tyfq>x-WFH@ua~~6RfiJT{EaAIJ(4>0>H+S zuaXV5^Mc3N22DCOySLgzx^K%qs4KtW5gf(X+hNp7ttD^|RI<_vFFtZP(ZT5iu(twh zt;P4Ydp%pITQf+dd|N>}$aRgGO*+x@F;Q$9#=pp9fa(+GgIUSeo025ZRb+;zhDuu4 zJ>7EI3B2B{IE7;>9S6%o*n$_MYRY9FSH+p<$V=h@hcT+|9oRD9o#e&DqWfP&uNeR` z{PWL1)|4^JCHsY}5kH7!&e|VMXRPIO$QDGgSs(BtF>p6vl-;E=i1ric4On02y=lr+ zLi1O9iG4chwn|EP4Cz+P{vPZbkiNFJ=9$|h+vVqzmT;cO-@5K2nG9<)71RjuVqB&^ zI+X@H#_mL4)J8l6@|wd_nd{1+H!i}g+{+GZ+fN40FS%$qrC^4E&T_Dbo=}#Ov7L-J z%{?&A(MK#5uowK62wo3ZLtq{8nFMxZN4RyS8G8;oi~x2H`Qfs**5ulP!T$R+Ld`0Q zE<1qKKo6I2tmv{?>mAc(p5I|WH39*C8aUQ-wh>Ke@6d+X2L{H=4^PFNKZ!Er_|XM> z#yZU4Cef#}FcX7En$%jE9fkHh)?ZxZCi-dEdb%i#jTP`;DDe>jfDtcZz40I?(>;?7W4toS_BI2F|>VyXc=`Q(1ye-_<58lc8+tJvOs69wjHzLeuH!oVB&Gc+J&tQHi=+ zciJAdK1`Q<=46saPP|;Dnmr{@p{}g`(7oTNvt)0rM2nG zJ)VcYQA_T+%)B0eItcr;z!fK^LuJ=hp@!eT=9Ovd&fOT48kd6q-H{T*QjzMDH&68T+EZb2ihf^LH-H#9hBFm+iz74LvhS3qJ4d0R~{ZwXO` zIcAI1IJ!f$CtiEjlrT|%JI$yN7&N1xU0)ZfHT21k{~ltooHy<179vGHg|kshpFnOr z1dI>uNL=?doLJX+;ZlU+fh)o`P9Yp4%#CyGHjaA__0}@^}5FNCmo|^FeID zSPi4-B0mp3yw{<(`E_R8a*j*L3Ea#@j;E{V%5h{Bk7U-B3PfaSqXZ1(DfS*^q0-4z z4Sya9w{!GZp?BAR4N(iNUC0=fl4?4SshJ?T7K$;0k9;`3cf_GwR= zAn0_sB++j}h28GjmIOi~gJP+>jY-s>R8n5n1`&$_fkH%2UY}O1juYF64sX17*DMxc zF%(L2&*)i#3`=YfQN6#FHMo#dcENqO{yFXWeUD6T^jzKf2KPFmA&{<=Z0_TFJ9VbS zN|E1^I~sGb^-MY^B>KBE!w2J<@^@nz`Q&87X3rIi)&~aMRFc{Ew!LmUN>oRpYIu;) zAs4QBww|hv(u5`NE975qTT;^Y64?{M0kAncz0A_rNK~I(*OMU`x+^6{{xk@j72bZo z+(!v}WoHazS7Q_WahxK}Ts-H)vk6}$(jpR?I5n_NRBBRwzO^Jc|9Ks>cD5uA^-tFD z&~G4Svv#I>ng!ipQf@wv+>c3;`A+DN!|u_MbO+%myZ6O5SR;=)XuWHZVi9aP_Br-# zW&xbc!%nYi&s=MYAPY~8JXx`~`iEMrtTwOS3wn|=-F-l5MR5_{Jp0ji{>0$^#X6xV zG3!EYe}}8jRtH%L%mYLce?x9eXB$O!9tUC+o50Xt7yJ0tPaeqvrG= zH~Fi7F4@5pEd$#yUs6xZ`qSWQKB`wW`GJ$7VrfRX%XU@xgwLey`^`ga^}^Ipo=M|b z8u@mf^r0xI5$C(mVui;TK1ur$q=O)*-`Jdc{3L-&DgUszb}G+JwkvT!!T&m@QU)!nh7ha_c%!F(>LpmD!2-Uw~+RkP0cBE>PUBh|5J+-u1^ zpfr%Glgp7ZyHaDt8OzF z3Q9S4^;{gFngjjvSnzqEH>G{VV?p|_ z1lT9}r(*+V=Hnb{bLbisbYI_~3NAd(ddz6q$=@nSM(OFeLy3H%d@xd&iTxG1r-}C4 zSi(NGw#yalss&?6>`+4R6oG*}(m`ewvhzmJKebVGX?|`btxVKdf;=2!1G zGWvv8xVQ1$@G_4h6cybX5&KfEeq1ly*)i6dFTM8A7wZy!$QhZdZ5< z(l#z|uX|1xhCAD?P04dPMs4todZsg68tj*JBK`e-Cc5w@U1F>HP4f&j_+&7O(=pl5 zv#0m{XW4Y$Z_9Qx(uL%E(veTbk6d@&ky6l*>6Scm{rQDAwSQS8=Ji{TbK^8z#|XBf zD&A|_uv=>3a4wQYg0X3nMOhJUeS3x20jcbbiSBXq33lp$7h}dyKI>BpD@Cm;o@v-9 z8t2z#OumUu@{6|x$3$&{5<6iHpUEdB!pk)@eQX+H1F%EYrMP;eG0&B0o zNJaV5BAla1kV@y;PJ!X>lO@_+fYF(K2Aa`i5iV213QB^^Por(K z6wj}FbW?rZ%`**p@w#&pG8%2rz=D{?<2;{Aoo6w>;w5x))P&%%hBe8jiV$`{-dRf@ z_9LEFCFw@?gR{M$#icbRfB&mCK#fD-F?5%_HK#FV&Lr-0U2aS|rY}XMo%GxCQW*@- zUnAyEZolvfH=&UTet6N}TsR0o%QweoJinwOLKju0p4#8@*Y z7y`v2biLDYEs~(mV~p((uWi<$%JLM-s{;}B=;CKwd%VQErPzAy;rNTc2j=yqV3KEL zHY20ubLOj8ren4I8GNjGRcTP;9m0+#hfk#IjSQJe*1$K@PtwTzE6;%$*@JzRKrHo$ zFocGvji<_xD)~y2sOy&Tg7vfvxI}tWB>xy1Z4fr@3J#x+`Hp5_@HXyKe(YpDaB9T91pW_u5Iu=Oa|JyB=Nky(e{3 z0npSQ=e;UQUDFNDdt{INHxm+oGF&u!*|w=TllUn~f!HX>ntvAfy`r*DOmGjwOpkcp zJA&8QRxnUxm>ajJ0%>|JkI^B;mS6_8A@UdO_S%LuHw#^FF|gmNqL^+jZNeTAM5mrn z%C{osr-0217V$YoP|A4);3P&$feUHaD3TUTL|0Gp*nu=&*I%h<(sI`cTemcSa-) zZ%0Jy=O9-u+pNA1tV!K=fq>izFoH)&eqy9NF^m#SB#ZVF%Ql56m@KNxusw^(*ffuL zFDRYU*=WxKXj>I46HtTf;&SJ7MAm zR3E6r5~A74Qi|`=?}`yG$4L>TBqCEsVel>|ink;oF$A8*W%EZLZ-pb&e`3?;c;-%Z zDYB)MSeA!jXkA$w5~3@QB`}d;)G^HFxq5-HE?lTj?J^g9HA#|mzklUlN%)*l^_oU} zWaj$IEM_c!MU74^Xdqq{D4ILTAd_u5f8V8=(WE|p)pjJwiMCcqs6Ho5P;anfL=j8N z9Pnbj7!%XGXamSFTjiG)8WjNX-23o;;UjsJB;&YxQrR;tSEGt$eo{%2cS00jTP#5? z2L!TV22IiIcvR@%l|?kpE&+aWyO04c_Hyrlmo@J`jJeh*f=qA`dQ?w8WNLrn$;y9` zsX$eFjqs@V&C6i`oWNzRdtQydCWSCID&l$*HwDQ@IzD{KARxW?yw!5locIk#Vw9NL z+=(p9tr3Y7G}7D3tgH6;Im#h#c2z7+>3|Yj3>Zy={4G|*rxj^w*_@@Xy;|XN?DyiZ z@}@Cv$o3B%4R9_asqx$KoXIJYZmbk=Fs)Cbc|+z$R%b~+s2O}QF=>EO6uQTm0Zb(W zyk$(asO?Zgk+t*iSFo8q`r8_+;HNW{z4h>l6Pu3jQO-B*U_B>&deWZboPfzYeJ-OH zim^ydqd-Dw@Dsy$<9A_m8? zqHxg*v9zkOy+QxnjE)Nd0A9r&W%chz6T)*-E;1et-5Uw{YV6(9UJP&?W(-+Lf zHGI<4pH8?qcP5r_DBzW&m&V>TuTo{=gmI@XPJv`-eQ+cJjpK0=u=3ZZ@7!|oMqg<# zc7NvF7tDMq%)<1H-Baze>?c59|8%($&9kDK-J4L!(sVjz0=}qs^M?8EjRBA-+GlQI z?_a!TWa{|tUMWaO!y zjgfT3QuU|>RWO5FvBY1Pr=lxf>yRtUkk!BAdEsZ9XLcnL6oD5zSpw$ycN#E7U#reC z*~7YTOa{16yIg^*{#qkPjfq9j(uVmObU(P=i~|Wj(XbX48qdXI46M#( zzbyDPz+IC6h^3wvdsNW09Jz*nsP8hpg$VsKKPM~OQii;cqHIDVkfho7uA#|8yoeHk z{OSHRz4!%xcJE`lCP3j1B@mZziGglgfO{!!SK3fumLpyz@uWprSrF_&SO+2KVmJwU zL+NhkfRk>V7;5ychg+Ts&+y>ucsvMfq~TEoGIOAqg*>l*+>*GaU?QXkqb8E@rvm~J za}5^CBD&5`qLwlu3Syr<`?OlsntQ$D%9lYF$PB1RM?({eT9nwp^4Ds6s(iKJZ-KDN zh!Bn|db?3HPGJ6MapOHT$1Gg`fZ6JQAf1I=eeZv0Yh+AY;SW5Q{SxE{e z4@vAU33#1{iW3La?F;aX+j&8NlWK@X7@9GK_Jak+(d)49>tWAtWH2>z@_cg2u327F zu~&CVO!=nuRp_<`Qp4-8w|OC&e6j`_nszMFXEdJ}p>|f6pAw%eP2WO7z?dyQ5r2aCK znWVNc4f+9XU9*@eFC7#%WetB&HwpdHgGJf5L0Ach*?a?sA9>w7S(V7oWYv51w&#-|VX}=Kvh0ZhDv5gC z#NpsY-j|9jXn_e4Xp_%y`A=T8Q~8uQkesR+agW) zI{xsk&rHeI(foP?5caI+WLHU`=kNRvQE^kP=vlYR`<(j)-uNg9ygNF@K)6$GCr z63D*Fo?H9PL5^_LVLbYgRA1=xEHEs)N&9CO6l2fJBh-VETAe6xp0-{V>qUSCjmyZQ zez_E#kXvlnK+`;^1}Bo<%YS+|SU?WuRD4T#@i9dcy+9W)df03vn9~p|Gqx5+wGPk+ z>k{GiS9((B2>*zpT-EgN>xy3r6C{E(eqHL@Gd*MmJSV4fNR{C-r<)_f%+v#*bZ3YY zVZVGQg)x|=Sa&1ASV$K^}J;#@8N`cKux8Zr#VB#+m{v_D= zQ;2_5lr2Zez9lsovI+bti5IuLC$+Z*KfYRiZc7KP7r90Szbjqcm$EVCohZZDyLZXq z1+$-+ZK7H+6YRm%WZKDl))hGRlq1~~2szPve>h7Zxn~W zv|nC37RkTYrDPGjs|0eRR-w|8HVNqK5oq-61_yX0hPgkpgvtKaQ`0x`ac&gB@Iq_MzFGak%?GCNt<4MC{L*oiZ zXfM%=xYNtN4O^mb%N%?Ir_Ota#cS))&^LSq+r-mJa?)S{oVO-bBE{zt4J;m88j-(pEmz+d)53aZ!7SVB%=agqJyCE!cvDK2ucBVnel1 z-*d%*UQFjv`FuC`-A&+~GTiJ#kTG2rv#>bK@czRdb1bhs!JOn83FDqvk*UM|G{WcY zgp(Rf5v6V)oZj*|W|7q?TO_KnA8!IH2}n%hIe?ApVG8K1ICGC|pLmk%N{1lWxAnj* zYslpEMHj)Ely6|9wKAAvPaK*9lhH;wA&QO2yV)l%4fA?Pr-mKYQtoM=9xmVmo#!4A z3(`QVXSnd(viuPj&!r^lf__}$EQA>1F=*$5jb1-j$L=QgtwJlW^bwTvZM(cWaF4pA z1&X3G(m1YnN%;e<2e#_jo{HWnnSB4z%KMJ1hm}`f%%v^Vii&6(%3l&LR#YKHvjPqpXlIm_CAt&M&|7M7AG4zd(;*QnOGqSwvl(FmfnMib0l>(!& zJi{I()>m4Z^|A45pqK)*2ADIcVR7e{VH`-vNq^1NmU#~Kjl zA1(1>As`>Y`C%YT@=HRZ+;$|ken#RTV;Zkwp|PuZV*ziR^l-=KF0UBSymp20ED3Ju^Irm-$tVuh#Qcs8D zASvgjZ4)LZvBMpvGwkTGz!<^n40e7zD&A73s==V^C`}B{#4hK3lB7(tzs1WZH(apmfjYDvK)y9%OoRk?N~f^3IMR?p2doF zA}WZ7`5EqYeF5%;;yPXk7D{3GzY{1~^z5b7#eEfJ=zgro{8A9R|K0u=!2t&HPhDjm z{uO+t#*DGS=Ack_T~z)m4q#fEK#wp&_aeY3>xNAwGyPQothQNCTij}!026D3^_|ps z4HcLYvi01d)GLWuohRO;KOuVm?LZjWIQd|NJUESQ3AE`i5}I;HW$=oA z-^g1XtB>ty-l&lMRXc}%SN^EQVn-%*0llCG!YY9#MJr7eJUiPXokJ}1Go5#?rC5+w z17^b>9vN-uS~X;PD8V4jXZ|QuS32|j-Yc)(=F$9#qL;c;O1pKDQ!vvw)!t!U#?CK1 zub5bU$e808cQ?yP0O5*Ec~-pEE_0|^j=Yy{h&NUf`(6)g^6{%x&*vH%72|b=6gqMi zdh-FD&XT+r_6~CVVv;$zTsU>ax2_SO6-9qWuPNQdY$kw`R9Xah(ojVWH}^BICmyC4 zi;Lk)ka7@JNKt8XxPOC%r)*3rv8D94S+t07<->3d{CGni91Ft6XK}?^4U!C%)-^kZ z9(Z)>tsG!tNZR=KNjWZUm_BNDXh4^g2(aXLrN&j`s0$|GIsgYSR9N8%Fyy)YYv3j;a9C~z>tQ+mtVKX zN(fmoY28vxWC*K86E+M>UH5J7Mf*Pk!! zs`o&OvIgz&W(2-I9P}A$#GsBbBo=-;R#7m^_4xIzPaUZLQRqQ!YRM5cJwX+I+1VWG z_uQ3PcyWR51%+<6P3Za7n1{PGEDf67(`Z$QGEVzBGzIA@9_;4I=H0!mdi87v1blxO zE1qnO*V}iY+A6hHSQB3fZc;+bVYUpY`l1$N)f^$a zZX*MTCzt-^p_WL_w<^Z)@J^T(m{?4X+uXd{HD!R(Iop{J#FMCh9rkckft_y5)SS$F zcj2@e5HQ#v;oes^?o_zyr9S179d?Qhl3usQyExuY$On0LSGk8hG30*Ndyycwu=CL} zL`(zk2hE$8)Flx%xwEc?xak(!8}h*O*=*2Q=k(G{!vN$d!~%crVtTkkT1l+Knkh1C ztAvb}`ZlI4;_OTszbw^Mh#4>=U=e2e<}vpa7GkPO>qLf){0$GY5w@y4G34UdqEEN8 ziAP(_KAn}rmnwyNztBHwJI0WJl(S&zi_fG)!uZqw;_XVI-zGQ z2LiKmA3oMTYzms7$lk)AxlRl2q)`d^`ZR)cjtbl>1Z}M@%b-sdC(eUnLEs)G8O!*z zg^rmIe#SXWN%jUdJ5g8nfAs81&+vw+36Cv#wfZh~l!r1$*^h|6hCW8P!y@?R`r} z=~4v*0cir#iy*yA2Px9KkPr~*gpPDV5d;B43B7}mP^2TE^b&dpDWOY?fZ!9)IrrS7 z_x=2g=N;qb(;gW+bM2Ki=Uj8`z4q@wI`d zV8*!>JC_vX!pWj2NgQh$KNE?kL^A61CF+(EkIVi$c0qPeIYI^?v6TQKE6(M^?&cG( z0rC6872huX;*oR5g=j#|G;d?tL2R${(dlOq`&zHEULlq(0vb#Nw2_w5As?(fm$2#Q zQz@i|@P4%Lk+*A_B2*N!r^lL&Hr3pK`)10RvB4`#dUB|-Y5H;N4^d-2+|v1Saa0w>^xS8PnMot*{|VSF|R-2^8$8&AfoEN`)#MlGe)P9>pQvpRL$5mrL~ z)8>K)c2p8dZ@;f_+%JQf2z;#W`uv$VA!gV==IXiP8!^Jb(tND~I`&A-NTIKfxd8C$ z5QER3_*D)h!Ivx$-=t6qR!V5J5Af@bFSa5=iE=)?MWCZ#i2$@fF5vYTVbQ4#d-tVg zSiad>d~Ca>l1<6?TWGB6mIJ!nW@*nk@DRc#<9a=gGP z{3v>^xf%gM_M#2?J!zkA%5>GVwhon@6spexgCkP~Au{&0x~;!1fCRgi)tolc4k)z!-aJp4R zJM+)V8SU>NEg5;~=MJb~@+Uid=2Jg~maXT!>nx2^6d(bja$furj8z4kApn}LkFcrU z#yP|8X=cA79a)Ph0|)mmE@5~}m|{Mkp-aa0$M7L*n~}(>7*lG~*AiKU7sJs={O;i% zV+)NB`I6elKgLomG!_kC>SZ$=J#_y%SVHg!sa1B{e->rZZ}xo1`Vt_1+sg`zknBqap5p&Kd}Pu z(Dbr`TeGF*HA-}w^SPtH10wL7qSt<<_md?T$UX3EBjD5)M+1^ z;~|@@Ik3GFa(&#!EGusRQNerw{I0O!~vTUp>%dE0CL+TPOj~3Y^_)==f1!4lGj`Xj6Ej z8efqF!NT05SBN?1V=f)Sx1&)FnmgH70ff5~_O4lr-75Bq@1K%!8+JDNxgZkL3x2vn zcuu#wKuFf`(yN%$W4HIq`=Jy&7|o`E*Tb!EXF^u$&1E3rq>L`chgWx5KD~2YYH>!@ zienP5woSp*bTguPyF-l4a-2nQJ@25_)4g1F>va18p!=%4?#K7(D{K2ngI6c1 z1qxl$SUDx5d3TjTkn#S*2WOKFJYRhAu#&dk?5%v{0;$ixQQPO$%i0nhav8nOx`i3! zC$oTGa4)L+o*GJkp^W@tL-;gb)(|?i!TCaObj{}VqFtPOYZ(lR>bhQ@<2%JRY&6BzirBg zT(5J?=?CA7!QAM31gHz;L-mz1QBAgu7c(*%KIN2b{2sB>tu`))MD!Tx+<60j29{Im zIJZ0JRic2)ytw=({mhBH6tCJ)CpFfKN?&m@WOrz;yF;n5Ki>l2)!9Z!4&Scq#K}K$ z+^iMV@n!?)nVUwXtGA|m1LrMF(dW@6eDk=Y6EB$8D0vD#&%R+mGibzxV#QvGiN4mA z)_!#0a8r|LI(>^Y4aRfr+c;f(Mz2v8L2B!qZ6gbKQ*^evEkKyV5Qhwti@m5_hfV2c zynE>z_?GDK_cHt~k@;7~ar_jPVkRG-yC%|do>wy3v^J~FmSS%boK1Y^PZzlu1%!4% zl;Z#|U4>J_$nyQ{)7R=b2*Q$L);Upq*IgcO0b|*dTlXa#slw&T!aFrNoNOUA^7pku z{2HplyP%J(Q^072;>7dC>=Hf(;Nt2x4}ym(-g84rd`i6nYDQUbub-dwCh#ZmyS{46 zoS&|@9!rVJiP~~jcTm`5k;S~`Z;LbaScAmOJwQO?Q~|*5rlZhk6NXa5JaQ$Be{FN; zAaqP6mAquiunsu;)6OQ(kpNj;g5nl@ToTUcsV{Qpd-u&$#xmY0s68GnZQ}$Ky81e$ zLIW0_Z~+Rz0G3BKV08_Dgd@8=GY-KM6}eaDyXeLofxXiGifc(l>;6K??=%$f@hp`O zPl=*U2k4U?U{}Bw3G=I>9K5I1_=>C(;^y?Xj-QGiZ5@`L@e=I66q<$6z9_bVAfE+K z;d~Xb2qv%P!xmJXeXfc6KnynDP}=m5CX3d80S>!;d$;4E9uU>{_D;niike{Tfj)4S zVP9U46%hB{I4W3cq(QBcG02#aP+89KWdtwh-PLeWTpD^899j!#JnjcrrxkO9{454n zxCH;z9jZ7NwDZb!i<8pnZYkWL;rlxQe|jHk1YFWKZd)q)3=TRUo}-7Ny{9@dB}-dJ zrq7C=aNW@^b3-FdA6l|)(UmRmIl~MOSAu+|1i;w{zdw^T~g(3tx4nR0S@|KYmbz zH{2@d+g0c-P4-^-Q->TJvbljf>a+sR=y}kMoGa4OwcZ>lec-NBs)E~Z)oE5iqz`4> zBgd-{CYJmKrwb=1i3W@5A!Fq$oY&t9G2pgNT3DS;q)`r1Q`LB5pMGf@@@J@JWS@I9 zOs#VpZE9n06b$UD4XdOOt_(j1qjKNLbBapYKXCFh)gzKNz2YKF4Twt$vh5u1^&mXF z=oT#QCz5V;SjaQ_o)mFPame!?wsn);$yLsZ1ylBYl`b=~UbONs2|@2$tcCP9*m=12 z*?e%S-S6(st8BTk=^~chk@sHMFK(CZKiJppbEXsO7-Nkwl|@O-h<>kAgvrprbU&Z6 zdp+H5bQwRYZMi0}*6~hG=&Gr#)9%N%<^r?^YSU><43dki<>%MV_Tnhyd&`Sg2n5#9o4y(sEK=T$`Le>x2X$CN^>N5# z)~~A|n?Wpxvdo0+ANY6B(ws@fc{}vX27-c#FIn_}ydtnTHrlBcu|O70g~`22(_$|< zZsxB_<7W>E%`{UC<#(_ftMnQPp2nI=M<56_;az)gR-Y6z+xZ2mKc@6x(vjG|cp8r% zu)lm3IC4PMpFBkW-E-A|*aP}5lMpb2F1tzw!={Fm$zU^cJ75#g^5Zs@qQ!GI^-f94 zefw8|R&d5IhO7wFM3)~Lk($z4O)6}=E#5)}`n#6kdXye|I2J&*;HKBWj z(BP_TizGN*WG^ZWXW=8|G@`#`Dtcn2KbU^eKuS$^N{vqm2t?9|_wSC4YceWVjlT-lp@gFlxDA$p+CgcTl7eEm`zBe@$ z%;#eII36WwU03*m0GF`4#`IM4d<*1)-Sxj_mcvg9G(QGmTk^E*a)R|*5Q?gF;cZXd z`p28(IdG}SW*%=!AY^Wb*SxR?gF>}{%K8aovZ7q>lZz|wYqw>8$|!yJbOj1&o(_|A zO<{$|l0m_>zLgU3fwbq$-8bfTEWL%w-JMMW1_OvUnnb?eI_^_F&oJ$hiQr&g>e5#+Ja{b(1Lu%>yG5tE zuReHxt2RRbkd+WUmwC34AUuK%?19C5*qGz_2a#cR6^geVSt1ik3~kJot@ibt_6O)~ z1vr}rzysr>fftwlb@X({g3GQQA8V0S4O-_aqHX~)an8N^Xv=~r1mH)ut5kqCS>ju+ zoJ9jR_-1_)@z~Eg<+TkjeHzV_CPRYA;amsSaGbCGojx~Jj&aDHBVkqTz=*^)Z9?>k z>7!=8+geCN9n?XLsbKA*=9?#MM-vyzydn*Gp!p2R02V@)KvpFHJKxfWG^dC19C?T3 zXYeZgPfy3&I8Ri)Mz>4*YNk;0W^!zU!X)X}SEu2y@t{S!w^s1zE`%r7naEXHa1CCYKIfm|KJI$Kiw<0%7j~oVUIg1Tv%+^k{t)0 z-o|2XO}TRy-kMMQ^qloj&0{(w`w}-QX^0Y0qGlz+HgQ3)l>j*!4csA%)+rts{S~l> zDxN zX&5f?yEm9cR5shGeCA4D*e7^)o{Xc_sZhTk$kmbuVQsKBJ89Q8lol0Y8M?3EE6nX z1XEF6^Nd@0e%L8pJ#sY)1VPS|cM6WAKiK@@lHgu*>d|CeR#_({*P2LI+^Rp^#>KbG zPm`Gb#9V4f@y#qjMXA!k$8%UP$#dg~1K&M5Z+(OmSyxmUSRU;``9riZEg#Or7&2|5 zZskJEf)c;HQ{~_91B%NWclFL0O8NfTdqK6T7y&%`Qt*;z`;7Vx+iuCbTFr^%85>HJ&@P7 zj{8STln$j8s?J!__kwJkegEMLUsaBxFEf(&;%x+oySlK?@5Y*%>5{9Byv^zm>wnLaP&*07UDf5xh) z6*?oycoV(CYfs(^K?M#)DaHGJYZel=lfNRQD@Vk!u8UVM3RoZ`L^b_h$qLuo+zoqt z56iy}X#|4eK}tYk zF3I+VRHOy`dFS(L(+`*jDnHd|OCa||@5JDL@lcokykwMZS zCq7LIgj+gL&}v3Osx@l8G|-fGvLL0Z$4X+jk{b#5)PwKD9LH<~)fVZAZ*RrzCnQ9Q zg#}5$rnYQ+O#E^C`znjIroVGl>-2y6D1XZ-GQ5i4>wZLvQ8t-k&%(ZXz;X`vy z4%9=LU#%S&6z5`1Gp=+cChN5lw@bH6$5>_toGS~~D1|@DZHILO1o$IjwC<WZMvp6 zaK?0!>!7Lo?%M(foInh{ACS+gyl_)(2^D!7$?9cZyf@zYa40&J>HT#=_L znS-w9wu3Fbuya9hbAFy9evgk3&~qlj-hv`it@23&Jz09yvXj5{Ed?kJ8w_!J@5x@h z$~UNi0C;rOS{(~vtCPl$(4Y9Y5AtlhDZ!od&IjP!|U$UV4xh0@; z?~bpPbF0Xe?l!92=x0cE(HT3Z(a`&o1Yu$B$Y%U39Jd+q#lct39ILbV%QLNxKbas$ zQ_&Gz&eU%Dcnk}m=5i2L?RLREey0G%SbKiMfoBZ3KJBE!t^L-|ts|}a?mdsw>kc(~ z80Q;PJ2Tb3rvG@jI7f>R$Na^1S3W*Tbg@%zKhmCc`ex^*x=2I0vmCWB7u2PnAa3;X zc5H!!qbGs^rTXRG<9oa1?_rBfRaC@*)1Eum5QT3aRCHvcgJ|jQ{2b*p6a0MAE@?G2)<;;X_q+{PGkiteLZ)}qF6kw(`~cy z&a8yrjboIUHvRF4$&gEKaZTzi51AE#^??ZIN7Z)(QLz!yodM?gz%4;qxRNttm`q17kBz`l>$X<>7-+c|VaCsZ@vaGvXXSBh-gT zLoV7F|RAiVX}ZiK7+m`i`}>bM2i7(Fni1}fb=2ksDclbt^! zSR5uX>{C3&uSfZAzYtpoIz>QYO>Kz91byLsMiv*XRi^i^0ofPX2+{dEyBSZRI!EzN z#!wPwZybt2Ey`L#Y=ebNDmmef=L=#)=C$~MmxjqgT)~N7E1nI%c2szSa_?zx5JB3|vzhTI-5k}jg;tVHZJ zkawD_G;hty!UQ2r+=Om(n=kzI249b8z&{a6DkrVczG6o;b|D0$qf*$!tAOns60l1so}3>7^3H! zC2ahG@r;)R^95fa(LL7Y!vagKnERg2@ok~>yaQi!&4LGoW!>~$iBo+~?#0%=+(Yyz zbrvZ`Ya?1pxYh~1chRDidTL*-ontRH6Qc6Wn0kh^Sr0HS{{PWa?(7`yY{2J1Wx^53Mim0o` z3KPjFM^XKOC~Nnz_L^sr@0(obczl9iw*2agX6@Y- zp?M`F#ka_&9_M_2rDpShFd}}qXZuwNszpy}b*AG9VDCzPsxKF0hfsNCV)9&i`2JWj zi+92x1q(I_xXFyAXg@D-QCQQS6o=$Fr(KPo&Eq3~@rh?r)Hc>%HqRUV!BVKXO_1fZ zS4w@-!`mKu!a37-d%|){AnXYl3SLbN2?KPsIa2SczJ)!3iAe<alsjr^l%WF;R1&p?Z@Y3|e=943<~F=?NpmRHOI zbl{dvlfG~vu|STeW8k9ndjiMY zX><{#VAX>owL4_I3=Crrsm*U^CI-Bkkk5fwx9Vv zlUE7A8=tWe4^@9aqRJV}=s{l!zxv9kdr-M7?xE8T3XrF#hPO7?gN$97Xz$j8z{|Ub zmnW_Z;yWIJRK6yhF7)->g8hp=61^9M2F=jJALbF)&$;4B!(H#40Wzi|9bE$ux3B$R zQwMQAdm}OVcxiPYO{9XAz$d$^z$ZayhZ`YdWLlOhXnHW(yq}{vlJaC!Hkh2Yc&oVyRe>5OV-#6xTCF9u=Thfm44iei ze1usx4XK3LkwrTW<!`y{eGOGT?3vF`Mm zIa~(*@X+LCDTB!3pkEc|aN%2vsQkiBaRLjH%Yii*&8}XYtYW-qWL7a{Lut1GBe5-f zNXV~NngF!RJvkE`76P2N>mR*C`rOO~zpvfI?eV=b1=kwOgws9tOJ}q;q~v}U4$bSZ zG{+_jJ)T0i|5}SUnDm1PEwAsGh+a9gy}qK2i9TE8(SfS}Wa_Y(5ko#!05xw3nL8oE!(k!XXjvfde4JoIVtnR2B}!^?%|kkJcqt&pmZ;gDEf z&BCm+ju*XVCk=V4aHqg1m>8p6-7|r{Q%jbzCOF=qp~{20IZzMGyH{f8ep3CMW7eW9 z?OnK0O+u%ebLZIXgv$GuQtu?GXW#p0Bip8LAKjYlxcRGjv2~@8!;ld9a6D&pL0~a zV7-Zgh`KE18HwyCb6lrqZ@_u4=X&mAkqtK~4^2(wg*c3_fVs99SCN$PxM@nfs({Oy zH-kznzFhlHF8A8(?24j!GabgiGR2Aaec?>dM8?WV0V-dEVTcIHyWAf{b7^b6PTlj- zW}cfSFT_pPJY&vhRbQMNzFuklZ#whJ47o{i$v&fugt3SgxpvAyEyl|q&Paj*>3*A^ zM?CYlGHqJAwr9){Cyb;lv2xNC*do{lSaFK%!G7d_EQ5TZT_Mc8b6M zO&Jw`T>ckETn;-+8w3BJFnfRY2T1idwwZ(jW49ABQ1&lXvHwQG6;bAvES}OhUjKK$ z|49F8ZtMGMj_nnEOV4>437O z9~-+Ez8VIZNb9=(Su zvwRtEvHEid=b>#nG!_2Ls+5@9`ij;$KR4jXT-t|04C5qcIx)#y*Z% zwq-B52XO|n6hGKPuGfi=b5`eQ=QQgmgJ_z`3dFtif$5aVfwk|(kDeHYh^{p&{n9S3 z&iqlC18$tBlpNbkI{%Hn58x|2AuGTg+j(8Cjv5o}L2o3VuIndGOiyrD9~6*fw~H_4 z9JXJzp6(TDC}PRN13bVtgfG?k`?dor5AUfHqzC-Q$m>`(r?$C}M%D(6{)TRzyMg9`iTDcmsU*3O`s)#H?N=3J}wfsxIQ_3H{qtmyb#DD zojpt-I7?f-ZtLSs6iwJl>Rh{l)4i-i3BRdtVg>W=Up&|B91auPONM4KNoOfn_LZI< zpZ(^C9tTbRhKj~@KSz~rWFdQKqmervwELqkmZS=(3DlkpF-O)`8Ulj6TO)sZ$TTER(UU=^+4G%~WC`N$+oKk#6X|s1E%=UEr zTnE0bWeB4<-C9i9z7#(FI`K_vBda&|U#e99rc5q4WAjkMb&BaTTC7OPpM}>i8$Ey& zlIwK7dJZ${Utm%ZCEuHv$Ztg$u)C3C! zY6%jE8!-9Y*7Qg^0s1c?~BXQdLAmcuc^EIi$m#|6Dcwe zPJQtA!HCnI6LjQ672iI1F9tuy)rKe?gzL1gSnX|y;7Qz`C<1*!GYOh z^zcH=O7u|@^Umd1emvo8-Q02uV#hxfr|B3Lr z&o}>)arhaCy6Fy(oUFSXa@yGi;Qa}D^S=8S=qHzCzRQPI7_ zqOI}L)A7RQt*fsFpSYnic*WLY3qfv~n-Hegp2ME+=z2 zj2myHP7gm#Vv>Q+2afD4_|zbZf9{m_HUwUN|MInCoQOm91I9P>59#A1d|0PI;MA+b z0@Y!t7OkuKGaYdP0^?#OzRxR@a$2*FlJrF~##hk2iy(RQFyi`X5W_tlcD-D97trNd zsTjmQnh$Ev{G3SpcTVC6&8L93QhKFVZWN$9Hn%q-PQL}-xK8U>wkFH?GA$d#;mxQg zT1ezDN%I|=?#CQb%E2=A#e4v~BTv^MBAr=Zv50p#C*`#D0Mi zLHs8xJ zSc;Pu2f|BNc@+-WXY{0#Li{PfFBiN@*@U$ z{7h#VUVSsjdLD3@TF6;#=D*XN?nW6&y8B-e75_3&LyCJ)QeqKxr|xf)m=m%vd`fSR zxwoQ!tKfv%sba{LGonxaC1dv=M*r{q#+n^=?&WrKahhoLYw@&FLpCkwnE5C4)GlL$ z>_*JuW=eb__>!NbV*0xEbgIEzt(u><@HG6;^4apwxL?An4snZzF8;e_H?IEIiLFMi zKN?VG%a@MLb22_2bMgP9%UE~GvCAppTUJ>?M~iNSZZ|!Xr&i;M#dW`qmsVlNyUrR=>Z-Re>pKiev%1bPc zgHV`G1pi7qHKiVASPZCO{JVZ*bi>BvN-fWNB?qgVHlPB!Z({jZ-!CrcubYN(=AU@I zx>2n4v2%Xt@*MEB&-@icddpW_1gP^j2tP}0g*0|fkitI;=3QZkZ|g&umFV@ij8*8m z*6*{rV~|2aKYV?xr?USli+m{?!(n2DJ?F?_%z#}Lie3Nk73cb&t@XuVc51+#^Cl9v z;!N9@8^*=kp*j}Bzk;4Z15cK!)@zF-mxc#mqlfu28Zk7Vy3rsk;8ZBB&42@l0voJ-qkwpIW;Ao)s|%6$F?P_{@nQ z?a$)=cX45T8&fxH$gQjXQEB|64xb9gRQvw*I2!*Sie9)FgY3Eg78U!C_}P`m#GLe8 zXyG5~E&q^LFbTsLygT}X;(t2K*#pcffoZ=(|35GLpU1qv4c>P8N%*&8Bz5@zmc~l- zw&wny7oYaq;QtF+TU7AfF;d=tsQLZTw~o7*rA&HAZ{i={{$s`Rzr*z(LHB=I8ts3F p>%YS6f1~R^v&#R^li|@X9DSt!(cx=X|67=ky0W%Xm4bEH{{d<02)O_N literal 0 HcmV?d00001 diff --git a/docs/index.html b/docs/index.html index 95a69476..5808c8f2 100644 --- a/docs/index.html +++ b/docs/index.html @@ -83,6 +83,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/index.xml b/docs/index.xml index 5cc6df8c..f9314eb0 100644 --- a/docs/index.xml +++ b/docs/index.xml @@ -6,11 +6,27 @@ Recent content on WTF - A Terminal Dashboard Hugo -- gohugo.io en-us - Sat, 16 Jun 2018 14:22:18 -0700 + Wed, 27 Jun 2018 15:55:42 -0700 + + Gerrit + https://wtfutil.com/posts/modules/gerrit/ + Wed, 27 Jun 2018 15:55:42 -0700 + + https://wtfutil.com/posts/modules/gerrit/ + Displays information about your projects hosted on Gerrit: +Open Incoming Reviews All open reviews that are requesting your approval. +My Outgoing Reviews All open reviews created by you. +Source Code wtf/gerrit/ Required ENV Variables Key: WTF_GERRIT_PASSWORD Action: Your Gerrit HTTP Password. +Keyboard Commands Key: / Action: Open/close the widget&rsquo;s help window. +Key: h Action: Show the previous project. +Key: l Action: Show the next project. +Key: ← Action: Show the previous project. + + Logger https://wtfutil.com/posts/modules/logger/ @@ -144,7 +160,7 @@ summary enabled Determines whether or not this module is executed and if its dat Compare crypto currencies using CryptoCompare. Source Code wtf/cryptoexchanges/cryptolive/ Required ENV Vars None. Keyboard Commands None. -Configuration cryptolive:colors:from:name:coraldisplayName:greyto:name:whiteprice:greencurrencies:BTC:displayName:Bitcointo:-USD-EUR-ETHETH:displayName:Ethereumto:-USD-EUR-ETHenabled:trueposition:top:5left:2height:1width:2updateInterval:15 Attributes colors.from.name Values: Any X11 color name. +Configuration cryptolive:enabled:trueposition:top:5left:2height:1width:2updateInterval:15currencies:BTC:displayName:Bitcointo:-USD-EUR-ETH-LTC-DOGELTC:displayName:Ethereumto:-USD-EUR-BTCtop:BTC:displayName:Bitcoinlimit:5to:-USDcolors:from:name:coraldisplayName:greyto:name:whiteprice:greentop:from:name:greydisplayName:coralto:name:redfield:whitevalue:green Attributes colors.from.name Values: Any X11 color name. colors.from.dispayName Values: Any X11 color name. colors.to.name Values: Any X11 color name. colors.to.price Values: Any X11 color name. diff --git a/docs/posts/configuration/attributes/index.html b/docs/posts/configuration/attributes/index.html index 4693df6e..8ad20352 100644 --- a/docs/posts/configuration/attributes/index.html +++ b/docs/posts/configuration/attributes/index.html @@ -82,6 +82,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/configuration/index.html b/docs/posts/configuration/index.html index 03874b43..3fb8a0a2 100644 --- a/docs/posts/configuration/index.html +++ b/docs/posts/configuration/index.html @@ -82,6 +82,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/configuration/iterm2/index.html b/docs/posts/configuration/iterm2/index.html index 9c2711fa..428b0980 100644 --- a/docs/posts/configuration/iterm2/index.html +++ b/docs/posts/configuration/iterm2/index.html @@ -82,6 +82,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/glossary/index.html b/docs/posts/glossary/index.html index 3395b0f7..31e18fdf 100644 --- a/docs/posts/glossary/index.html +++ b/docs/posts/glossary/index.html @@ -82,6 +82,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/index.html b/docs/posts/index.html index a2487887..837c5820 100644 --- a/docs/posts/index.html +++ b/docs/posts/index.html @@ -84,6 +84,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + @@ -119,6 +120,13 @@ height="0" width="0" style="display:none;visibility:hidden">

Posts

  • + + Gerrit + + + + +
  • Logger diff --git a/docs/posts/index.xml b/docs/posts/index.xml index 490c9f72..ed56af43 100644 --- a/docs/posts/index.xml +++ b/docs/posts/index.xml @@ -6,11 +6,27 @@ Recent content in Posts on WTF - A Terminal Dashboard Hugo -- gohugo.io en-us - Sat, 16 Jun 2018 14:22:18 -0700 + Wed, 27 Jun 2018 15:55:42 -0700 + + Gerrit + https://wtfutil.com/posts/modules/gerrit/ + Wed, 27 Jun 2018 15:55:42 -0700 + + https://wtfutil.com/posts/modules/gerrit/ + Displays information about your projects hosted on Gerrit: +Open Incoming Reviews All open reviews that are requesting your approval. +My Outgoing Reviews All open reviews created by you. +Source Code wtf/gerrit/ Required ENV Variables Key: WTF_GERRIT_PASSWORD Action: Your Gerrit HTTP Password. +Keyboard Commands Key: / Action: Open/close the widget&rsquo;s help window. +Key: h Action: Show the previous project. +Key: l Action: Show the next project. +Key: ← Action: Show the previous project. + + Logger https://wtfutil.com/posts/modules/logger/ @@ -144,7 +160,7 @@ summary enabled Determines whether or not this module is executed and if its dat Compare crypto currencies using CryptoCompare. Source Code wtf/cryptoexchanges/cryptolive/ Required ENV Vars None. Keyboard Commands None. -Configuration cryptolive:colors:from:name:coraldisplayName:greyto:name:whiteprice:greencurrencies:BTC:displayName:Bitcointo:-USD-EUR-ETHETH:displayName:Ethereumto:-USD-EUR-ETHenabled:trueposition:top:5left:2height:1width:2updateInterval:15 Attributes colors.from.name Values: Any X11 color name. +Configuration cryptolive:enabled:trueposition:top:5left:2height:1width:2updateInterval:15currencies:BTC:displayName:Bitcointo:-USD-EUR-ETH-LTC-DOGELTC:displayName:Ethereumto:-USD-EUR-BTCtop:BTC:displayName:Bitcoinlimit:5to:-USDcolors:from:name:coraldisplayName:greyto:name:whiteprice:greentop:from:name:greydisplayName:coralto:name:redfield:whitevalue:green Attributes colors.from.name Values: Any X11 color name. colors.from.dispayName Values: Any X11 color name. colors.to.name Values: Any X11 color name. colors.to.price Values: Any X11 color name. diff --git a/docs/posts/installation/index.html b/docs/posts/installation/index.html index 8f9e574e..3d1a2a0e 100644 --- a/docs/posts/installation/index.html +++ b/docs/posts/installation/index.html @@ -82,6 +82,7 @@ height="0" width="0" style="display:none;visibility:hidden">
  • + diff --git a/docs/posts/modules/bamboohr/index.html b/docs/posts/modules/bamboohr/index.html index fa4ad8dd..ead69742 100644 --- a/docs/posts/modules/bamboohr/index.html +++ b/docs/posts/modules/bamboohr/index.html @@ -82,6 +82,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/circleci/index.html b/docs/posts/modules/circleci/index.html index fc921b7d..506d3835 100644 --- a/docs/posts/modules/circleci/index.html +++ b/docs/posts/modules/circleci/index.html @@ -82,6 +82,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/clocks/index.html b/docs/posts/modules/clocks/index.html index fc7444cf..a7a3fcc7 100644 --- a/docs/posts/modules/clocks/index.html +++ b/docs/posts/modules/clocks/index.html @@ -82,6 +82,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/cmdrunner/index.html b/docs/posts/modules/cmdrunner/index.html index f73e20a6..e23d45cc 100644 --- a/docs/posts/modules/cmdrunner/index.html +++ b/docs/posts/modules/cmdrunner/index.html @@ -82,6 +82,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/cryptocurrencies/bittrex/index.html b/docs/posts/modules/cryptocurrencies/bittrex/index.html index d9ad0f03..1496dbfe 100644 --- a/docs/posts/modules/cryptocurrencies/bittrex/index.html +++ b/docs/posts/modules/cryptocurrencies/bittrex/index.html @@ -82,6 +82,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/cryptocurrencies/blockfolio/index.html b/docs/posts/modules/cryptocurrencies/blockfolio/index.html index fe130623..5e67be49 100644 --- a/docs/posts/modules/cryptocurrencies/blockfolio/index.html +++ b/docs/posts/modules/cryptocurrencies/blockfolio/index.html @@ -82,6 +82,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/cryptocurrencies/cryptolive/index.html b/docs/posts/modules/cryptocurrencies/cryptolive/index.html index 6e5ea50c..8fe0beda 100644 --- a/docs/posts/modules/cryptocurrencies/cryptolive/index.html +++ b/docs/posts/modules/cryptocurrencies/cryptolive/index.html @@ -82,6 +82,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + @@ -151,33 +152,49 @@ height="0" width="0" style="display:none;visibility:hidden">

    Configuration

    cryptolive:
    -  colors:
    +  enabled: true
    +  position:
    +    top: 5
    +    left: 2
    +    height: 1
    +    width: 2  
    +  updateInterval: 15
    +  currencies: 
    +    BTC:
    +      displayName: Bitcoin
    +      to: 
    +        - USD
    +        - EUR
    +        - ETH
    +        - LTC
    +        - DOGE
    +    LTC:
    +      displayName: Ethereum
    +      to: 
    +        - USD
    +        - EUR
    +        - BTC
    +  top:
    +    BTC:
    +      displayName: Bitcoin
    +      limit: 5
    +      to:
    +        - USD
    +  colors: 
         from:
           name: coral
           displayName: grey
         to:
           name: white
           price: green
    -  currencies:
    -    BTC:
    -      displayName: Bitcoin
    +    top:
    +      from:
    +        name: grey
    +        displayName: coral
           to:
    -        - USD
    -        - EUR
    -        - ETH
    -    ETH:
    -      displayName: Ethereum
    -      to:
    -        - USD
    -        - EUR
    -        - ETH
    -  enabled: true
    -  position:
    -    top: 5
    -    left: 2
    -    height: 1
    -    width: 2
    -  updateInterval: 15
    + name: red + field: white + value: green

    Attributes

    colors.from.name
    diff --git a/docs/posts/modules/gcal/index.html b/docs/posts/modules/gcal/index.html index 89c2960a..9c80a9b9 100644 --- a/docs/posts/modules/gcal/index.html +++ b/docs/posts/modules/gcal/index.html @@ -82,6 +82,7 @@ height="0" width="0" style="display:none;visibility:hidden">

    + diff --git a/docs/posts/modules/gerrit/index.html b/docs/posts/modules/gerrit/index.html new file mode 100644 index 00000000..ec7e5f5a --- /dev/null +++ b/docs/posts/modules/gerrit/index.html @@ -0,0 +1,225 @@ + + + + + + + + + + + + +Gerrit | WTF - A Terminal Dashboard + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +

    Gerrit

    + +
    + +
    + + + +

    gerrit screenshot

    + +

    Displays information about your projects hosted on Gerrit:

    + +

    Open Incoming Reviews

    + +

    All open reviews that are requesting your approval.

    + +

    My Outgoing Reviews

    + +

    All open reviews created by you.

    + +

    Source Code

    +
    wtf/gerrit/
    +

    Required ENV Variables

    + +

    Key: WTF_GERRIT_PASSWORD
    +Action: Your Gerrit HTTP Password.

    + +

    Keyboard Commands

    + +

    Key: /
    +Action: Open/close the widget’s help window.

    + +

    Key: h
    +Action: Show the previous project.

    + +

    Key: l
    +Action: Show the next project.

    + +

    Key:
    +Action: Show the previous project.

    + +

    Key:
    +Action: Show the next project.

    + +

    Configuration

    +
    gerrit:
    +  enabled: true
    +  position:
    +    top: 2
    +    left: 3
    +    height: 2
    +    width: 2
    +  refreshInterval: 300
    +  projects:
    +  - org/test-project"
    +  - dotfiles
    +  username: "myname"
    +

    Attributes

    + +

    enabled
    +Determines whether or not this module is executed and if its data displayed onscreen.
    +Values: true, false.

    + +

    position
    +Defines where in the grid this module’s widget will be displayed.

    + +

    refreshInterval
    +How often, in seconds, this module will update its data.
    +Values: A positive integer, 0..n.

    + +

    domain
    +Optional. Your Gerrit corporate domain.
    +Values: A valid URI.

    + +

    projects
    +A list of Gerrit project names to fetch data for.

    + +

    username
    +Your Gerrit username.

    + +

    verifyServerCertificate
    +Optional
    +Determines whether or not the server’s certificate chain and host name are verified.
    +Values: true, false.

    + +
    + + +
    + + + + diff --git a/docs/posts/modules/git/index.html b/docs/posts/modules/git/index.html index 914e1135..f90b4e13 100644 --- a/docs/posts/modules/git/index.html +++ b/docs/posts/modules/git/index.html @@ -82,6 +82,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/github/index.html b/docs/posts/modules/github/index.html index 101b4a39..4580bea5 100644 --- a/docs/posts/modules/github/index.html +++ b/docs/posts/modules/github/index.html @@ -82,6 +82,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/gitlab/index.html b/docs/posts/modules/gitlab/index.html index f2cc6795..e0e91c79 100644 --- a/docs/posts/modules/gitlab/index.html +++ b/docs/posts/modules/gitlab/index.html @@ -82,6 +82,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/gspreadsheet/index.html b/docs/posts/modules/gspreadsheet/index.html index 531b940b..48726ddc 100644 --- a/docs/posts/modules/gspreadsheet/index.html +++ b/docs/posts/modules/gspreadsheet/index.html @@ -82,6 +82,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/index.html b/docs/posts/modules/index.html index 41821a23..86ea00db 100644 --- a/docs/posts/modules/index.html +++ b/docs/posts/modules/index.html @@ -82,6 +82,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/ipapi/index.html b/docs/posts/modules/ipapi/index.html index 76070be3..77be17f2 100644 --- a/docs/posts/modules/ipapi/index.html +++ b/docs/posts/modules/ipapi/index.html @@ -82,6 +82,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/ipinfo/index.html b/docs/posts/modules/ipinfo/index.html index c7c4f848..735a03fd 100644 --- a/docs/posts/modules/ipinfo/index.html +++ b/docs/posts/modules/ipinfo/index.html @@ -82,6 +82,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/jenkins/index.html b/docs/posts/modules/jenkins/index.html index 5c75a711..6bd495bf 100644 --- a/docs/posts/modules/jenkins/index.html +++ b/docs/posts/modules/jenkins/index.html @@ -82,6 +82,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/jira/index.html b/docs/posts/modules/jira/index.html index 823d7260..a670349b 100644 --- a/docs/posts/modules/jira/index.html +++ b/docs/posts/modules/jira/index.html @@ -82,6 +82,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/logger/index.html b/docs/posts/modules/logger/index.html index b7ddaf34..9f9d0f24 100644 --- a/docs/posts/modules/logger/index.html +++ b/docs/posts/modules/logger/index.html @@ -82,6 +82,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/newrelic/index.html b/docs/posts/modules/newrelic/index.html index 6c4ef130..b8d9b8af 100644 --- a/docs/posts/modules/newrelic/index.html +++ b/docs/posts/modules/newrelic/index.html @@ -82,6 +82,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/opsgenie/index.html b/docs/posts/modules/opsgenie/index.html index 014f7132..d85f1fbd 100644 --- a/docs/posts/modules/opsgenie/index.html +++ b/docs/posts/modules/opsgenie/index.html @@ -82,6 +82,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/power/index.html b/docs/posts/modules/power/index.html index f201f50b..106b6276 100644 --- a/docs/posts/modules/power/index.html +++ b/docs/posts/modules/power/index.html @@ -82,6 +82,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/prettyweather/index.html b/docs/posts/modules/prettyweather/index.html index af6ab8f0..fc8807ae 100644 --- a/docs/posts/modules/prettyweather/index.html +++ b/docs/posts/modules/prettyweather/index.html @@ -82,6 +82,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/security/index.html b/docs/posts/modules/security/index.html index 844eeab0..1c3892e3 100644 --- a/docs/posts/modules/security/index.html +++ b/docs/posts/modules/security/index.html @@ -82,6 +82,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/textfile/index.html b/docs/posts/modules/textfile/index.html index 1575885c..37684dfa 100644 --- a/docs/posts/modules/textfile/index.html +++ b/docs/posts/modules/textfile/index.html @@ -82,6 +82,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/todo/index.html b/docs/posts/modules/todo/index.html index e60fc6c9..64eabf95 100644 --- a/docs/posts/modules/todo/index.html +++ b/docs/posts/modules/todo/index.html @@ -82,6 +82,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/trello/index.html b/docs/posts/modules/trello/index.html index e72ba604..30f0dc0d 100644 --- a/docs/posts/modules/trello/index.html +++ b/docs/posts/modules/trello/index.html @@ -82,6 +82,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/weather/index.html b/docs/posts/modules/weather/index.html index c7daf446..c9e28f90 100644 --- a/docs/posts/modules/weather/index.html +++ b/docs/posts/modules/weather/index.html @@ -82,6 +82,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/overview/index.html b/docs/posts/overview/index.html index cf0b869e..f77aaaef 100644 --- a/docs/posts/overview/index.html +++ b/docs/posts/overview/index.html @@ -82,6 +82,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/sitemap.xml b/docs/sitemap.xml index 26ce6ad7..8e94ebea 100644 --- a/docs/sitemap.xml +++ b/docs/sitemap.xml @@ -2,6 +2,11 @@ + + https://wtfutil.com/posts/modules/gerrit/ + 2018-06-27T15:55:42-07:00 + + https://wtfutil.com/posts/modules/logger/ 2018-06-16T14:22:18-07:00 @@ -174,7 +179,7 @@ https://wtfutil.com/posts/ - 2018-06-16T14:22:18-07:00 + 2018-06-27T15:55:42-07:00 0 @@ -185,7 +190,7 @@ https://wtfutil.com/ - 2018-06-16T14:22:18-07:00 + 2018-06-27T15:55:42-07:00 0 diff --git a/docs/tags/index.html b/docs/tags/index.html index cf7e7563..3a799d0c 100644 --- a/docs/tags/index.html +++ b/docs/tags/index.html @@ -84,6 +84,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + From bbb5d9a1da148ec4d487654f59af7dc14467f193 Mon Sep 17 00:00:00 2001 From: Anand Sudhir Prayaga Date: Thu, 28 Jun 2018 16:50:49 +0200 Subject: [PATCH 04/21] Update dependencies --- Gopkg.lock | 12 ++--- vendor/github.com/adlio/trello/label.go | 2 +- .../andygrunwald/go-gerrit/changes.go | 2 +- vendor/github.com/olebedev/config/doc.go | 2 +- vendor/github.com/rivo/tview/application.go | 45 ++++++++----------- vendor/github.com/xanzy/go-gitlab/issues.go | 2 +- 6 files changed, 29 insertions(+), 36 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index c429c1fe..9211154d 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -17,7 +17,7 @@ branch = "master" name = "github.com/andygrunwald/go-gerrit" packages = ["."] - revision = "5632c7fad122548dfabeb3011e98d3b0a08a89d7" + revision = "5ea603106b4869cc3f38322ee17d5fc1360ee1e3" [[projects]] branch = "master" @@ -110,7 +110,7 @@ branch = "master" name = "github.com/rivo/tview" packages = ["."] - revision = "306abd9cb98c97417ab6c58aa0400b2e5daac88b" + revision = "83483397e826c343edb7b8c1f33fb7983dda9fc5" [[projects]] name = "github.com/stretchr/testify" @@ -119,10 +119,10 @@ version = "v1.2.2" [[projects]] + branch = "master" name = "github.com/xanzy/go-gitlab" packages = ["."] - revision = "5c6e84fea386746fd31ff46da2253f6b7ed7dce2" - version = "v0.10.6" + revision = "6ada444068f460636db26ca0dd66cf2aa518fa8f" [[projects]] branch = "master" @@ -137,7 +137,7 @@ "context", "context/ctxhttp" ] - revision = "afe8f62b1d6bbd81f31868121a50b06d8188e1f9" + revision = "e514e69ffb8bc3c76a71ae40de0118d794855992" [[projects]] branch = "master" @@ -173,7 +173,7 @@ "googleapi/internal/uritemplates", "sheets/v4" ] - revision = "3639d6d93f377f39a1de765fa4ef37b3c7ca8bd9" + revision = "082d671a2a341aa205b22b580fa69747dcc3cdc8" [[projects]] name = "google.golang.org/appengine" diff --git a/vendor/github.com/adlio/trello/label.go b/vendor/github.com/adlio/trello/label.go index 343542a2..c805b1f2 100644 --- a/vendor/github.com/adlio/trello/label.go +++ b/vendor/github.com/adlio/trello/label.go @@ -25,4 +25,4 @@ func (b *Board) GetLabels(args Arguments) (labels []*Label, err error) { path := fmt.Sprintf("boards/%s/labels", b.ID) err = b.client.Get(path, args, &labels) return -} +} \ No newline at end of file diff --git a/vendor/github.com/andygrunwald/go-gerrit/changes.go b/vendor/github.com/andygrunwald/go-gerrit/changes.go index db5f6a72..301819f5 100644 --- a/vendor/github.com/andygrunwald/go-gerrit/changes.go +++ b/vendor/github.com/andygrunwald/go-gerrit/changes.go @@ -288,7 +288,7 @@ type ChangeInfo struct { Labels map[string]LabelInfo `json:"labels,omitempty"` PermittedLabels map[string][]string `json:"permitted_labels,omitempty"` RemovableReviewers []AccountInfo `json:"removable_reviewers,omitempty"` - Reviewers map[string][]AccountInfo `json:"removable_reviewers,omitempty"` + Reviewers map[string][]AccountInfo `json:"reviewers,omitempty"` Messages []ChangeMessageInfo `json:"messages,omitempty"` CurrentRevision string `json:"current_revision,omitempty"` Revisions map[string]RevisionInfo `json:"revisions,omitempty"` diff --git a/vendor/github.com/olebedev/config/doc.go b/vendor/github.com/olebedev/config/doc.go index d0ab6062..f1bd7f33 100644 --- a/vendor/github.com/olebedev/config/doc.go +++ b/vendor/github.com/olebedev/config/doc.go @@ -22,7 +22,7 @@ Let's start with a simple YAML file config.yml: We can parse it using ParseYaml(), which will return a *Config instance on success: - + file, err := ioutil.ReadFile("config.yml") if err != nil { panic(err) diff --git a/vendor/github.com/rivo/tview/application.go b/vendor/github.com/rivo/tview/application.go index 31821d9d..f8935781 100644 --- a/vendor/github.com/rivo/tview/application.go +++ b/vendor/github.com/rivo/tview/application.go @@ -41,8 +41,8 @@ type Application struct { // was drawn. afterDraw func(screen tcell.Screen) - // If this value is true, the application has entered suspended mode. - suspended bool + // Halts the event loop during suspended mode. + suspendMutex sync.Mutex } // NewApplication creates and returns a new application. @@ -103,28 +103,20 @@ func (a *Application) Run() error { // Start event loop. for { - a.Lock() + // Do not poll events during suspend mode + a.suspendMutex.Lock() + a.RLock() screen := a.screen - if a.suspended { - a.suspended = false // Clear previous suspended flag. - } - a.Unlock() + a.RUnlock() if screen == nil { + a.suspendMutex.Unlock() break } // Wait for next event. event := a.screen.PollEvent() + a.suspendMutex.Unlock() if event == nil { - a.Lock() - if a.suspended { - // This screen was renewed due to suspended mode. - a.suspended = false - a.Unlock() - continue // Resume. - } - a.Unlock() - // The screen was finalized. Exit the loop. break } @@ -158,9 +150,9 @@ func (a *Application) Run() error { } } case *tcell.EventResize: - a.Lock() + a.RLock() screen := a.screen - a.Unlock() + a.RUnlock() screen.Clear() a.Draw() } @@ -171,8 +163,8 @@ func (a *Application) Run() error { // Stop stops the application, causing Run() to return. func (a *Application) Stop() { - a.RLock() - defer a.RUnlock() + a.Lock() + defer a.Unlock() if a.screen == nil { return } @@ -188,17 +180,18 @@ func (a *Application) Stop() { // was called. If false is returned, the application was already suspended, // terminal UI mode was not exited, and "f" was not called. func (a *Application) Suspend(f func()) bool { - a.Lock() + a.RLock() - if a.suspended || a.screen == nil { - // Application is already suspended. - a.Unlock() + if a.screen == nil { + // Screen has not yet been initialized. + a.RUnlock() return false } // Enter suspended mode. - a.suspended = true - a.Unlock() + a.suspendMutex.Lock() + defer a.suspendMutex.Unlock() + a.RUnlock() a.Stop() // Deal with panics during suspended mode. Exit the program. diff --git a/vendor/github.com/xanzy/go-gitlab/issues.go b/vendor/github.com/xanzy/go-gitlab/issues.go index 411146a3..481721d9 100644 --- a/vendor/github.com/xanzy/go-gitlab/issues.go +++ b/vendor/github.com/xanzy/go-gitlab/issues.go @@ -310,7 +310,7 @@ type UpdateIssueOptions struct { Title *string `url:"title,omitempty" json:"title,omitempty"` Description *string `url:"description,omitempty" json:"description,omitempty"` Confidential *bool `url:"confidential,omitempty" json:"confidential,omitempty"` - AssigneeID *int `url:"assignee_id,omitempty" json:"assignee_id,omitempty"` + AssigneeIDs []int `url:"assignee_ids,omitempty" json:"assignee_ids,omitempty"` MilestoneID *int `url:"milestone_id,omitempty" json:"milestone_id,omitempty"` Labels Labels `url:"labels,comma,omitempty" json:"labels,omitempty"` StateEvent *string `url:"state_event,omitempty" json:"state_event,omitempty"` From 5de4dd65a5987fac9ef637c068928119fc3a0760 Mon Sep 17 00:00:00 2001 From: Chris Cummer Date: Thu, 28 Jun 2018 17:40:36 -0700 Subject: [PATCH 05/21] Formatting fixes --- cfg/config_files.go | 5 ++--- cryptoexchanges/blockfolio/widget.go | 12 ++++++------ jenkins/client.go | 3 +-- jenkins/view.go | 2 +- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/cfg/config_files.go b/cfg/config_files.go index 21eb1a67..5bce14f3 100644 --- a/cfg/config_files.go +++ b/cfg/config_files.go @@ -81,11 +81,10 @@ func CreateConfigFile() { } // If the file is empty, write to it - file, err := os.Stat(filePath) + file, _ := os.Stat(filePath) if file.Size() == 0 { - err = ioutil.WriteFile(filePath, []byte(simpleConfig), 0644) - if err != nil { + if ioutil.WriteFile(filePath, []byte(simpleConfig), 0644) != nil { panic(err) } } diff --git a/cryptoexchanges/blockfolio/widget.go b/cryptoexchanges/blockfolio/widget.go index 72349738..cb6906c6 100644 --- a/cryptoexchanges/blockfolio/widget.go +++ b/cryptoexchanges/blockfolio/widget.go @@ -73,15 +73,15 @@ func contentFrom(positions *AllPositionsResponse) string { const magic = "edtopjhgn2345piuty89whqejfiobh89-2q453" type Position struct { - Coin string `json:coin` - LastPriceFiat float32 `json:lastPriceFiat` - TwentyFourHourPercentChangeFiat float32 `json:twentyFourHourPercentChangeFiat` - Quantity float32 `json:quantity` - HoldingValueFiat float32 `json:holdingValueFiat` + Coin string `json:"coin"` + LastPriceFiat float32 `json:"lastPriceFiat"` + TwentyFourHourPercentChangeFiat float32 `json:"twentyFourHourPercentChangeFiat"` + Quantity float32 `json:"quantity"` + HoldingValueFiat float32 `json:"holdingValueFiat"` } type AllPositionsResponse struct { - PositionList []Position `json:positionList` + PositionList []Position `json:"positionList"` } func MakeApiRequest(token string, method string) ([]byte, error) { diff --git a/jenkins/client.go b/jenkins/client.go index e599b9f7..ddad50d6 100644 --- a/jenkins/client.go +++ b/jenkins/client.go @@ -23,7 +23,7 @@ func Create(jenkinsURL string, username string, apiKey string) (*View, error) { } jenkinsAPIURL := parsedJenkinsURL.ResolveReference(parsedSuffix) - req, err := http.NewRequest("GET", jenkinsAPIURL.String(), nil) + req, _ := http.NewRequest("GET", jenkinsAPIURL.String(), nil) req.SetBasicAuth(username, apiKey) httpClient := &http.Client{} @@ -45,7 +45,6 @@ func ensureLastSlash(URL string) string { /* -------------------- Unexported Functions -------------------- */ - func parseJson(obj interface{}, text io.Reader) { jsonStream, err := ioutil.ReadAll(text) if err != nil { diff --git a/jenkins/view.go b/jenkins/view.go index 03a7f6ed..11c079ad 100644 --- a/jenkins/view.go +++ b/jenkins/view.go @@ -6,5 +6,5 @@ type View struct { Jobs []Job `json:"jobs"` Name string `json:"name"` Property []string `json:"property"` - url string `json:"url"` + Url string `json:"url"` } From 5c4634f93a19db8adb101c68eb19c4e2ff2f5b86 Mon Sep 17 00:00:00 2001 From: Chris Cummer Date: Thu, 28 Jun 2018 17:49:40 -0700 Subject: [PATCH 06/21] Tweaking minor code issues --- cryptoexchanges/blockfolio/widget.go | 4 ++-- {wtf_tests => wtftests}/bargraph/bargraph_test.go | 0 {wtf_tests => wtftests}/colors_test.go | 2 +- {wtf_tests => wtftests}/datetime_test.go | 2 +- {wtf_tests => wtftests}/position_test.go | 2 +- {wtf_tests => wtftests}/utils_test.go | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) rename {wtf_tests => wtftests}/bargraph/bargraph_test.go (100%) rename {wtf_tests => wtftests}/colors_test.go (93%) rename {wtf_tests => wtftests}/datetime_test.go (96%) rename {wtf_tests => wtftests}/position_test.go (96%) rename {wtf_tests => wtftests}/utils_test.go (99%) diff --git a/cryptoexchanges/blockfolio/widget.go b/cryptoexchanges/blockfolio/widget.go index cb6906c6..c915057e 100644 --- a/cryptoexchanges/blockfolio/widget.go +++ b/cryptoexchanges/blockfolio/widget.go @@ -105,10 +105,10 @@ func MakeApiRequest(token string, method string) ([]byte, error) { } func GetAllPositions(token string) (*AllPositionsResponse, error) { - jsn, err := MakeApiRequest(token, "get_all_positions") + jsn, _ := MakeApiRequest(token, "get_all_positions") var parsed AllPositionsResponse - err = json.Unmarshal(jsn, &parsed) + err := json.Unmarshal(jsn, &parsed) if err != nil { log.Fatalf("Failed to parse json %v", err) return nil, err diff --git a/wtf_tests/bargraph/bargraph_test.go b/wtftests/bargraph/bargraph_test.go similarity index 100% rename from wtf_tests/bargraph/bargraph_test.go rename to wtftests/bargraph/bargraph_test.go diff --git a/wtf_tests/colors_test.go b/wtftests/colors_test.go similarity index 93% rename from wtf_tests/colors_test.go rename to wtftests/colors_test.go index a52da11b..79344ebd 100644 --- a/wtf_tests/colors_test.go +++ b/wtftests/colors_test.go @@ -1,4 +1,4 @@ -package wtf_tests +package wtftests import ( "testing" diff --git a/wtf_tests/datetime_test.go b/wtftests/datetime_test.go similarity index 96% rename from wtf_tests/datetime_test.go rename to wtftests/datetime_test.go index 8415d345..a9159d07 100644 --- a/wtf_tests/datetime_test.go +++ b/wtftests/datetime_test.go @@ -1,4 +1,4 @@ -package wtf_tests +package wtftests import ( "testing" diff --git a/wtf_tests/position_test.go b/wtftests/position_test.go similarity index 96% rename from wtf_tests/position_test.go rename to wtftests/position_test.go index 370a3373..336bc964 100644 --- a/wtf_tests/position_test.go +++ b/wtftests/position_test.go @@ -1,4 +1,4 @@ -package wtf_tests +package wtftests import ( "testing" diff --git a/wtf_tests/utils_test.go b/wtftests/utils_test.go similarity index 99% rename from wtf_tests/utils_test.go rename to wtftests/utils_test.go index d78425cc..b1aa7994 100644 --- a/wtf_tests/utils_test.go +++ b/wtftests/utils_test.go @@ -1,4 +1,4 @@ -package wtf_tests +package wtftests import ( "testing" From 18b657a8611cefb41f4b150091f0d83705ca6187 Mon Sep 17 00:00:00 2001 From: Chris Cummer Date: Thu, 28 Jun 2018 17:53:02 -0700 Subject: [PATCH 07/21] Add Go Report Card to readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8fbdf9a6..ab4e5848 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ - + # WTF From ea930d02b778e3a0caa3b07d47cb01f516a57542 Mon Sep 17 00:00:00 2001 From: Chris Cummer Date: Thu, 28 Jun 2018 17:55:44 -0700 Subject: [PATCH 08/21] Fix path in travis config --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 05f6591d..f8b7c5df 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,4 +9,4 @@ before_install: - export TRAVIS_BUILD_DIR=$HOME/gopath/src/github.com/senorprogrammer/wtf - cd $HOME/gopath/src/github.com/senorprogrammer/wtf -script: go get ./... && go get github.com/go-test/deep && go test -v github.com/senorprogrammer/wtf/wtf_tests/... +script: go get ./... && go get github.com/go-test/deep && go test -v github.com/senorprogrammer/wtf/wtftests/... From 3f010f8a9fdec349338d6d9e73020a00f279e6f3 Mon Sep 17 00:00:00 2001 From: Chris Cummer Date: Fri, 29 Jun 2018 05:14:17 -0700 Subject: [PATCH 09/21] Refactor GCal calendar code to simplify things --- gcal/display.go | 219 +++++++++++++++++++++++++++++++++++++++++++++++ gcal/widget.go | 220 ++---------------------------------------------- 2 files changed, 225 insertions(+), 214 deletions(-) create mode 100644 gcal/display.go diff --git a/gcal/display.go b/gcal/display.go new file mode 100644 index 00000000..7ad94464 --- /dev/null +++ b/gcal/display.go @@ -0,0 +1,219 @@ +package gcal + +import ( + "fmt" + "regexp" + "strings" + "time" + + "github.com/senorprogrammer/wtf/wtf" + "google.golang.org/api/calendar/v3" +) + +func (widget *Widget) display() { + if widget.events == nil || len(widget.events.Items) == 0 { + return + } + + widget.mutex.Lock() + defer widget.mutex.Unlock() + + widget.View.SetText(widget.contentFrom(widget.events)) +} + +func (widget *Widget) contentFrom(events *calendar.Events) string { + if events == nil { + return "" + } + + var prevEvent *calendar.Event + + str := "" + + for _, event := range events.Items { + timestamp := fmt.Sprintf("[%s]%s", + widget.descriptionColor(event), + widget.eventTimestamp(event)) + + title := fmt.Sprintf("[%s]%s", + widget.titleColor(event), + widget.eventSummary(event, widget.conflicts(event, events))) + + lineOne := fmt.Sprintf( + "%s %s %s %s %s[white]", + widget.dayDivider(event, prevEvent), + widget.responseIcon(event), + timestamp, + title, + widget.timeUntil(event), + ) + + str = str + fmt.Sprintf("%s%s\n\n", + lineOne, + widget.location(event), // prefixes newline if non-empty + ) + + prevEvent = event + } + + return str +} + +func (widget *Widget) dayDivider(event, prevEvent *calendar.Event) string { + var prevStartTime time.Time + + if prevEvent != nil { + prevStartTime, _ = time.Parse(time.RFC3339, prevEvent.Start.DateTime) + } + + currStartTime, _ := time.Parse(time.RFC3339, event.Start.DateTime) + + if currStartTime.Day() != prevStartTime.Day() { + _, _, width, _ := widget.View.GetInnerRect() + + return fmt.Sprintf("[%s]", wtf.Config.UString("wtf.mods.gcal.colors.day", "forestgreen")) + + wtf.CenterText(currStartTime.Format(wtf.FullDateFormat), width) + + "\n" + } + + return "" +} + +func (widget *Widget) descriptionColor(event *calendar.Event) string { + color := wtf.Config.UString("wtf.mods.gcal.colors.description", "white") + + if widget.eventIsPast(event) { + color = wtf.Config.UString("wtf.mods.gcal.colors.past", "gray") + } + + return color +} + +func (widget *Widget) eventSummary(event *calendar.Event, conflict bool) string { + summary := event.Summary + + if widget.eventIsNow(event) { + summary = fmt.Sprintf( + "%s %s", + wtf.Config.UString("wtf.mods.gcal.currentIcon", "🔸"), + event.Summary, + ) + } + + if conflict { + return fmt.Sprintf("%s %s", wtf.Config.UString("wtf.mods.gcal.conflictIcon", "🚨"), summary) + } else { + return summary + } +} + +func (widget *Widget) eventTimestamp(event *calendar.Event) string { + if widget.eventIsAllDay(event) { + startTime, _ := time.Parse("2006-01-02", event.Start.Date) + return startTime.Format(wtf.FriendlyDateFormat) + } else { + startTime, _ := time.Parse(time.RFC3339, event.Start.DateTime) + return startTime.Format(wtf.MinimumTimeFormat) + } +} + +// timeUuntil returns the number of hours or days until the event +// If the event is in the past, returns nil +func (widget *Widget) timeUntil(event *calendar.Event) string { + startTime, _ := time.Parse(time.RFC3339, event.Start.DateTime) + duration := time.Until(startTime).Round(time.Minute) + + if duration < 0 { + return "" + } + + days := duration / (24 * time.Hour) + duration -= days * (24 * time.Hour) + + hours := duration / time.Hour + duration -= hours * time.Hour + + mins := duration / time.Minute + + untilStr := "" + + color := "[lightblue]" + if days > 0 { + untilStr = fmt.Sprintf("%dd", days) + } else if hours > 0 { + untilStr = fmt.Sprintf("%dh", hours) + } else { + untilStr = fmt.Sprintf("%dm", mins) + if mins < 30 { + color = "[red]" + } + } + + return color + untilStr + "[white]" +} + +func (widget *Widget) titleColor(event *calendar.Event) string { + color := wtf.Config.UString("wtf.mods.gcal.colors.title", "white") + + for _, untypedArr := range wtf.Config.UList("wtf.mods.gcal.colors.highlights") { + highlightElements := wtf.ToStrs(untypedArr.([]interface{})) + + match, _ := regexp.MatchString( + strings.ToLower(highlightElements[0]), + strings.ToLower(event.Summary), + ) + + if match == true { + color = highlightElements[1] + } + } + + if widget.eventIsPast(event) { + color = wtf.Config.UString("wtf.mods.gcal.colors.past", "gray") + } + + return color +} + +func (widget *Widget) location(event *calendar.Event) string { + if wtf.Config.UBool("wtf.mods.gcal.displayLocation", true) == false { + return "" + } + + if event.Location == "" { + return "" + } + + return fmt.Sprintf( + "\n [%s]%s", + widget.descriptionColor(event), + event.Location, + ) +} + +func (widget *Widget) responseIcon(event *calendar.Event) string { + if false == wtf.Config.UBool("wtf.mods.gcal.displayResponseStatus", true) { + return "" + } + + for _, attendee := range event.Attendees { + if attendee.Email == wtf.Config.UString("wtf.mods.gcal.email") { + icon := "[gray]" + + switch attendee.ResponseStatus { + case "accepted": + return icon + "✔︎" + case "declined": + return icon + "✘" + case "needsAction": + return icon + "?" + case "tentative": + return icon + "~" + default: + return icon + " " + } + } + } + + return " " +} diff --git a/gcal/widget.go b/gcal/widget.go index b66ac960..28032ac6 100644 --- a/gcal/widget.go +++ b/gcal/widget.go @@ -1,9 +1,6 @@ package gcal import ( - "fmt" - "regexp" - "strings" "sync" "time" @@ -48,16 +45,6 @@ func (widget *Widget) Disable() { /* -------------------- Unexported Functions -------------------- */ -func (widget *Widget) display() { - if widget.events == nil || len(widget.events.Items) == 0 { - return - } - - widget.mutex.Lock() - defer widget.mutex.Unlock() - widget.View.SetText(widget.contentFrom(widget.events)) -} - // conflicts returns TRUE if this event conflicts with another, FALSE if it does not func (widget *Widget) conflicts(event *calendar.Event, events *calendar.Events) bool { conflict := false @@ -82,99 +69,8 @@ func (widget *Widget) conflicts(event *calendar.Event, events *calendar.Events) return conflict } -func (widget *Widget) contentFrom(events *calendar.Events) string { - if events == nil { - return "" - } - - var prevEvent *calendar.Event - - str := "" - for _, event := range events.Items { - conflict := widget.conflicts(event, events) - - dayDivider := widget.dayDivider(event, prevEvent) - responseIcon := widget.responseIcon(event) - timestamp := fmt.Sprintf("[%s]%s", - widget.descriptionColor(event), - widget.eventTimestamp(event)) - title := fmt.Sprintf("[%s]%s", - widget.titleColor(event), - widget.eventSummary(event, conflict)) - until := widget.until(event) - - lineOne := fmt.Sprintf( - "%s %s %s %s %s[white]", - dayDivider, - responseIcon, - timestamp, - title, - until, - ) - str = str + fmt.Sprintf("%s%s\n\n", - lineOne, - widget.location(event), // prefixes newline if non-empty - ) - - prevEvent = event - } - - return str -} - -func (widget *Widget) dayDivider(event, prevEvent *calendar.Event) string { - var prevStartTime time.Time - if prevEvent != nil { - prevStartTime, _ = time.Parse(time.RFC3339, prevEvent.Start.DateTime) - } - currStartTime, _ := time.Parse(time.RFC3339, event.Start.DateTime) - - if currStartTime.Day() != prevStartTime.Day() { - _, _, width, _ := widget.View.GetInnerRect() - return fmt.Sprintf("[%s]", wtf.Config.UString("wtf.mods.gcal.colors.day", "forestgreen")) + - wtf.CenterText(currStartTime.Format(wtf.FullDateFormat), width) + - "\n" - } - - return "" -} - -func (widget *Widget) descriptionColor(event *calendar.Event) string { - color := wtf.Config.UString("wtf.mods.gcal.colors.description", "white") - - if widget.eventIsPast(event) { - color = wtf.Config.UString("wtf.mods.gcal.colors.past", "gray") - } - - return color -} - -func (widget *Widget) eventSummary(event *calendar.Event, conflict bool) string { - summary := event.Summary - - if widget.eventIsNow(event) { - summary = fmt.Sprintf( - "%s %s", - wtf.Config.UString("wtf.mods.gcal.currentIcon", "🔸"), - event.Summary, - ) - } - - if conflict { - return fmt.Sprintf("%s %s", wtf.Config.UString("wtf.mods.gcal.conflictIcon", "🚨"), summary) - } else { - return summary - } -} - -func (widget *Widget) eventTimestamp(event *calendar.Event) string { - if len(event.Start.Date) > 0 { - startTime, _ := time.Parse("2006-01-02", event.Start.Date) - return startTime.Format(wtf.FriendlyDateFormat) - } else { - startTime, _ := time.Parse(time.RFC3339, event.Start.DateTime) - return startTime.Format(wtf.MinimumTimeFormat) - } +func (widget *Widget) eventIsAllDay(event *calendar.Event) bool { + return len(event.Start.Date) > 0 } // eventIsNow returns true if the event is happening now, false if it not @@ -186,116 +82,12 @@ func (widget *Widget) eventIsNow(event *calendar.Event) bool { } func (widget *Widget) eventIsPast(event *calendar.Event) bool { - ts, _ := time.Parse(time.RFC3339, event.Start.DateTime) - return (widget.eventIsNow(event) == false) && ts.Before(time.Now()) -} - -func (widget *Widget) titleColor(event *calendar.Event) string { - color := wtf.Config.UString("wtf.mods.gcal.colors.title", "white") - - for _, untypedArr := range wtf.Config.UList("wtf.mods.gcal.colors.highlights") { - highlightElements := wtf.ToStrs(untypedArr.([]interface{})) - - match, _ := regexp.MatchString( - strings.ToLower(highlightElements[0]), - strings.ToLower(event.Summary), - ) - - if match == true { - color = highlightElements[1] - } - } - - if widget.eventIsPast(event) { - color = wtf.Config.UString("wtf.mods.gcal.colors.past", "gray") - } - - return color -} - -func (widget *Widget) location(event *calendar.Event) string { - if wtf.Config.UBool("wtf.mods.gcal.displayLocation", true) == false { - return "" - } - - if event.Location == "" { - return "" - } - - return fmt.Sprintf( - "\n [%s]%s", - widget.descriptionColor(event), - event.Location, - ) -} - -func (widget *Widget) responseIcon(event *calendar.Event) string { - if false == wtf.Config.UBool("wtf.mods.gcal.displayResponseStatus", true) { - return "" - } - - response := "" - - for _, attendee := range event.Attendees { - if attendee.Email == wtf.Config.UString("wtf.mods.gcal.email") { - response = attendee.ResponseStatus - break - } - } - - icon := "[gray]" - - switch response { - case "accepted": - icon = icon + "✔︎" - case "declined": - icon = icon + "✘" - case "needsAction": - icon = icon + "?" - case "tentative": - icon = icon + "~" - default: - icon = icon + " " - } - - return icon -} - -// until returns the number of hours or days until the event -// If the event is in the past, returns nil -func (widget *Widget) until(event *calendar.Event) string { - startTime, _ := time.Parse(time.RFC3339, event.Start.DateTime) - duration := time.Until(startTime) - - duration = duration.Round(time.Minute) - - if duration < 0 { - return "" - } - - days := duration / (24 * time.Hour) - duration -= days * (24 * time.Hour) - - hours := duration / time.Hour - duration -= hours * time.Hour - - mins := duration / time.Minute - - untilStr := "" - - var color = "[lightblue]" - if days > 0 { - untilStr = fmt.Sprintf("%dd", days) - } else if hours > 0 { - untilStr = fmt.Sprintf("%dh", hours) + if widget.eventIsAllDay(event) { + return false } else { - untilStr = fmt.Sprintf("%dm", mins) - if mins < 30 { - color = "[red]" - } + ts, _ := time.Parse(time.RFC3339, event.Start.DateTime) + return (widget.eventIsNow(event) == false) && ts.Before(time.Now()) } - - return color + untilStr + "[white]" } func updateLoop(widget *Widget) { From f8dabfb800bffa7f9617b30569a4712a42ac5f3a Mon Sep 17 00:00:00 2001 From: Chris Cummer Date: Fri, 29 Jun 2018 12:25:25 -0700 Subject: [PATCH 10/21] Wrap Google's calendar Event in a CalEvent struct --- gcal/cal_event.go | 104 ++++++++++++++++++++++++++++++++ gcal/client.go | 13 +++- gcal/display.go | 148 ++++++++++++++++++++++------------------------ gcal/widget.go | 69 ++++----------------- 4 files changed, 200 insertions(+), 134 deletions(-) create mode 100644 gcal/cal_event.go diff --git a/gcal/cal_event.go b/gcal/cal_event.go new file mode 100644 index 00000000..c119b9c7 --- /dev/null +++ b/gcal/cal_event.go @@ -0,0 +1,104 @@ +package gcal + +import ( + "time" + + "github.com/senorprogrammer/wtf/wtf" + "google.golang.org/api/calendar/v3" +) + +type CalEvent struct { + event *calendar.Event +} + +func NewCalEvent(event *calendar.Event) *CalEvent { + calEvent := CalEvent{ + event: event, + } + + return &calEvent +} + +/* -------------------- Exported Functions -------------------- */ + +func (calEvent *CalEvent) AllDay() bool { + return len(calEvent.event.Start.Date) > 0 +} + +func (calEvent *CalEvent) ConflictsWith(otherEvents []*CalEvent) bool { + hasConflict := false + + for _, otherEvent := range otherEvents { + if calEvent.event == otherEvent.event { + continue + } + + if calEvent.Start().Before(otherEvent.End()) && calEvent.End().After(otherEvent.Start()) { + hasConflict = true + break + } + } + + return hasConflict +} + +func (calEvent *CalEvent) Now() bool { + return time.Now().After(calEvent.Start()) && time.Now().Before(calEvent.End()) +} + +func (calEvent *CalEvent) Past() bool { + if calEvent.AllDay() { + // FIXME: This should calculate properly + return false + } else { + return (calEvent.Now() == false) && calEvent.Start().Before(time.Now()) + } +} + +func (calEvent *CalEvent) ResponseFor(email string) string { + for _, attendee := range calEvent.event.Attendees { + if attendee.Email == email { + return attendee.ResponseStatus + } + } + + return "" +} + +/* -------------------- DateTimes -------------------- */ + +func (calEvent *CalEvent) End() time.Time { + var calcTime string + + if calEvent.AllDay() { + calcTime = calEvent.event.End.Date + } else { + calcTime = calEvent.event.End.DateTime + } + + end, _ := time.Parse(time.RFC3339, calcTime) + return end +} + +func (calEvent *CalEvent) Start() time.Time { + var calcTime string + + if calEvent.AllDay() { + calcTime = calEvent.event.Start.Date + } else { + calcTime = calEvent.event.Start.DateTime + } + + start, _ := time.Parse(time.RFC3339, calcTime) + return start +} + +func (calEvent *CalEvent) Timestamp() string { + if calEvent.AllDay() { + startTime, _ := time.Parse("2006-01-02", calEvent.event.Start.Date) + return startTime.Format(wtf.FriendlyDateFormat) + } else { + startTime, _ := time.Parse(time.RFC3339, calEvent.event.Start.DateTime) + return startTime.Format(wtf.MinimumTimeFormat) + } +} diff --git a/gcal/client.go b/gcal/client.go index 4f100090..4523ebb7 100644 --- a/gcal/client.go +++ b/gcal/client.go @@ -1,6 +1,8 @@ /* * This butt-ugly code is direct from Google itself * https://developers.google.com/calendar/quickstart/go +* +* With some changes by me to improve things a bit. */ package gcal @@ -27,7 +29,7 @@ import ( /* -------------------- Exported Functions -------------------- */ -func Fetch() (*calendar.Events, error) { +func Fetch() ([]*CalEvent, error) { ctx := context.Background() secretPath, _ := wtf.ExpandHomeDir(wtf.Config.UString("wtf.mods.gcal.secretFile")) @@ -75,13 +77,20 @@ func Fetch() (*calendar.Events, error) { return time.Parse(time.RFC3339, event.Start.DateTime) } } + sort.Slice(events.Items, func(i, j int) bool { dateA, _ := timeDateChooser(events.Items[i]) dateB, _ := timeDateChooser(events.Items[j]) return dateA.Before(dateB) }) - return &events, err + // Wrap the calendar events in our custom CalEvent + calEvents := []*CalEvent{} + for _, event := range events.Items { + calEvents = append(calEvents, NewCalEvent(event)) + } + + return calEvents, err } /* -------------------- Unexported Functions -------------------- */ diff --git a/gcal/display.go b/gcal/display.go index 7ad94464..a193291a 100644 --- a/gcal/display.go +++ b/gcal/display.go @@ -7,96 +7,107 @@ import ( "time" "github.com/senorprogrammer/wtf/wtf" - "google.golang.org/api/calendar/v3" ) +func (widget *Widget) sortedEvents() ([]*CalEvent, []*CalEvent) { + allDayEvents := []*CalEvent{} + timedEvents := []*CalEvent{} + + for _, calEvent := range widget.calEvents { + if calEvent.AllDay() { + allDayEvents = append(allDayEvents, calEvent) + } else { + timedEvents = append(timedEvents, calEvent) + } + } + + return allDayEvents, timedEvents +} + func (widget *Widget) display() { - if widget.events == nil || len(widget.events.Items) == 0 { + if widget.calEvents == nil || len(widget.calEvents) == 0 { return } widget.mutex.Lock() defer widget.mutex.Unlock() - widget.View.SetText(widget.contentFrom(widget.events)) + _, timedEvents := widget.sortedEvents() + widget.View.SetText(widget.contentFrom(timedEvents)) } -func (widget *Widget) contentFrom(events *calendar.Events) string { - if events == nil { +func (widget *Widget) contentFrom(calEvents []*CalEvent) string { + if (calEvents == nil) || (len(calEvents) == 0) { return "" } - var prevEvent *calendar.Event + var str string + var prevEvent *CalEvent - str := "" - - for _, event := range events.Items { - timestamp := fmt.Sprintf("[%s]%s", - widget.descriptionColor(event), - widget.eventTimestamp(event)) + for _, calEvent := range calEvents { + timestamp := fmt.Sprintf("[%s]%s", widget.descriptionColor(calEvent), calEvent.Timestamp()) title := fmt.Sprintf("[%s]%s", - widget.titleColor(event), - widget.eventSummary(event, widget.conflicts(event, events))) + widget.titleColor(calEvent), + widget.eventSummary(calEvent, calEvent.ConflictsWith(calEvents)), + ) lineOne := fmt.Sprintf( "%s %s %s %s %s[white]", - widget.dayDivider(event, prevEvent), - widget.responseIcon(event), + widget.dayDivider(calEvent, prevEvent), + widget.responseIcon(calEvent), timestamp, title, - widget.timeUntil(event), + widget.timeUntil(calEvent), ) str = str + fmt.Sprintf("%s%s\n\n", lineOne, - widget.location(event), // prefixes newline if non-empty + widget.location(calEvent), ) - prevEvent = event + prevEvent = calEvent } return str } -func (widget *Widget) dayDivider(event, prevEvent *calendar.Event) string { +func (widget *Widget) dayDivider(event, prevEvent *CalEvent) string { var prevStartTime time.Time if prevEvent != nil { - prevStartTime, _ = time.Parse(time.RFC3339, prevEvent.Start.DateTime) + prevStartTime = prevEvent.Start() } - currStartTime, _ := time.Parse(time.RFC3339, event.Start.DateTime) + if event.Start().Day() != prevStartTime.Day() { + //_, _, width, _ := widget.View.GetInnerRect() - if currStartTime.Day() != prevStartTime.Day() { - _, _, width, _ := widget.View.GetInnerRect() - - return fmt.Sprintf("[%s]", wtf.Config.UString("wtf.mods.gcal.colors.day", "forestgreen")) + - wtf.CenterText(currStartTime.Format(wtf.FullDateFormat), width) + + return fmt.Sprintf("[%s::b]", + wtf.Config.UString("wtf.mods.gcal.colors.day", "forestgreen")) + + //wtf.CenterText(event.Start().Format(wtf.FullDateFormat), width) + + event.Start().Format(wtf.FullDateFormat) + "\n" } return "" } -func (widget *Widget) descriptionColor(event *calendar.Event) string { - color := wtf.Config.UString("wtf.mods.gcal.colors.description", "white") - - if widget.eventIsPast(event) { - color = wtf.Config.UString("wtf.mods.gcal.colors.past", "gray") +func (widget *Widget) descriptionColor(calEvent *CalEvent) string { + if calEvent.Past() { + return wtf.Config.UString("wtf.mods.gcal.colors.past", "gray") + } else { + return wtf.Config.UString("wtf.mods.gcal.colors.description", "white") } - - return color } -func (widget *Widget) eventSummary(event *calendar.Event, conflict bool) string { - summary := event.Summary +func (widget *Widget) eventSummary(calEvent *CalEvent, conflict bool) string { + summary := calEvent.event.Summary - if widget.eventIsNow(event) { + if calEvent.Now() { summary = fmt.Sprintf( "%s %s", wtf.Config.UString("wtf.mods.gcal.currentIcon", "🔸"), - event.Summary, + summary, ) } @@ -107,21 +118,10 @@ func (widget *Widget) eventSummary(event *calendar.Event, conflict bool) string } } -func (widget *Widget) eventTimestamp(event *calendar.Event) string { - if widget.eventIsAllDay(event) { - startTime, _ := time.Parse("2006-01-02", event.Start.Date) - return startTime.Format(wtf.FriendlyDateFormat) - } else { - startTime, _ := time.Parse(time.RFC3339, event.Start.DateTime) - return startTime.Format(wtf.MinimumTimeFormat) - } -} - -// timeUuntil returns the number of hours or days until the event +// timeUntil returns the number of hours or days until the event // If the event is in the past, returns nil -func (widget *Widget) timeUntil(event *calendar.Event) string { - startTime, _ := time.Parse(time.RFC3339, event.Start.DateTime) - duration := time.Until(startTime).Round(time.Minute) +func (widget *Widget) timeUntil(calEvent *CalEvent) string { + duration := time.Until(calEvent.Start()).Round(time.Minute) if duration < 0 { return "" @@ -152,7 +152,7 @@ func (widget *Widget) timeUntil(event *calendar.Event) string { return color + untilStr + "[white]" } -func (widget *Widget) titleColor(event *calendar.Event) string { +func (widget *Widget) titleColor(calEvent *CalEvent) string { color := wtf.Config.UString("wtf.mods.gcal.colors.title", "white") for _, untypedArr := range wtf.Config.UList("wtf.mods.gcal.colors.highlights") { @@ -160,7 +160,7 @@ func (widget *Widget) titleColor(event *calendar.Event) string { match, _ := regexp.MatchString( strings.ToLower(highlightElements[0]), - strings.ToLower(event.Summary), + strings.ToLower(calEvent.event.Summary), ) if match == true { @@ -168,51 +168,47 @@ func (widget *Widget) titleColor(event *calendar.Event) string { } } - if widget.eventIsPast(event) { + if calEvent.Past() { color = wtf.Config.UString("wtf.mods.gcal.colors.past", "gray") } return color } -func (widget *Widget) location(event *calendar.Event) string { +func (widget *Widget) location(calEvent *CalEvent) string { if wtf.Config.UBool("wtf.mods.gcal.displayLocation", true) == false { return "" } - if event.Location == "" { + if calEvent.event.Location == "" { return "" } return fmt.Sprintf( "\n [%s]%s", - widget.descriptionColor(event), - event.Location, + widget.descriptionColor(calEvent), + calEvent.event.Location, ) } -func (widget *Widget) responseIcon(event *calendar.Event) string { +func (widget *Widget) responseIcon(calEvent *CalEvent) string { if false == wtf.Config.UBool("wtf.mods.gcal.displayResponseStatus", true) { return "" } - for _, attendee := range event.Attendees { - if attendee.Email == wtf.Config.UString("wtf.mods.gcal.email") { - icon := "[gray]" + icon := "[gray]" - switch attendee.ResponseStatus { - case "accepted": - return icon + "✔︎" - case "declined": - return icon + "✘" - case "needsAction": - return icon + "?" - case "tentative": - return icon + "~" - default: - return icon + " " - } - } + switch calEvent.ResponseFor(wtf.Config.UString("wtf.mods.gcal.email")) { + case "accepted": + return icon + "✔︎" + case "declined": + return icon + "✘" + case "needsAction": + return icon + "?" + case "tentative": + return icon + "~" + default: + return icon + " " } return " " diff --git a/gcal/widget.go b/gcal/widget.go index 28032ac6..7ad4bc25 100644 --- a/gcal/widget.go +++ b/gcal/widget.go @@ -5,15 +5,14 @@ import ( "time" "github.com/senorprogrammer/wtf/wtf" - "google.golang.org/api/calendar/v3" ) type Widget struct { wtf.TextWidget - events *calendar.Events - ch chan struct{} - mutex sync.Mutex + calEvents []*CalEvent + ch chan struct{} + mutex sync.Mutex } func NewWidget() *Widget { @@ -29,67 +28,25 @@ func NewWidget() *Widget { /* -------------------- Exported Functions -------------------- */ -func (widget *Widget) Refresh() { - events, _ := Fetch() - widget.events = events - - widget.UpdateRefreshedAt() - - widget.display() -} - func (widget *Widget) Disable() { close(widget.ch) widget.TextWidget.Disable() } -/* -------------------- Unexported Functions -------------------- */ - -// conflicts returns TRUE if this event conflicts with another, FALSE if it does not -func (widget *Widget) conflicts(event *calendar.Event, events *calendar.Events) bool { - conflict := false - - for _, otherEvent := range events.Items { - if event == otherEvent { - continue - } - - eventStart, _ := time.Parse(time.RFC3339, event.Start.DateTime) - eventEnd, _ := time.Parse(time.RFC3339, event.End.DateTime) - - otherEnd, _ := time.Parse(time.RFC3339, otherEvent.End.DateTime) - otherStart, _ := time.Parse(time.RFC3339, otherEvent.Start.DateTime) - - if eventStart.Before(otherEnd) && eventEnd.After(otherStart) { - conflict = true - break - } - } - - return conflict -} - -func (widget *Widget) eventIsAllDay(event *calendar.Event) bool { - return len(event.Start.Date) > 0 -} - -// eventIsNow returns true if the event is happening now, false if it not -func (widget *Widget) eventIsNow(event *calendar.Event) bool { - startTime, _ := time.Parse(time.RFC3339, event.Start.DateTime) - endTime, _ := time.Parse(time.RFC3339, event.End.DateTime) - - return time.Now().After(startTime) && time.Now().Before(endTime) -} - -func (widget *Widget) eventIsPast(event *calendar.Event) bool { - if widget.eventIsAllDay(event) { - return false +func (widget *Widget) Refresh() { + calEvents, err := Fetch() + if err != nil { + widget.calEvents = []*CalEvent{} } else { - ts, _ := time.Parse(time.RFC3339, event.Start.DateTime) - return (widget.eventIsNow(event) == false) && ts.Before(time.Now()) + widget.calEvents = calEvents } + + widget.UpdateRefreshedAt() + widget.display() } +/* -------------------- Unexported Functions -------------------- */ + func updateLoop(widget *Widget) { interval := wtf.Config.UInt("wtf.mods.gcal.textInterval", 30) if interval == 0 { From 276b9b1798dc0e53dfe23e17575dd7840eb3291b Mon Sep 17 00:00:00 2001 From: Chris Cummer Date: Fri, 29 Jun 2018 15:24:34 -0700 Subject: [PATCH 11/21] New and improved multicalendar display format --- gcal/display.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/gcal/display.go b/gcal/display.go index a193291a..838da89a 100644 --- a/gcal/display.go +++ b/gcal/display.go @@ -53,18 +53,22 @@ func (widget *Widget) contentFrom(calEvents []*CalEvent) string { ) lineOne := fmt.Sprintf( - "%s %s %s %s %s[white]", + "%s %s %s %s[white]\n", widget.dayDivider(calEvent, prevEvent), widget.responseIcon(calEvent), timestamp, title, + ) + + str = str + fmt.Sprintf("%s %s%s\n", + lineOne, + widget.location(calEvent), widget.timeUntil(calEvent), ) - str = str + fmt.Sprintf("%s%s\n\n", - lineOne, - widget.location(calEvent), - ) + if (widget.location(calEvent) != "") || (widget.timeUntil(calEvent) != "") { + str = str + "\n" + } prevEvent = calEvent } @@ -80,11 +84,9 @@ func (widget *Widget) dayDivider(event, prevEvent *CalEvent) string { } if event.Start().Day() != prevStartTime.Day() { - //_, _, width, _ := widget.View.GetInnerRect() return fmt.Sprintf("[%s::b]", wtf.Config.UString("wtf.mods.gcal.colors.day", "forestgreen")) + - //wtf.CenterText(event.Start().Format(wtf.FullDateFormat), width) + event.Start().Format(wtf.FullDateFormat) + "\n" } @@ -185,7 +187,7 @@ func (widget *Widget) location(calEvent *CalEvent) string { } return fmt.Sprintf( - "\n [%s]%s", + "[%s]%s ", widget.descriptionColor(calEvent), calEvent.event.Location, ) From 4c8266335a473d39cb7a09c2e4feb1d2f6ddeeb6 Mon Sep 17 00:00:00 2001 From: Bill Keenan Date: Wed, 4 Jul 2018 10:45:16 -0400 Subject: [PATCH 12/21] fixed bargraph test --- wtftests/bargraph/bargraph_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wtftests/bargraph/bargraph_test.go b/wtftests/bargraph/bargraph_test.go index 3135c925..41c2ef7b 100644 --- a/wtftests/bargraph/bargraph_test.go +++ b/wtftests/bargraph/bargraph_test.go @@ -14,10 +14,10 @@ func makeData() [][2]int64 { const lineCount = 2 var stats [lineCount][2]int64 - stats[0][1] = 1530122942 + stats[0][1] = 1530122942000 stats[0][0] = 100 - stats[1][1] = 1530132942 + stats[1][1] = 1531142942000 stats[1][0] = 210 return stats[:] @@ -29,5 +29,5 @@ func TestOutput(t *testing.T) { result := BuildStars(makeData(), 20, "*") - Equal(t, result, "Jan 18, 1970 -\t [red]*[white] - (100)\nJan 18, 1970 -\t [red]********************[white] - (210)\n") + Equal(t, "Jun 27, 2018 -\t [red]*[white] - (100)\nJul 09, 2018 -\t [red]********************[white] - (210)\n", result) } From 97c9ed38864eb442d248eada09226dac7e0807de Mon Sep 17 00:00:00 2001 From: Lineu Felipe Date: Thu, 5 Jul 2018 00:47:35 -0300 Subject: [PATCH 13/21] add todoist widget --- Gopkg.lock | 8 +- Gopkg.toml | 4 + _site/content/posts/modules/todoist.md | 88 ++++++++++++++ _site/static/imgs/modules/todoist.png | Bin 0 -> 3335 bytes .../hyde-hyde/layouts/partials/sidebar.html | 1 + todoist/display.go | 64 ++++++++++ todoist/list.go | 79 ++++++++++++ todoist/widget.go | 113 ++++++++++++++++++ wtf.go | 4 +- 9 files changed, 359 insertions(+), 2 deletions(-) create mode 100644 _site/content/posts/modules/todoist.md create mode 100644 _site/static/imgs/modules/todoist.png create mode 100644 todoist/display.go create mode 100644 todoist/list.go create mode 100644 todoist/widget.go diff --git a/Gopkg.lock b/Gopkg.lock index 9211154d..8a0e37d8 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -25,6 +25,12 @@ packages = ["."] revision = "6a9abf92e34f4de62ac671caee3143f10b98892d" +[[projects]] + branch = "master" + name = "github.com/darkSasori/todoist" + packages = ["."] + revision = "ec6b38b374ab9c60cc9716d2083ae66eb9383d03" + [[projects]] name = "github.com/davecgh/go-spew" packages = ["spew"] @@ -201,6 +207,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "a7a00554f9040d7617458773eafa64b82f9502eace145152cb50eb082800e936" + inputs-digest = "b2141b5945354e95e2f3e8f1f6eb182de11e21f0fe1188862c6dc57983c8cbc4" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 6221a4d7..9261787d 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -85,6 +85,10 @@ branch = "master" name = "github.com/adlio/trello" +[[constraint]] + branch = "master" + name = "github.com/darkSasori/todoist" + [prune] go-tests = true unused-packages = true diff --git a/_site/content/posts/modules/todoist.md b/_site/content/posts/modules/todoist.md new file mode 100644 index 00000000..bb3beb11 --- /dev/null +++ b/_site/content/posts/modules/todoist.md @@ -0,0 +1,88 @@ +--- +title: "Todoist" +date: 2018-07-05T22:55:55-03:00 +draft: false +--- + +Displays all itens on specified project. + +todoist screenshot + +## Source Code + +```bash +wtf/todoist/ +``` + +## Required ENV Variables + +Key: `WTF_TODOIST_TOKEN`
    +Value: Your Todoist API Token.
    + +_You can get your API Token at: todoist.com/prefs/integrations._ + +## Keyboard Commands + +Key: `h`
    +Action: Show the previous project. + +Key: `←`
    +Action: Show the previous project. + +Key: `l`
    +Action: Show the next project. + +Key: `→`
    +Action: Show the next project. + +Key: `j`
    +Action: Select the next item in the list. + +Key: `↓`
    +Action: Select the next item in the list. + +Key: `k`
    +Action: Select the previous item in the list. + +Key: `↑`
    +Action: Select the previous item in the list. + +Key: `c`
    +Action: Close current item. + +Key: `d`
    +Action: Delete current item. + +Key: `r`
    +Action: Reload all projects. + +## Configuration + +```yaml +todoist: + projects: + - project_id + enabled: true + position: + height: 1 + left: 2 + top: 0 + width: 1 + refreshInterval: 3600 +``` + +### Attributes + +`enabled`
    +Determines whether or not this module is executed and if its data displayed onscreen.
    +Values: `true`, `false`. + +`projects`
    +The todoist projects to fetch items from.
    + +`refreshInterval`
    +How often, in seconds, this module will update its data.
    +Values: A positive integer, `0..n`. + +`position`
    +Where in the grid this module's widget will be displayed.
    diff --git a/_site/static/imgs/modules/todoist.png b/_site/static/imgs/modules/todoist.png new file mode 100644 index 0000000000000000000000000000000000000000..086d0ec8673a4f407e2b0c382fd9e547711a0909 GIT binary patch literal 3335 zcmeHK=Q|r*8xLBvwOW)uXpEK)qtq&DRqPl|Q8QX2_KrPDNs6ZwwTs8D{lt94hzh0k zSPf#u4$_)Mh!Jo63Gb)(%X?qfIrljq&hOmUz0U8%n;7Y^Uj5@L003as)75$o0MJ>} z*4@nXwArPB97j`Ed~_}S0RXn1i$RzEkc|%jU}@FU(s&X4X_MgT!3m4$+J&i*FYzmH z%k$M}iRjd@m0k}r%eiVa?ibMZN!XXzT#;oiS+G@e)s5lo3v@h>g1YGp zKfZ0SI)7_sStsPQM}m%KA^`Blu9?7_rh7t0GGl_(NS^wDpb*cs`gPm-#us{C7RnArGrsT#}GaP!20 zCFO;I{(ygZ5lTqLm-fdh=`C73ia${jp&MC>dqH6j*RGRo!yv`Vn0a?yb1Ew z_EwlR&vW}Wm%7vBvu{4uJa_Xp^ZXQC=Nm*^a)1T5-Rmw_`dZ3juiCCj_j>N2xtZAk zdB$5uIK>-tMs8ipud1!>^j${aNT;WJ!^NZP@tk6>x8)3<)YsK{e;?E?Hbr7h&COSL zgKKSCQ-@$BN@kwffhTHSe6K`gcqycA;}E%!XeoN(2QQG785O3+vH^Ext2}>Of%Aer z2V?fj443K_ii(7>kHR}Kr@-GsdCI-Ly^)yt?ZZAJ>C?!lJ2$euRswQ2*ViZi@hzU& zOwtn0%gf_5etKguunicxUU)<}K0ZErG8B=W0Pf}}J<)dv;s(dFX~%I;*)X2PHDk7J zT7QlXjV@%{FP&ho$$>;WO$HCCS4eAn6fV$xaq&OTAX4}5?}m2R)#05RvJFLT@tc8d zQy%$Vj^WBFx;>6E;8_p%3;Fdu{bA6?zXgj&C!IdWHstPDAed?U?1{;OlExhg-j-k=p?ni-D36hrMjd6 ze6Fat&N(4-8wf0zvHFUFL+JhmJ9ieo zu(9#r{Yq;ruY=^keEj@ewEI~E^;{oxm-N0vA`qH|BddeC%kF7k@3dw}zqwONs($t3 zbq^McJv%#NWo6ANbImcQ1=r3_GSzNi(ox1W$5|$ON#Qx=(;M-RWe%}(b93WW7A-9; zztD5Dv;54LX1#)FoyXcbx3ZFffnghS-pNr^UtizaI_1_x>%w~Ow~TXCiN)2H4XdS@ zb_s;YrFb#6+bT!BJkXNGDLPByTvH)rHxC53#^L^qA!}j`w7#`*eiehbx7 za2&T#4p1t1KU!r*7}sTTGy*)0BFCT^)|np#u@?P2Jt z^*TXzCp`Cm!Bbv$DT(SEfF>QPe(-w7K16s=pl&QpdY{W<`rZDxvR)Axqb{#U1An$y zR-Au~8IY2a-QG??o+(E`0&skuE||^6im>z6mFdkLiKu{x_n!9m+N(~C@8q67H zxe91COKpcJXzl6btM~hyXSTRHY0NMp-EJ^EOF6{sUJ-gAkg?i#tvcu1<3Hf%bIREL zFkq1niiD49c*(q*>y+pa6|#w2q*{mHrOPV4UyCsM5_FSJT-}D1)!qtc!y0k_k^Fc{ zh|ER$U2LUC3siK&@4SgiZeRr5pk?U?nmYn)^9G1YJ`3vlespY-FU9nsmpr`w@AUJ$;617ZY0$?yW zr)=;riNR8GW&*)>i9T9=p*UI9s>y9!Bw6JL9)vPowbvt5zltxT)2UqzNyf&s9V8OwPPlwt1 zA5^olu87wn7#~SYAQ%Nv^0I*V3y**Rf~J@Lul_eK$aXjmha#|Bn#^gIyyy{l zA@g0_34K!E1PbOR#)*Xi3xeXlF4cB?VG$L|j{gg+f2H-RstT#ru^B q7|y|lR6E(8uUO$TD6+?G5-OUl?^}u literal 0 HcmV?d00001 diff --git a/_site/themes/hyde-hyde/layouts/partials/sidebar.html b/_site/themes/hyde-hyde/layouts/partials/sidebar.html index 3bb31b75..2da8d975 100644 --- a/_site/themes/hyde-hyde/layouts/partials/sidebar.html +++ b/_site/themes/hyde-hyde/layouts/partials/sidebar.html @@ -47,6 +47,7 @@ +
diff --git a/todoist/display.go b/todoist/display.go new file mode 100644 index 00000000..8cbfa858 --- /dev/null +++ b/todoist/display.go @@ -0,0 +1,64 @@ +package todoist + +import ( + "fmt" + + "github.com/gdamore/tcell" + "github.com/rivo/tview" + "github.com/senorprogrammer/wtf/wtf" +) + +func (w *Widget) display() { + if len(w.list) == 0 { + return + } + list := w.list[w.idx] + + w.View.SetTitle(fmt.Sprintf("%s- [green]%s[white] ", w.Name, list.Project.Name)) + str := wtf.SigilStr(len(w.list), w.idx, w.View) + "\n" + + for index, item := range list.items { + if index == list.index { + str = str + fmt.Sprintf("[%s]", wtf.Config.UString("wtf.colors.border.focused", "grey")) + } + str = str + fmt.Sprintf("| | %s[white]\n", tview.Escape(item.Content)) + } + + w.View.Clear() + w.View.SetText(str) +} + +func (w *Widget) keyboardIntercept(event *tcell.EventKey) *tcell.EventKey { + if len(w.list) == 0 { + return event + } + + switch string(event.Rune()) { + case "r": + w.Refresh() + return nil + case "d": + w.Delete() + return nil + case "c": + w.Close() + return nil + } + + switch fromVim(event) { + case tcell.KeyLeft: + w.Prev() + return nil + case tcell.KeyRight: + w.Next() + return nil + case tcell.KeyUp: + w.UP() + return nil + case tcell.KeyDown: + w.Down() + return nil + } + + return event +} diff --git a/todoist/list.go b/todoist/list.go new file mode 100644 index 00000000..81b9d55e --- /dev/null +++ b/todoist/list.go @@ -0,0 +1,79 @@ +package todoist + +import ( + "fmt" + + "github.com/darkSasori/todoist" +) + +type List struct { + todoist.Project + items []todoist.Task + index int +} + +func NewList(id int) *List { + project, err := todoist.GetProject(id) + if err != nil { + panic(err) + } + + list := &List{ + Project: project, + index: -1, + } + list.loadItems() + return list +} + +func (l List) isFirst() bool { + return l.index == 0 +} + +func (l List) isLast() bool { + return l.index >= len(l.items)-1 +} + +func (l *List) up() { + l.index = l.index - 1 + if l.index < 0 { + l.index = len(l.items) - 1 + } +} + +func (l *List) down() { + if l.index == -1 { + l.index = 0 + return + } + + l.index = l.index + 1 + if l.index >= len(l.items) { + l.index = 0 + } +} + +func (l *List) loadItems() { + tasks, err := todoist.ListTask(todoist.QueryParam{"project_id": fmt.Sprintf("%d", l.ID)}) + if err != nil { + panic(err) + } + + l.items = tasks +} + +func (l *List) close() { + if err := l.items[l.index].Close(); err != nil { + panic(err) + } + + l.loadItems() +} + +func (l *List) delete() { + if err := l.items[l.index].Delete(); err != nil { + panic(err) + } + + l.loadItems() +} diff --git a/todoist/widget.go b/todoist/widget.go new file mode 100644 index 00000000..e40089fa --- /dev/null +++ b/todoist/widget.go @@ -0,0 +1,113 @@ +package todoist + +import ( + "os" + + "github.com/darkSasori/todoist" + "github.com/gdamore/tcell" + "github.com/rivo/tview" + "github.com/senorprogrammer/wtf/wtf" +) + +type Widget struct { + wtf.TextWidget + + app *tview.Application + pages *tview.Pages + list []*List + idx int +} + +func NewWidget(app *tview.Application, pages *tview.Pages) *Widget { + widget := Widget{ + TextWidget: wtf.NewTextWidget(" Todoist ", "todoist", true), + + app: app, + pages: pages, + } + + todoist.Token = os.Getenv("WTF_TODOIST_TOKEN") + widget.list = loadProjects() + widget.View.SetInputCapture(widget.keyboardIntercept) + + return &widget +} + +func (w *Widget) Refresh() { + if w.Disabled() || len(w.list) == 0 { + return + } + + w.UpdateRefreshedAt() + w.display() +} + +func (w *Widget) Next() { + w.idx = w.idx + 1 + if w.idx == len(w.list) { + w.idx = 0 + } + + w.display() +} + +func (w *Widget) Prev() { + w.idx = w.idx - 1 + if w.idx < 0 { + w.idx = len(w.list) - 1 + } + + w.display() +} + +func (w *Widget) Down() { + w.list[w.idx].down() + w.display() +} + +func (w *Widget) UP() { + w.list[w.idx].up() + w.display() +} + +func (w *Widget) Close() { + w.list[w.idx].close() + if w.list[w.idx].isLast() { + w.UP() + return + } + w.Down() +} + +func (w *Widget) Delete() { + w.list[w.idx].close() + if w.list[w.idx].isLast() { + w.UP() + return + } + w.Down() +} + +func loadProjects() []*List { + lists := []*List{} + for _, id := range wtf.Config.UList("wtf.mods.todoist.projects") { + list := NewList(id.(int)) + lists = append(lists, list) + } + + return lists +} + +func fromVim(event *tcell.EventKey) tcell.Key { + switch string(event.Rune()) { + case "h": + return tcell.KeyLeft + case "l": + return tcell.KeyRight + case "k": + return tcell.KeyUp + case "j": + return tcell.KeyDown + } + return event.Key() +} diff --git a/wtf.go b/wtf.go index 79b2a6d8..c7709776 100644 --- a/wtf.go +++ b/wtf.go @@ -39,6 +39,7 @@ import ( "github.com/senorprogrammer/wtf/system" "github.com/senorprogrammer/wtf/textfile" "github.com/senorprogrammer/wtf/todo" + "github.com/senorprogrammer/wtf/todoist" "github.com/senorprogrammer/wtf/trello" "github.com/senorprogrammer/wtf/weatherservices/prettyweather" "github.com/senorprogrammer/wtf/weatherservices/weather" @@ -218,6 +219,8 @@ func addWidget(app *tview.Application, pages *tview.Pages, widgetName string) { Widgets = append(Widgets, textfile.NewWidget(app, pages)) case "todo": Widgets = append(Widgets, todo.NewWidget(app, pages)) + case "todoist": + Widgets = append(Widgets, todoist.NewWidget(app, pages)) case "trello": Widgets = append(Widgets, trello.NewWidget()) case "weather": @@ -233,7 +236,6 @@ func makeWidgets(app *tview.Application, pages *tview.Pages) { if enabled := Config.UBool("wtf.mods."+mod+".enabled", false); enabled { addWidget(app, pages, mod) } - } } From b84a4394d723810866ea7cef69b420919b3e7d74 Mon Sep 17 00:00:00 2001 From: Chris Cummer Date: Wed, 11 Jul 2018 09:45:34 -0700 Subject: [PATCH 14/21] Close #239. Update the tcell dependency --- Gopkg.lock | 16 +- Gopkg.toml | 20 +- vendor/github.com/gdamore/tcell/.travis.yml | 2 + vendor/github.com/gdamore/tcell/README.md | 108 ++++---- vendor/github.com/gdamore/tcell/cell.go | 8 +- .../github.com/gdamore/tcell/console_win.go | 39 +-- vendor/github.com/gdamore/tcell/doc.go | 12 +- vendor/github.com/gdamore/tcell/simulation.go | 9 +- vendor/github.com/gdamore/tcell/tcell.png | Bin 0 -> 5336 bytes vendor/github.com/gdamore/tcell/tcell.svg | 93 +++++++ .../gdamore/tcell/terminfo/README.md | 8 + .../gdamore/tcell/terminfo/mkdatabase.sh | 189 -------------- .../gdamore/tcell/terminfo/mkinfo.go | 242 ++++++++++++++---- .../gdamore/tcell/terminfo/models.txt | 2 - .../gdamore/tcell/terminfo/term_Eterm.go | 24 -- .../tcell/terminfo/term_Eterm_256color.go | 105 -------- .../gdamore/tcell/terminfo/term_adm3a.go | 2 +- .../gdamore/tcell/terminfo/term_ansi.go | 2 +- .../gdamore/tcell/terminfo/term_aterm.go | 4 +- .../gdamore/tcell/terminfo/term_bsdos_pc.go | 2 +- .../gdamore/tcell/terminfo/term_cygwin.go | 2 +- .../gdamore/tcell/terminfo/term_d200.go | 4 +- .../gdamore/tcell/terminfo/term_dtterm.go | 2 +- .../gdamore/tcell/terminfo/term_gnome.go | 5 +- .../tcell/terminfo/term_gnome_256color.go | 43 ++-- .../gdamore/tcell/terminfo/term_hz1500.go | 2 +- .../gdamore/tcell/terminfo/term_konsole.go | 4 +- .../tcell/terminfo/term_konsole_256color.go | 4 +- .../gdamore/tcell/terminfo/term_linux.go | 11 +- .../gdamore/tcell/terminfo/term_pcansi.go | 2 +- .../gdamore/tcell/terminfo/term_rxvt.go | 4 +- .../tcell/terminfo/term_rxvt_256color.go | 4 +- .../tcell/terminfo/term_rxvt_unicode.go | 2 +- .../terminfo/term_rxvt_unicode_256color.go | 2 +- .../gdamore/tcell/terminfo/term_screen.go | 5 +- .../tcell/terminfo/term_screen_256color.go | 5 +- .../gdamore/tcell/terminfo/term_st.go | 9 +- .../tcell/terminfo/term_st_256color.go | 156 +++++++++++ .../gdamore/tcell/terminfo/term_st_meta.go | 2 +- .../tcell/terminfo/term_st_meta_256color.go | 2 +- .../gdamore/tcell/terminfo/term_sun.go | 3 +- .../gdamore/tcell/terminfo/term_sun_color.go | 4 +- .../gdamore/tcell/terminfo/term_termite.go | 152 +++++++++++ .../gdamore/tcell/terminfo/term_tvi950.go | 2 +- .../gdamore/tcell/terminfo/term_vt100.go | 2 +- .../gdamore/tcell/terminfo/term_vt102.go | 2 +- .../gdamore/tcell/terminfo/term_vt220.go | 1 + .../gdamore/tcell/terminfo/term_vt320.go | 2 +- .../gdamore/tcell/terminfo/term_vt420.go | 5 +- .../gdamore/tcell/terminfo/term_vt52.go | 2 +- .../gdamore/tcell/terminfo/term_wy50.go | 2 +- .../gdamore/tcell/terminfo/term_wy60.go | 2 +- .../gdamore/tcell/terminfo/term_wy99_ansi.go | 4 +- .../gdamore/tcell/terminfo/term_wy99a_ansi.go | 4 +- .../gdamore/tcell/terminfo/term_xfce.go | 4 +- .../gdamore/tcell/terminfo/term_xnuppc.go | 4 +- .../gdamore/tcell/terminfo/term_xterm.go | 6 +- .../tcell/terminfo/term_xterm_256color.go | 3 +- .../gdamore/tcell/terminfo/terminfo.go | 83 ++++-- vendor/github.com/gdamore/tcell/tscreen.go | 12 +- .../github.com/gdamore/tcell/tscreen_bsd.go | 4 +- .../gdamore/tcell/tscreen_darwin.go | 140 ++++++++++ .../google/go-github/github/apps.go | 57 +++-- .../github.com/xanzy/go-gitlab/deployments.go | 4 +- vendor/github.com/xanzy/go-gitlab/gitlab.go | 21 -- .../github.com/xanzy/go-gitlab/pipelines.go | 2 +- 66 files changed, 1069 insertions(+), 615 deletions(-) create mode 100644 vendor/github.com/gdamore/tcell/tcell.png create mode 100644 vendor/github.com/gdamore/tcell/tcell.svg create mode 100644 vendor/github.com/gdamore/tcell/terminfo/README.md delete mode 100755 vendor/github.com/gdamore/tcell/terminfo/mkdatabase.sh delete mode 100644 vendor/github.com/gdamore/tcell/terminfo/term_Eterm.go delete mode 100644 vendor/github.com/gdamore/tcell/terminfo/term_Eterm_256color.go create mode 100644 vendor/github.com/gdamore/tcell/terminfo/term_st_256color.go create mode 100644 vendor/github.com/gdamore/tcell/terminfo/term_termite.go create mode 100644 vendor/github.com/gdamore/tcell/tscreen_darwin.go diff --git a/Gopkg.lock b/Gopkg.lock index 9211154d..52307702 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -38,13 +38,13 @@ revision = "b23993cbb6353f0e6aa98d0ee318a34728f628b9" [[projects]] + branch = "master" name = "github.com/gdamore/tcell" packages = [ ".", "terminfo" ] - revision = "061d51a604c546b48e92253cb65190d76cecf4c6" - version = "v1.0.0" + revision = "de7e78efa4a71b3f36c7154989c529dbdf9ae623" [[projects]] name = "github.com/golang/protobuf" @@ -56,7 +56,7 @@ branch = "master" name = "github.com/google/go-github" packages = ["github"] - revision = "60d040d2dafa18fa3e86cbf22fbc3208ef9ef1e0" + revision = "60f2773bd99aa86164bc80bf370be6ba63b47dea" [[projects]] branch = "master" @@ -119,10 +119,10 @@ version = "v1.2.2" [[projects]] - branch = "master" name = "github.com/xanzy/go-gitlab" packages = ["."] - revision = "6ada444068f460636db26ca0dd66cf2aa518fa8f" + revision = "79dad8e74fd097eb2e0fd0883f1978213e88107a" + version = "v0.10.7" [[projects]] branch = "master" @@ -137,7 +137,7 @@ "context", "context/ctxhttp" ] - revision = "e514e69ffb8bc3c76a71ae40de0118d794855992" + revision = "039a4258aec0ad3c79b905677cceeab13b296a77" [[projects]] branch = "master" @@ -173,7 +173,7 @@ "googleapi/internal/uritemplates", "sheets/v4" ] - revision = "082d671a2a341aa205b22b580fa69747dcc3cdc8" + revision = "1d2d9cc0ae74226e8076e2a87e211c8fea38a160" [[projects]] name = "google.golang.org/appengine" @@ -201,6 +201,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "a7a00554f9040d7617458773eafa64b82f9502eace145152cb50eb082800e936" + inputs-digest = "04892edb6b5f0be61b391ccead307ed15899532db05a17b9e28c00ee32a34861" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 6221a4d7..f36a5e92 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -31,11 +31,11 @@ [[constraint]] name = "github.com/gdamore/tcell" - version = "1.0.0" + branch = "master" -[[constraint]] - name = "github.com/go-test/deep" - version = "1.0.1" +#[[constraint]] + #name = "github.com/go-test/deep" + #version = "1.0.1" [[constraint]] name = "github.com/google/go-github" @@ -54,36 +54,36 @@ version = "1.4.0" [[constraint]] - branch = "master" name = "github.com/olebedev/config" + branch = "master" [[constraint]] name = "github.com/radovskyb/watcher" version = "1.0.2" [[constraint]] - branch = "master" name = "github.com/rivo/tview" + branch = "master" [[constraint]] - branch = "master" name = "github.com/yfronto/newrelic" + branch = "master" [[constraint]] - branch = "master" name = "golang.org/x/oauth2" + branch = "master" [[constraint]] - branch = "master" name = "google.golang.org/api" + branch = "master" [[constraint]] name = "gopkg.in/yaml.v2" version = "2.2.1" [[constraint]] - branch = "master" name = "github.com/adlio/trello" + branch = "master" [prune] go-tests = true diff --git a/vendor/github.com/gdamore/tcell/.travis.yml b/vendor/github.com/gdamore/tcell/.travis.yml index d3fc7d43..e523b890 100644 --- a/vendor/github.com/gdamore/tcell/.travis.yml +++ b/vendor/github.com/gdamore/tcell/.travis.yml @@ -5,6 +5,8 @@ go: - 1.6.x - 1.7.x - 1.8.x + - 1.9.x + - 1.10.x - master before_install: diff --git a/vendor/github.com/gdamore/tcell/README.md b/vendor/github.com/gdamore/tcell/README.md index b3fcba90..d59a3ea0 100644 --- a/vendor/github.com/gdamore/tcell/README.md +++ b/vendor/github.com/gdamore/tcell/README.md @@ -1,4 +1,4 @@ -## tcell +## tcell [![Linux Status](https://img.shields.io/travis/gdamore/tcell.svg?label=linux)](https://travis-ci.org/gdamore/tcell) [![Windows Status](https://img.shields.io/appveyor/ci/gdamore/tcell.svg?label=windows)](https://ci.appveyor.com/project/gdamore/tcell) @@ -9,12 +9,6 @@ [![codecov](https://codecov.io/gh/gdamore/tcell/branch/master/graph/badge.svg)](https://codecov.io/gh/gdamore/tcell) -> _Tcell is a work in progress (Gamma). -> Please use with caution; interfaces may change in before final release. -> That said, our confidence in Tcell's stability is increasing. If you -> would like to use it in your own application, it is recommended that -> you drop a message to garrett@damore.org before commitment._ - Package tcell provides a cell based view for text terminals, like xterm. It was inspired by termbox, but differs from termbox in some important ways. It also adds substantial functionality beyond termbox. @@ -27,32 +21,30 @@ ways. It also adds substantial functionality beyond termbox. * [gomatrix](https://github.com/gdamore/gomatrix) - converted from Termbox * [micro](https://github.com/zyedidia/micro/) - lightweight text editor with syntax-highlighting and themes * [godu](https://github.com/viktomas/godu) - simple golang utility helping to discover large files/folders. +* [tview](https://github.com/rivo/tview) - rich interactive widgets for terminal UIs +* [tui-go](https://github.com/marcusolsson/tui-go) - UI library for terminal apps +* [gomandelbrot](https://github.com/rgm3/gomandelbrot) - Mandelbrot! +* [WTF](https://github.com/senorprogrammer/wtf)- Personal information dashboard for your terminal ## Pure Go Terminfo Database First, it includes a full parser and expander for terminfo capability strings, so that it can avoid hard coding escape strings for formatting. It also favors -portability, and includes support for all POSIX systems, at the slight expense -of needing cgo support for terminal initializations. (This may be corrected -when Go provides standard support for terminal handling via termio ioctls on -all POSIX platforms.) The database itself, while built using CGO, as well -as the parser for it, is implemented in Pure Go. +portability, and includes support for all POSIX systems. -The database is also flexible & extensible, and can modified by either running a -program to build the database, or hand editing of simple JSON files. +The database is also flexible & extensible, and can modified by either running +a program to build the entire database, or an entry for just a single terminal. ## More Portable -Tcell is portable to a wider variety of systems. It relies on standard -POSIX supported function calls (on POSIX platforms) for setting terminal -modes, which leads to improved support for a broader array of platforms. -This does come at the cost of requiring your code to be able to use CGO, but -we believe that the vastly improved portability justifies this -requirement. Note that the functions called are part of the standard C -library, so there shouldn't be any additional external requirements beyond -that required for every POSIX program. +Tcell is portable to a wider variety of systems. Tcell is believed +to work with all of the systems officially supported by golang with +the exception of nacl (which lacks any kind of a terminal interface). +(Plan9 is not supported by Tcell, but it is experimental status only +in golang.) For all of these systems *except Solaris/illumos*, Tcell +is pure Go, with no need for CGO. -## No async IO +## No Async IO Tcell is able to operate without requiring SIGIO signals (unlike Termbox), or asynchronous I/O, and can instead use standard Go file @@ -63,7 +55,7 @@ to fewer surprises. ## Richer Unicode & non-Unicode support -Tcell includes enhanced support for Unicode, include wide characters and +Tcell includes enhanced support for Unicode, including wide characters and combining characters, provided your terminal can support them. Note that Windows terminals generally don't support the full Unicode repertoire. @@ -75,10 +67,10 @@ drawing certain characters. ## More Function Keys -It also has richer support for a larger number of -special keys that some terminals can send. +It also has richer support for a larger number of special keys that some +terminals can send. -## Better color handling +## Better Color Handling Tcell will respect your terminal's color space as specified within your terminfo entries, so that for example attempts to emit color sequences on VT100 terminals @@ -88,22 +80,15 @@ In Windows mode, Tcell supports 16 colors, bold, dim, and reverse, instead of just termbox's 8 colors with reverse. (Note that there is some conflation with bold/dim and colors.) -Tcell maps 16 colors down to 8, for Terminals that need it. (The upper +Tcell maps 16 colors down to 8, for terminals that need it. (The upper 8 colors are just brighter versions of the lower 8.) -## Better mouse support +## Better Mouse Support Tcell supports enhanced mouse tracking mode, so your application can receive regular mouse motion events, and wheel events, if your terminal supports it. -## Why not just patch termbox-go? - -I started this project originally by submitting patches to the author of -go-termbox, but due to some fundamental differences of opinion, I thought -it might be simpler just to start from scratch. At this point, Tcell has -far exceeded the capabilities of termbox. - -## Termbox compatibility +## Termbox Compatibility A compatibility layer for termbox is provided in the compat directory. To use it, try importing "github.com/gdamore/tcell/termbox" @@ -174,18 +159,18 @@ avoiding repeated sequences or drawing the same cell on refresh updates. (Not relevent for Windows users.) The Terminfo implementation operates with two forms of database. The first -is the database.go file, which contains a number of real database entries +is the built-in go database, which contains a number of real database entries that are compiled into the program directly. This should minimize calling out to database file searches. -The second is a JSON file, that contains the same information, which can -be located either by the $TCELLDB environment file, $HOME/.tcelldb, or is -located in the Go source directory as database.json. +The second is in the form of JSON files, that contain the same information, +which can be located either by the $TCELLDB environment file, $HOME/.tcelldb, +or is located in the Go source directory as database.json. -These files (both the Go database.go and the database.json) file can be -generated using the mkinfo.go program. If you need to regnerate the -entire set for some reason, run the mkdatabase.sh file. The generation -uses the terminfo routines on the system to populate the data files. +These files (both the Go and the JSON files) can be generated using the +mkinfo.go program. If you need to regnerate the entire set for some reason, +run the mkdatabase.sh file. The generation uses the infocmp(1) program on +the system to collect the necessary information. The mkinfo.go program can also be used to generate specific database entries for named terminals, in case your favorite terminal is missing. (If you @@ -227,32 +212,27 @@ and examine "physical" screen contents. ## Platforms -### Systems (Linux, FreeBSD, MacOS, Solaris, etc.) +### POSIX (Linux, FreeBSD, MacOS, Solaris, etc.) -On POSIX systems, a POSIX termios implementation with /dev/tty is required. -On a small subset of these platforms (such as Solaris/illumos), we require -cgo to run, in order to access termios. (Note that Linux and BSD systems -do not require CGO for most purposes.) +For mainstream systems with a suitably well defined system call interface +to tty settings, everything works using pure Go. -(Note: CGO support is required if you wish to rebuild the terminal database -from the system's native terminfo binary files. This is because we use the -system's native libterminfo to access that binary data. We probably could -eliminate that in the future by using a terminfo decompiler such as infocmp.) +For the remainder (right now means only Solaris/illumos) we use POSIX function +calls to manage termios, which implies that CGO is required on those platforms. ### Windows Windows console mode applications are supported. Unfortunately mintty and other cygwin style applications are not supported. -Modern console applications like ConEmu support all the good features -(resize, mouse tracking, etc.) +Modern console applications like ConEmu, as well as the Windows 10 +console itself, support all the good features (resize, mouse tracking, etc.) I haven't figured out how to cleanly resolve the dichotomy between cygwin style termios and the Windows Console API; it seems that perhaps nobody else has either. If anyone has suggestions, let me know! Really, if you're using a Windows application, you should use the native Windows console or a -fully compatible console implementation. Hopefully the Windows 10 console -is more functional in this regard. +fully compatible console implementation. ### Plan9 and Native Client (Nacl) @@ -260,3 +240,15 @@ The nacl and plan9 platforms won't work, but compilation stubs are supplied for folks that want to include parts of this in software targetting those platforms. The Simulation screen works, but as Tcell doesn't know how to allocate a real screen object on those platforms, NewScreen() will fail. + +If anyone has wisdom about how to improve support for either of these, +please let me know. PRs are especially welcome. + +### Commercial Support + +This software is absolutely free, but if you want to obtain commercial +support (giving prioritized access to the developer, etc. on an hourly +rate), please drop a line to info@staysail.tech + +I also welcome donations at Patreon, if you just want to feel good about +defraying development costs: https://www.patreon.com/gedamore diff --git a/vendor/github.com/gdamore/tcell/cell.go b/vendor/github.com/gdamore/tcell/cell.go index b54abcbc..496f10f7 100644 --- a/vendor/github.com/gdamore/tcell/cell.go +++ b/vendor/github.com/gdamore/tcell/cell.go @@ -48,12 +48,13 @@ func (cb *CellBuffer) SetContent(x int, y int, if x >= 0 && y >= 0 && x < cb.w && y < cb.h { c := &cb.cells[(y*cb.w)+x] + c.currComb = append([]rune{}, combc...) i := 0 - for i < len(combc) { - r := combc[i] + for i < len(c.currComb) { + r := c.currComb[i] if runewidth.RuneWidth(r) != 0 { // not a combining character, yank it - combc = append(combc[:i-1], combc[i+1:]...) + c.currComb = append(c.currComb[:i-1], c.currComb[i+1:]...) continue } i++ @@ -63,7 +64,6 @@ func (cb *CellBuffer) SetContent(x int, y int, c.width = runewidth.RuneWidth(mainc) } c.currMain = mainc - c.currComb = combc c.currStyle = style } } diff --git a/vendor/github.com/gdamore/tcell/console_win.go b/vendor/github.com/gdamore/tcell/console_win.go index e27bf6a3..bd05fdf0 100644 --- a/vendor/github.com/gdamore/tcell/console_win.go +++ b/vendor/github.com/gdamore/tcell/console_win.go @@ -17,25 +17,25 @@ package tcell import ( + "errors" "sync" "syscall" "unicode/utf16" "unsafe" - "errors" ) type cScreen struct { - in syscall.Handle - out syscall.Handle + in syscall.Handle + out syscall.Handle cancelflag syscall.Handle - scandone chan struct{} - evch chan Event - quit chan struct{} - curx int - cury int - style Style - clear bool - fini bool + scandone chan struct{} + evch chan Event + quit chan struct{} + curx int + cury int + style Style + clear bool + fini bool w int h int @@ -117,7 +117,7 @@ var ( ) const ( - w32Infinite = ^uintptr(0) + w32Infinite = ^uintptr(0) w32WaitObject0 = uintptr(0) ) @@ -183,7 +183,7 @@ func (s *cScreen) CharacterSet() string { } func (s *cScreen) EnableMouse() { - s.setInMode(modeResizeEn | modeMouseEn) + s.setInMode(modeResizeEn | modeMouseEn | modeExtndFlg) } func (s *cScreen) DisableMouse() { @@ -530,7 +530,7 @@ func (s *cScreen) getConsoleInput() error { uintptr(pWaitObjects), uintptr(0), w32Infinite) - // WaitForMultipleObjects returns WAIT_OBJECT_0 + the index. + // WaitForMultipleObjects returns WAIT_OBJECT_0 + the index. switch rv { case w32WaitObject0: // s.cancelFlag return errors.New("cancelled") @@ -565,8 +565,14 @@ func (s *cScreen) getConsoleInput() error { if krec.ch != 0 { // synthesized key code for krec.repeat > 0 { - s.PostEvent(NewEventKey(KeyRune, rune(krec.ch), - mod2mask(krec.mod))) + // convert shift+tab to backtab + if mod2mask(krec.mod) == ModShift && krec.ch == vkTab { + s.PostEvent(NewEventKey(KeyBacktab, 0, + ModNone)) + } else { + s.PostEvent(NewEventKey(KeyRune, rune(krec.ch), + mod2mask(krec.mod))) + } krec.repeat-- } return nil @@ -920,6 +926,7 @@ func (s *cScreen) clearScreen(style Style) { } const ( + modeExtndFlg uint32 = 0x0080 modeMouseEn uint32 = 0x0010 modeResizeEn uint32 = 0x0008 modeWrapEOL uint32 = 0x0002 diff --git a/vendor/github.com/gdamore/tcell/doc.go b/vendor/github.com/gdamore/tcell/doc.go index 93ebecfd..b6719613 100644 --- a/vendor/github.com/gdamore/tcell/doc.go +++ b/vendor/github.com/gdamore/tcell/doc.go @@ -1,4 +1,4 @@ -// Copyright 2015 The TCell Authors +// Copyright 2018 The TCell Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use file except in compliance with the License. @@ -25,7 +25,7 @@ // of course. (Windows, XTerm, and iTerm 2 are known to work very well.) // // If the environment is not Unicode by default, such as an ISO8859 based -// locale or GB18030, Tcell can convert input and outupt, so that your +// locale or GB18030, Tcell can convert input and output, so that your // terminal can operate in whatever locale is most convenient, while the // application program can just assume "everything is UTF-8". Reasonable // defaults are used for updating characters to something suitable for @@ -34,6 +34,14 @@ // not available. If no ACS is available, then some ASCII fallbacks will // be used. // +// Note that support for non-UTF-8 locales (other than C) must be enabled +// by the application using RegisterEncoding() -- we don't have them all +// enabled by default to avoid bloating the application unneccessarily. +// (These days UTF-8 is good enough for almost everyone, and nobody should +// be using legacy locales anymore.) Also, actual glyphs for various code +// point will only be displayed if your terminal or emulator (or the font +// the emulator is using) supports them. +// // A rich set of keycodes is supported, with support for up to 65 function // keys, and various other special keys. // diff --git a/vendor/github.com/gdamore/tcell/simulation.go b/vendor/github.com/gdamore/tcell/simulation.go index 73980239..850a7b3d 100644 --- a/vendor/github.com/gdamore/tcell/simulation.go +++ b/vendor/github.com/gdamore/tcell/simulation.go @@ -109,6 +109,7 @@ type simscreen struct { func (s *simscreen) Init() error { s.evch = make(chan Event, 10) + s.quit = make(chan struct{}) s.fillchar = 'X' s.fillstyle = StyleDefault s.mouse = false @@ -369,7 +370,7 @@ func (s *simscreen) InjectMouse(x, y int, buttons ButtonMask, mod ModMask) { } func (s *simscreen) InjectKey(key Key, r rune, mod ModMask) { - ev := NewEventKey(KeyRune, r, ModNone) + ev := NewEventKey(key, r, mod) s.PostEvent(ev) } @@ -441,8 +442,10 @@ func (s *simscreen) SetSize(w, h int) { newc[(row*w)+col] = s.front[(row*s.physw)+col] } } - s.physw = w - s.physh = h + s.cursorx, s.cursory = -1, -1 + s.physw, s.physh = w, h + s.front = newc + s.back.Resize(w, h) s.Unlock() } diff --git a/vendor/github.com/gdamore/tcell/tcell.png b/vendor/github.com/gdamore/tcell/tcell.png new file mode 100644 index 0000000000000000000000000000000000000000..24333c4ae9eb1b7565908ff33e37076c90163d16 GIT binary patch literal 5336 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 literal 0 HcmV?d00001 diff --git a/vendor/github.com/gdamore/tcell/tcell.svg b/vendor/github.com/gdamore/tcell/tcell.svg new file mode 100644 index 00000000..d8695d55 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/tcell.svg @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + tcell + + diff --git a/vendor/github.com/gdamore/tcell/terminfo/README.md b/vendor/github.com/gdamore/tcell/terminfo/README.md new file mode 100644 index 00000000..b3337014 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/terminfo/README.md @@ -0,0 +1,8 @@ +To run the database: + +./mkinfo -all + +You can also generate a single entry: + +./mkinfo -db + diff --git a/vendor/github.com/gdamore/tcell/terminfo/mkdatabase.sh b/vendor/github.com/gdamore/tcell/terminfo/mkdatabase.sh deleted file mode 100755 index fd968bdc..00000000 --- a/vendor/github.com/gdamore/tcell/terminfo/mkdatabase.sh +++ /dev/null @@ -1,189 +0,0 @@ -#!/bin/bash - -# Copyright 2017 The TCell Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use file except in compliance with the License. -# You may obtain a copy of the license at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# -# When called with no arguments, this shell script builds the Go database, -# which is somewhat minimal for size reasons (it only contains the most -# commonly used entries), and then builds the complete JSON database. -# -# To limit the action to only building one or more terminals, specify them -# on the command line: -# -# ./mkdatabase xterm -# -# The script will also find and update or add any terminal "aliases". -# It does not remove any old entries. -# -# To add to the set of terminals that we compile into the Go database, -# add their names to the models.txt file. -# - -# This script is not very efficient, but there isn't really a better way -# without writing code to decode the terminfo binary format directly. -# Its not worth worrying about. - -# This script also requires bash, although ksh93 should work as well, because -# we use arrays, which are not specified in POSIX. - -export LANG=C -export LC_CTYPE=C - -progress() -{ - typeset -i num=$1 - typeset -i tot=$2 - typeset -i x - typeset back - typeset s - - if (( tot < 1 )) - then - s=$(printf "[ %d ]" $num) - back="\b\b\b\b\b" - x=$num - while (( x >= 10 )) - do - back="${back}\b" - x=$(( x / 10 )) - done - - else - x=$(( num * 100 / tot )) - s=$(printf "<%3d%%>" $x) - back="\b\b\b\b\b\b" - fi - printf "%s${back}" "$s" -} - -ord() -{ - printf "%02x" "'$1'" -} - -goterms=( $(cat models.txt) ) -args=( $* ) -if (( ${#args[@]} == 0 )) -then - args=( $(toe -a | cut -f1) ) -fi - -printf "Scanning terminal definitions: " -i=0 -aliases=() -models=() -for term in ${args[@]} -do - case "${term}" in - *-truecolor) - line="${term}|24-bit color" - ;; - *) - line=$(infocmp $term | head -2 | tail -1) - if [[ -z "$line" ]] - then - echo "Cannot find terminfo for $term" - exit 1 - fi - # take off the trailing comma - line=${line%,} - esac - - # grab primary name - term=${line%%|*} - all+=( ${term} ) - - # should this be in our go terminals? - for model in ${goterms[@]} - do - if [[ "${model}" == "${term}" ]] - then - models+=( ${term} ) - fi - done - - # chop off primary name - line=${line#${term}} - line=${line#|} - # chop off description - line=${line%|*} - while [[ "$line" != "" ]] - do - a=${line%%|*} - aliases+=( ${a}=${term} ) - line=${line#$a} - line=${line#|} - done - i=$(( i + 1 )) - progress $i ${#args[@]} -done -echo -# make sure we have mkinfo -printf "Building mkinfo: " -go build mkinfo.go -echo "done." - -# Build all the go database files for the "interesting" terminals". -printf "Building Go database: " -i=0 -for model in ${models[@]} -do - safe=$(echo $model | tr - _) - file=term_${safe}.go - ./mkinfo -go $file $model - go fmt ${file} >/dev/null - i=$(( i + 1 )) - progress $i ${#models[@]} -done -echo - -printf "Building JSON database: " - -# The JSON files are located for each terminal in a file with the -# terminal name, in the following fashion "database/x/xterm.json - -i=0 -for model in ${all[@]} -do - letter=$(ord ${model:0:1}) - dir=database/${letter} - file=${dir}/${model}.gz - mkdir -p ${dir} - ./mkinfo -nofatal -quiet -gzip -json ${file} ${model} - i=$(( i + 1 )) - progress $i ${#all[@]} -done -echo - -printf "Building JSON aliases: " -i=0 -for model in ${aliases[@]} -do - canon=${model#*=} - model=${model%=*} - letter=$(ord ${model:0:1}) - cletter=$(ord ${canon:0:1}) - dir=database/${letter} - file=${dir}/${model} - if [[ -f database/${cletter}/${canon}.gz ]] - then - [[ -d ${dir} ]] || mkdir -p ${dir} - # Generally speaking the aliases are better uncompressed - ./mkinfo -nofatal -quiet -json ${file} ${model} - fi - i=$(( i + 1 )) - progress $i ${#aliases[@]} -done -echo diff --git a/vendor/github.com/gdamore/tcell/terminfo/mkinfo.go b/vendor/github.com/gdamore/tcell/terminfo/mkinfo.go index ba85330c..50d70f74 100644 --- a/vendor/github.com/gdamore/tcell/terminfo/mkinfo.go +++ b/vendor/github.com/gdamore/tcell/terminfo/mkinfo.go @@ -1,6 +1,6 @@ // +build ignore -// Copyright 2017 The TCell Authors +// Copyright 2018 The TCell Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use file except in compliance with the License. @@ -22,6 +22,8 @@ // // mkinfo [-init] [-go file.go] [-json file.json] [-quiet] [-nofatal] [...] // +// -all scan terminfo to determine database entries to use +// -db generate database entries (database/*), implied for -all // -gzip specifies output should be compressed (json only) // -go specifies Go output into the named file. Use - for stdout. // -json specifies JSON output in the named file. Use - for stdout @@ -31,8 +33,10 @@ package main import ( + "bufio" "bytes" "compress/gzip" + "crypto/sha1" "encoding/json" "errors" "flag" @@ -40,6 +44,7 @@ import ( "io" "os" "os/exec" + "path" "regexp" "strconv" "strings" @@ -74,6 +79,8 @@ const ( ESC ) +var notaddressable = errors.New("terminal not cursor addressable") + func unescape(s string) string { // Various escapes are in \x format. Control codes are // encoded as ^M (carat followed by ASCII equivalent). @@ -101,8 +108,13 @@ func unescape(s string) string { switch c { case 'E', 'e': buf.WriteByte(0x1b) - case '0': - buf.WriteByte(0) + case '0', '1', '2', '3', '4', '5', '6', '7': + if i+2 < len(s) && s[i+1] >= '0' && s[i+1] <= '7' && s[i+2] >= '0' && s[i+2] <= '7' { + buf.WriteByte(((c - '0') * 64) + ((s[i+1] - '0') * 8) + (s[i+2] - '0')) + i = i + 2 + } else if c == '0' { + buf.WriteByte(0) + } case 'n': buf.WriteByte('\n') case 'r': @@ -126,6 +138,25 @@ func unescape(s string) string { return (buf.String()) } +func getallterms() ([]string, error) { + out := []string{} + cmd := exec.Command("toe", "-a") + output := &bytes.Buffer{} + cmd.Stdout = output + err := cmd.Run() + if err != nil { + return nil, err + } + lines := strings.Split(output.String(), "\n") + for _, l := range lines { + fields := strings.Fields(l) + if len(fields) > 0 { + out = append(out, fields[0]) + } + } + return out, nil +} + func (tc *termcap) setupterm(name string) error { cmd := exec.Command("infocmp", "-1", name) output := &bytes.Buffer{} @@ -178,7 +209,7 @@ func (tc *termcap) setupterm(name string) error { if k := strings.SplitN(val, "=", 2); len(k) == 2 { tc.strs[k[0]] = unescape(k[1]) } else if k := strings.SplitN(val, "#", 2); len(k) == 2 { - if u, err := strconv.ParseUint(k[1], 10, 0); err != nil { + if u, err := strconv.ParseUint(k[1], 0, 0); err != nil { return (err) } else { tc.nums[k[0]] = int(u) @@ -428,7 +459,7 @@ func getinfo(name string) (*terminfo.Terminfo, string, error) { t.Colors = 0 } if t.SetCursor == "" { - return nil, "", errors.New("terminal not cursor addressable") + return nil, "", notaddressable } // For padding, we lookup the pad char. If that isn't present, @@ -672,13 +703,135 @@ func dotGoInfo(w io.Writer, t *terminfo.Terminfo, desc string) { fmt.Fprintln(w, "}") } +var packname = "terminfo" + +func dotGoFile(fname string, term *terminfo.Terminfo, desc string, makeDir bool) error { + w := os.Stdout + var e error + if fname != "-" && fname != "" { + if makeDir { + dname := path.Dir(fname) + _ = os.Mkdir(dname, 0777) + } + if w, e = os.Create(fname); e != nil { + return e + } + } + dotGoHeader(w, packname) + dotGoInfo(w, term, desc) + dotGoTrailer(w) + if w != os.Stdout { + w.Close() + } + cmd := exec.Command("go", "fmt", fname) + cmd.Run() + return nil +} + +func dotGzFile(fname string, term *terminfo.Terminfo, makeDir bool) error { + + var w io.WriteCloser = os.Stdout + var e error + if fname != "-" && fname != "" { + if makeDir { + dname := path.Dir(fname) + _ = os.Mkdir(dname, 0777) + } + if w, e = os.Create(fname); e != nil { + return e + } + } + + w = gzip.NewWriter(w) + + js, e := json.Marshal(term) + fmt.Fprintln(w, string(js)) + + if w != os.Stdout { + w.Close() + } + return nil +} + +func jsonFile(fname string, term *terminfo.Terminfo, makeDir bool) error { + w := os.Stdout + var e error + if fname != "-" && fname != "" { + if makeDir { + dname := path.Dir(fname) + _ = os.Mkdir(dname, 0777) + } + if w, e = os.Create(fname); e != nil { + return e + } + } + + js, e := json.Marshal(term) + fmt.Fprintln(w, string(js)) + + if w != os.Stdout { + w.Close() + } + return nil +} + +func dumpDatabase(terms map[string]*terminfo.Terminfo, descs map[string]string) { + + // Load models .text + mfile, e := os.Open("models.txt") + models := make(map[string]bool) + if e != nil { + fmt.Fprintf(os.Stderr, "Failed reading models.txt: %v", e) + } + scanner := bufio.NewScanner(mfile) + for scanner.Scan() { + models[scanner.Text()] = true + } + + for name, t := range terms { + + // If this is one of our builtin models, generate the GO file + if models[name] { + desc := descs[name] + safename := strings.Replace(name, "-", "_", -1) + goname := fmt.Sprintf("term_%s.go", safename) + e = dotGoFile(goname, t, desc, true) + if e != nil { + fmt.Fprintf(os.Stderr, "Failed creating %s: %v", goname, e) + os.Exit(1) + } + continue + } + + hash := fmt.Sprintf("%x", sha1.Sum([]byte(name))) + fname := fmt.Sprintf("%s.gz", hash[0:8]) + fname = path.Join("database", hash[0:2], fname) + e = dotGzFile(fname, t, true) + if e != nil { + fmt.Fprintf(os.Stderr, "Failed creating %s: %v", fname, e) + os.Exit(1) + } + + for _, a := range t.Aliases { + hash = fmt.Sprintf("%x", sha1.Sum([]byte(a))) + fname = path.Join("database", hash[0:2], hash[0:8]) + e = jsonFile(fname, &terminfo.Terminfo{Name: t.Name}, true) + if e != nil { + fmt.Fprintf(os.Stderr, "Failed creating %s: %v", fname, e) + os.Exit(1) + } + } + } +} + func main() { gofile := "" jsonfile := "" - packname := "terminfo" nofatal := false quiet := false dogzip := false + all := false + db := false flag.StringVar(&gofile, "go", "", "generate go source in named file") flag.StringVar(&jsonfile, "json", "", "generate json in named file") @@ -686,11 +839,21 @@ func main() { flag.BoolVar(&nofatal, "nofatal", false, "errors are not fatal") flag.BoolVar(&quiet, "quiet", false, "suppress error messages") flag.BoolVar(&dogzip, "gzip", false, "compress json output") + flag.BoolVar(&all, "all", false, "load all terminals from terminfo") + flag.BoolVar(&db, "db", false, "generate json db file in place") flag.Parse() var e error - js := []byte{} args := flag.Args() + if all { + db = true // implied + allterms, e := getallterms() + if e != nil { + fmt.Fprintf(os.Stderr, "Failed: %v", e) + os.Exit(1) + } + args = append(args, allterms...) + } if len(args) == 0 { args = []string{os.Getenv("TERM")} } @@ -700,6 +863,9 @@ func main() { for _, term := range args { if t, desc, e := getinfo(term); e != nil { + if all && e == notaddressable { + continue + } if !quiet { fmt.Fprintf(os.Stderr, "Failed loading %s: %v\n", term, e) @@ -717,53 +883,33 @@ func main() { // No data. os.Exit(0) } - if gofile != "" { - w := os.Stdout - if gofile != "-" { - if w, e = os.Create(gofile); e != nil { - fmt.Fprintf(os.Stderr, "Failed: %v", e) - os.Exit(1) - } - } - dotGoHeader(w, packname) + + if db { + dumpDatabase(tdata, descs) + } else if gofile != "" { for term, t := range tdata { if t.Name == term { - dotGoInfo(w, t, descs[term]) + e = dotGoFile(gofile, t, descs[term], false) + if e != nil { + fmt.Fprintf(os.Stderr, "Failed %s: %v", gofile, e) + os.Exit(1) + } } } - dotGoTrailer(w) - if w != os.Stdout { - w.Close() - } + } else { - o := os.Stdout - if jsonfile != "-" && jsonfile != "" { - if o, e = os.Create(jsonfile); e != nil { - fmt.Fprintf(os.Stderr, "Failed: %v", e) + for _, t := range tdata { + if dogzip { + if e = dotGzFile(jsonfile, t, false); e != nil { + fmt.Fprintf(os.Stderr, "Failed %s: %v", gofile, e) + os.Exit(1) + } + } else { + if e = jsonFile(jsonfile, t, false); e != nil { + fmt.Fprintf(os.Stderr, "Failed %s: %v", gofile, e) + os.Exit(1) + } } } - var w io.WriteCloser - w = o - if dogzip { - w = gzip.NewWriter(o) - } - for _, term := range args { - if t := tdata[term]; t != nil { - js, e = json.Marshal(t) - fmt.Fprintln(w, string(js)) - } - // arguably if there is more than one term, this - // should be a javascript array, but that's not how - // we load it. We marshal objects one at a time from - // the file. - } - if e != nil { - fmt.Fprintf(os.Stderr, "Failed: %v", e) - os.Exit(1) - } - w.Close() - if w != o { - o.Close() - } } } diff --git a/vendor/github.com/gdamore/tcell/terminfo/models.txt b/vendor/github.com/gdamore/tcell/terminfo/models.txt index ac0676d6..718eb9bc 100644 --- a/vendor/github.com/gdamore/tcell/terminfo/models.txt +++ b/vendor/github.com/gdamore/tcell/terminfo/models.txt @@ -8,8 +8,6 @@ cygwin d200 d210 dtterm -Eterm -Eterm-256color eterm gnome gnome-256color diff --git a/vendor/github.com/gdamore/tcell/terminfo/term_Eterm.go b/vendor/github.com/gdamore/tcell/terminfo/term_Eterm.go deleted file mode 100644 index 9290e32b..00000000 --- a/vendor/github.com/gdamore/tcell/terminfo/term_Eterm.go +++ /dev/null @@ -1,24 +0,0 @@ -// Generated automatically. DO NOT HAND-EDIT. - -package terminfo - -func init() { - // gnu emacs term.el terminal emulation - AddTerminfo(&Terminfo{ - Name: "eterm", - Columns: 80, - Lines: 24, - Bell: "\a", - Clear: "\x1b[H\x1b[J", - EnterCA: "\x1b7\x1b[?47h", - ExitCA: "\x1b[2J\x1b[?47l\x1b8", - AttrOff: "\x1b[m", - Underline: "\x1b[4m", - Bold: "\x1b[1m", - Reverse: "\x1b[7m", - PadChar: "\x00", - SetCursor: "\x1b[%i%p1%d;%p2%dH", - CursorBack1: "\b", - CursorUp1: "\x1b[A", - }) -} diff --git a/vendor/github.com/gdamore/tcell/terminfo/term_Eterm_256color.go b/vendor/github.com/gdamore/tcell/terminfo/term_Eterm_256color.go deleted file mode 100644 index af2f5ff1..00000000 --- a/vendor/github.com/gdamore/tcell/terminfo/term_Eterm_256color.go +++ /dev/null @@ -1,105 +0,0 @@ -// Generated automatically. DO NOT HAND-EDIT. - -package terminfo - -func init() { - // Eterm with xterm 256-colors - AddTerminfo(&Terminfo{ - Name: "Eterm-256color", - Columns: 80, - Lines: 24, - Colors: 256, - Bell: "\a", - Clear: "\x1b[H\x1b[2J", - EnterCA: "\x1b7\x1b[?47h", - ExitCA: "\x1b[2J\x1b[?47l\x1b8", - ShowCursor: "\x1b[?25h", - HideCursor: "\x1b[?25l", - AttrOff: "\x1b[m\x0017", - Underline: "\x1b[4m", - Bold: "\x1b[1m", - Blink: "\x1b[5m", - Reverse: "\x1b[7m", - SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m", - SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m", - SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m", - PadChar: "\x00", - AltChars: "``aaffggjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", - EnterAcs: "\x0e", - ExitAcs: "\x0f", - EnableAcs: "\x1b)0", - Mouse: "\x1b[M", - MouseMode: "%?%p1%{1}%=%t%'h'%Pa%e%'l'%Pa%;\x1b[?1000%ga%c\x1b[?1002%ga%c\x1b[?1003%ga%c\x1b[?1006%ga%c", - SetCursor: "\x1b[%i%p1%d;%p2%dH", - CursorBack1: "\b", - CursorUp1: "\x1b[A", - KeyUp: "\x1b[A", - KeyDown: "\x1b[B", - KeyRight: "\x1b[C", - KeyLeft: "\x1b[D", - KeyInsert: "\x1b[2~", - KeyDelete: "\x1b[3~", - KeyBackspace: "\b", - KeyHome: "\x1b[7~", - KeyEnd: "\x1b[8~", - KeyPgUp: "\x1b[5~", - KeyPgDn: "\x1b[6~", - KeyF1: "\x1b[11~", - KeyF2: "\x1b[12~", - KeyF3: "\x1b[13~", - KeyF4: "\x1b[14~", - KeyF5: "\x1b[15~", - KeyF6: "\x1b[17~", - KeyF7: "\x1b[18~", - KeyF8: "\x1b[19~", - KeyF9: "\x1b[20~", - KeyF10: "\x1b[21~", - KeyF11: "\x1b[23~", - KeyF12: "\x1b[24~", - KeyF13: "\x1b[25~", - KeyF14: "\x1b[26~", - KeyF15: "\x1b[28~", - KeyF16: "\x1b[29~", - KeyF17: "\x1b[31~", - KeyF18: "\x1b[32~", - KeyF19: "\x1b[33~", - KeyF20: "\x1b[34~", - KeyF21: "\x1b[23$", - KeyF22: "\x1b[24$", - KeyF23: "\x1b[11^", - KeyF24: "\x1b[12^", - KeyF25: "\x1b[13^", - KeyF26: "\x1b[14^", - KeyF27: "\x1b[15^", - KeyF28: "\x1b[17^", - KeyF29: "\x1b[18^", - KeyF30: "\x1b[19^", - KeyF31: "\x1b[20^", - KeyF32: "\x1b[21^", - KeyF33: "\x1b[23^", - KeyF34: "\x1b[24^", - KeyF35: "\x1b[25^", - KeyF36: "\x1b[26^", - KeyF37: "\x1b[28^", - KeyF38: "\x1b[29^", - KeyF39: "\x1b[31^", - KeyF40: "\x1b[32^", - KeyF41: "\x1b[33^", - KeyF42: "\x1b[34^", - KeyF43: "\x1b[23@", - KeyF44: "\x1b[24@", - KeyHelp: "\x1b[28~", - KeyShfLeft: "\x1b[d", - KeyShfRight: "\x1b[c", - KeyShfUp: "\x1b[a", - KeyShfDown: "\x1b[b", - KeyCtrlLeft: "\x1b[Od", - KeyCtrlRight: "\x1b[Oc", - KeyCtrlUp: "\x1b[Oa", - KeyCtrlDown: "\x1b[Ob", - KeyShfHome: "\x1b[7$", - KeyShfEnd: "\x1b[8$", - KeyCtrlHome: "\x1b[7^", - KeyCtrlEnd: "\x1b[8^", - }) -} diff --git a/vendor/github.com/gdamore/tcell/terminfo/term_adm3a.go b/vendor/github.com/gdamore/tcell/terminfo/term_adm3a.go index dc57809e..d8709dac 100644 --- a/vendor/github.com/gdamore/tcell/terminfo/term_adm3a.go +++ b/vendor/github.com/gdamore/tcell/terminfo/term_adm3a.go @@ -9,7 +9,7 @@ func init() { Columns: 80, Lines: 24, Bell: "\a", - Clear: "\x0032$<1/>", + Clear: "\x1a$<1/>", PadChar: "\x00", SetCursor: "\x1b=%p1%' '%+%c%p2%' '%+%c", CursorBack1: "\b", diff --git a/vendor/github.com/gdamore/tcell/terminfo/term_ansi.go b/vendor/github.com/gdamore/tcell/terminfo/term_ansi.go index 52a68ea4..a7909931 100644 --- a/vendor/github.com/gdamore/tcell/terminfo/term_ansi.go +++ b/vendor/github.com/gdamore/tcell/terminfo/term_ansi.go @@ -20,7 +20,7 @@ func init() { SetBg: "\x1b[4%p1%dm", SetFgBg: "\x1b[3%p1%d;4%p2%dm", PadChar: "\x00", - AltChars: "+\x0020,\x0021-\x0030.\x190333`\x0004a261f370g361h260j331k277l332m300n305o~p304q304r304s_t303u264v301w302x263y363z362{343|330}234~376", + AltChars: "+\x10,\x11-\x18.\x190\xdb`\x04a\xb1f\xf8g\xf1h\xb0j\xd9k\xbfl\xdam\xc0n\xc5o~p\xc4q\xc4r\xc4s_t\xc3u\xb4v\xc1w\xc2x\xb3y\xf3z\xf2{\xe3|\xd8}\x9c~\xfe", EnterAcs: "\x1b[11m", ExitAcs: "\x1b[10m", SetCursor: "\x1b[%i%p1%d;%p2%dH", diff --git a/vendor/github.com/gdamore/tcell/terminfo/term_aterm.go b/vendor/github.com/gdamore/tcell/terminfo/term_aterm.go index 94daaafa..77177a22 100644 --- a/vendor/github.com/gdamore/tcell/terminfo/term_aterm.go +++ b/vendor/github.com/gdamore/tcell/terminfo/term_aterm.go @@ -15,7 +15,7 @@ func init() { ExitCA: "\x1b[2J\x1b[?47l\x1b8", ShowCursor: "\x1b[?25h", HideCursor: "\x1b[?25l", - AttrOff: "\x1b[m\x0017", + AttrOff: "\x1b[m\x0f", Underline: "\x1b[4m", Bold: "\x1b[1m", Blink: "\x1b[5m", @@ -41,7 +41,7 @@ func init() { KeyLeft: "\x1b[D", KeyInsert: "\x1b[2~", KeyDelete: "\x1b[3~", - KeyBackspace: "177", + KeyBackspace: "\u007f", KeyHome: "\x1b[7~", KeyEnd: "\x1b[8~", KeyPgUp: "\x1b[5~", diff --git a/vendor/github.com/gdamore/tcell/terminfo/term_bsdos_pc.go b/vendor/github.com/gdamore/tcell/terminfo/term_bsdos_pc.go index 413ea649..c0561261 100644 --- a/vendor/github.com/gdamore/tcell/terminfo/term_bsdos_pc.go +++ b/vendor/github.com/gdamore/tcell/terminfo/term_bsdos_pc.go @@ -20,7 +20,7 @@ func init() { SetBg: "\x1b[4%p1%dm", SetFgBg: "\x1b[3%p1%d;4%p2%dm", PadChar: "\x00", - AltChars: "+\x0020,\x0021-\x0030.\x190333`\x0004a261f370g361h260j331k277l332m300n305o~p304q304r304s_t303u264v301w302x263y363z362{343|330}234~376", + AltChars: "+\x10,\x11-\x18.\x190\xdb`\x04a\xb1f\xf8g\xf1h\xb0j\xd9k\xbfl\xdam\xc0n\xc5o~p\xc4q\xc4r\xc4s_t\xc3u\xb4v\xc1w\xc2x\xb3y\xf3z\xf2{\xe3|\xd8}\x9c~\xfe", EnterAcs: "\x1b[11m", ExitAcs: "\x1b[10m", SetCursor: "\x1b[%i%p1%d;%p2%dH", diff --git a/vendor/github.com/gdamore/tcell/terminfo/term_cygwin.go b/vendor/github.com/gdamore/tcell/terminfo/term_cygwin.go index 1fdc571c..568bbe14 100644 --- a/vendor/github.com/gdamore/tcell/terminfo/term_cygwin.go +++ b/vendor/github.com/gdamore/tcell/terminfo/term_cygwin.go @@ -19,7 +19,7 @@ func init() { SetBg: "\x1b[4%p1%dm", SetFgBg: "\x1b[3%p1%d;4%p2%dm", PadChar: "\x00", - AltChars: "+\x0020,\x0021-\x0030.\x190333`\x0004a261f370g361h260j331k277l332m300n305o~p304q304r304s_t303u264v301w302x263y363z362{343|330}234~376", + AltChars: "+\x10,\x11-\x18.\x190\xdb`\x04a\xb1f\xf8g\xf1h\xb0j\xd9k\xbfl\xdam\xc0n\xc5o~p\xc4q\xc4r\xc4s_t\xc3u\xb4v\xc1w\xc2x\xb3y\xf3z\xf2{\xe3|\xd8}\x9c~\xfe", EnterAcs: "\x1b[11m", ExitAcs: "\x1b[10m", SetCursor: "\x1b[%i%p1%d;%p2%dH", diff --git a/vendor/github.com/gdamore/tcell/terminfo/term_d200.go b/vendor/github.com/gdamore/tcell/terminfo/term_d200.go index e8016797..611f6ee8 100644 --- a/vendor/github.com/gdamore/tcell/terminfo/term_d200.go +++ b/vendor/github.com/gdamore/tcell/terminfo/term_d200.go @@ -11,14 +11,14 @@ func init() { Lines: 24, Bell: "\a", Clear: "\f", - AttrOff: "\x0017\x0025\x0035\x0036E", + AttrOff: "\x0f\x15\x1d\x1eE", Underline: "\x14", Bold: "\x1eD\x14", Dim: "\x1c", Blink: "\x0e", Reverse: "\x1eD", PadChar: "\x00", - SetCursor: "\x0020%p2%c%p1%c", + SetCursor: "\x10%p2%c%p1%c", CursorBack1: "\x19", CursorUp1: "\x17", KeyUp: "\x17", diff --git a/vendor/github.com/gdamore/tcell/terminfo/term_dtterm.go b/vendor/github.com/gdamore/tcell/terminfo/term_dtterm.go index c173d56b..9c563c50 100644 --- a/vendor/github.com/gdamore/tcell/terminfo/term_dtterm.go +++ b/vendor/github.com/gdamore/tcell/terminfo/term_dtterm.go @@ -13,7 +13,7 @@ func init() { Clear: "\x1b[H\x1b[J", ShowCursor: "\x1b[?25h", HideCursor: "\x1b[?25l", - AttrOff: "\x1b[m\x0017", + AttrOff: "\x1b[m\x0f", Underline: "\x1b[4m", Bold: "\x1b[1m", Dim: "\x1b[2m", diff --git a/vendor/github.com/gdamore/tcell/terminfo/term_gnome.go b/vendor/github.com/gdamore/tcell/terminfo/term_gnome.go index 0b7b85a7..d7907f4c 100644 --- a/vendor/github.com/gdamore/tcell/terminfo/term_gnome.go +++ b/vendor/github.com/gdamore/tcell/terminfo/term_gnome.go @@ -15,9 +15,10 @@ func init() { ExitCA: "\x1b[2J\x1b[?47l\x1b8", ShowCursor: "\x1b[?25h", HideCursor: "\x1b[?25l", - AttrOff: "\x1b[0m\x0017", + AttrOff: "\x1b[0m\x0f", Underline: "\x1b[4m", Bold: "\x1b[1m", + Dim: "\x1b[2m", Reverse: "\x1b[7m", EnterKeypad: "\x1b[?1h\x1b=", ExitKeypad: "\x1b[?1l\x1b>", @@ -40,7 +41,7 @@ func init() { KeyLeft: "\x1bOD", KeyInsert: "\x1b[2~", KeyDelete: "\x1b[3~", - KeyBackspace: "177", + KeyBackspace: "\u007f", KeyHome: "\x1bOH", KeyEnd: "\x1bOF", KeyPgUp: "\x1b[5~", diff --git a/vendor/github.com/gdamore/tcell/terminfo/term_gnome_256color.go b/vendor/github.com/gdamore/tcell/terminfo/term_gnome_256color.go index 23834898..342699d4 100644 --- a/vendor/github.com/gdamore/tcell/terminfo/term_gnome_256color.go +++ b/vendor/github.com/gdamore/tcell/terminfo/term_gnome_256color.go @@ -15,9 +15,10 @@ func init() { ExitCA: "\x1b[2J\x1b[?47l\x1b8", ShowCursor: "\x1b[?25h", HideCursor: "\x1b[?25l", - AttrOff: "\x1b[0m\x0017", + AttrOff: "\x1b[0m\x0f", Underline: "\x1b[4m", Bold: "\x1b[1m", + Dim: "\x1b[2m", Reverse: "\x1b[7m", EnterKeypad: "\x1b[?1h\x1b=", ExitKeypad: "\x1b[?1l\x1b>", @@ -40,7 +41,7 @@ func init() { KeyLeft: "\x1bOD", KeyInsert: "\x1b[2~", KeyDelete: "\x1b[3~", - KeyBackspace: "177", + KeyBackspace: "\u007f", KeyHome: "\x1bOH", KeyEnd: "\x1bOF", KeyPgUp: "\x1b[5~", @@ -57,10 +58,10 @@ func init() { KeyF10: "\x1b[21~", KeyF11: "\x1b[23~", KeyF12: "\x1b[24~", - KeyF13: "\x1bO1;2P", - KeyF14: "\x1bO1;2Q", - KeyF15: "\x1bO1;2R", - KeyF16: "\x1bO1;2S", + KeyF13: "\x1b[1;2P", + KeyF14: "\x1b[1;2Q", + KeyF15: "\x1b[1;2R", + KeyF16: "\x1b[1;2S", KeyF17: "\x1b[15;2~", KeyF18: "\x1b[17;2~", KeyF19: "\x1b[18;2~", @@ -69,10 +70,10 @@ func init() { KeyF22: "\x1b[21;2~", KeyF23: "\x1b[23;2~", KeyF24: "\x1b[24;2~", - KeyF25: "\x1bO1;5P", - KeyF26: "\x1bO1;5Q", - KeyF27: "\x1bO1;5R", - KeyF28: "\x1bO1;5S", + KeyF25: "\x1b[1;5P", + KeyF26: "\x1b[1;5Q", + KeyF27: "\x1b[1;5R", + KeyF28: "\x1b[1;5S", KeyF29: "\x1b[15;5~", KeyF30: "\x1b[17;5~", KeyF31: "\x1b[18;5~", @@ -81,10 +82,10 @@ func init() { KeyF34: "\x1b[21;5~", KeyF35: "\x1b[23;5~", KeyF36: "\x1b[24;5~", - KeyF37: "\x1bO1;6P", - KeyF38: "\x1bO1;6Q", - KeyF39: "\x1bO1;6R", - KeyF40: "\x1bO1;6S", + KeyF37: "\x1b[1;6P", + KeyF38: "\x1b[1;6Q", + KeyF39: "\x1b[1;6R", + KeyF40: "\x1b[1;6S", KeyF41: "\x1b[15;6~", KeyF42: "\x1b[17;6~", KeyF43: "\x1b[18;6~", @@ -93,10 +94,10 @@ func init() { KeyF46: "\x1b[21;6~", KeyF47: "\x1b[23;6~", KeyF48: "\x1b[24;6~", - KeyF49: "\x1bO1;3P", - KeyF50: "\x1bO1;3Q", - KeyF51: "\x1bO1;3R", - KeyF52: "\x1bO1;3S", + KeyF49: "\x1b[1;3P", + KeyF50: "\x1b[1;3Q", + KeyF51: "\x1b[1;3R", + KeyF52: "\x1b[1;3S", KeyF53: "\x1b[15;3~", KeyF54: "\x1b[17;3~", KeyF55: "\x1b[18;3~", @@ -105,9 +106,9 @@ func init() { KeyF58: "\x1b[21;3~", KeyF59: "\x1b[23;3~", KeyF60: "\x1b[24;3~", - KeyF61: "\x1bO1;4P", - KeyF62: "\x1bO1;4Q", - KeyF63: "\x1bO1;4R", + KeyF61: "\x1b[1;4P", + KeyF62: "\x1b[1;4Q", + KeyF63: "\x1b[1;4R", KeyBacktab: "\x1b[Z", KeyShfLeft: "\x1b[1;2D", KeyShfRight: "\x1b[1;2C", diff --git a/vendor/github.com/gdamore/tcell/terminfo/term_hz1500.go b/vendor/github.com/gdamore/tcell/terminfo/term_hz1500.go index 8af0eff5..34ef6efa 100644 --- a/vendor/github.com/gdamore/tcell/terminfo/term_hz1500.go +++ b/vendor/github.com/gdamore/tcell/terminfo/term_hz1500.go @@ -11,7 +11,7 @@ func init() { Bell: "\a", Clear: "~\x1c", PadChar: "\x00", - SetCursor: "~\x0021%p2%p2%?%{30}%>%t%' '%+%;%'`'%+%c%p1%'`'%+%c", + SetCursor: "~\x11%p2%p2%?%{30}%>%t%' '%+%;%'`'%+%c%p1%'`'%+%c", CursorBack1: "\b", CursorUp1: "~\f", KeyUp: "~\f", diff --git a/vendor/github.com/gdamore/tcell/terminfo/term_konsole.go b/vendor/github.com/gdamore/tcell/terminfo/term_konsole.go index c2689de4..5c4e4ec4 100644 --- a/vendor/github.com/gdamore/tcell/terminfo/term_konsole.go +++ b/vendor/github.com/gdamore/tcell/terminfo/term_konsole.go @@ -14,7 +14,7 @@ func init() { ExitCA: "\x1b[2J\x1b[?47l\x1b8", ShowCursor: "\x1b[?25h", HideCursor: "\x1b[?25l", - AttrOff: "\x1b[0m\x0017", + AttrOff: "\x1b[0m\x0f", Underline: "\x1b[4m", Bold: "\x1b[1m", Blink: "\x1b[5m", @@ -39,7 +39,7 @@ func init() { KeyLeft: "\x1bOD", KeyInsert: "\x1b[2~", KeyDelete: "\x1b[3~", - KeyBackspace: "177", + KeyBackspace: "\u007f", KeyHome: "\x1bOH", KeyEnd: "\x1bOF", KeyPgUp: "\x1b[5~", diff --git a/vendor/github.com/gdamore/tcell/terminfo/term_konsole_256color.go b/vendor/github.com/gdamore/tcell/terminfo/term_konsole_256color.go index 7adc8ea2..cbe0314d 100644 --- a/vendor/github.com/gdamore/tcell/terminfo/term_konsole_256color.go +++ b/vendor/github.com/gdamore/tcell/terminfo/term_konsole_256color.go @@ -14,7 +14,7 @@ func init() { ExitCA: "\x1b[2J\x1b[?47l\x1b8", ShowCursor: "\x1b[?25h", HideCursor: "\x1b[?25l", - AttrOff: "\x1b[0m\x0017", + AttrOff: "\x1b[0m\x0f", Underline: "\x1b[4m", Bold: "\x1b[1m", Blink: "\x1b[5m", @@ -39,7 +39,7 @@ func init() { KeyLeft: "\x1bOD", KeyInsert: "\x1b[2~", KeyDelete: "\x1b[3~", - KeyBackspace: "177", + KeyBackspace: "\u007f", KeyHome: "\x1bOH", KeyEnd: "\x1bOF", KeyPgUp: "\x1b[5~", diff --git a/vendor/github.com/gdamore/tcell/terminfo/term_linux.go b/vendor/github.com/gdamore/tcell/terminfo/term_linux.go index e35a4775..a3d18720 100644 --- a/vendor/github.com/gdamore/tcell/terminfo/term_linux.go +++ b/vendor/github.com/gdamore/tcell/terminfo/term_linux.go @@ -11,7 +11,7 @@ func init() { Clear: "\x1b[H\x1b[J", ShowCursor: "\x1b[?25h\x1b[?0c", HideCursor: "\x1b[?25l\x1b[?1c", - AttrOff: "\x1b[0;10m", + AttrOff: "\x1b[m\x0f", Underline: "\x1b[4m", Bold: "\x1b[1m", Dim: "\x1b[2m", @@ -21,9 +21,10 @@ func init() { SetBg: "\x1b[4%p1%dm", SetFgBg: "\x1b[3%p1%d;4%p2%dm", PadChar: "\x00", - AltChars: "+\x0020,\x0021-\x0030.\x190333`\x0004a261f370g361h260i316j331k277l332m300n305o~p304q304r304s_t303u264v301w302x263y363z362{343|330}234~376", - EnterAcs: "\x1b[11m", - ExitAcs: "\x1b[10m", + AltChars: "++,,--..00__``aaffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}c~~", + EnterAcs: "\x0e", + ExitAcs: "\x0f", + EnableAcs: "\x1b(B\x1b)0", Mouse: "\x1b[M", MouseMode: "%?%p1%{1}%=%t%'h'%Pa%e%'l'%Pa%;\x1b[?1000%ga%c\x1b[?1002%ga%c\x1b[?1003%ga%c\x1b[?1006%ga%c", SetCursor: "\x1b[%i%p1%d;%p2%dH", @@ -35,7 +36,7 @@ func init() { KeyLeft: "\x1b[D", KeyInsert: "\x1b[2~", KeyDelete: "\x1b[3~", - KeyBackspace: "177", + KeyBackspace: "\u007f", KeyHome: "\x1b[1~", KeyEnd: "\x1b[4~", KeyPgUp: "\x1b[5~", diff --git a/vendor/github.com/gdamore/tcell/terminfo/term_pcansi.go b/vendor/github.com/gdamore/tcell/terminfo/term_pcansi.go index 055b3acf..270ee459 100644 --- a/vendor/github.com/gdamore/tcell/terminfo/term_pcansi.go +++ b/vendor/github.com/gdamore/tcell/terminfo/term_pcansi.go @@ -20,7 +20,7 @@ func init() { SetBg: "\x1b[4%p1%dm", SetFgBg: "\x1b[3%p1%d;4%p2%dm", PadChar: "\x00", - AltChars: "+\x0020,\x0021-\x0030.\x190333`\x0004a261f370g361h260j331k277l332m300n305o~p304q304r304s_t303u264v301w302x263y363z362{343|330}234~376", + AltChars: "+\x10,\x11-\x18.\x190\xdb`\x04a\xb1f\xf8g\xf1h\xb0j\xd9k\xbfl\xdam\xc0n\xc5o~p\xc4q\xc4r\xc4s_t\xc3u\xb4v\xc1w\xc2x\xb3y\xf3z\xf2{\xe3|\xd8}\x9c~\xfe", EnterAcs: "\x1b[12m", ExitAcs: "\x1b[10m", SetCursor: "\x1b[%i%p1%d;%p2%dH", diff --git a/vendor/github.com/gdamore/tcell/terminfo/term_rxvt.go b/vendor/github.com/gdamore/tcell/terminfo/term_rxvt.go index dab1758b..9ce8e805 100644 --- a/vendor/github.com/gdamore/tcell/terminfo/term_rxvt.go +++ b/vendor/github.com/gdamore/tcell/terminfo/term_rxvt.go @@ -15,7 +15,7 @@ func init() { ExitCA: "\x1b[2J\x1b[?47l\x1b8", ShowCursor: "\x1b[?25h", HideCursor: "\x1b[?25l", - AttrOff: "\x1b[m\x0017", + AttrOff: "\x1b[m\x0f", Underline: "\x1b[4m", Bold: "\x1b[1m", Blink: "\x1b[5m", @@ -41,7 +41,7 @@ func init() { KeyLeft: "\x1b[D", KeyInsert: "\x1b[2~", KeyDelete: "\x1b[3~", - KeyBackspace: "\b", + KeyBackspace: "\u007f", KeyHome: "\x1b[7~", KeyEnd: "\x1b[8~", KeyPgUp: "\x1b[5~", diff --git a/vendor/github.com/gdamore/tcell/terminfo/term_rxvt_256color.go b/vendor/github.com/gdamore/tcell/terminfo/term_rxvt_256color.go index 5aceb4ee..d2cd3108 100644 --- a/vendor/github.com/gdamore/tcell/terminfo/term_rxvt_256color.go +++ b/vendor/github.com/gdamore/tcell/terminfo/term_rxvt_256color.go @@ -15,7 +15,7 @@ func init() { ExitCA: "\x1b[2J\x1b[?47l\x1b8", ShowCursor: "\x1b[?25h", HideCursor: "\x1b[?25l", - AttrOff: "\x1b[m\x0017", + AttrOff: "\x1b[m\x0f", Underline: "\x1b[4m", Bold: "\x1b[1m", Blink: "\x1b[5m", @@ -41,7 +41,7 @@ func init() { KeyLeft: "\x1b[D", KeyInsert: "\x1b[2~", KeyDelete: "\x1b[3~", - KeyBackspace: "\b", + KeyBackspace: "\u007f", KeyHome: "\x1b[7~", KeyEnd: "\x1b[8~", KeyPgUp: "\x1b[5~", diff --git a/vendor/github.com/gdamore/tcell/terminfo/term_rxvt_unicode.go b/vendor/github.com/gdamore/tcell/terminfo/term_rxvt_unicode.go index 6da67b72..3aebf6b5 100644 --- a/vendor/github.com/gdamore/tcell/terminfo/term_rxvt_unicode.go +++ b/vendor/github.com/gdamore/tcell/terminfo/term_rxvt_unicode.go @@ -39,7 +39,7 @@ func init() { KeyLeft: "\x1b[D", KeyInsert: "\x1b[2~", KeyDelete: "\x1b[3~", - KeyBackspace: "177", + KeyBackspace: "\u007f", KeyHome: "\x1b[7~", KeyEnd: "\x1b[8~", KeyPgUp: "\x1b[5~", diff --git a/vendor/github.com/gdamore/tcell/terminfo/term_rxvt_unicode_256color.go b/vendor/github.com/gdamore/tcell/terminfo/term_rxvt_unicode_256color.go index ac87fb96..276f5808 100644 --- a/vendor/github.com/gdamore/tcell/terminfo/term_rxvt_unicode_256color.go +++ b/vendor/github.com/gdamore/tcell/terminfo/term_rxvt_unicode_256color.go @@ -39,7 +39,7 @@ func init() { KeyLeft: "\x1b[D", KeyInsert: "\x1b[2~", KeyDelete: "\x1b[3~", - KeyBackspace: "177", + KeyBackspace: "\u007f", KeyHome: "\x1b[7~", KeyEnd: "\x1b[8~", KeyPgUp: "\x1b[5~", diff --git a/vendor/github.com/gdamore/tcell/terminfo/term_screen.go b/vendor/github.com/gdamore/tcell/terminfo/term_screen.go index 17b6a0c8..d9dca02b 100644 --- a/vendor/github.com/gdamore/tcell/terminfo/term_screen.go +++ b/vendor/github.com/gdamore/tcell/terminfo/term_screen.go @@ -15,9 +15,10 @@ func init() { ExitCA: "\x1b[?1049l", ShowCursor: "\x1b[34h\x1b[?25h", HideCursor: "\x1b[?25l", - AttrOff: "\x1b[m\x0017", + AttrOff: "\x1b[m\x0f", Underline: "\x1b[4m", Bold: "\x1b[1m", + Dim: "\x1b[2m", Blink: "\x1b[5m", Reverse: "\x1b[7m", EnterKeypad: "\x1b[?1h\x1b=", @@ -41,7 +42,7 @@ func init() { KeyLeft: "\x1bOD", KeyInsert: "\x1b[2~", KeyDelete: "\x1b[3~", - KeyBackspace: "\b", + KeyBackspace: "\u007f", KeyHome: "\x1b[1~", KeyEnd: "\x1b[4~", KeyPgUp: "\x1b[5~", diff --git a/vendor/github.com/gdamore/tcell/terminfo/term_screen_256color.go b/vendor/github.com/gdamore/tcell/terminfo/term_screen_256color.go index fdc2c922..40fda226 100644 --- a/vendor/github.com/gdamore/tcell/terminfo/term_screen_256color.go +++ b/vendor/github.com/gdamore/tcell/terminfo/term_screen_256color.go @@ -15,9 +15,10 @@ func init() { ExitCA: "\x1b[?1049l", ShowCursor: "\x1b[34h\x1b[?25h", HideCursor: "\x1b[?25l", - AttrOff: "\x1b[m\x0017", + AttrOff: "\x1b[m\x0f", Underline: "\x1b[4m", Bold: "\x1b[1m", + Dim: "\x1b[2m", Blink: "\x1b[5m", Reverse: "\x1b[7m", EnterKeypad: "\x1b[?1h\x1b=", @@ -41,7 +42,7 @@ func init() { KeyLeft: "\x1bOD", KeyInsert: "\x1b[2~", KeyDelete: "\x1b[3~", - KeyBackspace: "\b", + KeyBackspace: "\u007f", KeyHome: "\x1b[1~", KeyEnd: "\x1b[4~", KeyPgUp: "\x1b[5~", diff --git a/vendor/github.com/gdamore/tcell/terminfo/term_st.go b/vendor/github.com/gdamore/tcell/terminfo/term_st.go index 04500529..272af39c 100644 --- a/vendor/github.com/gdamore/tcell/terminfo/term_st.go +++ b/vendor/github.com/gdamore/tcell/terminfo/term_st.go @@ -3,9 +3,10 @@ package terminfo func init() { - // simpleterm + // simpleterm 0.4.1 AddTerminfo(&Terminfo{ Name: "st", + Aliases: []string{"stterm"}, Columns: 80, Lines: 24, Colors: 8, @@ -18,7 +19,6 @@ func init() { AttrOff: "\x1b[0m", Underline: "\x1b[4m", Bold: "\x1b[1m", - Dim: "\x1b[2m", Blink: "\x1b[5m", Reverse: "\x1b[7m", EnterKeypad: "\x1b[?1h\x1b=", @@ -26,7 +26,8 @@ func init() { SetFg: "\x1b[3%p1%dm", SetBg: "\x1b[4%p1%dm", SetFgBg: "\x1b[3%p1%d;4%p2%dm", - AltChars: "+C,D-A.B0E``aaffgghFiGjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", + PadChar: "\x00", + AltChars: "``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", EnterAcs: "\x1b(0", ExitAcs: "\x1b(B", EnableAcs: "\x1b)0", @@ -41,7 +42,7 @@ func init() { KeyLeft: "\x1bOD", KeyInsert: "\x1b[2~", KeyDelete: "\x1b[3~", - KeyBackspace: "177", + KeyBackspace: "\u007f", KeyHome: "\x1b[1~", KeyEnd: "\x1b[4~", KeyPgUp: "\x1b[5~", diff --git a/vendor/github.com/gdamore/tcell/terminfo/term_st_256color.go b/vendor/github.com/gdamore/tcell/terminfo/term_st_256color.go new file mode 100644 index 00000000..9b4256f3 --- /dev/null +++ b/vendor/github.com/gdamore/tcell/terminfo/term_st_256color.go @@ -0,0 +1,156 @@ +// Generated automatically. DO NOT HAND-EDIT. + +package terminfo + +func init() { + // simpleterm with 256 colors + AddTerminfo(&Terminfo{ + Name: "st-256color", + Aliases: []string{"stterm-256color"}, + Columns: 80, + Lines: 24, + Colors: 256, + Bell: "\a", + Clear: "\x1b[H\x1b[2J", + EnterCA: "\x1b[?1049h", + ExitCA: "\x1b[?1049l", + ShowCursor: "\x1b[?12l\x1b[?25h", + HideCursor: "\x1b[?25l", + AttrOff: "\x1b[0m", + Underline: "\x1b[4m", + Bold: "\x1b[1m", + Blink: "\x1b[5m", + Reverse: "\x1b[7m", + EnterKeypad: "\x1b[?1h\x1b=", + ExitKeypad: "\x1b[?1l\x1b>", + SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m", + SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m", + SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m", + PadChar: "\x00", + AltChars: "``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", + EnterAcs: "\x1b(0", + ExitAcs: "\x1b(B", + EnableAcs: "\x1b)0", + Mouse: "\x1b[M", + MouseMode: "%?%p1%{1}%=%t%'h'%Pa%e%'l'%Pa%;\x1b[?1000%ga%c\x1b[?1002%ga%c\x1b[?1003%ga%c\x1b[?1006%ga%c", + SetCursor: "\x1b[%i%p1%d;%p2%dH", + CursorBack1: "\b", + CursorUp1: "\x1b[A", + KeyUp: "\x1bOA", + KeyDown: "\x1bOB", + KeyRight: "\x1bOC", + KeyLeft: "\x1bOD", + KeyInsert: "\x1b[2~", + KeyDelete: "\x1b[3~", + KeyBackspace: "\u007f", + KeyHome: "\x1b[1~", + KeyEnd: "\x1b[4~", + KeyPgUp: "\x1b[5~", + KeyPgDn: "\x1b[6~", + KeyF1: "\x1bOP", + KeyF2: "\x1bOQ", + KeyF3: "\x1bOR", + KeyF4: "\x1bOS", + KeyF5: "\x1b[15~", + KeyF6: "\x1b[17~", + KeyF7: "\x1b[18~", + KeyF8: "\x1b[19~", + KeyF9: "\x1b[20~", + KeyF10: "\x1b[21~", + KeyF11: "\x1b[23~", + KeyF12: "\x1b[24~", + KeyF13: "\x1b[1;2P", + KeyF14: "\x1b[1;2Q", + KeyF15: "\x1b[1;2R", + KeyF16: "\x1b[1;2S", + KeyF17: "\x1b[15;2~", + KeyF18: "\x1b[17;2~", + KeyF19: "\x1b[18;2~", + KeyF20: "\x1b[19;2~", + KeyF21: "\x1b[20;2~", + KeyF22: "\x1b[21;2~", + KeyF23: "\x1b[23;2~", + KeyF24: "\x1b[24;2~", + KeyF25: "\x1b[1;5P", + KeyF26: "\x1b[1;5Q", + KeyF27: "\x1b[1;5R", + KeyF28: "\x1b[1;5S", + KeyF29: "\x1b[15;5~", + KeyF30: "\x1b[17;5~", + KeyF31: "\x1b[18;5~", + KeyF32: "\x1b[19;5~", + KeyF33: "\x1b[20;5~", + KeyF34: "\x1b[21;5~", + KeyF35: "\x1b[23;5~", + KeyF36: "\x1b[24;5~", + KeyF37: "\x1b[1;6P", + KeyF38: "\x1b[1;6Q", + KeyF39: "\x1b[1;6R", + KeyF40: "\x1b[1;6S", + KeyF41: "\x1b[15;6~", + KeyF42: "\x1b[17;6~", + KeyF43: "\x1b[18;6~", + KeyF44: "\x1b[19;6~", + KeyF45: "\x1b[20;6~", + KeyF46: "\x1b[21;6~", + KeyF47: "\x1b[23;6~", + KeyF48: "\x1b[24;6~", + KeyF49: "\x1b[1;3P", + KeyF50: "\x1b[1;3Q", + KeyF51: "\x1b[1;3R", + KeyF52: "\x1b[1;3S", + KeyF53: "\x1b[15;3~", + KeyF54: "\x1b[17;3~", + KeyF55: "\x1b[18;3~", + KeyF56: "\x1b[19;3~", + KeyF57: "\x1b[20;3~", + KeyF58: "\x1b[21;3~", + KeyF59: "\x1b[23;3~", + KeyF60: "\x1b[24;3~", + KeyF61: "\x1b[1;4P", + KeyF62: "\x1b[1;4Q", + KeyF63: "\x1b[1;4R", + KeyClear: "\x1b[3;5~", + KeyBacktab: "\x1b[Z", + KeyShfLeft: "\x1b[1;2D", + KeyShfRight: "\x1b[1;2C", + KeyShfUp: "\x1b[1;2A", + KeyShfDown: "\x1b[1;2B", + KeyCtrlLeft: "\x1b[1;5D", + KeyCtrlRight: "\x1b[1;5C", + KeyCtrlUp: "\x1b[1;5A", + KeyCtrlDown: "\x1b[1;5B", + KeyMetaLeft: "\x1b[1;9D", + KeyMetaRight: "\x1b[1;9C", + KeyMetaUp: "\x1b[1;9A", + KeyMetaDown: "\x1b[1;9B", + KeyAltLeft: "\x1b[1;3D", + KeyAltRight: "\x1b[1;3C", + KeyAltUp: "\x1b[1;3A", + KeyAltDown: "\x1b[1;3B", + KeyAltShfLeft: "\x1b[1;4D", + KeyAltShfRight: "\x1b[1;4C", + KeyAltShfUp: "\x1b[1;4A", + KeyAltShfDown: "\x1b[1;4B", + KeyMetaShfLeft: "\x1b[1;10D", + KeyMetaShfRight: "\x1b[1;10C", + KeyMetaShfUp: "\x1b[1;10A", + KeyMetaShfDown: "\x1b[1;10B", + KeyCtrlShfLeft: "\x1b[1;6D", + KeyCtrlShfRight: "\x1b[1;6C", + KeyCtrlShfUp: "\x1b[1;6A", + KeyCtrlShfDown: "\x1b[1;6B", + KeyShfHome: "\x1b[1;2H", + KeyShfEnd: "\x1b[1;2F", + KeyCtrlHome: "\x1b[1;5H", + KeyCtrlEnd: "\x1b[1;5F", + KeyAltHome: "\x1b[1;9H", + KeyAltEnd: "\x1b[1;9F", + KeyCtrlShfHome: "\x1b[1;6H", + KeyCtrlShfEnd: "\x1b[1;6F", + KeyMetaShfHome: "\x1b[1;10H", + KeyMetaShfEnd: "\x1b[1;10F", + KeyAltShfHome: "\x1b[1;4H", + KeyAltShfEnd: "\x1b[1;4F", + }) +} diff --git a/vendor/github.com/gdamore/tcell/terminfo/term_st_meta.go b/vendor/github.com/gdamore/tcell/terminfo/term_st_meta.go index f1cfd792..1fd5f144 100644 --- a/vendor/github.com/gdamore/tcell/terminfo/term_st_meta.go +++ b/vendor/github.com/gdamore/tcell/terminfo/term_st_meta.go @@ -41,7 +41,7 @@ func init() { KeyLeft: "\x1bOD", KeyInsert: "\x1b[2~", KeyDelete: "\x1b[3~", - KeyBackspace: "177", + KeyBackspace: "\u007f", KeyHome: "\x1b[1~", KeyEnd: "\x1b[4~", KeyPgUp: "\x1b[5~", diff --git a/vendor/github.com/gdamore/tcell/terminfo/term_st_meta_256color.go b/vendor/github.com/gdamore/tcell/terminfo/term_st_meta_256color.go index 10909993..5f181b2a 100644 --- a/vendor/github.com/gdamore/tcell/terminfo/term_st_meta_256color.go +++ b/vendor/github.com/gdamore/tcell/terminfo/term_st_meta_256color.go @@ -41,7 +41,7 @@ func init() { KeyLeft: "\x1bOD", KeyInsert: "\x1b[2~", KeyDelete: "\x1b[3~", - KeyBackspace: "177", + KeyBackspace: "\u007f", KeyHome: "\x1b[1~", KeyEnd: "\x1b[4~", KeyPgUp: "\x1b[5~", diff --git a/vendor/github.com/gdamore/tcell/terminfo/term_sun.go b/vendor/github.com/gdamore/tcell/terminfo/term_sun.go index 5c0eaa49..5858b4c2 100644 --- a/vendor/github.com/gdamore/tcell/terminfo/term_sun.go +++ b/vendor/github.com/gdamore/tcell/terminfo/term_sun.go @@ -21,7 +21,8 @@ func init() { KeyDown: "\x1b[B", KeyRight: "\x1b[C", KeyLeft: "\x1b[D", - KeyDelete: "177", + KeyInsert: "\x1b[247z", + KeyDelete: "\u007f", KeyBackspace: "\b", KeyHome: "\x1b[214z", KeyEnd: "\x1b[220z", diff --git a/vendor/github.com/gdamore/tcell/terminfo/term_sun_color.go b/vendor/github.com/gdamore/tcell/terminfo/term_sun_color.go index c5dd6ecc..e4ebe96b 100644 --- a/vendor/github.com/gdamore/tcell/terminfo/term_sun_color.go +++ b/vendor/github.com/gdamore/tcell/terminfo/term_sun_color.go @@ -12,6 +12,7 @@ func init() { Bell: "\a", Clear: "\f", AttrOff: "\x1b[m", + Bold: "\x1b[1m", Reverse: "\x1b[7m", SetFg: "\x1b[3%p1%dm", SetBg: "\x1b[4%p1%dm", @@ -24,7 +25,8 @@ func init() { KeyDown: "\x1b[B", KeyRight: "\x1b[C", KeyLeft: "\x1b[D", - KeyDelete: "177", + KeyInsert: "\x1b[247z", + KeyDelete: "\u007f", KeyBackspace: "\b", KeyHome: "\x1b[214z", KeyEnd: "\x1b[220z", diff --git a/vendor/github.com/gdamore/tcell/terminfo/term_termite.go b/vendor/github.com/gdamore/tcell/terminfo/term_termite.go new file mode 100644 index 00000000..8e7f683c --- /dev/null +++ b/vendor/github.com/gdamore/tcell/terminfo/term_termite.go @@ -0,0 +1,152 @@ +// Generated automatically. DO NOT HAND-EDIT. + +package terminfo + +func init() { + // VTE-based terminal + AddTerminfo(&Terminfo{ + Name: "xterm-termite", + Columns: 80, + Lines: 24, + Colors: 256, + Bell: "\a", + Clear: "\x1b[H\x1b[2J", + EnterCA: "\x1b[?1049h", + ExitCA: "\x1b[?1049l", + ShowCursor: "\x1b[?12l\x1b[?25h", + HideCursor: "\x1b[?25l", + AttrOff: "\x1b(B\x1b[m", + Underline: "\x1b[4m", + Bold: "\x1b[1m", + Dim: "\x1b[2m", + Reverse: "\x1b[7m", + EnterKeypad: "\x1b[?1h\x1b=", + ExitKeypad: "\x1b[?1l\x1b>", + SetFg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m", + SetBg: "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m", + SetFgBg: "\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;;%?%p2%{8}%<%t4%p2%d%e%p2%{16}%<%t10%p2%{8}%-%d%e48;5;%p2%d%;m", + AltChars: "++,,--..00``aaffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", + EnterAcs: "\x1b(0", + ExitAcs: "\x1b(B", + Mouse: "\x1b[M", + MouseMode: "%?%p1%{1}%=%t%'h'%Pa%e%'l'%Pa%;\x1b[?1000%ga%c\x1b[?1002%ga%c\x1b[?1003%ga%c\x1b[?1006%ga%c", + SetCursor: "\x1b[%i%p1%d;%p2%dH", + CursorBack1: "\b", + CursorUp1: "\x1b[A", + KeyUp: "\x1bOA", + KeyDown: "\x1bOB", + KeyRight: "\x1bOC", + KeyLeft: "\x1bOD", + KeyInsert: "\x1b[2~", + KeyDelete: "\x1b[3~", + KeyBackspace: "\xff", + KeyHome: "\x1bOH", + KeyEnd: "\x1bOF", + KeyPgUp: "\x1b[5~", + KeyPgDn: "\x1b[6~", + KeyF1: "\x1bOP", + KeyF2: "\x1bOQ", + KeyF3: "\x1bOR", + KeyF4: "\x1bOS", + KeyF5: "\x1b[15~", + KeyF6: "\x1b[17~", + KeyF7: "\x1b[18~", + KeyF8: "\x1b[19~", + KeyF9: "\x1b[20~", + KeyF10: "\x1b[21~", + KeyF11: "\x1b[23~", + KeyF12: "\x1b[24~", + KeyF13: "\x1b[1;2P", + KeyF14: "\x1b[1;2Q", + KeyF15: "\x1b[1;2R", + KeyF16: "\x1b[1;2S", + KeyF17: "\x1b[15;2~", + KeyF18: "\x1b[17;2~", + KeyF19: "\x1b[18;2~", + KeyF20: "\x1b[19;2~", + KeyF21: "\x1b[20;2~", + KeyF22: "\x1b[21;2~", + KeyF23: "\x1b[23;2~", + KeyF24: "\x1b[24;2~", + KeyF25: "\x1b[1;5P", + KeyF26: "\x1b[1;5Q", + KeyF27: "\x1b[1;5R", + KeyF28: "\x1b[1;5S", + KeyF29: "\x1b[15;5~", + KeyF30: "\x1b[17;5~", + KeyF31: "\x1b[18;5~", + KeyF32: "\x1b[19;5~", + KeyF33: "\x1b[20;5~", + KeyF34: "\x1b[21;5~", + KeyF35: "\x1b[23;5~", + KeyF36: "\x1b[24;5~", + KeyF37: "\x1b[1;6P", + KeyF38: "\x1b[1;6Q", + KeyF39: "\x1b[1;6R", + KeyF40: "\x1b[1;6S", + KeyF41: "\x1b[15;6~", + KeyF42: "\x1b[17;6~", + KeyF43: "\x1b[18;6~", + KeyF44: "\x1b[19;6~", + KeyF45: "\x1b[20;6~", + KeyF46: "\x1b[21;6~", + KeyF47: "\x1b[23;6~", + KeyF48: "\x1b[24;6~", + KeyF49: "\x1b[1;3P", + KeyF50: "\x1b[1;3Q", + KeyF51: "\x1b[1;3R", + KeyF52: "\x1b[1;3S", + KeyF53: "\x1b[15;3~", + KeyF54: "\x1b[17;3~", + KeyF55: "\x1b[18;3~", + KeyF56: "\x1b[19;3~", + KeyF57: "\x1b[20;3~", + KeyF58: "\x1b[21;3~", + KeyF59: "\x1b[23;3~", + KeyF60: "\x1b[24;3~", + KeyF61: "\x1b[1;4P", + KeyF62: "\x1b[1;4Q", + KeyF63: "\x1b[1;4R", + KeyBacktab: "\x1b[Z", + KeyShfLeft: "\x1b[1;2D", + KeyShfRight: "\x1b[1;2C", + KeyShfUp: "\x1b[1;2A", + KeyShfDown: "\x1b[1;2B", + KeyCtrlLeft: "\x1b[1;5D", + KeyCtrlRight: "\x1b[1;5C", + KeyCtrlUp: "\x1b[1;5A", + KeyCtrlDown: "\x1b[1;5B", + KeyMetaLeft: "\x1b[1;9D", + KeyMetaRight: "\x1b[1;9C", + KeyMetaUp: "\x1b[1;9A", + KeyMetaDown: "\x1b[1;9B", + KeyAltLeft: "\x1b[1;3D", + KeyAltRight: "\x1b[1;3C", + KeyAltUp: "\x1b[1;3A", + KeyAltDown: "\x1b[1;3B", + KeyAltShfLeft: "\x1b[1;4D", + KeyAltShfRight: "\x1b[1;4C", + KeyAltShfUp: "\x1b[1;4A", + KeyAltShfDown: "\x1b[1;4B", + KeyMetaShfLeft: "\x1b[1;10D", + KeyMetaShfRight: "\x1b[1;10C", + KeyMetaShfUp: "\x1b[1;10A", + KeyMetaShfDown: "\x1b[1;10B", + KeyCtrlShfLeft: "\x1b[1;6D", + KeyCtrlShfRight: "\x1b[1;6C", + KeyCtrlShfUp: "\x1b[1;6A", + KeyCtrlShfDown: "\x1b[1;6B", + KeyShfHome: "\x1b[1;2H", + KeyShfEnd: "\x1b[1;2F", + KeyCtrlHome: "\x1b[1;5H", + KeyCtrlEnd: "\x1b[1;5F", + KeyAltHome: "\x1b[1;9H", + KeyAltEnd: "\x1b[1;9F", + KeyCtrlShfHome: "\x1b[1;6H", + KeyCtrlShfEnd: "\x1b[1;6F", + KeyMetaShfHome: "\x1b[1;10H", + KeyMetaShfEnd: "\x1b[1;10F", + KeyAltShfHome: "\x1b[1;4H", + KeyAltShfEnd: "\x1b[1;4F", + }) +} diff --git a/vendor/github.com/gdamore/tcell/terminfo/term_tvi950.go b/vendor/github.com/gdamore/tcell/terminfo/term_tvi950.go index b8222dc1..49d9e4ba 100644 --- a/vendor/github.com/gdamore/tcell/terminfo/term_tvi950.go +++ b/vendor/github.com/gdamore/tcell/terminfo/term_tvi950.go @@ -14,7 +14,7 @@ func init() { Underline: "\x1bG8", Reverse: "\x1bG4", PadChar: "\x00", - AltChars: "b\x0011c\x0014d\re\ni\x0013", + AltChars: "b\tc\fd\re\ni\v", EnterAcs: "\x15", ExitAcs: "\x18", SetCursor: "\x1b=%p1%' '%+%c%p2%' '%+%c", diff --git a/vendor/github.com/gdamore/tcell/terminfo/term_vt100.go b/vendor/github.com/gdamore/tcell/terminfo/term_vt100.go index 47c4996b..8293cdaa 100644 --- a/vendor/github.com/gdamore/tcell/terminfo/term_vt100.go +++ b/vendor/github.com/gdamore/tcell/terminfo/term_vt100.go @@ -11,7 +11,7 @@ func init() { Lines: 24, Bell: "\a", Clear: "\x1b[H\x1b[J$<50>", - AttrOff: "\x1b[m\x0017$<2>", + AttrOff: "\x1b[m\x0f$<2>", Underline: "\x1b[4m$<2>", Bold: "\x1b[1m$<2>", Blink: "\x1b[5m$<2>", diff --git a/vendor/github.com/gdamore/tcell/terminfo/term_vt102.go b/vendor/github.com/gdamore/tcell/terminfo/term_vt102.go index 0199eb61..414d36b3 100644 --- a/vendor/github.com/gdamore/tcell/terminfo/term_vt102.go +++ b/vendor/github.com/gdamore/tcell/terminfo/term_vt102.go @@ -10,7 +10,7 @@ func init() { Lines: 24, Bell: "\a", Clear: "\x1b[H\x1b[J$<50>", - AttrOff: "\x1b[m\x0017$<2>", + AttrOff: "\x1b[m\x0f$<2>", Underline: "\x1b[4m$<2>", Bold: "\x1b[1m$<2>", Blink: "\x1b[5m$<2>", diff --git a/vendor/github.com/gdamore/tcell/terminfo/term_vt220.go b/vendor/github.com/gdamore/tcell/terminfo/term_vt220.go index a0a2b77a..5d0d7489 100644 --- a/vendor/github.com/gdamore/tcell/terminfo/term_vt220.go +++ b/vendor/github.com/gdamore/tcell/terminfo/term_vt220.go @@ -29,6 +29,7 @@ func init() { KeyRight: "\x1b[C", KeyLeft: "\x1b[D", KeyInsert: "\x1b[2~", + KeyDelete: "\x1b[3~", KeyBackspace: "\b", KeyPgUp: "\x1b[5~", KeyPgDn: "\x1b[6~", diff --git a/vendor/github.com/gdamore/tcell/terminfo/term_vt320.go b/vendor/github.com/gdamore/tcell/terminfo/term_vt320.go index cb4da0ae..7832e082 100644 --- a/vendor/github.com/gdamore/tcell/terminfo/term_vt320.go +++ b/vendor/github.com/gdamore/tcell/terminfo/term_vt320.go @@ -33,7 +33,7 @@ func init() { KeyLeft: "\x1bOD", KeyInsert: "\x1b[2~", KeyDelete: "\x1b[3~", - KeyBackspace: "177", + KeyBackspace: "\u007f", KeyHome: "\x1b[1~", KeyPgUp: "\x1b[5~", KeyPgDn: "\x1b[6~", diff --git a/vendor/github.com/gdamore/tcell/terminfo/term_vt420.go b/vendor/github.com/gdamore/tcell/terminfo/term_vt420.go index fbccd1f3..b8a4a1c2 100644 --- a/vendor/github.com/gdamore/tcell/terminfo/term_vt420.go +++ b/vendor/github.com/gdamore/tcell/terminfo/term_vt420.go @@ -10,7 +10,9 @@ func init() { Lines: 24, Bell: "\a", Clear: "\x1b[H\x1b[2J$<50>", - AttrOff: "\x1b[m$<2>", + ShowCursor: "\x1b[?25h", + HideCursor: "\x1b[?25l", + AttrOff: "\x1b[m\x1b(B$<2>", Underline: "\x1b[4m", Bold: "\x1b[1m$<2>", Blink: "\x1b[5m$<2>", @@ -21,6 +23,7 @@ func init() { AltChars: "``aaffggjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~", EnterAcs: "\x1b(0$<2>", ExitAcs: "\x1b(B$<4>", + EnableAcs: "\x1b)0", SetCursor: "\x1b[%i%p1%d;%p2%dH$<10>", CursorBack1: "\b", CursorUp1: "\x1b[A", diff --git a/vendor/github.com/gdamore/tcell/terminfo/term_vt52.go b/vendor/github.com/gdamore/tcell/terminfo/term_vt52.go index a98d0fd3..2bf190b7 100644 --- a/vendor/github.com/gdamore/tcell/terminfo/term_vt52.go +++ b/vendor/github.com/gdamore/tcell/terminfo/term_vt52.go @@ -11,7 +11,7 @@ func init() { Bell: "\a", Clear: "\x1bH\x1bJ", PadChar: "\x00", - AltChars: ".kffgghhompoqqss", + AltChars: "+h.k0affggolpnqprrss", EnterAcs: "\x1bF", ExitAcs: "\x1bG", SetCursor: "\x1bY%p1%' '%+%c%p2%' '%+%c", diff --git a/vendor/github.com/gdamore/tcell/terminfo/term_wy50.go b/vendor/github.com/gdamore/tcell/terminfo/term_wy50.go index 1294629c..022dda9d 100644 --- a/vendor/github.com/gdamore/tcell/terminfo/term_wy50.go +++ b/vendor/github.com/gdamore/tcell/terminfo/term_wy50.go @@ -13,7 +13,7 @@ func init() { Clear: "\x1b+$<20>", ShowCursor: "\x1b`1", HideCursor: "\x1b`0", - AttrOff: "\x1b(\x1bH\x0003", + AttrOff: "\x1b(\x1bH\x03", Dim: "\x1b`7\x1b)", Reverse: "\x1b`6\x1b)", PadChar: "\x00", diff --git a/vendor/github.com/gdamore/tcell/terminfo/term_wy60.go b/vendor/github.com/gdamore/tcell/terminfo/term_wy60.go index 7427fdbf..a737bb85 100644 --- a/vendor/github.com/gdamore/tcell/terminfo/term_wy60.go +++ b/vendor/github.com/gdamore/tcell/terminfo/term_wy60.go @@ -15,7 +15,7 @@ func init() { ExitCA: "\x1bw1", ShowCursor: "\x1b`1", HideCursor: "\x1b`0", - AttrOff: "\x1b(\x1bH\x0003\x1bG0\x1bcD", + AttrOff: "\x1b(\x1bH\x03\x1bG0\x1bcD", Underline: "\x1bG8", Dim: "\x1bGp", Blink: "\x1bG2", diff --git a/vendor/github.com/gdamore/tcell/terminfo/term_wy99_ansi.go b/vendor/github.com/gdamore/tcell/terminfo/term_wy99_ansi.go index 41e31c52..6bf0d69c 100644 --- a/vendor/github.com/gdamore/tcell/terminfo/term_wy99_ansi.go +++ b/vendor/github.com/gdamore/tcell/terminfo/term_wy99_ansi.go @@ -12,7 +12,7 @@ func init() { Clear: "\x1b[H\x1b[J$<200>", ShowCursor: "\x1b[34h\x1b[?25h", HideCursor: "\x1b[?25l", - AttrOff: "\x1b[m\x0017\x1b[\"q", + AttrOff: "\x1b[m\x0f\x1b[\"q", Underline: "\x1b[4m", Bold: "\x1b[1m", Dim: "\x1b[2m", @@ -26,7 +26,7 @@ func init() { ExitAcs: "\x0f", EnableAcs: "\x1b)0", SetCursor: "\x1b[%i%p1%d;%p2%dH", - CursorBack1: "\x0010$<1>", + CursorBack1: "\b$<1>", CursorUp1: "\x1bM", KeyUp: "\x1bOA", KeyDown: "\x1bOB", diff --git a/vendor/github.com/gdamore/tcell/terminfo/term_wy99a_ansi.go b/vendor/github.com/gdamore/tcell/terminfo/term_wy99a_ansi.go index 6cdac0db..1d7f6f21 100644 --- a/vendor/github.com/gdamore/tcell/terminfo/term_wy99a_ansi.go +++ b/vendor/github.com/gdamore/tcell/terminfo/term_wy99a_ansi.go @@ -12,7 +12,7 @@ func init() { Clear: "\x1b[H\x1b[J$<200>", ShowCursor: "\x1b[34h\x1b[?25h", HideCursor: "\x1b[?25l", - AttrOff: "\x1b[m\x0017\x1b[\"q", + AttrOff: "\x1b[m\x0f\x1b[\"q", Underline: "\x1b[4m", Bold: "\x1b[1m", Dim: "\x1b[2m", @@ -26,7 +26,7 @@ func init() { ExitAcs: "\x0f", EnableAcs: "\x1b)0", SetCursor: "\x1b[%i%p1%d;%p2%dH", - CursorBack1: "\x0010$<1>", + CursorBack1: "\b$<1>", CursorUp1: "\x1bM", KeyUp: "\x1bOA", KeyDown: "\x1bOB", diff --git a/vendor/github.com/gdamore/tcell/terminfo/term_xfce.go b/vendor/github.com/gdamore/tcell/terminfo/term_xfce.go index 3651dd70..bb39be95 100644 --- a/vendor/github.com/gdamore/tcell/terminfo/term_xfce.go +++ b/vendor/github.com/gdamore/tcell/terminfo/term_xfce.go @@ -15,7 +15,7 @@ func init() { ExitCA: "\x1b[2J\x1b[?47l\x1b8", ShowCursor: "\x1b[?25h", HideCursor: "\x1b[?25l", - AttrOff: "\x1b[0m\x0017", + AttrOff: "\x1b[0m\x0f", Underline: "\x1b[4m", Bold: "\x1b[1m", Reverse: "\x1b[7m", @@ -40,7 +40,7 @@ func init() { KeyLeft: "\x1bOD", KeyInsert: "\x1b[2~", KeyDelete: "\x1b[3~", - KeyBackspace: "177", + KeyBackspace: "\u007f", KeyHome: "\x1bOH", KeyEnd: "\x1bOF", KeyPgUp: "\x1b[5~", diff --git a/vendor/github.com/gdamore/tcell/terminfo/term_xnuppc.go b/vendor/github.com/gdamore/tcell/terminfo/term_xnuppc.go index d3d68820..b1dafd6b 100644 --- a/vendor/github.com/gdamore/tcell/terminfo/term_xnuppc.go +++ b/vendor/github.com/gdamore/tcell/terminfo/term_xnuppc.go @@ -9,7 +9,7 @@ func init() { Aliases: []string{"darwin"}, Colors: 8, Clear: "\x1b[H\x1b[J", - AttrOff: "\x1b[m\x0017", + AttrOff: "\x1b[m", Underline: "\x1b[4m", Bold: "\x1b[1m", Reverse: "\x1b[7m", @@ -26,6 +26,6 @@ func init() { KeyDown: "\x1bOB", KeyRight: "\x1bOC", KeyLeft: "\x1bOD", - KeyBackspace: "177", + KeyBackspace: "\u007f", }) } diff --git a/vendor/github.com/gdamore/tcell/terminfo/term_xterm.go b/vendor/github.com/gdamore/tcell/terminfo/term_xterm.go index 9cab4a20..1e4d296e 100644 --- a/vendor/github.com/gdamore/tcell/terminfo/term_xterm.go +++ b/vendor/github.com/gdamore/tcell/terminfo/term_xterm.go @@ -3,9 +3,10 @@ package terminfo func init() { - // xterm terminal emulator (X Window System) + // X11 terminal emulator AddTerminfo(&Terminfo{ Name: "xterm", + Aliases: []string{"xterm-debian"}, Columns: 80, Lines: 24, Colors: 8, @@ -18,6 +19,7 @@ func init() { AttrOff: "\x1b(B\x1b[m", Underline: "\x1b[4m", Bold: "\x1b[1m", + Dim: "\x1b[2m", Blink: "\x1b[5m", Reverse: "\x1b[7m", EnterKeypad: "\x1b[?1h\x1b=", @@ -39,7 +41,7 @@ func init() { KeyLeft: "\x1bOD", KeyInsert: "\x1b[2~", KeyDelete: "\x1b[3~", - KeyBackspace: "\b", + KeyBackspace: "\u007f", KeyHome: "\x1bOH", KeyEnd: "\x1bOF", KeyPgUp: "\x1b[5~", diff --git a/vendor/github.com/gdamore/tcell/terminfo/term_xterm_256color.go b/vendor/github.com/gdamore/tcell/terminfo/term_xterm_256color.go index d30ceeef..f95d21e8 100644 --- a/vendor/github.com/gdamore/tcell/terminfo/term_xterm_256color.go +++ b/vendor/github.com/gdamore/tcell/terminfo/term_xterm_256color.go @@ -18,6 +18,7 @@ func init() { AttrOff: "\x1b(B\x1b[m", Underline: "\x1b[4m", Bold: "\x1b[1m", + Dim: "\x1b[2m", Blink: "\x1b[5m", Reverse: "\x1b[7m", EnterKeypad: "\x1b[?1h\x1b=", @@ -39,7 +40,7 @@ func init() { KeyLeft: "\x1bOD", KeyInsert: "\x1b[2~", KeyDelete: "\x1b[3~", - KeyBackspace: "\b", + KeyBackspace: "\u007f", KeyHome: "\x1bOH", KeyEnd: "\x1bOF", KeyPgUp: "\x1b[5~", diff --git a/vendor/github.com/gdamore/tcell/terminfo/terminfo.go b/vendor/github.com/gdamore/tcell/terminfo/terminfo.go index ea8c6d7b..881b9e01 100644 --- a/vendor/github.com/gdamore/tcell/terminfo/terminfo.go +++ b/vendor/github.com/gdamore/tcell/terminfo/terminfo.go @@ -1,4 +1,4 @@ -// Copyright 2017 The TCell Authors +// Copyright 2018 The TCell Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use file except in compliance with the License. @@ -17,12 +17,14 @@ package terminfo import ( "bytes" "compress/gzip" + "crypto/sha1" "encoding/json" "errors" "fmt" "io" "os" "path" + "path/filepath" "strconv" "strings" "sync" @@ -753,9 +755,23 @@ func loadFromFile(fname string, term string) (*Terminfo, error) { // LookupTerminfo attempts to find a definition for the named $TERM. // It first looks in the builtin database, which should cover just about // everyone. If it can't find one there, then it will attempt to read -// one from the JSON file located in either $TCELLDB, $HOME/.tcelldb -// or in this package's source directory as database.json). +// one from the JSON file located in either $TCELLDB, $HOME/.tcelldb, +// or as a database file. +// +// The database files are named by taking terminal name, hashing it through +// sha1, and then a subdirectory of the form database/hash[0:2]/hash[0:8] +// (with an optional .gz extension). +// +// For other local database files, we will look for the database file using +// the terminal name, so database/term[0:2]/term[0:8], again with optional +// .gz extension. func LookupTerminfo(name string) (*Terminfo, error) { + if name == "" { + // else on windows: index out of bounds + // on the name[0] reference below + return nil, ErrTermNotFound + } + dblock.Lock() t := terminfos[name] dblock.Unlock() @@ -766,38 +782,65 @@ func LookupTerminfo(name string) (*Terminfo, error) { letter := fmt.Sprintf("%02x", name[0]) gzfile := path.Join(letter, name+".gz") jsfile := path.Join(letter, name) + hash := fmt.Sprintf("%x", sha1.Sum([]byte(name))) + gzhfile := path.Join(hash[0:2], hash[0:8]+".gz") + jshfile := path.Join(hash[0:2], hash[0:8]) // Build up the search path. Old versions of tcell used a // single database file, whereas the new ones locate them // in JSON (optionally compressed) files. // - // The search path looks like: + // The search path for "xterm" (SHA1 sig e2e28a8e...) looks + // like this: // - // $TCELLDB/x/xterm.gz - // $TCELLDB/x/xterm + // $TCELLDB/78/xterm.gz + // $TCELLDB/78/xterm // $TCELLDB - // $HOME/.tcelldb/x/xterm.gz - // $HOME/.tcelldb/x/xterm + // $HOME/.tcelldb/e2/e2e28a8e.gz + // $HOME/.tcelldb/e2/e2e28a8e + // $HOME/.tcelldb/78/xterm.gz + // $HOME/.tcelldb/78/xterm // $HOME/.tcelldb - // $GOPATH/terminfo/database/x/xterm.gz - // $GOPATH/terminfo/database/x/xterm + // $GOPATH/terminfo/database/e2/e2e28a8e.gz + // $GOPATH/terminfo/database/e2/e2e28a8e + // $GOPATH/terminfo/database/78/xterm.gz + // $GOPATH/terminfo/database/78/xterm // + // Note that the legacy name lookups (78/xterm etc.) are + // provided for compatibility. We do not actually deliver + // any files with this style of naming, to avoid collisions + // on case insensitive filesystems. (*cough* mac *cough*). + + // If $GOPATH set, honor it, else assume $HOME/go just like + // modern golang does. + gopath := os.Getenv("GOPATH") + if gopath == "" { + gopath = path.Join(os.Getenv("HOME"), "go") + } if pth := os.Getenv("TCELLDB"); pth != "" { - files = append(files, path.Join(pth, gzfile)) - files = append(files, path.Join(pth, jsfile)) - files = append(files, pth) + files = append(files, + path.Join(pth, gzfile), + path.Join(pth, jsfile), + pth) } if pth := os.Getenv("HOME"); pth != "" { pth = path.Join(pth, ".tcelldb") - files = append(files, path.Join(pth, gzfile)) - files = append(files, path.Join(pth, jsfile)) - files = append(files, pth) + files = append(files, + path.Join(pth, gzhfile), + path.Join(pth, jshfile), + path.Join(pth, gzfile), + path.Join(pth, jsfile), + pth) } - for _, pth := range strings.Split(os.Getenv("GOPATH"), string(os.PathListSeparator)) { - pth = path.Join(pth, "src", "github.com", "gdamore", "tcell", "terminfo", "database") - files = append(files, path.Join(pth, gzfile)) - files = append(files, path.Join(pth, jsfile)) + for _, pth := range filepath.SplitList(gopath) { + pth = path.Join(pth, "src", "github.com", + "gdamore", "tcell", "terminfo", "database") + files = append(files, + path.Join(pth, gzhfile), + path.Join(pth, jshfile), + path.Join(pth, gzfile), + path.Join(pth, jsfile)) } for _, fname := range files { diff --git a/vendor/github.com/gdamore/tcell/tscreen.go b/vendor/github.com/gdamore/tcell/tscreen.go index 7e0d0b97..4c64e833 100644 --- a/vendor/github.com/gdamore/tcell/tscreen.go +++ b/vendor/github.com/gdamore/tcell/tscreen.go @@ -383,8 +383,10 @@ outer: } func (t *tScreen) Fini() { - ti := t.ti t.Lock() + defer t.Unlock() + + ti := t.ti t.cells.Resize(0, 0) t.TPuts(ti.ShowCursor) t.TPuts(ti.AttrOff) @@ -395,11 +397,15 @@ func (t *tScreen) Fini() { t.curstyle = Style(-1) t.clear = false t.fini = true - t.Unlock() - if t.quit != nil { + select { + case <-t.quit: + // do nothing, already closed + + default: close(t.quit) } + t.termioFini() } diff --git a/vendor/github.com/gdamore/tcell/tscreen_bsd.go b/vendor/github.com/gdamore/tcell/tscreen_bsd.go index cce510ec..86d749b7 100644 --- a/vendor/github.com/gdamore/tcell/tscreen_bsd.go +++ b/vendor/github.com/gdamore/tcell/tscreen_bsd.go @@ -1,6 +1,6 @@ -// +build darwin freebsd netbsd openbsd dragonfly +// +build freebsd netbsd openbsd dragonfly -// Copyright 2017 The TCell Authors +// Copyright 2018 The TCell Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use file except in compliance with the License. diff --git a/vendor/github.com/gdamore/tcell/tscreen_darwin.go b/vendor/github.com/gdamore/tcell/tscreen_darwin.go new file mode 100644 index 00000000..df51cb5f --- /dev/null +++ b/vendor/github.com/gdamore/tcell/tscreen_darwin.go @@ -0,0 +1,140 @@ +// +build darwin + +// Copyright 2018 The TCell Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use file except in compliance with the License. +// You may obtain a copy of the license at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tcell + +// The Darwin system is *almost* a real BSD system, but it suffers from +// a brain damaged TTY driver. This TTY driver does not actually +// wake up in poll() or similar calls, which means that we cannot reliably +// shut down the terminal without resorting to obscene custom C code +// and a dedicated poller thread. +// +// So instead, we do a best effort, and simply try to do the close in the +// background. Probably this will cause a leak of two goroutines and +// maybe also the file descriptor, meaning that applications on Darwin +// can't reinitialize the screen, but that's probably a very rare behavior, +// and accepting that is the best of some very poor alternative options. +// +// Maybe someday Apple will fix there tty driver, but its been broken for +// a long time (probably forever) so holding one's breath is contraindicated. + +import ( + "os" + "os/signal" + "syscall" + "unsafe" +) + +type termiosPrivate syscall.Termios + +func (t *tScreen) termioInit() error { + var e error + var newtios termiosPrivate + var fd uintptr + var tios uintptr + var ioc uintptr + t.tiosp = &termiosPrivate{} + + if t.in, e = os.OpenFile("/dev/tty", os.O_RDONLY, 0); e != nil { + goto failed + } + if t.out, e = os.OpenFile("/dev/tty", os.O_WRONLY, 0); e != nil { + goto failed + } + + tios = uintptr(unsafe.Pointer(t.tiosp)) + ioc = uintptr(syscall.TIOCGETA) + fd = uintptr(t.out.Fd()) + if _, _, e1 := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioc, tios, 0, 0, 0); e1 != 0 { + e = e1 + goto failed + } + + // On this platform (FreeBSD and family), the baud rate is stored + // directly as an integer in termios.c_ospeed. No bitmasking required. + t.baud = int(t.tiosp.Ospeed) + newtios = *t.tiosp + newtios.Iflag &^= syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | + syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | + syscall.ICRNL | syscall.IXON + newtios.Oflag &^= syscall.OPOST + newtios.Lflag &^= syscall.ECHO | syscall.ECHONL | syscall.ICANON | + syscall.ISIG | syscall.IEXTEN + newtios.Cflag &^= syscall.CSIZE | syscall.PARENB + newtios.Cflag |= syscall.CS8 + + tios = uintptr(unsafe.Pointer(&newtios)) + + ioc = uintptr(syscall.TIOCSETA) + if _, _, e1 := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioc, tios, 0, 0, 0); e1 != 0 { + e = e1 + goto failed + } + + signal.Notify(t.sigwinch, syscall.SIGWINCH) + + if w, h, e := t.getWinSize(); e == nil && w != 0 && h != 0 { + t.cells.Resize(w, h) + } + + return nil + +failed: + if t.in != nil { + t.in.Close() + } + if t.out != nil { + t.out.Close() + } + return e +} + +func (t *tScreen) termioFini() { + + signal.Stop(t.sigwinch) + + <-t.indoneq + + if t.out != nil { + fd := uintptr(t.out.Fd()) + ioc := uintptr(syscall.TIOCSETAF) + tios := uintptr(unsafe.Pointer(t.tiosp)) + syscall.Syscall6(syscall.SYS_IOCTL, fd, ioc, tios, 0, 0, 0) + t.out.Close() + } + + // See above -- we background this call which might help, but + // really the tty is probably open. + + go func() { + if t.in != nil { + t.in.Close() + } + }() +} + +func (t *tScreen) getWinSize() (int, int, error) { + + fd := uintptr(t.out.Fd()) + dim := [4]uint16{} + dimp := uintptr(unsafe.Pointer(&dim)) + ioc := uintptr(syscall.TIOCGWINSZ) + if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, + fd, ioc, dimp, 0, 0, 0); err != 0 { + return -1, -1, err + } + return int(dim[1]), int(dim[0]), nil +} diff --git a/vendor/github.com/google/go-github/github/apps.go b/vendor/github.com/google/go-github/github/apps.go index c1f7f138..249a449b 100644 --- a/vendor/github.com/google/go-github/github/apps.go +++ b/vendor/github.com/google/go-github/github/apps.go @@ -126,23 +126,7 @@ func (s *AppsService) ListInstallations(ctx context.Context, opt *ListOptions) ( // // GitHub API docs: https://developer.github.com/v3/apps/#get-a-single-installation func (s *AppsService) GetInstallation(ctx context.Context, id int64) (*Installation, *Response, error) { - u := fmt.Sprintf("app/installations/%v", id) - - req, err := s.client.NewRequest("GET", u, nil) - if err != nil { - return nil, nil, err - } - - // TODO: remove custom Accept header when this API fully launches. - req.Header.Set("Accept", mediaTypeIntegrationPreview) - - i := new(Installation) - resp, err := s.client.Do(ctx, req, i) - if err != nil { - return nil, resp, err - } - - return i, resp, nil + return s.getInstallation(ctx, fmt.Sprintf("app/installations/%v", id)) } // ListUserInstallations lists installations that are accessible to the authenticated user. @@ -195,3 +179,42 @@ func (s *AppsService) CreateInstallationToken(ctx context.Context, id int64) (*I return t, resp, nil } + +// FindOrganizationInstallation finds the organization's installation information. +// +// GitHub API docs: https://developer.github.com/v3/apps/#find-organization-installation +func (s *AppsService) FindOrganizationInstallation(ctx context.Context, org string) (*Installation, *Response, error) { + return s.getInstallation(ctx, fmt.Sprintf("orgs/%v/installation", org)) +} + +// FindRepositoryInstallation finds the repository's installation information. +// +// GitHub API docs: https://developer.github.com/v3/apps/#find-repository-installation +func (s *AppsService) FindRepositoryInstallation(ctx context.Context, owner, repo string) (*Installation, *Response, error) { + return s.getInstallation(ctx, fmt.Sprintf("repos/%v/%v/installation", owner, repo)) +} + +// FindUserInstallation finds the user's installation information. +// +// GitHub API docs: https://developer.github.com/v3/apps/#find-repository-installation +func (s *AppsService) FindUserInstallation(ctx context.Context, user string) (*Installation, *Response, error) { + return s.getInstallation(ctx, fmt.Sprintf("users/%v/installation", user)) +} + +func (s *AppsService) getInstallation(ctx context.Context, url string) (*Installation, *Response, error) { + req, err := s.client.NewRequest("GET", url, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeIntegrationPreview) + + i := new(Installation) + resp, err := s.client.Do(ctx, req, i) + if err != nil { + return nil, resp, err + } + + return i, resp, nil +} diff --git a/vendor/github.com/xanzy/go-gitlab/deployments.go b/vendor/github.com/xanzy/go-gitlab/deployments.go index a648605e..e43d1c4a 100644 --- a/vendor/github.com/xanzy/go-gitlab/deployments.go +++ b/vendor/github.com/xanzy/go-gitlab/deployments.go @@ -68,8 +68,8 @@ type Deployment struct { // https://docs.gitlab.com/ce/api/deployments.html#list-project-deployments type ListProjectDeploymentsOptions struct { ListOptions - OrderBy *OrderByValue `url:"order_by,omitempty" json:"order_by,omitempty"` - Sort *string `url:"sort,omitempty" json:"sort,omitempty"` + OrderBy *string `url:"order_by,omitempty" json:"order_by,omitempty"` + Sort *string `url:"sort,omitempty" json:"sort,omitempty"` } // ListProjectDeployments gets a list of deployments in a project. diff --git a/vendor/github.com/xanzy/go-gitlab/gitlab.go b/vendor/github.com/xanzy/go-gitlab/gitlab.go index 6019d87a..65cb4198 100644 --- a/vendor/github.com/xanzy/go-gitlab/gitlab.go +++ b/vendor/github.com/xanzy/go-gitlab/gitlab.go @@ -186,19 +186,6 @@ var notificationLevelTypes = map[string]NotificationLevelValue{ "custom": CustomNotificationLevel, } -// OrderByValue represent in which order to sort the item -type OrderByValue string - -// These constants represent all valid order by values. -const ( - OrderByCreatedAt OrderByValue = "created_at" - OrderByID OrderByValue = "id" - OrderByIID OrderByValue = "iid" - OrderByRef OrderByValue = "ref" - OrderByStatus OrderByValue = "status" - OrderByUserID OrderByValue = "user_id" -) - // VisibilityValue represents a visibility level within GitLab. // // GitLab API docs: https://docs.gitlab.com/ce/api/ @@ -832,14 +819,6 @@ func NotificationLevel(v NotificationLevelValue) *NotificationLevelValue { return p } -// OrderBy is a helper routine that allocates a new OrderByValue -// to store v and returns a pointer to it. -func OrderBy(v OrderByValue) *OrderByValue { - p := new(OrderByValue) - *p = v - return p -} - // Visibility is a helper routine that allocates a new VisibilityValue // to store v and returns a pointer to it. func Visibility(v VisibilityValue) *VisibilityValue { diff --git a/vendor/github.com/xanzy/go-gitlab/pipelines.go b/vendor/github.com/xanzy/go-gitlab/pipelines.go index 0c736dd2..952d0d2c 100644 --- a/vendor/github.com/xanzy/go-gitlab/pipelines.go +++ b/vendor/github.com/xanzy/go-gitlab/pipelines.go @@ -87,7 +87,7 @@ type ListProjectPipelinesOptions struct { YamlErrors *bool `url:"yaml_errors,omitempty" json:"yaml_errors,omitempty"` Name *string `url:"name,omitempty" json:"name,omitempty"` Username *string `url:"username,omitempty" json:"username,omitempty"` - OrderBy *OrderByValue `url:"order_by,omitempty" json:"order_by,omitempty"` + OrderBy *string `url:"order_by,omitempty" json:"order_by,omitempty"` Sort *string `url:"sort,omitempty" json:"sort,omitempty"` } From fad99a22623889d94065e0c6f63aa5a9a4a58cb3 Mon Sep 17 00:00:00 2001 From: Chris Cummer Date: Wed, 11 Jul 2018 10:00:00 -0700 Subject: [PATCH 15/21] Add todoist dependency --- Gopkg.lock | 2 +- .../github.com/darkSasori/todoist/.gitignore | 2 + .../github.com/darkSasori/todoist/.travis.yml | 19 +++ .../github.com/darkSasori/todoist/Gopkg.lock | 21 +++ .../github.com/darkSasori/todoist/Gopkg.toml | 34 ++++ vendor/github.com/darkSasori/todoist/LICENSE | 21 +++ .../github.com/darkSasori/todoist/README.md | 6 + .../github.com/darkSasori/todoist/projects.go | 149 +++++++++++++++++ vendor/github.com/darkSasori/todoist/task.go | 158 ++++++++++++++++++ .../github.com/darkSasori/todoist/todoist.go | 145 ++++++++++++++++ 10 files changed, 556 insertions(+), 1 deletion(-) create mode 100644 vendor/github.com/darkSasori/todoist/.gitignore create mode 100644 vendor/github.com/darkSasori/todoist/.travis.yml create mode 100644 vendor/github.com/darkSasori/todoist/Gopkg.lock create mode 100644 vendor/github.com/darkSasori/todoist/Gopkg.toml create mode 100644 vendor/github.com/darkSasori/todoist/LICENSE create mode 100644 vendor/github.com/darkSasori/todoist/README.md create mode 100644 vendor/github.com/darkSasori/todoist/projects.go create mode 100644 vendor/github.com/darkSasori/todoist/task.go create mode 100644 vendor/github.com/darkSasori/todoist/todoist.go diff --git a/Gopkg.lock b/Gopkg.lock index 9c34c32e..bfa5b6c7 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -207,6 +207,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "04892edb6b5f0be61b391ccead307ed15899532db05a17b9e28c00ee32a34861" + inputs-digest = "9eaa70ed639c832e3cde26a4270f4c7b9124960952aa76506f702c2c296d5019" solver-name = "gps-cdcl" solver-version = 1 diff --git a/vendor/github.com/darkSasori/todoist/.gitignore b/vendor/github.com/darkSasori/todoist/.gitignore new file mode 100644 index 00000000..9a990fe4 --- /dev/null +++ b/vendor/github.com/darkSasori/todoist/.gitignore @@ -0,0 +1,2 @@ +*.out +vendor/* diff --git a/vendor/github.com/darkSasori/todoist/.travis.yml b/vendor/github.com/darkSasori/todoist/.travis.yml new file mode 100644 index 00000000..0052d33c --- /dev/null +++ b/vendor/github.com/darkSasori/todoist/.travis.yml @@ -0,0 +1,19 @@ +language: go + +notifications: + email: false + +go: + - "1.10.3" + - master + +before_install: + - curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh + - go get -u github.com/golang/lint/golint + +install: + - dep ensure + +script: + - golint -set_exit_status + - go test -v diff --git a/vendor/github.com/darkSasori/todoist/Gopkg.lock b/vendor/github.com/darkSasori/todoist/Gopkg.lock new file mode 100644 index 00000000..99656b15 --- /dev/null +++ b/vendor/github.com/darkSasori/todoist/Gopkg.lock @@ -0,0 +1,21 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + branch = "master" + name = "github.com/darkSasori/todoist" + packages = ["."] + revision = "ab3a9f0b9d551ab4e57f1caae9bb20d5aa51ec10" + +[[projects]] + branch = "v1" + name = "gopkg.in/jarcoal/httpmock.v1" + packages = ["."] + revision = "16f9a43967d613f0adc2000f0094a17b9f6c4c20" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "4d0d4d6dd7b4141be119cb48281ea7c10f05d3037c0e4ac49560cf4af21917a7" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/vendor/github.com/darkSasori/todoist/Gopkg.toml b/vendor/github.com/darkSasori/todoist/Gopkg.toml new file mode 100644 index 00000000..ec866dff --- /dev/null +++ b/vendor/github.com/darkSasori/todoist/Gopkg.toml @@ -0,0 +1,34 @@ +# Gopkg.toml example +# +# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md +# for detailed Gopkg.toml documentation. +# +# required = ["github.com/user/thing/cmd/thing"] +# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] +# +# [[constraint]] +# name = "github.com/user/project" +# version = "1.0.0" +# +# [[constraint]] +# name = "github.com/user/project2" +# branch = "dev" +# source = "github.com/myfork/project2" +# +# [[override]] +# name = "github.com/x/y" +# version = "2.4.0" +# +# [prune] +# non-go = false +# go-tests = true +# unused-packages = true + + +[[constraint]] + branch = "v1" + name = "gopkg.in/jarcoal/httpmock.v1" + +[prune] + go-tests = true + unused-packages = true diff --git a/vendor/github.com/darkSasori/todoist/LICENSE b/vendor/github.com/darkSasori/todoist/LICENSE new file mode 100644 index 00000000..cc68e07a --- /dev/null +++ b/vendor/github.com/darkSasori/todoist/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Lineu Felipe + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/darkSasori/todoist/README.md b/vendor/github.com/darkSasori/todoist/README.md new file mode 100644 index 00000000..8ffc3976 --- /dev/null +++ b/vendor/github.com/darkSasori/todoist/README.md @@ -0,0 +1,6 @@ +# todoist +[![godoc](https://godoc.org/github.com/darkSasori/todoist?status.svg)](https://godoc.org/github.com/darkSasori/todoist) +[![Build Status](https://travis-ci.org/darkSasori/todoist.svg?branch=master)](https://travis-ci.org/darkSasori/todoist) +[![Go Report Card](https://goreportcard.com/badge/github.com/darkSasori/todoist)](https://goreportcard.com/report/github.com/darkSasori/todoist) + +Unofficial todoist api implementation diff --git a/vendor/github.com/darkSasori/todoist/projects.go b/vendor/github.com/darkSasori/todoist/projects.go new file mode 100644 index 00000000..9493b18a --- /dev/null +++ b/vendor/github.com/darkSasori/todoist/projects.go @@ -0,0 +1,149 @@ +package todoist + +import ( + "encoding/json" + "fmt" + "io" + "net/http" +) + +// Project is a model of todoist project entity +type Project struct { + ID int `json:"id"` + Name string `json:"name"` + CommentCount int `json:"comment_count"` + Order int `json:"order"` + Indent int `json:"indent"` +} + +func decodeProject(body io.ReadCloser) (Project, error) { + defer body.Close() + decoder := json.NewDecoder(body) + var project Project + + if err := decoder.Decode(&project); err != nil { + return Project{}, err + } + return project, nil +} + +// ListProject return all projects +// +// Example: +// todoist.Token = "your token" +// projects, err := todoist.ListProject() +// if err != nil { +// panic(err) +// } +// fmt.Println(projects) +func ListProject() ([]Project, error) { + res, err := makeRequest(http.MethodGet, "projects", nil) + if err != nil { + return []Project{}, err + } + + defer res.Body.Close() + decoder := json.NewDecoder(res.Body) + var projects []Project + + if err := decoder.Decode(&projects); err != nil { + return []Project{}, err + } + + return projects, nil +} + +// GetProject return a project by id +// +// Example: +// todoist.Token = "your token" +// project, err := todoist.GetProject(1) +// if err != nil { +// panic(err) +// } +// fmt.Println(project) +func GetProject(id int) (Project, error) { + path := fmt.Sprintf("projects/%d", id) + res, err := makeRequest(http.MethodGet, path, nil) + if err != nil { + return Project{}, err + } + + return decodeProject(res.Body) +} + +// CreateProject create a new project with a name +// +// Example: +// todoist.Token = "your token" +// project, err := todoist.CreateProject("New Project") +// if err != nil { +// panic(err) +// } +// fmt.Println(project) +func CreateProject(name string) (Project, error) { + project := struct { + Name string `json:"name"` + }{ + name, + } + + res, err := makeRequest(http.MethodPost, "projects", project) + if err != nil { + return Project{}, err + } + + return decodeProject(res.Body) +} + +// Delete project +// +// Example: +// todoist.Token = "your token" +// project, err := todoist.GetProject(1) +// if err != nil { +// panic(err) +// } +// err = project.Delete() +// if err != nil { +// panic(err) +// } +func (p Project) Delete() error { + path := fmt.Sprintf("projects/%d", p.ID) + _, err := makeRequest(http.MethodDelete, path, nil) + if err != nil { + return err + } + + return nil +} + +// Update project +// +// Example: +// todoist.Token = "your token" +// project, err := todoist.GetProject(1) +// if err != nil { +// panic(err) +// } +// project.Name = "updated" +// err = project.Update() +// if err != nil { +// panic(err) +// } +// fmt.Println(project) +func (p Project) Update() error { + path := fmt.Sprintf("projects/%d", p.ID) + project := struct { + Name string `json:"name"` + }{ + p.Name, + } + + _, err := makeRequest(http.MethodPost, path, project) + if err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/darkSasori/todoist/task.go b/vendor/github.com/darkSasori/todoist/task.go new file mode 100644 index 00000000..f2c0ce06 --- /dev/null +++ b/vendor/github.com/darkSasori/todoist/task.go @@ -0,0 +1,158 @@ +package todoist + +import ( + "encoding/json" + "fmt" + "io" + "net/http" +) + +// Task is a model of todoist project entity +type Task struct { + ID int `json:"id"` + CommentCount int `json:"comment_count"` + Completed bool `json:"completed"` + Content string `json:"content"` + Indent int `json:"indent"` + LabelIDs []int `json:"label_ids"` + Order int `json:"order"` + Priority int `json:"priority"` + ProjectID int `json:"project_id"` + Due Due `json:"due"` +} + +// Due is a model of todoist project entity +type Due struct { + String string `json:"string"` + Date string `json:"date"` + Datetime CustomTime `json:"datetime"` + Timezone string `json:"timezone"` +} + +func (t Task) taskSave() taskSave { + return taskSave{ + t.Content, + t.ProjectID, + t.Order, + t.LabelIDs, + t.Priority, + t.Due.String, + t.Due.Datetime, + "en", + } +} + +func decodeTask(body io.ReadCloser) (Task, error) { + defer body.Close() + decoder := json.NewDecoder(body) + var task Task + + if err := decoder.Decode(&task); err != nil { + return Task{}, err + } + return task, nil +} + +// QueryParam is a map[string]string to build http query +type QueryParam map[string]string + +func (qp QueryParam) String() string { + if len(qp) == 0 { + return "" + } + + ret := "?" + for key, value := range qp { + if ret != "?" { + ret = ret + "&" + } + ret = ret + key + "=" + value + } + + return ret +} + +// ListTask return all task, you can filter using QueryParam +// See documentation: https://developer.todoist.com/rest/v8/#get-tasks +func ListTask(qp QueryParam) ([]Task, error) { + path := fmt.Sprintf("tasks%s", qp) + res, err := makeRequest(http.MethodGet, path, nil) + if err != nil { + return []Task{}, err + } + + defer res.Body.Close() + decoder := json.NewDecoder(res.Body) + var tasks []Task + + if err := decoder.Decode(&tasks); err != nil { + return []Task{}, err + } + + return tasks, nil +} + +// GetTask return a task by id +func GetTask(id int) (Task, error) { + path := fmt.Sprintf("tasks/%d", id) + res, err := makeRequest(http.MethodGet, path, nil) + if err != nil { + return Task{}, err + } + + return decodeTask(res.Body) +} + +// CreateTask create a new task +func CreateTask(task Task) (Task, error) { + res, err := makeRequest(http.MethodPost, "tasks", task.taskSave()) + if err != nil { + return Task{}, err + } + + return decodeTask(res.Body) +} + +// Delete remove a task +func (t Task) Delete() error { + path := fmt.Sprintf("tasks/%d", t.ID) + _, err := makeRequest(http.MethodDelete, path, nil) + if err != nil { + return err + } + + return nil +} + +// Update a task +func (t Task) Update() error { + path := fmt.Sprintf("tasks/%d", t.ID) + _, err := makeRequest(http.MethodPost, path, t.taskSave()) + if err != nil { + return err + } + + return nil +} + +// Close mask task as done +func (t Task) Close() error { + path := fmt.Sprintf("tasks/%d/close", t.ID) + _, err := makeRequest(http.MethodPost, path, nil) + if err != nil { + return err + } + + return nil +} + +// Reopen a task +func (t Task) Reopen() error { + path := fmt.Sprintf("tasks/%d/reopen", t.ID) + _, err := makeRequest(http.MethodPost, path, nil) + if err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/darkSasori/todoist/todoist.go b/vendor/github.com/darkSasori/todoist/todoist.go new file mode 100644 index 00000000..d487426c --- /dev/null +++ b/vendor/github.com/darkSasori/todoist/todoist.go @@ -0,0 +1,145 @@ +package todoist + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + "time" +) + +// Token save the personal token from todoist +var Token string +var todoistURL = "https://beta.todoist.com/API/v8/" + +func makeRequest(method, endpoint string, data interface{}) (*http.Response, error) { + url := todoistURL + endpoint + body := bytes.NewBuffer([]byte{}) + + if data != nil { + json, err := json.Marshal(data) + if err != nil { + return nil, err + } + body = bytes.NewBuffer(json) + } + + req, err := http.NewRequest(method, url, body) + if err != nil { + return nil, err + } + + bearer := fmt.Sprintf("Bearer %s", Token) + req.Header.Add("Authorization", bearer) + + if data != nil { + req.Header.Add("Content-Type", "application/json") + } + + client := &http.Client{} + res, err := client.Do(req) + if err != nil { + return nil, err + } + + if res.StatusCode >= 400 { + defer res.Body.Close() + str, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, err + } + return nil, fmt.Errorf(string(str)) + } + + return res, nil +} + +const ctLayout = "2006-01-02T15:04:05+00:00" + +// CustomTime had a custom json date format +type CustomTime struct { + time.Time +} + +// UnmarshalJSON convert from []byte to CustomTime +func (ct *CustomTime) UnmarshalJSON(b []byte) (err error) { + s := strings.Trim(string(b), "\"") + if s == "null" { + ct.Time = time.Time{} + return nil + } + + ct.Time, err = time.Parse(ctLayout, s) + return err +} + +// MarshalJSON convert CustomTime to []byte +func (ct CustomTime) MarshalJSON() ([]byte, error) { + if ct.Time.IsZero() { + return []byte("null"), nil + } + return []byte(`"` + ct.Time.Format(ctLayout) + `"`), nil +} + +type taskSave struct { + Content string `json:"content"` + ProjectID int `json:"project_id,omitempty"` + Order int `json:"order,omitempty"` + LabelIDs []int `json:"label_ids,omitempty"` + Priority int `json:"priority,omitempty"` + DueString string `json:"due_string,omitempty"` + DueDateTime CustomTime `json:"due_datetime,omitempty"` + DueLang string `json:"due_lang,omitempty"` +} + +func (ts taskSave) MarshalJSON() ([]byte, error) { + buffer := bytes.NewBufferString("{") + + if ts.Content == "" { + return nil, fmt.Errorf("Content is empty") + } + buffer.WriteString(fmt.Sprintf("\"content\":\"%s\"", ts.Content)) + + if ts.ProjectID != 0 { + buffer.WriteString(fmt.Sprintf(",\"project_id\":%d", ts.ProjectID)) + } + + if ts.Order != 0 { + buffer.WriteString(fmt.Sprintf(",\"order\":%d", ts.Order)) + } + + if !ts.DueDateTime.IsZero() { + buffer.WriteString(",\"due_datetime\":") + json, err := json.Marshal(ts.DueDateTime) + if err != nil { + return nil, err + } + buffer.Write(json) + } + + if len(ts.LabelIDs) != 0 { + buffer.WriteString(",\"label_ids\":") + json, err := json.Marshal(ts.LabelIDs) + if err != nil { + return nil, err + } + buffer.Write(json) + } + + if ts.Priority != 0 { + buffer.WriteString(fmt.Sprintf(",\"priority\":%d", ts.Priority)) + } + + if ts.DueString != "" { + buffer.WriteString(fmt.Sprintf(",\"due_string\":\"%s\"", ts.DueString)) + } + + if ts.DueLang != "" { + buffer.WriteString(fmt.Sprintf(",\"due_lang\":\"%s\"", ts.DueLang)) + } + + buffer.WriteString("}") + return buffer.Bytes(), nil +} From d1bdeb7876be332c8f172a987d7e1932522179b7 Mon Sep 17 00:00:00 2001 From: Chris Cummer Date: Wed, 11 Jul 2018 10:24:49 -0700 Subject: [PATCH 16/21] Add @darkSasori as a contributor --- .all-contributorsrc | 7 +++++++ README.md | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index f0232bd6..b8879094 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -217,6 +217,13 @@ "name": "Jagdeep Singh", "avatar_url": "https://avatars3.githubusercontent.com/u/3717137?v=4", "profile": "https://jagdeep.me", + "contributions": [] + }, + { + "login": "darkSasori", + "name": "Lineu Felipe", + "avatar_url": "https://avatars0.githubusercontent.com/u/889171?v=4", + "profile": "https://github.com/darkSasori", "contributions": [ ] } diff --git a/README.md b/README.md index ab4e5848..0ae15796 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ Thanks goes to these wonderful people: | [
baustinanki](https://github.com/baustinanki)
| [
lucus lee](https://github.com/lixin9311)
| [
Mike Lloyd](https://github.com/mxplusb)
| [
Sergio Rubio](http://rubiojr.rbel.co)
| [
Farhad Farahi](https://github.com/FarhadF)
| [
Lasantha Kularatne](http://lasantha.blogspot.com/)
| [
Mark Old](https://github.com/dlom)
| | [
flw](http://flw.tools/)
| [
David Barda](https://github.com/davebarda)
| [
Geoff Lee](https://github.com/matrinox)
| [
George Opritescu](http://international.github.io)
| [
Grazfather](https://twitter.com/Grazfather)
| [
Michael Cordell](http://www.mikecordell.com/)
| [
Patrick José Pereira](http://patrick.ibexcps.com)
| | [
sherod taylor](https://github.com/sherodtaylor)
| [
Andrew Scott](http://cogentia.io)
| [
Anand Sudhir Prayaga](https://github.com/anandsudhir)
| [
Lassi Piironen](https://github.com/lsipii)
| [
BlackWebWolf](https://github.com/BlackWebWolf)
| [
andrewzolotukhin](https://github.com/andrewzolotukhin)
| [
Leon Stigter](https://retgits.github.io)
| -| [
Amr Tamimi](https://tamimi.se)
| [
Jagdeep Singh](https://jagdeep.me)
| +| [
Amr Tamimi](https://tamimi.se)
| [
Jagdeep Singh](https://jagdeep.me)
| [
Lineu Felipe](https://github.com/darkSasori)
[💻](https://github.com/senorprogrammer/wtf/commits?author=darkSasori "Code") | This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome! From 4d1ab92b478eb2855e654ee39bf73dde8719b13d Mon Sep 17 00:00:00 2001 From: Chris Cummer Date: Wed, 11 Jul 2018 10:27:57 -0700 Subject: [PATCH 17/21] Add documentation for Todoist module --- _site/content/posts/modules/todoist.md | 4 +++- docs/404.html | 1 + docs/categories/index.html | 1 + docs/index.html | 1 + docs/index.xml | 18 +++++++++++++++++- docs/posts/configuration/attributes/index.html | 1 + docs/posts/configuration/index.html | 1 + docs/posts/configuration/iterm2/index.html | 1 + docs/posts/glossary/index.html | 1 + docs/posts/index.html | 8 ++++++++ docs/posts/index.xml | 18 +++++++++++++++++- docs/posts/installation/index.html | 1 + docs/posts/modules/bamboohr/index.html | 1 + docs/posts/modules/circleci/index.html | 1 + docs/posts/modules/clocks/index.html | 1 + docs/posts/modules/cmdrunner/index.html | 1 + .../cryptocurrencies/bittrex/index.html | 1 + .../cryptocurrencies/blockfolio/index.html | 1 + .../cryptocurrencies/cryptolive/index.html | 1 + docs/posts/modules/gcal/index.html | 1 + docs/posts/modules/gerrit/index.html | 1 + docs/posts/modules/git/index.html | 1 + docs/posts/modules/github/index.html | 1 + docs/posts/modules/gitlab/index.html | 1 + docs/posts/modules/gspreadsheet/index.html | 1 + docs/posts/modules/index.html | 1 + docs/posts/modules/ipapi/index.html | 1 + docs/posts/modules/ipinfo/index.html | 1 + docs/posts/modules/jenkins/index.html | 1 + docs/posts/modules/jira/index.html | 1 + docs/posts/modules/logger/index.html | 1 + docs/posts/modules/newrelic/index.html | 1 + docs/posts/modules/opsgenie/index.html | 1 + docs/posts/modules/power/index.html | 1 + docs/posts/modules/prettyweather/index.html | 1 + docs/posts/modules/security/index.html | 1 + docs/posts/modules/textfile/index.html | 1 + docs/posts/modules/todo/index.html | 1 + docs/posts/modules/trello/index.html | 1 + docs/posts/modules/weather/index.html | 1 + docs/posts/overview/index.html | 1 + docs/sitemap.xml | 9 +++++++-- docs/tags/index.html | 1 + 43 files changed, 90 insertions(+), 5 deletions(-) diff --git a/_site/content/posts/modules/todoist.md b/_site/content/posts/modules/todoist.md index bb3beb11..4467f165 100644 --- a/_site/content/posts/modules/todoist.md +++ b/_site/content/posts/modules/todoist.md @@ -4,7 +4,9 @@ date: 2018-07-05T22:55:55-03:00 draft: false --- -Displays all itens on specified project. +Added in `v0.0.11`. + +Displays all items on specified project. todoist screenshot diff --git a/docs/404.html b/docs/404.html index 2e9ef6b7..0fb566ce 100644 --- a/docs/404.html +++ b/docs/404.html @@ -100,6 +100,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/categories/index.html b/docs/categories/index.html index 1893a8de..fb9a29ee 100644 --- a/docs/categories/index.html +++ b/docs/categories/index.html @@ -102,6 +102,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/index.html b/docs/index.html index 5808c8f2..a8edc4eb 100644 --- a/docs/index.html +++ b/docs/index.html @@ -101,6 +101,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/index.xml b/docs/index.xml index f9314eb0..b2e1c6e1 100644 --- a/docs/index.xml +++ b/docs/index.xml @@ -6,11 +6,27 @@ Recent content on WTF - A Terminal Dashboard Hugo -- gohugo.io en-us - Wed, 27 Jun 2018 15:55:42 -0700 + Thu, 05 Jul 2018 22:55:55 -0300 + + Todoist + https://wtfutil.com/posts/modules/todoist/ + Thu, 05 Jul 2018 22:55:55 -0300 + + https://wtfutil.com/posts/modules/todoist/ + Added in v0.0.11. +Displays all items on specified project. +Source Code wtf/todoist/ Required ENV Variables Key: WTF_TODOIST_TOKEN Value: Your Todoist API Token. You can get your API Token at: todoist.com/prefs/integrations. +Keyboard Commands Key: h Action: Show the previous project. +Key: ← Action: Show the previous project. +Key: l Action: Show the next project. +Key: → Action: Show the next project. +Key: j Action: Select the next item in the list. + + Gerrit https://wtfutil.com/posts/modules/gerrit/ diff --git a/docs/posts/configuration/attributes/index.html b/docs/posts/configuration/attributes/index.html index 8ad20352..49031ee4 100644 --- a/docs/posts/configuration/attributes/index.html +++ b/docs/posts/configuration/attributes/index.html @@ -100,6 +100,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/configuration/index.html b/docs/posts/configuration/index.html index 3fb8a0a2..72bce846 100644 --- a/docs/posts/configuration/index.html +++ b/docs/posts/configuration/index.html @@ -100,6 +100,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/configuration/iterm2/index.html b/docs/posts/configuration/iterm2/index.html index 428b0980..67c5b99d 100644 --- a/docs/posts/configuration/iterm2/index.html +++ b/docs/posts/configuration/iterm2/index.html @@ -100,6 +100,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/glossary/index.html b/docs/posts/glossary/index.html index 31e18fdf..4e246476 100644 --- a/docs/posts/glossary/index.html +++ b/docs/posts/glossary/index.html @@ -100,6 +100,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/index.html b/docs/posts/index.html index 837c5820..52b9ee6b 100644 --- a/docs/posts/index.html +++ b/docs/posts/index.html @@ -102,6 +102,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + @@ -120,6 +121,13 @@ height="0" width="0" style="display:none;visibility:hidden">

Posts

  • + + Todoist + + + + +
  • Gerrit diff --git a/docs/posts/index.xml b/docs/posts/index.xml index ed56af43..1ca92370 100644 --- a/docs/posts/index.xml +++ b/docs/posts/index.xml @@ -6,11 +6,27 @@ Recent content in Posts on WTF - A Terminal Dashboard Hugo -- gohugo.io en-us - Wed, 27 Jun 2018 15:55:42 -0700 + Thu, 05 Jul 2018 22:55:55 -0300 + + Todoist + https://wtfutil.com/posts/modules/todoist/ + Thu, 05 Jul 2018 22:55:55 -0300 + + https://wtfutil.com/posts/modules/todoist/ + Added in v0.0.11. +Displays all items on specified project. +Source Code wtf/todoist/ Required ENV Variables Key: WTF_TODOIST_TOKEN Value: Your Todoist API Token. You can get your API Token at: todoist.com/prefs/integrations. +Keyboard Commands Key: h Action: Show the previous project. +Key: ← Action: Show the previous project. +Key: l Action: Show the next project. +Key: → Action: Show the next project. +Key: j Action: Select the next item in the list. + + Gerrit https://wtfutil.com/posts/modules/gerrit/ diff --git a/docs/posts/installation/index.html b/docs/posts/installation/index.html index 3d1a2a0e..34adb7d5 100644 --- a/docs/posts/installation/index.html +++ b/docs/posts/installation/index.html @@ -100,6 +100,7 @@ height="0" width="0" style="display:none;visibility:hidden">
  • +
diff --git a/docs/posts/modules/bamboohr/index.html b/docs/posts/modules/bamboohr/index.html index ead69742..23becca7 100644 --- a/docs/posts/modules/bamboohr/index.html +++ b/docs/posts/modules/bamboohr/index.html @@ -100,6 +100,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/circleci/index.html b/docs/posts/modules/circleci/index.html index 506d3835..5b0627fa 100644 --- a/docs/posts/modules/circleci/index.html +++ b/docs/posts/modules/circleci/index.html @@ -100,6 +100,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/clocks/index.html b/docs/posts/modules/clocks/index.html index a7a3fcc7..1113e5b7 100644 --- a/docs/posts/modules/clocks/index.html +++ b/docs/posts/modules/clocks/index.html @@ -100,6 +100,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/cmdrunner/index.html b/docs/posts/modules/cmdrunner/index.html index e23d45cc..5aed4c1d 100644 --- a/docs/posts/modules/cmdrunner/index.html +++ b/docs/posts/modules/cmdrunner/index.html @@ -100,6 +100,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/cryptocurrencies/bittrex/index.html b/docs/posts/modules/cryptocurrencies/bittrex/index.html index 1496dbfe..b728e8e2 100644 --- a/docs/posts/modules/cryptocurrencies/bittrex/index.html +++ b/docs/posts/modules/cryptocurrencies/bittrex/index.html @@ -100,6 +100,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/cryptocurrencies/blockfolio/index.html b/docs/posts/modules/cryptocurrencies/blockfolio/index.html index 5e67be49..6adfc5e6 100644 --- a/docs/posts/modules/cryptocurrencies/blockfolio/index.html +++ b/docs/posts/modules/cryptocurrencies/blockfolio/index.html @@ -100,6 +100,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/cryptocurrencies/cryptolive/index.html b/docs/posts/modules/cryptocurrencies/cryptolive/index.html index 8fe0beda..ae56f675 100644 --- a/docs/posts/modules/cryptocurrencies/cryptolive/index.html +++ b/docs/posts/modules/cryptocurrencies/cryptolive/index.html @@ -100,6 +100,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/gcal/index.html b/docs/posts/modules/gcal/index.html index 9c80a9b9..2f6df721 100644 --- a/docs/posts/modules/gcal/index.html +++ b/docs/posts/modules/gcal/index.html @@ -100,6 +100,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/gerrit/index.html b/docs/posts/modules/gerrit/index.html index ec7e5f5a..7b6b599e 100644 --- a/docs/posts/modules/gerrit/index.html +++ b/docs/posts/modules/gerrit/index.html @@ -100,6 +100,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/git/index.html b/docs/posts/modules/git/index.html index f90b4e13..fe40f145 100644 --- a/docs/posts/modules/git/index.html +++ b/docs/posts/modules/git/index.html @@ -100,6 +100,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/github/index.html b/docs/posts/modules/github/index.html index 4580bea5..188869f9 100644 --- a/docs/posts/modules/github/index.html +++ b/docs/posts/modules/github/index.html @@ -100,6 +100,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/gitlab/index.html b/docs/posts/modules/gitlab/index.html index e0e91c79..7812d6a6 100644 --- a/docs/posts/modules/gitlab/index.html +++ b/docs/posts/modules/gitlab/index.html @@ -100,6 +100,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/gspreadsheet/index.html b/docs/posts/modules/gspreadsheet/index.html index 48726ddc..1aa1d038 100644 --- a/docs/posts/modules/gspreadsheet/index.html +++ b/docs/posts/modules/gspreadsheet/index.html @@ -100,6 +100,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/index.html b/docs/posts/modules/index.html index 86ea00db..5a506e17 100644 --- a/docs/posts/modules/index.html +++ b/docs/posts/modules/index.html @@ -100,6 +100,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/ipapi/index.html b/docs/posts/modules/ipapi/index.html index 77be17f2..c7c06274 100644 --- a/docs/posts/modules/ipapi/index.html +++ b/docs/posts/modules/ipapi/index.html @@ -100,6 +100,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/ipinfo/index.html b/docs/posts/modules/ipinfo/index.html index 735a03fd..a11210a1 100644 --- a/docs/posts/modules/ipinfo/index.html +++ b/docs/posts/modules/ipinfo/index.html @@ -100,6 +100,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/jenkins/index.html b/docs/posts/modules/jenkins/index.html index 6bd495bf..f1fd8578 100644 --- a/docs/posts/modules/jenkins/index.html +++ b/docs/posts/modules/jenkins/index.html @@ -100,6 +100,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/jira/index.html b/docs/posts/modules/jira/index.html index a670349b..edcfcad4 100644 --- a/docs/posts/modules/jira/index.html +++ b/docs/posts/modules/jira/index.html @@ -100,6 +100,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/logger/index.html b/docs/posts/modules/logger/index.html index 9f9d0f24..4e052125 100644 --- a/docs/posts/modules/logger/index.html +++ b/docs/posts/modules/logger/index.html @@ -100,6 +100,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/newrelic/index.html b/docs/posts/modules/newrelic/index.html index b8d9b8af..7919546e 100644 --- a/docs/posts/modules/newrelic/index.html +++ b/docs/posts/modules/newrelic/index.html @@ -100,6 +100,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/opsgenie/index.html b/docs/posts/modules/opsgenie/index.html index d85f1fbd..3ec66dd1 100644 --- a/docs/posts/modules/opsgenie/index.html +++ b/docs/posts/modules/opsgenie/index.html @@ -100,6 +100,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/power/index.html b/docs/posts/modules/power/index.html index 106b6276..47cf3131 100644 --- a/docs/posts/modules/power/index.html +++ b/docs/posts/modules/power/index.html @@ -100,6 +100,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/prettyweather/index.html b/docs/posts/modules/prettyweather/index.html index fc8807ae..3c3c89ad 100644 --- a/docs/posts/modules/prettyweather/index.html +++ b/docs/posts/modules/prettyweather/index.html @@ -100,6 +100,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/security/index.html b/docs/posts/modules/security/index.html index 1c3892e3..86fcfe76 100644 --- a/docs/posts/modules/security/index.html +++ b/docs/posts/modules/security/index.html @@ -100,6 +100,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/textfile/index.html b/docs/posts/modules/textfile/index.html index 37684dfa..76082334 100644 --- a/docs/posts/modules/textfile/index.html +++ b/docs/posts/modules/textfile/index.html @@ -100,6 +100,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/todo/index.html b/docs/posts/modules/todo/index.html index 64eabf95..afb9c6df 100644 --- a/docs/posts/modules/todo/index.html +++ b/docs/posts/modules/todo/index.html @@ -100,6 +100,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/trello/index.html b/docs/posts/modules/trello/index.html index 30f0dc0d..0bcd3488 100644 --- a/docs/posts/modules/trello/index.html +++ b/docs/posts/modules/trello/index.html @@ -100,6 +100,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/modules/weather/index.html b/docs/posts/modules/weather/index.html index c9e28f90..44ce4cd4 100644 --- a/docs/posts/modules/weather/index.html +++ b/docs/posts/modules/weather/index.html @@ -100,6 +100,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/posts/overview/index.html b/docs/posts/overview/index.html index f77aaaef..e0fdfea6 100644 --- a/docs/posts/overview/index.html +++ b/docs/posts/overview/index.html @@ -100,6 +100,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + diff --git a/docs/sitemap.xml b/docs/sitemap.xml index 8e94ebea..fbd256d7 100644 --- a/docs/sitemap.xml +++ b/docs/sitemap.xml @@ -2,6 +2,11 @@ + + https://wtfutil.com/posts/modules/todoist/ + 2018-07-05T22:55:55-03:00 + + https://wtfutil.com/posts/modules/gerrit/ 2018-06-27T15:55:42-07:00 @@ -179,7 +184,7 @@ https://wtfutil.com/posts/ - 2018-06-27T15:55:42-07:00 + 2018-07-05T22:55:55-03:00 0 @@ -190,7 +195,7 @@ https://wtfutil.com/ - 2018-06-27T15:55:42-07:00 + 2018-07-05T22:55:55-03:00 0 diff --git a/docs/tags/index.html b/docs/tags/index.html index 3a799d0c..cc1bd7e3 100644 --- a/docs/tags/index.html +++ b/docs/tags/index.html @@ -102,6 +102,7 @@ height="0" width="0" style="display:none;visibility:hidden"> + From c958a437991b3a7718b1f9a9cef296697b632fe6 Mon Sep 17 00:00:00 2001 From: Chris Cummer Date: Wed, 11 Jul 2018 10:30:22 -0700 Subject: [PATCH 18/21] Update documentation for Todoist module --- docs/imgs/modules/todoist.png | Bin 0 -> 3335 bytes docs/posts/modules/todoist/index.html | 226 ++++++++++++++++++++++++++ 2 files changed, 226 insertions(+) create mode 100644 docs/imgs/modules/todoist.png create mode 100644 docs/posts/modules/todoist/index.html diff --git a/docs/imgs/modules/todoist.png b/docs/imgs/modules/todoist.png new file mode 100644 index 0000000000000000000000000000000000000000..086d0ec8673a4f407e2b0c382fd9e547711a0909 GIT binary patch literal 3335 zcmeHK=Q|r*8xLBvwOW)uXpEK)qtq&DRqPl|Q8QX2_KrPDNs6ZwwTs8D{lt94hzh0k zSPf#u4$_)Mh!Jo63Gb)(%X?qfIrljq&hOmUz0U8%n;7Y^Uj5@L003as)75$o0MJ>} z*4@nXwArPB97j`Ed~_}S0RXn1i$RzEkc|%jU}@FU(s&X4X_MgT!3m4$+J&i*FYzmH z%k$M}iRjd@m0k}r%eiVa?ibMZN!XXzT#;oiS+G@e)s5lo3v@h>g1YGp zKfZ0SI)7_sStsPQM}m%KA^`Blu9?7_rh7t0GGl_(NS^wDpb*cs`gPm-#us{C7RnArGrsT#}GaP!20 zCFO;I{(ygZ5lTqLm-fdh=`C73ia${jp&MC>dqH6j*RGRo!yv`Vn0a?yb1Ew z_EwlR&vW}Wm%7vBvu{4uJa_Xp^ZXQC=Nm*^a)1T5-Rmw_`dZ3juiCCj_j>N2xtZAk zdB$5uIK>-tMs8ipud1!>^j${aNT;WJ!^NZP@tk6>x8)3<)YsK{e;?E?Hbr7h&COSL zgKKSCQ-@$BN@kwffhTHSe6K`gcqycA;}E%!XeoN(2QQG785O3+vH^Ext2}>Of%Aer z2V?fj443K_ii(7>kHR}Kr@-GsdCI-Ly^)yt?ZZAJ>C?!lJ2$euRswQ2*ViZi@hzU& zOwtn0%gf_5etKguunicxUU)<}K0ZErG8B=W0Pf}}J<)dv;s(dFX~%I;*)X2PHDk7J zT7QlXjV@%{FP&ho$$>;WO$HCCS4eAn6fV$xaq&OTAX4}5?}m2R)#05RvJFLT@tc8d zQy%$Vj^WBFx;>6E;8_p%3;Fdu{bA6?zXgj&C!IdWHstPDAed?U?1{;OlExhg-j-k=p?ni-D36hrMjd6 ze6Fat&N(4-8wf0zvHFUFL+JhmJ9ieo zu(9#r{Yq;ruY=^keEj@ewEI~E^;{oxm-N0vA`qH|BddeC%kF7k@3dw}zqwONs($t3 zbq^McJv%#NWo6ANbImcQ1=r3_GSzNi(ox1W$5|$ON#Qx=(;M-RWe%}(b93WW7A-9; zztD5Dv;54LX1#)FoyXcbx3ZFffnghS-pNr^UtizaI_1_x>%w~Ow~TXCiN)2H4XdS@ zb_s;YrFb#6+bT!BJkXNGDLPByTvH)rHxC53#^L^qA!}j`w7#`*eiehbx7 za2&T#4p1t1KU!r*7}sTTGy*)0BFCT^)|np#u@?P2Jt z^*TXzCp`Cm!Bbv$DT(SEfF>QPe(-w7K16s=pl&QpdY{W<`rZDxvR)Axqb{#U1An$y zR-Au~8IY2a-QG??o+(E`0&skuE||^6im>z6mFdkLiKu{x_n!9m+N(~C@8q67H zxe91COKpcJXzl6btM~hyXSTRHY0NMp-EJ^EOF6{sUJ-gAkg?i#tvcu1<3Hf%bIREL zFkq1niiD49c*(q*>y+pa6|#w2q*{mHrOPV4UyCsM5_FSJT-}D1)!qtc!y0k_k^Fc{ zh|ER$U2LUC3siK&@4SgiZeRr5pk?U?nmYn)^9G1YJ`3vlespY-FU9nsmpr`w@AUJ$;617ZY0$?yW zr)=;riNR8GW&*)>i9T9=p*UI9s>y9!Bw6JL9)vPowbvt5zltxT)2UqzNyf&s9V8OwPPlwt1 zA5^olu87wn7#~SYAQ%Nv^0I*V3y**Rf~J@Lul_eK$aXjmha#|Bn#^gIyyy{l zA@g0_34K!E1PbOR#)*Xi3xeXlF4cB?VG$L|j{gg+f2H-RstT#ru^B q7|y|lR6E(8uUO$TD6+?G5-OUl?^}u literal 0 HcmV?d00001 diff --git a/docs/posts/modules/todoist/index.html b/docs/posts/modules/todoist/index.html new file mode 100644 index 00000000..6891cd30 --- /dev/null +++ b/docs/posts/modules/todoist/index.html @@ -0,0 +1,226 @@ + + + + + + + + + + + + +Todoist | WTF - A Terminal Dashboard + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Todoist

+ +
+ +
+ + + +

Added in v0.0.11.

+ +

Displays all items on specified project.

+ +

todoist screenshot

+ +

Source Code

+
wtf/todoist/
+

Required ENV Variables

+ +

Key: WTF_TODOIST_TOKEN
+Value: Your Todoist API Token.

+ +

You can get your API Token at: todoist.com/prefs/integrations.

+ +

Keyboard Commands

+ +

Key: h
+Action: Show the previous project.

+ +

Key:
+Action: Show the previous project.

+ +

Key: l
+Action: Show the next project.

+ +

Key:
+Action: Show the next project.

+ +

Key: j
+Action: Select the next item in the list.

+ +

Key:
+Action: Select the next item in the list.

+ +

Key: k
+Action: Select the previous item in the list.

+ +

Key:
+Action: Select the previous item in the list.

+ +

Key: c
+Action: Close current item.

+ +

Key: d
+Action: Delete current item.

+ +

Key: r
+Action: Reload all projects.

+ +

Configuration

+
todoist:
+  projects:
+    - project_id
+  enabled: true
+  position:
+    height: 1
+    left: 2
+    top: 0
+    width: 1
+  refreshInterval: 3600
+

Attributes

+ +

enabled
+Determines whether or not this module is executed and if its data displayed onscreen.
+Values: true, false.

+ +

projects
+The todoist projects to fetch items from.

+ +

refreshInterval
+How often, in seconds, this module will update its data.
+Values: A positive integer, 0..n.

+ +

position
+Where in the grid this module’s widget will be displayed.

+ +
+ + +
+ + + + From 43a3036cb3a5a4941256d7fe3a6cd0ca8a4a0b55 Mon Sep 17 00:00:00 2001 From: Chris Cummer Date: Wed, 11 Jul 2018 13:46:43 -0700 Subject: [PATCH 19/21] Clean up the Todoist documentation a bit --- _site/content/posts/modules/todoist.md | 17 +++++++++-------- docs/posts/modules/todoist/index.html | 19 ++++++++++--------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/_site/content/posts/modules/todoist.md b/_site/content/posts/modules/todoist.md index 4467f165..b1a431bc 100644 --- a/_site/content/posts/modules/todoist.md +++ b/_site/content/posts/modules/todoist.md @@ -32,7 +32,7 @@ _You can get your API Token at: todoist.com/prefs/integrations._ Action: Show the previous project. Key: `l`
-Action: Show the next project. +Action: Show the next project. Key: `→`
Action: Show the next project. @@ -62,14 +62,14 @@ _You can get your API Token at: todoist.com/prefs/integrations._ ```yaml todoist: - projects: - - project_id enabled: true position: - height: 1 - left: 2 top: 0 + left: 2 + height: 1 width: 1 + projects: + - 122247497 refreshInterval: 3600 ``` @@ -79,12 +79,13 @@ todoist: Determines whether or not this module is executed and if its data displayed onscreen.
Values: `true`, `false`. +`position`
+Where in the grid this module's widget will be displayed.
+ `projects`
The todoist projects to fetch items from.
+Values: The integer ID of the project. `refreshInterval`
How often, in seconds, this module will update its data.
Values: A positive integer, `0..n`. - -`position`
-Where in the grid this module's widget will be displayed.
diff --git a/docs/posts/modules/todoist/index.html b/docs/posts/modules/todoist/index.html index 6891cd30..f4404ac4 100644 --- a/docs/posts/modules/todoist/index.html +++ b/docs/posts/modules/todoist/index.html @@ -159,7 +159,7 @@ height="0" width="0" style="display:none;visibility:hidden"> Action: Show the previous project.

Key: l
-Action: Show the next project.

+Action: Show the next project.

Key:
Action: Show the next project.

@@ -187,14 +187,14 @@ height="0" width="0" style="display:none;visibility:hidden">

Configuration

todoist:
-  projects:
-    - project_id
   enabled: true
   position:
-    height: 1
-    left: 2
     top: 0
+    left: 2
+    height: 1
     width: 1
+  projects:
+    - 122247497
   refreshInterval: 3600

Attributes

@@ -202,16 +202,17 @@ height="0" width="0" style="display:none;visibility:hidden"> Determines whether or not this module is executed and if its data displayed onscreen.
Values: true, false.

+

position
+Where in the grid this module’s widget will be displayed.

+

projects
-The todoist projects to fetch items from.

+The todoist projects to fetch items from.
+Values: The integer ID of the project.

refreshInterval
How often, in seconds, this module will update its data.
Values: A positive integer, 0..n.

-

position
-Where in the grid this module’s widget will be displayed.

-