Skip to content

Commit

Permalink
board: CLI tooling
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaelMure committed Dec 29, 2022
1 parent 19fa67a commit dcb4df0
Show file tree
Hide file tree
Showing 47 changed files with 1,644 additions and 47 deletions.
16 changes: 8 additions & 8 deletions cache/board_excerpt.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,20 @@ type BoardExcerpt struct {
CreateUnixTime int64
EditUnixTime int64

Title string
Description string
ItemCount int
Actors []entity.Id
Title string
Description string
ItemCount int
Participants []entity.Id

CreateMetadata map[string]string
}

func NewBoardExcerpt(b *BoardCache) *BoardExcerpt {
snap := b.Snapshot()

actorsIds := make([]entity.Id, 0, len(snap.Actors))
for _, actor := range snap.Actors {
actorsIds = append(actorsIds, actor.Id())
participantsIds := make([]entity.Id, 0, len(snap.Participants))
for _, participant := range snap.Participants {
participantsIds = append(participantsIds, participant.Id())
}

return &BoardExcerpt{
Expand All @@ -50,7 +50,7 @@ func NewBoardExcerpt(b *BoardCache) *BoardExcerpt {
Title: snap.Title,
Description: snap.Description,
ItemCount: snap.ItemCount(),
Actors: actorsIds,
Participants: participantsIds,
CreateMetadata: b.FirstOp().AllMetadata(),
}
}
Expand Down
145 changes: 145 additions & 0 deletions commands/board/board.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package boardcmd

import (
"fmt"
"strings"

text "github.com/MichaelMure/go-term-text"
"github.com/spf13/cobra"

"github.com/MichaelMure/git-bug/cache"
"github.com/MichaelMure/git-bug/commands/cmdjson"
"github.com/MichaelMure/git-bug/commands/completion"
"github.com/MichaelMure/git-bug/commands/execenv"
"github.com/MichaelMure/git-bug/util/colors"
)

type boardOptions struct {
metadataQuery []string
actorQuery []string
titleQuery []string
outputFormat string
}

func NewBoardCommand() *cobra.Command {
env := execenv.NewEnv()
options := boardOptions{}

cmd := &cobra.Command{
Use: "board",
Short: "List boards",
PreRunE: execenv.LoadBackend(env),
RunE: execenv.CloseBackend(env, func(cmd *cobra.Command, args []string) error {
return runBoard(env, options, args)
}),
}

flags := cmd.Flags()
flags.SortFlags = false

flags.StringSliceVarP(&options.metadataQuery, "metadata", "m", nil,
"Filter by metadata. Example: github-url=URL")
cmd.RegisterFlagCompletionFunc("author", completion.UserForQuery(env))
flags.StringSliceVarP(&options.actorQuery, "actor", "A", nil,
"Filter by actor")
cmd.RegisterFlagCompletionFunc("actor", completion.UserForQuery(env))
flags.StringSliceVarP(&options.titleQuery, "title", "t", nil,
"Filter by title")
flags.StringVarP(&options.outputFormat, "format", "f", "default",
"Select the output formatting style. Valid values are [default,plain,compact,id,json,org-mode]")
cmd.RegisterFlagCompletionFunc("format",
completion.From([]string{"default", "id", "json"}))

const selectGroup = "select"
cmd.AddGroup(&cobra.Group{ID: selectGroup, Title: "Implicit selection"})

addCmdWithGroup := func(child *cobra.Command, groupID string) {
cmd.AddCommand(child)
child.GroupID = groupID
}

addCmdWithGroup(newBoardDeselectCommand(), selectGroup)
addCmdWithGroup(newBoardSelectCommand(), selectGroup)

cmd.AddCommand(newBoardNewCommand())
cmd.AddCommand(newBoardRmCommand())
cmd.AddCommand(newBoardShowCommand())
cmd.AddCommand(newBoardDescriptionCommand())
cmd.AddCommand(newBoardTitleCommand())
cmd.AddCommand(newBoardAddDraftCommand())

return cmd
}

func runBoard(env *execenv.Env, opts boardOptions, args []string) error {
// TODO: query

allIds := env.Backend.Boards().AllIds()

excerpts := make([]*cache.BoardExcerpt, len(allIds))
for i, id := range allIds {
b, err := env.Backend.Boards().ResolveExcerpt(id)
if err != nil {
return err
}
excerpts[i] = b
}

switch opts.outputFormat {
case "json":
return boardJsonFormatter(env, excerpts)
case "id":
return boardIDFormatter(env, excerpts)
case "default":
return boardDefaultFormatter(env, excerpts)
default:
return fmt.Errorf("unknown format %s", opts.outputFormat)
}
}

func boardIDFormatter(env *execenv.Env, excerpts []*cache.BoardExcerpt) error {
for _, b := range excerpts {
env.Out.Println(b.Id().String())
}

return nil
}

func boardDefaultFormatter(env *execenv.Env, excerpts []*cache.BoardExcerpt) error {
for _, b := range excerpts {
// truncate + pad if needed
titleFmt := text.LeftPadMaxLine(strings.TrimSpace(b.Title), 50, 0)
descFmt := text.LeftPadMaxLine(strings.TrimSpace(b.Description), 50, 0)

var itemFmt string
switch {
case b.ItemCount < 1:
itemFmt = "empty"
case b.ItemCount < 1000:
itemFmt = fmt.Sprintf("%3d 📝", b.ItemCount)
default:
itemFmt = " ∞ 📝"

}

env.Out.Printf("%s\t%s\t%s\t%s\n",
colors.Cyan(b.Id().Human()),
titleFmt,
descFmt,
itemFmt,
)
}
return nil
}

func boardJsonFormatter(env *execenv.Env, excerpts []*cache.BoardExcerpt) error {
res := make([]cmdjson.BoardExcerpt, len(excerpts))
for i, b := range excerpts {
jsonBoard, err := cmdjson.NewBoardExcerpt(env.Backend, b)
if err != nil {
return err
}
res[i] = jsonBoard
}
return env.Out.PrintJSON(res)
}
96 changes: 96 additions & 0 deletions commands/board/board_adddraft.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package boardcmd

import (
"strconv"

"github.com/spf13/cobra"

buginput "github.com/MichaelMure/git-bug/commands/bug/input"
"github.com/MichaelMure/git-bug/commands/execenv"
"github.com/MichaelMure/git-bug/entity"
)

type boardAddDraftOptions struct {
title string
messageFile string
message string
column string
nonInteractive bool
}

func newBoardAddDraftCommand() *cobra.Command {
env := execenv.NewEnv()
options := boardAddDraftOptions{}

cmd := &cobra.Command{
Use: "add-draft [BOARD_ID]",
Short: "Add a draft item to a board",
PreRunE: execenv.LoadBackend(env),
RunE: execenv.CloseBackend(env, func(cmd *cobra.Command, args []string) error {
return runBoardAddDraft(env, options, args)
}),
ValidArgsFunction: BoardCompletion(env),
}

flags := cmd.Flags()
flags.SortFlags = false

flags.StringVarP(&options.title, "title", "t", "",
"Provide the title to describe the draft item")
flags.StringVarP(&options.message, "message", "m", "",
"Provide the message of the draft item")
flags.StringVarP(&options.messageFile, "file", "F", "",
"Take the message from the given file. Use - to read the message from the standard input")
flags.StringVarP(&options.column, "column", "c", "1",
"The column to add to. Either a column Id or prefix, or the column number starting from 1.")
// _ = cmd.MarkFlagRequired("column")
_ = cmd.RegisterFlagCompletionFunc("column", ColumnCompletion(env))
flags.BoolVar(&options.nonInteractive, "non-interactive", false, "Do not ask for user input")

return cmd
}

func runBoardAddDraft(env *execenv.Env, opts boardAddDraftOptions, args []string) error {
b, args, err := ResolveSelected(env.Backend, args)
if err != nil {
return err
}

var columnId entity.Id

index, err := strconv.Atoi(opts.column)
if err == nil && index-1 >= 0 && index-1 < len(b.Snapshot().Columns) {
columnId = b.Snapshot().Columns[index-1].Id
} else {
// TODO: ID or combined ID?
// TODO: resolve
}

if opts.messageFile != "" && opts.message == "" {
// Note: reuse the bug inputs
opts.title, opts.message, err = buginput.BugCreateFileInput(opts.messageFile)
if err != nil {
return err
}
}

if !opts.nonInteractive && opts.messageFile == "" && (opts.message == "" || opts.title == "") {
opts.title, opts.message, err = buginput.BugCreateEditorInput(env.Backend, opts.title, opts.message)
if err == buginput.ErrEmptyTitle {
env.Out.Println("Empty title, aborting.")
return nil
}
if err != nil {
return err
}
}

id, _, err := b.AddItemDraft(columnId, opts.title, opts.message, nil)
if err != nil {
return err
}

env.Out.Printf("%s created\n", id.Human())

return b.Commit()
}
38 changes: 38 additions & 0 deletions commands/board/board_description.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package boardcmd

import (
"github.com/spf13/cobra"

"github.com/MichaelMure/git-bug/commands/execenv"
)

func newBoardDescriptionCommand() *cobra.Command {
env := execenv.NewEnv()

cmd := &cobra.Command{
Use: "description [BOARD_ID]",
Short: "Display the description of a board",
PreRunE: execenv.LoadBackend(env),
RunE: execenv.CloseBackend(env, func(cmd *cobra.Command, args []string) error {
return runBoardDescription(env, args)
}),
ValidArgsFunction: BoardCompletion(env),
}

cmd.AddCommand(newBoardDescriptionEditCommand())

return cmd
}

func runBoardDescription(env *execenv.Env, args []string) error {
b, args, err := ResolveSelected(env.Backend, args)
if err != nil {
return err
}

snap := b.Snapshot()

env.Out.Println(snap.Description)

return nil
}
70 changes: 70 additions & 0 deletions commands/board/board_description_edit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package boardcmd

import (
"github.com/spf13/cobra"

"github.com/MichaelMure/git-bug/commands/execenv"
"github.com/MichaelMure/git-bug/commands/input"
"github.com/MichaelMure/git-bug/util/text"
)

type boardDescriptionEditOptions struct {
description string
nonInteractive bool
}

func newBoardDescriptionEditCommand() *cobra.Command {
env := execenv.NewEnv()
options := boardDescriptionEditOptions{}

cmd := &cobra.Command{
Use: "edit [BUG_ID]",
Short: "Edit a description of a board",
PreRunE: execenv.LoadBackendEnsureUser(env),
RunE: execenv.CloseBackend(env, func(cmd *cobra.Command, args []string) error {
return runBugDescriptionEdit(env, options, args)
}),
ValidArgsFunction: BoardCompletion(env),
}

flags := cmd.Flags()
flags.SortFlags = false

flags.StringVarP(&options.description, "description", "t", "",
"Provide a description for the board",
)
flags.BoolVar(&options.nonInteractive, "non-interactive", false, "Do not ask for user input")

return cmd
}

func runBugDescriptionEdit(env *execenv.Env, opts boardDescriptionEditOptions, args []string) error {
b, args, err := ResolveSelected(env.Backend, args)
if err != nil {
return err
}

snap := b.Snapshot()

if opts.description == "" {
if opts.nonInteractive {
env.Err.Println("No description given. Aborting.")
return nil
}
opts.description, err = input.PromptDefault("Board description", "description", snap.Description, input.Required)
if err != nil {
return err
}
}

if opts.description == snap.Description {
env.Err.Println("No change, aborting.")
}

_, err = b.SetDescription(text.CleanupOneLine(opts.description))
if err != nil {
return err
}

return b.Commit()
}

0 comments on commit dcb4df0

Please sign in to comment.