diff --git a/circleci/build.go b/circleci/build.go new file mode 100644 index 00000000..f0ee6cab --- /dev/null +++ b/circleci/build.go @@ -0,0 +1,10 @@ +package circleci + +type Build struct { + AuthorEmail string `json:"author_email"` + AuthorName string `json:"author_name"` + Branch string `json:"branch"` + BuildNum int `json:"build_num"` + Reponame string `json:"reponame"` + Status string `json:"status"` +} diff --git a/circleci/client.go b/circleci/client.go new file mode 100644 index 00000000..16d7eb82 --- /dev/null +++ b/circleci/client.go @@ -0,0 +1,80 @@ +package circleci + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "os" +) + +const APIEnvKey = "WTF_CIRCLE_API_KEY" + +func BuildsFor() ([]*Build, error) { + builds := []*Build{} + + resp, err := circleRequest("recent-builds") + if err != nil { + return builds, err + } + + parseJson(&builds, resp.Body) + + return builds, nil +} + +/* -------------------- Unexported Functions -------------------- */ + +var ( + circleAPIURL = &url.URL{Scheme: "https", Host: "circleci.com", Path: "/api/v1/"} +) + +func circleRequest(path string) (*http.Response, error) { + params := url.Values{} + params.Add("circle-token", apiKey()) + + url := circleAPIURL.ResolveReference(&url.URL{Path: path, RawQuery: params.Encode()}) + + req, err := http.NewRequest("GET", url.String(), nil) + req.Header.Add("Accept", "application/json") + req.Header.Add("Content-Type", "application/json") + if err != nil { + return nil, err + } + + httpClient := &http.Client{} + resp, err := httpClient.Do(req) + if err != nil { + return nil, err + } + + if resp.StatusCode < 200 || resp.StatusCode > 299 { + return nil, fmt.Errorf(resp.Status) + } + + return resp, nil +} + +func apiKey() string { + return os.Getenv(APIEnvKey) +} + +func parseJson(obj interface{}, text io.Reader) { + jsonStream, err := ioutil.ReadAll(text) + if err != nil { + panic(err) + } + + decoder := json.NewDecoder(bytes.NewReader(jsonStream)) + + for { + if err := decoder.Decode(obj); err == io.EOF { + break + } else if err != nil { + panic(err) + } + } +} diff --git a/circleci/widget.go b/circleci/widget.go new file mode 100644 index 00000000..0e3d6a28 --- /dev/null +++ b/circleci/widget.go @@ -0,0 +1,83 @@ +package circleci + +import ( + "fmt" + "github.com/olebedev/config" + "github.com/senorprogrammer/wtf/wtf" +) + +// Config is a pointer to the global config object +var Config *config.Config + +type Widget struct { + wtf.TextWidget +} + +func NewWidget() *Widget { + widget := Widget{ + TextWidget: wtf.NewTextWidget(" CircleCI ", "circleci", false), + } + + return &widget +} + +/* -------------------- Exported Functions -------------------- */ + +func (widget *Widget) Refresh() { + if widget.Disabled() { + return + } + + builds, err := BuildsFor() + + widget.UpdateRefreshedAt() + + widget.View.SetTitle(fmt.Sprintf("%s - Builds", widget.Name)) + + if err != nil { + widget.View.SetWrap(true) + fmt.Fprintf(widget.View, "%v", err) + } else { + widget.View.SetWrap(false) + widget.View.SetText(fmt.Sprintf("%s", widget.contentFrom(builds))) + } +} + +/* -------------------- Unexported Functions -------------------- */ + +func (widget *Widget) contentFrom(builds []*Build) string { + var str string + for idx, build := range builds { + if idx > 10 { + return str + } + + str = str + fmt.Sprintf( + "[%s] %s-%d (%s) [white]%s\n", + buildColor(build), + build.Reponame, + build.BuildNum, + build.Branch, + build.AuthorName, + ) + } + + return str +} + +func buildColor(b *Build) string { + var color string + + switch b.Status { + case "failed": + color = "red" + case "running": + color = "yellow" + case "success": + color = "green" + default: + color = "white" + } + + return color +} diff --git a/wtf.go b/wtf.go index 69ecfaa6..acb0d8d3 100644 --- a/wtf.go +++ b/wtf.go @@ -6,37 +6,38 @@ import ( "os" "time" - "github.com/andrewzolotukhin/wtf/bamboohr" - "github.com/andrewzolotukhin/wtf/bargraph" - "github.com/andrewzolotukhin/wtf/blockfolio" - "github.com/andrewzolotukhin/wtf/cfg" - "github.com/andrewzolotukhin/wtf/clocks" - "github.com/andrewzolotukhin/wtf/cmdrunner" - "github.com/andrewzolotukhin/wtf/cryptoexchanges/bittrex" - "github.com/andrewzolotukhin/wtf/cryptoexchanges/cryptolive" - "github.com/andrewzolotukhin/wtf/gcal" - "github.com/andrewzolotukhin/wtf/git" - "github.com/andrewzolotukhin/wtf/github" - "github.com/andrewzolotukhin/wtf/gspreadsheets" - "github.com/andrewzolotukhin/wtf/help" - "github.com/andrewzolotukhin/wtf/ipapi" - "github.com/andrewzolotukhin/wtf/ipinfo" - "github.com/andrewzolotukhin/wtf/jira" - "github.com/andrewzolotukhin/wtf/newrelic" - "github.com/andrewzolotukhin/wtf/opsgenie" - "github.com/andrewzolotukhin/wtf/power" - "github.com/andrewzolotukhin/wtf/prettyweather" - "github.com/andrewzolotukhin/wtf/security" - "github.com/andrewzolotukhin/wtf/status" - "github.com/andrewzolotukhin/wtf/system" - "github.com/andrewzolotukhin/wtf/textfile" - "github.com/andrewzolotukhin/wtf/todo" - "github.com/andrewzolotukhin/wtf/weather" - "github.com/andrewzolotukhin/wtf/wtf" "github.com/gdamore/tcell" "github.com/olebedev/config" "github.com/radovskyb/watcher" "github.com/rivo/tview" + "github.com/senorprogrammer/wtf/bamboohr" + "github.com/senorprogrammer/wtf/bargraph" + "github.com/senorprogrammer/wtf/blockfolio" + "github.com/senorprogrammer/wtf/cfg" + "github.com/senorprogrammer/wtf/circleci" + "github.com/senorprogrammer/wtf/clocks" + "github.com/senorprogrammer/wtf/cmdrunner" + "github.com/senorprogrammer/wtf/cryptoexchanges/bittrex" + "github.com/senorprogrammer/wtf/cryptoexchanges/cryptolive" + "github.com/senorprogrammer/wtf/gcal" + "github.com/senorprogrammer/wtf/git" + "github.com/senorprogrammer/wtf/github" + "github.com/senorprogrammer/wtf/gspreadsheets" + "github.com/senorprogrammer/wtf/help" + "github.com/senorprogrammer/wtf/ipapi" + "github.com/senorprogrammer/wtf/ipinfo" + "github.com/senorprogrammer/wtf/jira" + "github.com/senorprogrammer/wtf/newrelic" + "github.com/senorprogrammer/wtf/opsgenie" + "github.com/senorprogrammer/wtf/power" + "github.com/senorprogrammer/wtf/prettyweather" + "github.com/senorprogrammer/wtf/security" + "github.com/senorprogrammer/wtf/status" + "github.com/senorprogrammer/wtf/system" + "github.com/senorprogrammer/wtf/textfile" + "github.com/senorprogrammer/wtf/todo" + "github.com/senorprogrammer/wtf/weather" + "github.com/senorprogrammer/wtf/wtf" ) /* -------------------- Functions -------------------- */ @@ -177,6 +178,8 @@ func addWidget(app *tview.Application, pages *tview.Pages, widgetName string) { Widgets = append(Widgets, bargraph.NewWidget()) case "bittrex": Widgets = append(Widgets, bittrex.NewWidget()) + case "circleci": + Widgets = append(Widgets, circleci.NewWidget()) case "clocks": Widgets = append(Widgets, clocks.NewWidget()) case "cmdrunner": @@ -230,6 +233,7 @@ func makeWidgets(app *tview.Application, pages *tview.Pages) { bamboohr.Config = Config bargraph.Config = Config bittrex.Config = Config + circleci.Config = Config clocks.Config = Config cmdrunner.Config = Config cryptolive.Config = Config