1
0
mirror of https://github.com/taigrr/wtf synced 2025-01-18 04:03:14 -08:00

Merge branch 'master' into dependabot/go_modules/github.com/digitalocean/godo-1.46.0

This commit is contained in:
Chris Cummer 2020-10-08 11:42:05 -07:00
commit 2990ff4ac0
30 changed files with 693 additions and 525 deletions

View File

@ -973,14 +973,27 @@
"name": "Martin Johns", "name": "Martin Johns",
"avatar_url": "https://avatars1.githubusercontent.com/u/5269069?v=4", "avatar_url": "https://avatars1.githubusercontent.com/u/5269069?v=4",
"profile": "https://github.com/MartinJohns", "profile": "https://github.com/MartinJohns",
"contributions": [ "contributions": []
]
}, },
{ {
"login": "jamietanna", "login": "jamietanna",
"name": "Jamie Tanna", "name": "Jamie Tanna",
"avatar_url": "https://avatars0.githubusercontent.com/u/3315059?v=4", "avatar_url": "https://avatars0.githubusercontent.com/u/3315059?v=4",
"profile": "https://www.jvt.me", "profile": "https://www.jvt.me",
"contributions": []
},
{
"login": "trimble",
"name": "Todd Trimble",
"avatar_url": "https://avatars3.githubusercontent.com/u/371317?v=4",
"profile": "https://github.com/trimble",
"contributions": []
},
{
"login": "mhanberg",
"name": "Mitchell Hanberg",
"avatar_url": "https://avatars2.githubusercontent.com/u/5523984?v=4",
"profile": "https://www.mitchellhanberg.com",
"contributions": [ "contributions": [
] ]
} }

View File

@ -1,7 +1,7 @@
language: go language: go
go: go:
- "1.14.x" - "1.15.x"
before_install: before_install:
# Make sure travis builds work for forks # Make sure travis builds work for forks

View File

