diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..6dc949a --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,32 @@ +name: build +on: + push: + tags: + - v* + branches: + - main + pull_request: + +jobs: + buildandtest: + name: Build and test + strategy: + matrix: + go-version: [~1.21, ^1] + os: [ubuntu-latest] + runs-on: ${{ matrix.os }} + steps: + - name: Install Go + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go-version }} + + - name: Checkout code + uses: actions/checkout@v3 + + - name: fmt + run: make fmt + + - name: Build + run: make build + diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml new file mode 100644 index 0000000..55247ec --- /dev/null +++ b/.github/workflows/lint.yaml @@ -0,0 +1,26 @@ +name: golangci-lint +on: + push: + tags: + - v* + branches: + - main + pull_request: + +permissions: + contents: read + +jobs: + golangci: + name: lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v4 + with: + go-version: '1.21' + cache: false + - name: golangci-lint + uses: golangci/golangci-lint-action@v3 + with: + version: v1.55.2 diff --git a/Makefile b/Makefile index 006a9f0..6eb1549 100644 --- a/Makefile +++ b/Makefile @@ -15,3 +15,18 @@ clean: fi; \ done +.PHONY: run +run: + @go run ./main.go + +.PHONY: lint +lint: + @golangci-lint run + +.PHONY: fmt +fmt: + @go fmt ./... + +.PHONY: build +build: + @go build -v . diff --git a/api/api.go b/api/api.go index 060cf34..ca58200 100644 --- a/api/api.go +++ b/api/api.go @@ -343,6 +343,7 @@ func (m *Api) GetViewsFromSpace(spaceId string) ([]clickup.View, error) { return views, nil } +//nolint:unused func (m *Api) getFromCache(namespace string, key string, v interface{}) (bool, error) { data, ok := m.Cache.Get(namespace, key) if !ok { diff --git a/go.mod b/go.mod index 30b67a6..dcedcf4 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/charmbracelet/glamour v0.6.0 github.com/charmbracelet/lipgloss v0.9.1 github.com/charmbracelet/log v0.3.0 + golang.org/x/term v0.7.0 ) require ( @@ -24,7 +25,6 @@ require ( github.com/yuin/goldmark-emoji v1.0.1 // indirect golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect golang.org/x/net v0.0.0-20221002022538-bcab6841153b // indirect - golang.org/x/term v0.7.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/main.go b/main.go index 35197b7..818bfcd 100644 --- a/main.go +++ b/main.go @@ -30,7 +30,7 @@ func main() { logger.Info("Initializing config...") var cfg config.Config - fig.Load(&cfg, + if err := fig.Load(&cfg, fig.File("config.yaml"), fig.Dirs( ".", @@ -38,11 +38,15 @@ func main() { "/home/user/myapp", "$HOME/.config/clickup-tui", ), - ) + ); err != nil { + logger.Fatal(err) + } logger.Info("Initializing cache...") cache := cache.NewCache(slog.New(logger.WithPrefix("Cache"))) - defer cache.Dump() + defer func() { + _ = cache.Dump() + }() if err := cache.Load(); err != nil { logger.Fatal(err) diff --git a/pkg/clickup/folder.go b/pkg/clickup/folder.go index 859b67f..77f26af 100644 --- a/pkg/clickup/folder.go +++ b/pkg/clickup/folder.go @@ -34,4 +34,3 @@ func (c *Client) GetFolders(spaceId string) ([]Folder, error) { } return objmap.Folders, nil } - diff --git a/pkg/clickup/space.go b/pkg/clickup/space.go index a43fbc6..e9af324 100644 --- a/pkg/clickup/space.go +++ b/pkg/clickup/space.go @@ -23,7 +23,7 @@ type SpaceStatus struct { type RequestGetSpaces struct { Spaces []Space `json:"spaces"` - Err string `json:"err"` + Err string `json:"err"` } func (c *Client) GetSpaces(teamId string) ([]Space, error) { @@ -42,5 +42,3 @@ func (c *Client) GetSpaces(teamId string) ([]Space, error) { } return objmap.Spaces, nil } - - diff --git a/pkg/clickup/view.go b/pkg/clickup/view.go index d30962a..e4c46e8 100644 --- a/pkg/clickup/view.go +++ b/pkg/clickup/view.go @@ -133,12 +133,13 @@ func filterListViews(views []View) []View { } func (c *Client) GetViewsFromSpace(spaceId string) ([]View, error) { - errMsg := "Error occurs while getting views from space: %s. Error: %s" + errMsg := "Error occurs while getting views from space: %s. Error: %s. Raw data: %s" + errApiMsg := errMsg + " API response: %s" rawData, err := c.requestGet("/space/" + spaceId + "/view") if err != nil { - return nil, fmt.Errorf(errMsg, spaceId, err) + return nil, fmt.Errorf(errMsg, spaceId, err, "none") } var objmap RequestGetViews @@ -161,7 +162,8 @@ func (c *Client) GetViewsFromSpace(spaceId string) ([]View, error) { } if len(allViews) == 0 { - c.logger.Error("No views found in space: %s", spaceId) + c.logger.Error("No views found in space", + "space", spaceId) return []View{}, nil } @@ -176,31 +178,32 @@ func (c *Client) GetViewsFromFolder(folderId string) ([]View, error) { rawData, err := c.requestGet("/folder/" + folderId + "/view") if err != nil { - return nil, fmt.Errorf(errMsg, folderId, err) + return nil, fmt.Errorf(errMsg, + folderId, err) } var objmap RequestGetViews if err := json.Unmarshal(rawData, &objmap); err != nil { - return nil, fmt.Errorf( - errApiMsg, folderId, err, string(rawData)) + return nil, fmt.Errorf(errApiMsg, + folderId, err, string(rawData)) } if objmap.Err != "" { - return nil, fmt.Errorf( - errMsg, folderId, "API response contains error.", string(rawData)) + return nil, fmt.Errorf(errApiMsg, + folderId, objmap.Err, string(rawData)) } allViews := append(objmap.Views, objmap.RequiredViews.GetViews()...) for _, v := range allViews { if v.Id == "" || v.Name == "" { - return nil, fmt.Errorf( - "View id or name is empty, API response: %s", string(rawData)) + return nil, fmt.Errorf(errApiMsg, + folderId, "View id or name is empty", string(rawData)) } - } + if len(allViews) == 0 { - c.logger.Error("No views found in folder: %s", folderId) - return []View{}, nil + return []View{}, fmt.Errorf(errMsg, + folderId, "No views found in folder") } filteredViews := filterListViews(allViews) @@ -214,31 +217,34 @@ func (c *Client) GetViewsFromList(listId string) ([]View, error) { rawData, err := c.requestGet("/list/" + listId + "/view") if err != nil { - return nil, fmt.Errorf(errMsg, listId, err) + return nil, fmt.Errorf(errMsg, + listId, err) } var objmap RequestGetViews if err := json.Unmarshal(rawData, &objmap); err != nil { - return nil, fmt.Errorf( - errApiMsg, listId, err, string(rawData)) + return nil, fmt.Errorf(errApiMsg, + listId, err, string(rawData)) } if objmap.Err != "" { - return nil, fmt.Errorf( - errMsg, listId, "API response contains error.", string(rawData)) + return nil, fmt.Errorf(errApiMsg, + listId, objmap.Err, string(rawData)) } allViews := append(objmap.Views, objmap.RequiredViews.GetViews()...) for _, v := range allViews { if v.Id == "" || v.Name == "" { - return nil, fmt.Errorf( - "View id or name is empty, API response: %s", string(rawData)) + return nil, fmt.Errorf(errApiMsg, + listId, "View id or name is empty", string(rawData)) } } + if len(allViews) == 0 { return []View{}, nil } + filteredViews := filterListViews(allViews) return filteredViews, nil diff --git a/ui/ui.go b/ui/ui.go index 70f5d00..7697c0f 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -109,6 +109,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { cmds = append(cmds, cmd) return m, tea.Batch(cmds...) + } case tea.WindowSizeMsg: diff --git a/ui/widgets/help/widget.go b/ui/widgets/help/widget.go index be54661..9d60064 100644 --- a/ui/widgets/help/widget.go +++ b/ui/widgets/help/widget.go @@ -17,14 +17,12 @@ const WidgetId = "widgetHelp" type Model struct { WidgetId common.WidgetId - - lastKey string - ctx *context.UserContext - log *log.Logger - help help.Model ShowHelp bool - // keys keyMap + lastKey string + ctx *context.UserContext + log *log.Logger + help help.Model inputStyle lipgloss.Style } @@ -79,42 +77,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { return m, tea.Batch(cmds...) } -const ( - // In real life situations we'd adjust the document to fit the width we've - // detected. In the case of this example we're hardcoding the width, and - // later using the detected width only to truncate in order to avoid jaggy - // wrapping. - width = 96 - - columnWidth = 30 -) - func (m Model) View(keyMap help.KeyMap) string { - - // doc := strings.Builder{} - // { - // okButton := activeButtonStyle.Render("Yes") - // cancelButton := buttonStyle.Render("Maybe") - - // question := lipgloss.NewStyle().Width(50).Align(lipgloss.Center).Render("Are you sure you want to eat marmalade?") - // buttons := lipgloss.JoinHorizontal(lipgloss.Top, okButton, cancelButton) - // ui := lipgloss.JoinVertical(lipgloss.Center, question, buttons) - - // dialog := lipgloss.Place(width, 9, - // lipgloss.Center, lipgloss.Center, - // dialogBoxStyle.Render(ui), - // lipgloss.WithWhitespaceChars("猫咪"), - // lipgloss.WithWhitespaceForeground(subtle), - // ) - - // doc.WriteString(dialog + "\n\n") - // } - - // if physicalWidth > 0 { - // docStyle = docStyle.MaxWidth(physicalWidth) - // } - // return docStyle.Render(doc.String()) - var status string if m.lastKey == "" { status = "Waiting for input..." @@ -140,56 +103,3 @@ func (m Model) Init() tea.Cmd { m.log.Info("Initializing...") return nil } - -// keyMap defines a set of keybindings. To work for help it must satisfy -// key.Map. It could also very easily be a map[string]key.Binding. -// type keyMap struct { -// Up key.Binding -// Down key.Binding -// Left key.Binding -// Right key.Binding -// Help key.Binding -// Quit key.Binding -// } - -// ShortHelp returns keybindings to be shown in the mini help view. It's part -// of the key.Map interface. -// func (k keyMap) ShortHelp() []key.Binding { -// return []key.Binding{k.Help, k.Quit} -// } - -// // FullHelp returns keybindings for the expanded help view. It's part of the -// // key.Map interface. -// func (k keyMap) FullHelp() [][]key.Binding { -// return [][]key.Binding{ -// {k.Up, k.Down, k.Left, k.Right}, // first column -// {k.Help, k.Quit}, // second column -// } -// } - -// var keys = keyMap{ -// Up: key.NewBinding( -// key.WithKeys("up", "k"), -// key.WithHelp("↑/k", "move up"), -// ), -// Down: key.NewBinding( -// key.WithKeys("down", "j"), -// key.WithHelp("↓/j", "move down"), -// ), -// Left: key.NewBinding( -// key.WithKeys("left", "h"), -// key.WithHelp("←/h", "move left"), -// ), -// Right: key.NewBinding( -// key.WithKeys("right", "l"), -// key.WithHelp("→/l", "move right"), -// ), -// Help: key.NewBinding( -// key.WithKeys("?"), -// key.WithHelp("?", "toggle help"), -// ), -// Quit: key.NewBinding( -// key.WithKeys("q", "esc", "ctrl+c"), -// key.WithHelp("q", "quit"), -// ), -// } diff --git a/ui/widgets/lists/lists.go b/ui/widgets/lists/lists.go index e1b73c8..a3993be 100644 --- a/ui/widgets/lists/lists.go +++ b/ui/widgets/lists/lists.go @@ -38,8 +38,8 @@ func InitialModel(ctx *context.UserContext, logger *log.Logger) Model { ctx: ctx, SelectedFolder: "", SelectedList: listitem.Item{}, - lists: []clickup.List{}, - log: log, + lists: []clickup.List{}, + log: log, } } diff --git a/ui/widgets/tasks-tabs/utils.go b/ui/widgets/tasks-tabs/utils.go index b436339..f7270e5 100644 --- a/ui/widgets/tasks-tabs/utils.go +++ b/ui/widgets/tasks-tabs/utils.go @@ -11,10 +11,12 @@ var ( inactiveTabStyle = lipgloss.NewStyle().Background(lipgloss.Color("0")) ) +//nolint:unused func removeView(views []clickup.View, s int) []clickup.View { return append(views[:s], views[s+1:]...) } +//nolint:unused func viewsToIdList(views []clickup.View) []string { ids := []string{} for _, view := range views {