@ -5,6 +5,7 @@
### ⚡️ Added ### ⚡️ Added
* FeedReader module supports [display mode switching](https://github.com/wtfutil/wtf/pull/976) via the `t` key, by [@cyingfan](https://github.com/cyingfan) * FeedReader module supports [display mode switching](https://github.com/wtfutil/wtf/pull/976) via the `t` key, by [@cyingfan](https://github.com/cyingfan)
* Clocks module now supports `natural` ordering option, [#896](https://github.com/wtfutil/wtf/issues/896) by [@seanstoppable](https://github.com/Seanstoppable)
### 🐞 Fixed ### 🐞 Fixed

View File

@ -403,6 +403,10 @@ Dependency management in WTF is handled by [Go modules](https://github.com/golan
<td align="center"><a href="http://about.me/yingfan"><img src="https://avatars1.githubusercontent.com/u/10404961?v=4" width="48px;" alt=""/><br /><sub><b>Ying Fan Chong</b></sub></a><br /></td> <td align="center"><a href="http://about.me/yingfan"><img src="https://avatars1.githubusercontent.com/u/10404961?v=4" width="48px;" alt=""/><br /><sub><b>Ying Fan Chong</b></sub></a><br /></td>
<td align="center"><a href="https://github.com/MartinJohns"><img src="https://avatars1.githubusercontent.com/u/5269069?v=4" width="48px;" alt=""/><br /><sub><b>Martin Johns</b></sub></a><br /></td> <td align="center"><a href="https://github.com/MartinJohns"><img src="https://avatars1.githubusercontent.com/u/5269069?v=4" width="48px;" alt=""/><br /><sub><b>Martin Johns</b></sub></a><br /></td>
<td align="center"><a href="https://www.jvt.me"><img src="https://avatars0.githubusercontent.com/u/3315059?v=4" width="48px;" alt=""/><br /><sub><b>Jamie Tanna</b></sub></a><br /></td> <td align="center"><a href="https://www.jvt.me"><img src="https://avatars0.githubusercontent.com/u/3315059?v=4" width="48px;" alt=""/><br /><sub><b>Jamie Tanna</b></sub></a><br /></td>
<td align="center"><a href="https://github.com/trimble"><img src="https://avatars3.githubusercontent.com/u/371317?v=4" width="48px;" alt=""/><br /><sub><b>Todd Trimble</b></sub></a><br /></td>
</tr>
<tr>
<td align="center"><a href="https://www.mitchellhanberg.com"><img src="https://avatars2.githubusercontent.com/u/5523984?v=4" width="48px;" alt=""/><br /><sub><b>Mitchell Hanberg</b></sub></a><br /></td>
</tr> </tr>
</table> </table>

View File

@ -59,10 +59,9 @@ import (
"github.com/wtfutil/wtf/modules/subreddit" "github.com/wtfutil/wtf/modules/subreddit"
"github.com/wtfutil/wtf/modules/textfile" "github.com/wtfutil/wtf/modules/textfile"
"github.com/wtfutil/wtf/modules/todo" "github.com/wtfutil/wtf/modules/todo"
"github.com/wtfutil/wtf/modules/todoist" "github.com/wtfutil/wtf/modules/todo_plus"
"github.com/wtfutil/wtf/modules/transmission" "github.com/wtfutil/wtf/modules/transmission"
"github.com/wtfutil/wtf/modules/travisci" "github.com/wtfutil/wtf/modules/travisci"
"github.com/wtfutil/wtf/modules/trello"
"github.com/wtfutil/wtf/modules/twitch" "github.com/wtfutil/wtf/modules/twitch"
"github.com/wtfutil/wtf/modules/twitter" "github.com/wtfutil/wtf/modules/twitter"
"github.com/wtfutil/wtf/modules/twitterstats" "github.com/wtfutil/wtf/modules/twitterstats"
@ -269,9 +268,12 @@ func MakeWidget(
case "todo": case "todo":
settings := todo.NewSettingsFromYAML(moduleName, moduleConfig, config) settings := todo.NewSettingsFromYAML(moduleName, moduleConfig, config)
widget = todo.NewWidget(app, pages, settings) widget = todo.NewWidget(app, pages, settings)
case "todo_plus":
settings := todo_plus.NewSettingsFromYAML(moduleName, moduleConfig, config)
widget = todo_plus.NewWidget(app, pages, settings)
case "todoist": case "todoist":
settings := todoist.NewSettingsFromYAML(moduleName, moduleConfig, config) settings := todo_plus.FromTodoist(moduleName, moduleConfig, config)
widget = todoist.NewWidget(app, pages, settings) widget = todo_plus.NewWidget(app, pages, settings)
case "transmission": case "transmission":
settings := transmission.NewSettingsFromYAML(moduleName, moduleConfig, config) settings := transmission.NewSettingsFromYAML(moduleName, moduleConfig, config)
widget = transmission.NewWidget(app, pages, settings) widget = transmission.NewWidget(app, pages, settings)
@ -279,8 +281,8 @@ func MakeWidget(
settings := travisci.NewSettingsFromYAML(moduleName, moduleConfig, config) settings := travisci.NewSettingsFromYAML(moduleName, moduleConfig, config)
widget = travisci.NewWidget(app, pages, settings) widget = travisci.NewWidget(app, pages, settings)
case "trello": case "trello":
settings := trello.NewSettingsFromYAML(moduleName, moduleConfig, config) settings := todo_plus.FromTrello(moduleName, moduleConfig, config)
widget = trello.NewWidget(app, settings) widget = todo_plus.NewWidget(app, pages, settings)
case "twitch": case "twitch":
settings := twitch.NewSettingsFromYAML(moduleName, moduleConfig, config) settings := twitch.NewSettingsFromYAML(moduleName, moduleConfig, config)
widget = twitch.NewWidget(app, pages, settings) widget = twitch.NewWidget(app, pages, settings)

View File

@ -1,6 +1,7 @@
package clocks package clocks
import ( import (
"strings"
"time" "time"
) )
@ -18,6 +19,14 @@ func NewClock(label string, timeLoc *time.Location) Clock {
return clock return clock
} }
func BuildClock(label string, location string) (clock Clock, err error) {
timeLoc, err := time.LoadLocation(sanitizeLocation(location))
if err != nil {
return Clock{}, err
}
return NewClock(label, timeLoc), nil
}
func (clock *Clock) Date(dateFormat string) string { func (clock *Clock) Date(dateFormat string) string {
return clock.LocalTime().Format(dateFormat) return clock.LocalTime().Format(dateFormat)
} }
@ -33,3 +42,7 @@ func (clock *Clock) ToLocal(t time.Time) time.Time {
func (clock *Clock) Time(timeFormat string) string { func (clock *Clock) Time(timeFormat string) string {
return clock.LocalTime().Format(timeFormat) return clock.LocalTime().Format(timeFormat)
} }
func sanitizeLocation(locStr string) string {
return strings.Replace(locStr, " ", "_", -1)
}

View File

@ -10,7 +10,9 @@ type ClockCollection struct {
} }
func (clocks *ClockCollection) Sorted(sortOrder string) []Clock { func (clocks *ClockCollection) Sorted(sortOrder string) []Clock {
if sortOrder == "chronological" { if sortOrder == "natural" {
//no-op
} else if sortOrder == "chronological" {
clocks.SortedChronologically() clocks.SortedChronologically()
} else { } else {
clocks.SortedAlphabetically() clocks.SortedAlphabetically()

View File

@ -1,15 +1,13 @@
package clocks package clocks
import ( import "fmt"
"fmt"
)
func (widget *Widget) display(clocks []Clock, dateFormat string, timeFormat string) { func (widget *Widget) display(clocks []Clock, dateFormat string, timeFormat string) {
str := "" str := ""
if len(clocks) == 0 { if len(clocks) == 0 {
str = fmt.Sprintf("\n%s", " no timezone data available") str = fmt.Sprintf("\n%s", " no timezone data available")
} else { } else {
for idx, clock := range clocks { for idx, clock := range clocks {
str += fmt.Sprintf( str += fmt.Sprintf(
" [%s]%-12s %-10s %7s[white]\n", " [%s]%-12s %-10s %7s[white]\n",

View File

@ -15,10 +15,10 @@ const (
type Settings struct { type Settings struct {
common *cfg.Common common *cfg.Common
dateFormat string `help:"The format of the date string for all clocks." values:"Any valid Go date layout which is handled by Time.Format. Defaults to Jan 2."` dateFormat string `help:"The format of the date string for all clocks." values:"Any valid Go date layout which is handled by Time.Format. Defaults to Jan 2."`
timeFormat string `help:"The format of the time string for all clocks." values:"Any valid Go time layout which is handled by Time.Format. Defaults to 15:04 MST."` timeFormat string `help:"The format of the time string for all clocks." values:"Any valid Go time layout which is handled by Time.Format. Defaults to 15:04 MST."`
locations map[string]interface{} `help:"Defines the timezones for the world clocks that you want to display. key is a unique label that will be displayed in the UI. value is a timezone name." values:"Any TZ database timezone."` locations []Clock `help:"Defines the timezones for the world clocks that you want to display. key is a unique label that will be displayed in the UI. value is a timezone name." values:"Any TZ database timezone."`
sort string `help:"Defines the display order of the clocks in the widget." values:"'alphabetical' or 'chronological'. 'alphabetical' will sort in acending order by key, 'chronological' will sort in ascending order by date/time."` sort string `help:"Defines the display order of the clocks in the widget." values:"'alphabetical', 'chronological', or 'natural. 'alphabetical' will sort in ascending order by key, 'chronological' will sort in ascending order by date/time, 'natural' will keep ordering as per the config."`
} }
// NewSettingsFromYAML creates a new settings instance from a YAML config block // NewSettingsFromYAML creates a new settings instance from a YAML config block
@ -28,9 +28,40 @@ func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *co
dateFormat: ymlConfig.UString("dateFormat", utils.SimpleDateFormat), dateFormat: ymlConfig.UString("dateFormat", utils.SimpleDateFormat),
timeFormat: ymlConfig.UString("timeFormat", utils.SimpleTimeFormat), timeFormat: ymlConfig.UString("timeFormat", utils.SimpleTimeFormat),
locations: ymlConfig.UMap("locations"), locations: buildLocations(ymlConfig),
sort: ymlConfig.UString("sort"), sort: ymlConfig.UString("sort"),
} }
return &settings return &settings
} }
func buildLocations(ymlConfig *config.Config) []Clock {
clocks := []Clock{}
locations, err := ymlConfig.Map("locations")
if err == nil {
for k, v := range locations {
name := k
zone := v.(string)
clock, err := BuildClock(name, zone)
if err == nil {
clocks = append(clocks, clock)
}
}
return clocks
}
listLocations := ymlConfig.UList("locations")
for _, location := range listLocations {
if location, ok := location.(map[string]interface{}); ok {
for k, v := range location {
name := k
zone := v.(string)
clock, err := BuildClock(name, zone)
if err == nil {
clocks = append(clocks, clock)
}
}
}
}
return clocks
}

View File

@ -1,9 +1,6 @@
package clocks package clocks
import ( import (
"strings"
"time"
"github.com/rivo/tview" "github.com/rivo/tview"
"github.com/wtfutil/wtf/view" "github.com/wtfutil/wtf/view"
) )
@ -28,7 +25,7 @@ func NewWidget(app *tview.Application, settings *Settings) *Widget {
timeFormat: settings.timeFormat, timeFormat: settings.timeFormat,
} }
widget.clockColl = widget.buildClockCollection(settings.locations) widget.clockColl = widget.buildClockCollection()
return &widget return &widget
} }
@ -45,21 +42,10 @@ func (widget *Widget) Refresh() {
/* -------------------- Unexported Functions -------------------- */ /* -------------------- Unexported Functions -------------------- */
func (widget *Widget) buildClockCollection(locData map[string]interface{}) ClockCollection { func (widget *Widget) buildClockCollection() ClockCollection {
clockColl := ClockCollection{} clockColl := ClockCollection{}
for label, locStr := range locData { clockColl.Clocks = widget.settings.locations
timeLoc, err := time.LoadLocation(widget.sanitizeLocation(locStr.(string)))
if err != nil {
continue
}
clockColl.Clocks = append(clockColl.Clocks, NewClock(label, timeLoc))
}
return clockColl return clockColl
} }
func (widget *Widget) sanitizeLocation(locStr string) string {
return strings.Replace(locStr, " ", "_", -1)
}

View File

@ -4,6 +4,8 @@ import (
"fmt" "fmt"
"github.com/google/go-github/v26/github" "github.com/google/go-github/v26/github"
"golang.org/x/text/language"
"golang.org/x/text/message"
) )
func (widget *Widget) display() { func (widget *Widget) display() {
@ -54,7 +56,7 @@ func (widget *Widget) content() (string, string, bool) {
return title, str, false return title, str, false
} }
func (widget *Widget) displayMyPullRequests(repo *GithubRepo, username string) string { func (widget *Widget) displayMyPullRequests(repo *Repo, username string) string {
prs := repo.myPullRequests(username, widget.settings.enableStatus) prs := repo.myPullRequests(username, widget.settings.enableStatus)
prLength := len(prs) prLength := len(prs)
@ -77,7 +79,7 @@ func (widget *Widget) displayMyPullRequests(repo *GithubRepo, username string) s
return str return str
} }
func (widget *Widget) displayCustomQuery(repo *GithubRepo, filter string, perPage int) string { func (widget *Widget) displayCustomQuery(repo *Repo, filter string, perPage int) string {
res := repo.customIssueQuery(filter, perPage) res := repo.customIssueQuery(filter, perPage)
if res == nil { if res == nil {
@ -104,7 +106,7 @@ func (widget *Widget) displayCustomQuery(repo *GithubRepo, filter string, perPag
return str return str
} }
func (widget *Widget) displayMyReviewRequests(repo *GithubRepo, username string) string { func (widget *Widget) displayMyReviewRequests(repo *Repo, username string) string {
prs := repo.myReviewRequests(username) prs := repo.myReviewRequests(username)
if len(prs) == 0 { if len(prs) == 0 {
@ -121,18 +123,20 @@ func (widget *Widget) displayMyReviewRequests(repo *GithubRepo, username string)
return str return str
} }
func (widget *Widget) displayStats(repo *GithubRepo) string { func (widget *Widget) displayStats(repo *Repo) string {
prntr := message.NewPrinter(language.English)
str := fmt.Sprintf( str := fmt.Sprintf(
" PRs: %d Issues: %d Stars: %d\n", " PRs: %s Issues: %s Stars: %s\n",
repo.PullRequestCount(), prntr.Sprintf("%d", repo.PullRequestCount()),
repo.IssueCount(), prntr.Sprintf("%d", repo.IssueCount()),
repo.StarCount(), prntr.Sprintf("%d", repo.StarCount()),
) )
return str return str
} }
func (widget *Widget) title(repo *GithubRepo) string { func (widget *Widget) title(repo *Repo) string {
return fmt.Sprintf( return fmt.Sprintf(
"[%s]%s - %s[white]", "[%s]%s - %s[white]",
widget.settings.common.Colors.TextTheme.Title, widget.settings.common.Colors.TextTheme.Title,

View File

@ -15,8 +15,8 @@ const (
issuesPath = "/issues" issuesPath = "/issues"
) )
// GithubRepo defines a new GithubRepo structure // Repo defines a new GitHub Repo structure
type GithubRepo struct { type Repo struct {
apiKey string apiKey string
baseURL string baseURL string
uploadURL string uploadURL string
@ -29,8 +29,8 @@ type GithubRepo struct {
} }
// NewGithubRepo returns a new Github Repo with a name, owner, apiKey, baseURL and uploadURL // NewGithubRepo returns a new Github Repo with a name, owner, apiKey, baseURL and uploadURL
func NewGithubRepo(name, owner, apiKey, baseURL, uploadURL string) *GithubRepo { func NewGithubRepo(name, owner, apiKey, baseURL, uploadURL string) *Repo {
repo := GithubRepo{ repo := Repo{
Name: name, Name: name,
Owner: owner, Owner: owner,
@ -43,22 +43,22 @@ func NewGithubRepo(name, owner, apiKey, baseURL, uploadURL string) *GithubRepo {
} }
// Open will open the GitHub Repo URL using the utils helper // Open will open the GitHub Repo URL using the utils helper
func (repo *GithubRepo) Open() { func (repo *Repo) Open() {
utils.OpenFile(*repo.RemoteRepo.HTMLURL) utils.OpenFile(*repo.RemoteRepo.HTMLURL)
} }
// Open will open the GitHub Pull Requests URL using the utils helper // OpenPulls will open the GitHub Pull Requests URL using the utils helper
func (repo *GithubRepo) OpenPulls() { func (repo *Repo) OpenPulls() {
utils.OpenFile(*repo.RemoteRepo.HTMLURL + pullRequestsPath) utils.OpenFile(*repo.RemoteRepo.HTMLURL + pullRequestsPath)
} }
// Open will open the GitHub Issues URL using the utils helper // OpenIssues will open the GitHub Issues URL using the utils helper
func (repo *GithubRepo) OpenIssues() { func (repo *Repo) OpenIssues() {
utils.OpenFile(*repo.RemoteRepo.HTMLURL + issuesPath) utils.OpenFile(*repo.RemoteRepo.HTMLURL + issuesPath)
} }
// Refresh reloads the github data via the Github API // Refresh reloads the github data via the Github API
func (repo *GithubRepo) Refresh() { func (repo *Repo) Refresh() {
prs, err := repo.loadPullRequests() prs, err := repo.loadPullRequests()
repo.Err = err repo.Err = err
repo.PullRequests = prs repo.PullRequests = prs
@ -73,7 +73,7 @@ func (repo *GithubRepo) Refresh() {
/* -------------------- Counts -------------------- */ /* -------------------- Counts -------------------- */
// IssueCount return the total amount of issues as an int // IssueCount return the total amount of issues as an int
func (repo *GithubRepo) IssueCount() int { func (repo *Repo) IssueCount() int {
if repo.RemoteRepo == nil { if repo.RemoteRepo == nil {
return 0 return 0
} }
@ -84,12 +84,12 @@ func (repo *GithubRepo) IssueCount() int {
} }
// PullRequestCount returns the total amount of pull requests as an int // PullRequestCount returns the total amount of pull requests as an int
func (repo *GithubRepo) PullRequestCount() int { func (repo *Repo) PullRequestCount() int {
return len(repo.PullRequests) return len(repo.PullRequests)
} }
// StarCount returns the total amount of stars this repo has gained as an int // StarCount returns the total amount of stars this repo has gained as an int
func (repo *GithubRepo) StarCount() int { func (repo *Repo) StarCount() int {
if repo.RemoteRepo == nil { if repo.RemoteRepo == nil {
return 0 return 0
} }
@ -99,7 +99,7 @@ func (repo *GithubRepo) StarCount() int {
/* -------------------- Unexported Functions -------------------- */ /* -------------------- Unexported Functions -------------------- */
func (repo *GithubRepo) isGitHubEnterprise() bool { func (repo *Repo) isGitHubEnterprise() bool {
if len(repo.baseURL) > 0 { if len(repo.baseURL) > 0 {
if len(repo.uploadURL) == 0 { if len(repo.uploadURL) == 0 {
repo.uploadURL = repo.baseURL repo.uploadURL = repo.baseURL
@ -109,7 +109,7 @@ func (repo *GithubRepo) isGitHubEnterprise() bool {
return false return false
} }
func (repo *GithubRepo) oauthClient() *http.Client { func (repo *Repo) oauthClient() *http.Client {
tokenService := oauth2.StaticTokenSource( tokenService := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: repo.apiKey}, &oauth2.Token{AccessToken: repo.apiKey},
) )
@ -117,7 +117,7 @@ func (repo *GithubRepo) oauthClient() *http.Client {
return oauth2.NewClient(context.Background(), tokenService) return oauth2.NewClient(context.Background(), tokenService)
} }
func (repo *GithubRepo) githubClient() (*ghb.Client, error) { func (repo *Repo) githubClient() (*ghb.Client, error) {
oauthClient := repo.oauthClient() oauthClient := repo.oauthClient()
if repo.isGitHubEnterprise() { if repo.isGitHubEnterprise() {
@ -128,7 +128,7 @@ func (repo *GithubRepo) githubClient() (*ghb.Client, error) {
} }
// myPullRequests returns a list of pull requests created by username on this repo // myPullRequests returns a list of pull requests created by username on this repo
func (repo *GithubRepo) myPullRequests(username string, showStatus bool) []*ghb.PullRequest { func (repo *Repo) myPullRequests(username string, showStatus bool) []*ghb.PullRequest {
prs := []*ghb.PullRequest{} prs := []*ghb.PullRequest{}
for _, pr := range repo.PullRequests { for _, pr := range repo.PullRequests {
@ -149,7 +149,7 @@ func (repo *GithubRepo) myPullRequests(username string, showStatus bool) []*ghb.
// individualPRs takes a list of pull requests (presumably returned from // individualPRs takes a list of pull requests (presumably returned from
// github.PullRequests.List) and fetches them individually to get more detailed // github.PullRequests.List) and fetches them individually to get more detailed
// status info on each. see: https://developer.github.com/v3/git/#checking-mergeability-of-pull-requests // status info on each. see: https://developer.github.com/v3/git/#checking-mergeability-of-pull-requests
func (repo *GithubRepo) individualPRs(prs []*ghb.PullRequest) []*ghb.PullRequest { func (repo *Repo) individualPRs(prs []*ghb.PullRequest) []*ghb.PullRequest {
github, err := repo.githubClient() github, err := repo.githubClient()
if err != nil { if err != nil {
return prs return prs
@ -170,7 +170,7 @@ func (repo *GithubRepo) individualPRs(prs []*ghb.PullRequest) []*ghb.PullRequest
// myReviewRequests returns a list of pull requests for which username has been // myReviewRequests returns a list of pull requests for which username has been
// requested to do a code review // requested to do a code review
func (repo *GithubRepo) myReviewRequests(username string) []*ghb.PullRequest { func (repo *Repo) myReviewRequests(username string) []*ghb.PullRequest {
prs := []*ghb.PullRequest{} prs := []*ghb.PullRequest{}
for _, pr := range repo.PullRequests { for _, pr := range repo.PullRequests {
@ -184,7 +184,7 @@ func (repo *GithubRepo) myReviewRequests(username string) []*ghb.PullRequest {
return prs return prs
} }
func (repo *GithubRepo) customIssueQuery(filter string, perPage int) *ghb.IssuesSearchResult { func (repo *Repo) customIssueQuery(filter string, perPage int) *ghb.IssuesSearchResult {
github, err := repo.githubClient() github, err := repo.githubClient()
if err != nil { if err != nil {
return nil return nil
@ -199,7 +199,7 @@ func (repo *GithubRepo) customIssueQuery(filter string, perPage int) *ghb.Issues
return prs return prs
} }
func (repo *GithubRepo) loadPullRequests() ([]*ghb.PullRequest, error) { func (repo *Repo) loadPullRequests() ([]*ghb.PullRequest, error) {
github, err := repo.githubClient() github, err := repo.githubClient()
if err != nil { if err != nil {
return nil, err return nil, err
@ -217,7 +217,7 @@ func (repo *GithubRepo) loadPullRequests() ([]*ghb.PullRequest, error) {
return prs, nil return prs, nil
} }
func (repo *GithubRepo) loadRemoteRepository() (*ghb.Repository, error) { func (repo *Repo) loadRemoteRepository() (*ghb.Repository, error) {
github, err := repo.githubClient() github, err := repo.githubClient()
if err != nil { if err != nil {

View File

@ -15,7 +15,7 @@ type Widget struct {
view.KeyboardWidget view.KeyboardWidget
view.TextWidget view.TextWidget
GithubRepos []*GithubRepo GithubRepos []*Repo
settings *Settings settings *Settings
Selected int Selected int
@ -110,8 +110,8 @@ func (widget *Widget) HelpText() string {
/* -------------------- Unexported Functions -------------------- */ /* -------------------- Unexported Functions -------------------- */
func (widget *Widget) buildRepoCollection(repoData []string) []*GithubRepo { func (widget *Widget) buildRepoCollection(repoData []string) []*Repo {
githubRepos := []*GithubRepo{} githubRepos := []*Repo{}
for _, repo := range repoData { for _, repo := range repoData {
split := strings.Split(repo, "/") split := strings.Split(repo, "/")
@ -130,7 +130,7 @@ func (widget *Widget) buildRepoCollection(repoData []string) []*GithubRepo {
return githubRepos return githubRepos
} }
func (widget *Widget) currentGithubRepo() *GithubRepo { func (widget *Widget) currentGithubRepo() *Repo {
if len(widget.GithubRepos) == 0 { if len(widget.GithubRepos) == 0 {
return nil return nil
} }

View File

@ -0,0 +1,16 @@
package backend
import (
"github.com/olebedev/config"
)
type Backend interface {
Title() string
Setup(*config.Config)
BuildProjects() []*Project
GetProject(string) *Project
LoadTasks(string) ([]Task, error)
CloseTask(*Task) error
DeleteTask(*Task) error
Sources() []string
}

View File

@ -0,0 +1,66 @@
package backend
type Task struct {
ID string
Completed bool
Name string
}
type Project struct {
ID string
Name string
Index int
Tasks []Task
Err error
backend Backend
}
func (proj *Project) IsLast() bool {
return proj.Index >= len(proj.Tasks)-1
}
func (proj *Project) loadTasks() {
Tasks, err := proj.backend.LoadTasks(proj.ID)
proj.Err = err
proj.Tasks = Tasks
}
func (proj *Project) LongestLine() int {
maxLen := 0
for _, task := range proj.Tasks {
if len(task.Name) > maxLen {
maxLen = len(task.Name)
}
}
return maxLen
}
func (proj *Project) currentTask() *Task {
if proj.Index < 0 {
return nil
}
return &proj.Tasks[proj.Index]
}
func (proj *Project) CloseSelectedTask() {
currTask := proj.currentTask()
if currTask != nil {
_ = proj.backend.CloseTask(currTask)
proj.loadTasks()
}
}
func (proj *Project) DeleteSelectedTask() {
currTask := proj.currentTask()
if currTask != nil {
_ = proj.backend.DeleteTask(currTask)
proj.loadTasks()
}
}

View File

@ -0,0 +1,108 @@
package backend
import (
"strconv"
"github.com/olebedev/config"
"github.com/wtfutil/todoist"
)
type Todoist struct {
projects []interface{}
}
func (todo *Todoist) Title() string {
return "Todoist"
}
func (todo *Todoist) Setup(config *config.Config) {
todoist.Token = config.UString("apiKey")
todo.projects = config.UList("projects")
}
func (todo *Todoist) BuildProjects() []*Project {
projects := []*Project{}
for _, id := range todo.projects {
i := strconv.Itoa(id.(int))
proj := todo.GetProject(i)
projects = append(projects, proj)
}
return projects
}
func (todo *Todoist) GetProject(id string) *Project {
// Todoist seems to experience a lot of network issues on their side
// If we can't connect, handle it with an empty project until we can
proj := &Project{
Index: -1,
backend: todo,
}
i64, _ := strconv.ParseUint(id, 10, 32)
i := uint(i64)
project, err := todoist.GetProject(i)
if err != nil {
proj.Err = err
return proj
}
proj.ID = strconv.FormatUint(uint64(project.ID), 10)
proj.Name = project.Name
tasks, err := todo.LoadTasks(proj.ID)
proj.Err = err
proj.Tasks = tasks
return proj
}
func toTask(task todoist.Task) Task {
id := strconv.FormatUint(uint64(task.ID), 10)
return Task{
ID: id,
Completed: task.Completed,
Name: task.Content,
}
}
func (todo *Todoist) LoadTasks(id string) ([]Task, error) {
tasks, err := todoist.ListTask(todoist.QueryParam{"project_id": id})
if err != nil {
return nil, err
}
var finalTasks []Task
for _, item := range tasks {
finalTasks = append(finalTasks, toTask(item))
}
return finalTasks, nil
}
func (todo *Todoist) CloseTask(task *Task) error {
if task != nil {
i64, _ := strconv.ParseUint(task.ID, 10, 32)
i := uint(i64)
internal := todoist.Task{ID: i}
return internal.Close()
}
return nil
}
func (todo *Todoist) DeleteTask(task *Task) error {
if task != nil {
i64, _ := strconv.ParseUint(task.ID, 10, 32)
i := uint(i64)
internal := todoist.Task{ID: i}
return internal.Delete()
}
return nil
}
func (todo *Todoist) Sources() []string {
var result []string
for _, id := range todo.projects {
i := strconv.Itoa(id.(int))
result = append(result, i)
}
return result
}

View File

@ -0,0 +1,170 @@
package backend
import (
"fmt"
"log"
"github.com/adlio/trello"
"github.com/olebedev/config"
)
type Trello struct {
username string
boardName string
client *trello.Client
board string
projects []interface{}
}
func (todo *Trello) Title() string {
return "Trello"
}
func (todo *Trello) Setup(config *config.Config) {
todo.username = config.UString("username")
todo.boardName = config.UString("board")
todo.client = trello.NewClient(
config.UString("apiKey"),
config.UString("accessToken"),
)
board, err := getBoardID(todo.client, todo.username, todo.boardName)
if err != nil {
log.Fatal(err)
}
todo.board = board
todo.projects = config.UList("lists")
}
func getBoardID(client *trello.Client, username, boardName string) (string, error) {
member, err := client.GetMember(username, trello.Defaults())
if err != nil {
return "", err
}
boards, err := member.GetBoards(trello.Defaults())
if err != nil {
return "", err
}
for _, board := range boards {
if board.Name == boardName {
return board.ID, nil
}
}
return "", fmt.Errorf("could not find board with name %s", boardName)
}
func getListId(client *trello.Client, boardID string, listName string) (string, error) {
board, err := client.GetBoard(boardID, trello.Defaults())
if err != nil {
return "", err
}
boardLists, err := board.GetLists(trello.Defaults())
if err != nil {
return "", err
}
for _, list := range boardLists {
if list.Name == listName {
return list.ID, nil
}
}
return "", nil
}
func getCardsOnList(client *trello.Client, listID string) ([]*trello.Card, error) {
list, err := client.GetList(listID, trello.Defaults())
if err != nil {
return nil, err
}
cards, err := list.GetCards(trello.Defaults())
if err != nil {
return nil, err
}
return cards, nil
}
func (todo *Trello) BuildProjects() []*Project {
projects := []*Project{}
for _, id := range todo.projects {
proj := todo.GetProject(id.(string))
projects = append(projects, proj)
}
return projects
}
func (todo *Trello) GetProject(id string) *Project {
proj := &Project{
Index: -1,
backend: todo,
}
listId, err := getListId(todo.client, todo.board, id)
if err != nil {
proj.Err = err
return proj
}
proj.ID = listId
proj.Name = id
tasks, err := todo.LoadTasks(listId)
proj.Err = err
proj.Tasks = tasks
return proj
}
func fromTrello(task *trello.Card) Task {
return Task{
ID: task.ID,
Completed: task.Closed,
Name: task.Name,
}
}
func (todo *Trello) LoadTasks(id string) ([]Task, error) {
tasks, err := getCardsOnList(todo.client, id)
if err != nil {
return nil, err
}
var finalTasks []Task
for _, item := range tasks {
finalTasks = append(finalTasks, fromTrello(item))
}
return finalTasks, nil
}
func (todo *Trello) CloseTask(task *Task) error {
args := trello.Arguments{
"closed": "true",
}
if task != nil {
// Card has an internal client rep which we can't access
// Just force a lookup
internal, err := todo.client.GetCard(task.ID, trello.Arguments{})
if err != nil {
return err
}
return internal.Update(args)
}
return nil
}
func (todo *Trello) DeleteTask(task *Task) error {
return nil
}
func (todo *Trello) Sources() []string {
var result []string
for _, id := range todo.projects {
result = append(result, id.(string))
}
return result
}

View File

@ -1,4 +1,4 @@
package todoist package todo_plus
import ( import (
"fmt" "fmt"
@ -14,27 +14,26 @@ func (widget *Widget) content() (string, string, bool) {
return widget.CommonSettings().Title, "", false return widget.CommonSettings().Title, "", false
} }
if proj.err != nil { if proj.Err != nil {
return widget.CommonSettings().Title, proj.err.Error(), true return widget.CommonSettings().Title, proj.Err.Error(), true
} }
title := fmt.Sprintf( title := fmt.Sprintf(
"[%s]%s[white]", "[%s]%s[white]",
widget.settings.common.Colors.TextTheme.Title, widget.settings.common.Colors.TextTheme.Title,
proj.Project.Name, proj.Name)
)
str := "" str := ""
for idx, item := range proj.tasks { for idx, item := range proj.Tasks {
row := fmt.Sprintf( row := fmt.Sprintf(
`[%s]| | %s[%s]`, `[%s]| | %s[%s]`,
widget.RowColor(idx), widget.RowColor(idx),
tview.Escape(item.Content), tview.Escape(item.Name),
widget.RowColor(idx), widget.RowColor(idx),
) )
str += utils.HighlightableHelper(widget.View, row, idx, len(item.Content)) str += utils.HighlightableHelper(widget.View, row, idx, len(item.Name))
} }
return title, str, false return title, str, false
} }

View File

@ -1,21 +1,21 @@
package todoist package todo_plus
import "github.com/gdamore/tcell" import "github.com/gdamore/tcell"
func (widget *Widget) initializeKeyboardControls() { func (widget *Widget) initializeKeyboardControls() {
widget.InitializeCommonControls(widget.Refresh) widget.InitializeCommonControls(widget.Refresh)
widget.SetKeyboardChar("c", widget.Close, "Close item")
widget.SetKeyboardChar("d", widget.Delete, "Delete item") widget.SetKeyboardChar("d", widget.Delete, "Delete item")
widget.SetKeyboardChar("h", widget.PrevSource, "Select previous project")
widget.SetKeyboardChar("j", widget.Prev, "Select previous item") widget.SetKeyboardChar("j", widget.Prev, "Select previous item")
widget.SetKeyboardChar("k", widget.Next, "Select next item") widget.SetKeyboardChar("k", widget.Next, "Select next item")
widget.SetKeyboardChar("h", widget.PrevSource, "Select previous project")
widget.SetKeyboardChar("c", widget.Close, "Close item")
widget.SetKeyboardChar("l", widget.NextSource, "Select next project") widget.SetKeyboardChar("l", widget.NextSource, "Select next project")
widget.SetKeyboardChar("u", widget.Unselect, "Clear selection") widget.SetKeyboardChar("u", widget.Unselect, "Clear selection")
widget.SetKeyboardKey(tcell.KeyDown, widget.Next, "Select next item") widget.SetKeyboardKey(tcell.KeyDown, widget.Next, "Select next item")
widget.SetKeyboardKey(tcell.KeyUp, widget.Prev, "Select previous item")
widget.SetKeyboardKey(tcell.KeyEsc, widget.Unselect, "Clear selection") widget.SetKeyboardKey(tcell.KeyEsc, widget.Unselect, "Clear selection")
widget.SetKeyboardKey(tcell.KeyLeft, widget.PrevSource, "Select previous project") widget.SetKeyboardKey(tcell.KeyLeft, widget.PrevSource, "Select previous project")
widget.SetKeyboardKey(tcell.KeyRight, widget.NextSource, "Select next project") widget.SetKeyboardKey(tcell.KeyRight, widget.NextSource, "Select next project")
widget.SetKeyboardKey(tcell.KeyUp, widget.Prev, "Select previous item")
} }

View File

@ -0,0 +1,81 @@
package todo_plus
import (
"os"
"github.com/olebedev/config"
"github.com/wtfutil/wtf/cfg"
)
const (
defaultTitle = "Todo"
defaultFocusable = true
)
type Settings struct {
common *cfg.Common
backendType string
backendSettings *config.Config
}
func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings {
backend, _ := ymlConfig.Get("backendSettings")
settings := Settings{
common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig),
backendType: ymlConfig.UString("backendType"),
backendSettings: backend,
}
return &settings
}
func FromTodoist(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings {
apiKey := ymlConfig.UString("apiKey", ymlConfig.UString("apikey", os.Getenv("WTF_TODOIST_TOKEN")))
cfg.ModuleSecret(name, globalConfig, &apiKey).Load()
projects := ymlConfig.UList("projects")
backend, _ := config.ParseYaml("apiKey: " + apiKey)
_ = backend.Set(".projects", projects)
settings := Settings{
common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig),
backendType: "todoist",
backendSettings: backend,
}
return &settings
}
func FromTrello(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings {
accessToken := ymlConfig.UString("accessToken", ymlConfig.UString("apikey", os.Getenv("WTF_TRELLO_ACCESS_TOKEN")))
apiKey := ymlConfig.UString("apiKey", os.Getenv("WTF_TRELLO_API_KEY"))
cfg.ModuleSecret(name, globalConfig, &apiKey).Load()
board := ymlConfig.UString("board")
username := ymlConfig.UString("username")
var lists []interface{}
list, err := ymlConfig.String("list")
if err == nil {
lists = append(lists, list)
} else {
lists = ymlConfig.UList("list")
}
backend, _ := config.ParseYaml("apiKey: " + apiKey)
_ = backend.Set(".accessToken", accessToken)
_ = backend.Set(".board", board)
_ = backend.Set(".username", username)
_ = backend.Set(".lists", lists)
settings := Settings{
common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig),
backendType: "trello",
backendSettings: backend,
}
return &settings
}

View File

@ -1,8 +1,10 @@
package todoist package todo_plus
import ( import (
"log"
"github.com/rivo/tview" "github.com/rivo/tview"
"github.com/wtfutil/todoist" "github.com/wtfutil/wtf/modules/todo_plus/backend"
"github.com/wtfutil/wtf/view" "github.com/wtfutil/wtf/view"
) )
@ -12,8 +14,9 @@ type Widget struct {
view.MultiSourceWidget view.MultiSourceWidget
view.ScrollableWidget view.ScrollableWidget
projects []*Project projects []*backend.Project
settings *Settings settings *Settings
backend backend.Backend
} }
// NewWidget creates a new instance of a widget // NewWidget creates a new instance of a widget
@ -26,8 +29,9 @@ func NewWidget(app *tview.Application, pages *tview.Pages, settings *Settings) *
settings: settings, settings: settings,
} }
widget.loadAPICredentials() widget.backend = getBackend(settings.backendType)
widget.loadProjects() widget.backend.Setup(settings.backendSettings)
widget.CommonSettings().Title = widget.backend.Title()
widget.SetRenderFunction(widget.display) widget.SetRenderFunction(widget.display)
widget.initializeKeyboardControls() widget.initializeKeyboardControls()
@ -39,13 +43,28 @@ func NewWidget(app *tview.Application, pages *tview.Pages, settings *Settings) *
return &widget return &widget
} }
func getBackend(backendType string) backend.Backend {
switch backendType {
case "trello":
backend := &backend.Trello{}
return backend
case "todoist":
backend := &backend.Todoist{}
return backend
default:
log.Fatal(backendType + " is not a supported backend")
return nil
}
}
/* -------------------- Exported Functions -------------------- */ /* -------------------- Exported Functions -------------------- */
func (widget *Widget) CurrentProject() *Project { func (widget *Widget) CurrentProject() *backend.Project {
return widget.ProjectAt(widget.Idx) return widget.ProjectAt(widget.Idx)
} }
func (widget *Widget) ProjectAt(idx int) *Project { func (widget *Widget) ProjectAt(idx int) *backend.Project {
if len(widget.projects) == 0 { if len(widget.projects) == 0 {
return nil return nil
} }
@ -54,14 +73,13 @@ func (widget *Widget) ProjectAt(idx int) *Project {
} }
func (widget *Widget) Refresh() { func (widget *Widget) Refresh() {
if widget.Disabled() || widget.CurrentProject() == nil { if widget.Disabled() {
widget.SetItemCount(0)
return return
} }
widget.loadProjects() widget.projects = widget.backend.BuildProjects()
widget.Sources = widget.backend.Sources()
widget.SetItemCount(len(widget.CurrentProject().tasks)) widget.SetItemCount(len(widget.CurrentProject().Tasks))
widget.display() widget.display()
} }
@ -71,74 +89,57 @@ func (widget *Widget) HelpText() string {
func (widget *Widget) NextSource() { func (widget *Widget) NextSource() {
widget.MultiSourceWidget.NextSource() widget.MultiSourceWidget.NextSource()
widget.Selected = widget.CurrentProject().index widget.Selected = widget.CurrentProject().Index
widget.SetItemCount(len(widget.CurrentProject().tasks)) widget.SetItemCount(len(widget.CurrentProject().Tasks))
widget.RenderFunction() widget.RenderFunction()
} }
func (widget *Widget) PrevSource() { func (widget *Widget) PrevSource() {
widget.MultiSourceWidget.PrevSource() widget.MultiSourceWidget.PrevSource()
widget.Selected = widget.CurrentProject().index widget.Selected = widget.CurrentProject().Index
widget.SetItemCount(len(widget.CurrentProject().tasks)) widget.SetItemCount(len(widget.CurrentProject().Tasks))
widget.RenderFunction() widget.RenderFunction()
} }
func (widget *Widget) Prev() { func (widget *Widget) Prev() {
widget.ScrollableWidget.Prev() widget.ScrollableWidget.Prev()
widget.CurrentProject().index = widget.Selected widget.CurrentProject().Index = widget.Selected
} }
func (widget *Widget) Next() { func (widget *Widget) Next() {
widget.ScrollableWidget.Next() widget.ScrollableWidget.Next()
widget.CurrentProject().index = widget.Selected widget.CurrentProject().Index = widget.Selected
} }
func (widget *Widget) Unselect() { func (widget *Widget) Unselect() {
widget.ScrollableWidget.Unselect() widget.ScrollableWidget.Unselect()
widget.CurrentProject().index = -1 widget.CurrentProject().Index = -1
widget.RenderFunction() widget.RenderFunction()
} }
/* -------------------- Keyboard Movement -------------------- */ /* -------------------- Keyboard Movement -------------------- */
// Close closes the currently-selected task in the currently-selected project // Close closes the currently-selected task in the currently-selected project
func (widget *Widget) Close() { func (w *Widget) Close() {
widget.CurrentProject().closeSelectedTask() w.CurrentProject().CloseSelectedTask()
widget.SetItemCount(len(widget.CurrentProject().tasks)) w.SetItemCount(len(w.CurrentProject().Tasks))
if widget.CurrentProject().isLast() { if w.CurrentProject().IsLast() {
widget.Prev() w.Prev()
return return
} }
widget.CurrentProject().index = widget.Selected w.CurrentProject().Index = w.Selected
widget.RenderFunction() w.RenderFunction()
} }
// Delete deletes the currently-selected task in the currently-selected project // Delete deletes the currently-selected task in the currently-selected project
func (widget *Widget) Delete() { func (w *Widget) Delete() {
widget.CurrentProject().deleteSelectedTask() w.CurrentProject().DeleteSelectedTask()
widget.SetItemCount(len(widget.CurrentProject().tasks)) w.SetItemCount(len(w.CurrentProject().Tasks))
if widget.CurrentProject().isLast() { if w.CurrentProject().IsLast() {
widget.Prev() w.Prev()
} }
widget.CurrentProject().index = widget.Selected w.CurrentProject().Index = w.Selected
widget.RenderFunction() w.RenderFunction()
}
/* -------------------- Unexported Functions -------------------- */
func (widget *Widget) loadAPICredentials() {
todoist.Token = widget.settings.apiKey
}
func (widget *Widget) loadProjects() {
projects := []*Project{}
for _, id := range widget.settings.projects {
proj := NewProject(id)
projects = append(projects, proj)
}
widget.projects = projects
} }

View File

@ -1,93 +0,0 @@
package todoist
import (
"fmt"
"github.com/wtfutil/todoist"
)
type Project struct {
todoist.Project
index int
tasks []todoist.Task
err error
}
func NewProject(id uint) *Project {
// Todoist seems to experience a lot of network issues on their side
// If we can't connect, handle it with an empty project until we can
project, err := todoist.GetProject(id)
proj := &Project{
index: -1,
}
if err != nil {
proj.err = err
return proj
}
proj.Project = project
proj.loadTasks()
return proj
}
func (proj *Project) isLast() bool {
return proj.index >= len(proj.tasks)-1
}
func (proj *Project) loadTasks() {
tasks, err := todoist.ListTask(todoist.QueryParam{"project_id": fmt.Sprintf("%d", proj.ID)})
if err != nil {
proj.err = err
proj.tasks = nil
} else {
proj.err = nil
proj.tasks = tasks
}
}
func (proj *Project) LongestLine() int {
maxLen := 0
for _, task := range proj.tasks {
if len(task.Content) > maxLen {
maxLen = len(task.Content)
}
}
return maxLen
}
func (proj *Project) currentTask() *todoist.Task {
if proj.index < 0 {
return nil
}
return &proj.tasks[proj.index]
}
func (proj *Project) closeSelectedTask() {
currTask := proj.currentTask()
if currTask != nil {
if err := currTask.Close(); err != nil {
return
}
proj.loadTasks()
}
}
func (proj *Project) deleteSelectedTask() {
currTask := proj.currentTask()
if currTask != nil {
if err := currTask.Delete(); err != nil {
return
}
proj.loadTasks()
}
}

View File

@ -1,36 +0,0 @@
package todoist
import (
"os"
"github.com/olebedev/config"
"github.com/wtfutil/wtf/cfg"
"github.com/wtfutil/wtf/utils"
)
const (
defaultFocusable = true
defaultTitle = "Todoist"
)
// Settings defines the configuration properties for this module
type Settings struct {
common *cfg.Common
apiKey string `help:"Your Todoist API key"`
projects []uint
}
// NewSettingsFromYAML creates a new settings instance from a YAML config block
func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings {
settings := Settings{
common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig),
apiKey: ymlConfig.UString("apiKey", ymlConfig.UString("apikey", os.Getenv("WTF_TODOIST_TOKEN"))),
projects: utils.IntsToUints(utils.ToInts(ymlConfig.UList("projects"))),
}
cfg.ModuleSecret(name, globalConfig, &settings.apiKey).Load()
return &settings
}

View File

@ -1,13 +0,0 @@
package trello
type TrelloCard struct {
ID string
Name string
List string
Description string
}
type TrelloList struct {
ID string
Name string
}

View File

@ -1,106 +0,0 @@
package trello
import (
"fmt"
"github.com/adlio/trello"
)
func GetCards(client *trello.Client, username string, boardName string, listNames []string) (*SearchResult, error) {
boardID, err := getBoardID(client, username, boardName)
if err != nil {
return nil, err
}
lists, err := getLists(client, boardID, listNames)
if err != nil {
return nil, err
}
searchResult := &SearchResult{Total: 0}
searchResult.TrelloCards = make(map[string][]TrelloCard)
for _, list := range lists {
cards, err := getCardsOnList(client, list.ID)
if err != nil {
return nil, err
}
searchResult.Total += len(cards)
cardArray := make([]TrelloCard, 0)
for _, card := range cards {
trelloCard := TrelloCard{
ID: card.ID,
List: list.Name,
Name: card.Name,
Description: card.Desc,
}
cardArray = append(cardArray, trelloCard)
}
searchResult.TrelloCards[list.Name] = cardArray
}
return searchResult, nil
}
func getBoardID(client *trello.Client, username, boardName string) (string, error) {
member, err := client.GetMember(username, trello.Defaults())
if err != nil {
return "", err
}
boards, err := member.GetBoards(trello.Defaults())
if err != nil {
return "", err
}
for _, board := range boards {
if board.Name == boardName {
return board.ID, nil
}
}
return "", fmt.Errorf("could not find board with name %s", boardName)
}
func getLists(client *trello.Client, boardID string, listNames []string) ([]TrelloList, error) {
comparison := make(map[string]string, len(listNames))
results := []TrelloList{}
//convert to a map for comparison
for _, item := range listNames {
comparison[item] = ""
}
board, err := client.GetBoard(boardID, trello.Defaults())
if err != nil {
return nil, err
}
boardLists, err := board.GetLists(trello.Defaults())
if err != nil {
return nil, err
}
for _, list := range boardLists {
if _, ok := comparison[list.Name]; ok {
results = append(results, TrelloList{ID: list.ID, Name: list.Name})
}
}
return results, nil
}
func getCardsOnList(client *trello.Client, listID string) ([]*trello.Card, error) {
list, err := client.GetList(listID, trello.Defaults())
if err != nil {
return nil, err
}
cards, err := list.GetCards(trello.Defaults())
if err != nil {
return nil, err
}
return cards, nil
}

View File

@ -1,6 +0,0 @@
package trello
type SearchResult struct {
Total int
TrelloCards map[string][]TrelloCard
}

View File

@ -1,62 +0,0 @@
package trello
import (
"os"
"github.com/olebedev/config"
"github.com/wtfutil/wtf/cfg"
)
const (
defaultFocusable = false
defaultTitle = "Trello"
)
type Settings struct {
common *cfg.Common
accessToken string
apiKey string
board string
list []string
username string
}
func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings {
settings := Settings{
common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig),
accessToken: ymlConfig.UString("accessToken", ymlConfig.UString("apikey", os.Getenv("WTF_TRELLO_ACCESS_TOKEN"))),
apiKey: ymlConfig.UString("apiKey", os.Getenv("WTF_TRELLO_API_KEY")),
board: ymlConfig.UString("board"),
username: ymlConfig.UString("username"),
}
cfg.ModuleSecret(name+"-api", globalConfig, &settings.apiKey).Load()
cfg.ModuleSecret(name+"-access", globalConfig, &settings.accessToken).Load()
settings.list = buildLists(ymlConfig, globalConfig)
return &settings
}
func buildLists(ymlConfig *config.Config, globalConfig *config.Config) []string {
lists := []string{}
// Single list
list, err := ymlConfig.String("list")
if err == nil {
lists = append(lists, list)
return lists
}
// Array of lists
listList := ymlConfig.UList("list")
for _, listName := range listList {
if list, ok := listName.(string); ok {
lists = append(lists, list)
}
}
return lists
}

View File

@ -1,75 +0,0 @@
package trello
import (
"fmt"
"github.com/adlio/trello"
"github.com/rivo/tview"
"github.com/wtfutil/wtf/view"
)
type Widget struct {
view.TextWidget
settings *Settings
}
func NewWidget(app *tview.Application, settings *Settings) *Widget {
widget := Widget{
TextWidget: view.NewTextWidget(app, settings.common),
settings: settings,
}
return &widget
}
/* -------------------- Exported Functions -------------------- */
func (widget *Widget) Refresh() {
widget.Redraw(widget.content)
}
/* -------------------- Unexported Functions -------------------- */
func (widget *Widget) content() (string, string, bool) {
client := trello.NewClient(
widget.settings.apiKey,
widget.settings.accessToken,
)
// Get the cards
searchResult, err := GetCards(
client,
widget.settings.username,
widget.settings.board,
widget.settings.list,
)
var title string
content := ""
wrap := false
if err != nil {
wrap = true
title = widget.CommonSettings().Title
content = err.Error()
} else {
title = fmt.Sprintf(
"[white]%s: [green]%s ",
widget.CommonSettings().Title,
widget.settings.board,
)
for list, cardArray := range searchResult.TrelloCards {
content += fmt.Sprintf(" [%s]%s[white]\n", widget.settings.common.Colors.Subheading, list)
for _, card := range cardArray {
content += fmt.Sprintf(" %s[white]\n", card.Name)
}
content = fmt.Sprintf("%s\n", content)
}
}
return title, content, wrap
}

62
view/info_table_test.go Normal file
View File

@ -0,0 +1,62 @@
package view
import (
"testing"
"github.com/stretchr/testify/assert"
)
func makeMap() map[string]string {
m := make(map[string]string)
m["foo"] = "val1"
m["bar"] = "val2"
m["baz"] = "val3"
return m
}
func newTestTable(height, colWidth0, colWidth1 int) *InfoTable {
var headers [2]string
headers[0] = "hdr0"
headers[1] = "hdr1"
table := NewInfoTable(
headers[:],
makeMap(),
colWidth0,
colWidth1,
height,
)
return table
}
func Test_RenderSimpleInfoTable(t *testing.T) {
table := newTestTable(4, 1, 1).Render()
assert.Equal(t, " ----- ------ \n HDR0 HDR1 \n ----- ------ \n bar val2 \n baz val3 \n foo val1 \n ----- ------ \n", table)
}
func Test_RenderPaddedInfoTable(t *testing.T) {
table := newTestTable(6, 1, 1).Render()
assert.Equal(t, " ----- ------ \n HDR0 HDR1 \n ----- ------ \n bar val2 \n baz val3 \n foo val1 \n \n \n ----- ------ \n", table)
}
func Test_RenderWithSpecifiedWidthLeftColumn(t *testing.T) {
table := newTestTable(4, 10, 1).Render()
assert.Equal(t, " ------------ ------ \n HDR0 HDR1 \n ------------ ------ \n bar val2 \n baz val3 \n foo val1 \n ------------ ------ \n", table)
}
func Test_RenderWithSpecifiedWidthRightColumn(t *testing.T) {
table := newTestTable(4, 1, 10).Render()
assert.Equal(t, " ----- ------------ \n HDR0 HDR1 \n ----- ------------ \n bar val2 \n baz val3 \n foo val1 \n ----- ------------ \n", table)
}
func Test_RenderWithSpecifiedWidthBothColumns(t *testing.T) {
table := newTestTable(4, 15, 10).Render()
assert.Equal(t, " ----------------- ------------ \n HDR0 HDR1 \n ----------------- ------------ \n bar val2 \n baz val3 \n foo val1 \n ----------------- ------------ \n", table)
}

View File

@ -38,7 +38,9 @@ func (widget *TextWidget) Redraw(data func() (string, string, bool)) {
widget.View.Clear() widget.View.Clear()
widget.View.SetWrap(wrap) widget.View.SetWrap(wrap)
widget.View.SetTitle(widget.ContextualTitle(title)) widget.View.SetTitle(widget.ContextualTitle(title))
widget.View.SetText(strings.TrimSpace(content)) // widget.View.SetText(strings.TrimSpace(content))
widget.View.SetText(strings.TrimRight(content, "\n"))
// widget.View.SetText(content)
}) })
} }