diff --git a/plan/command/command.go b/plan/command/command.go index 2a8467d..f4ac5fd 100644 --- a/plan/command/command.go +++ b/plan/command/command.go @@ -44,6 +44,7 @@ func NewCLI(deps Dependencies) *CLI { return &CLI{ deps: deps, cmdArgs: []CommandArgs{ + NewShowArgs(), NewAddArgs(), NewDeleteArgs(), NewListArgs(), NewSyncArgs(), NewUpdateArgs(), }, diff --git a/plan/command/show.go b/plan/command/show.go index d47dcf0..91c8ed9 100644 --- a/plan/command/show.go +++ b/plan/command/show.go @@ -1 +1,70 @@ package command + +import ( + "errors" + "fmt" + "strconv" + + "go-mod.ewintr.nl/planner/plan/format" + "go-mod.ewintr.nl/planner/plan/storage" +) + +type ShowArgs struct { + localID int +} + +func NewShowArgs() ShowArgs { + return ShowArgs{} +} + +func (sa ShowArgs) Parse(main []string, fields map[string]string) (Command, error) { + if len(main) != 1 { + return nil, ErrWrongCommand + } + lid, err := strconv.Atoi(main[0]) + if err != nil { + return nil, ErrWrongCommand + } + + return &Show{ + args: ShowArgs{ + localID: lid, + }, + }, nil +} + +type Show struct { + args ShowArgs +} + +func (s *Show) Do(deps Dependencies) error { + id, err := deps.LocalIDRepo.FindOne(s.args.localID) + switch { + case errors.Is(err, storage.ErrNotFound): + return fmt.Errorf("could not find local id") + case err != nil: + return err + } + + tsk, err := deps.TaskRepo.Find(id) + if err != nil { + return fmt.Errorf("could not find task") + } + + var recurStr string + if tsk.Recurrer != nil { + recurStr = tsk.Recurrer.String() + } + data := [][]string{ + {"title", tsk.Title}, + {"local id", fmt.Sprintf("%d", s.args.localID)}, + {"date", tsk.Date.String()}, + {"time", tsk.Time.String()}, + {"duration", tsk.Duration.String()}, + {"recur", recurStr}, + // {"id", tsk.ID}, + } + fmt.Printf("\n%s\n", format.Table(data)) + + return nil +} diff --git a/plan/format/format.go b/plan/format/format.go new file mode 100644 index 0000000..fa24d37 --- /dev/null +++ b/plan/format/format.go @@ -0,0 +1,96 @@ +package format + +import ( + "os" + "os/exec" + "strconv" + "strings" +) + +func Table(data [][]string) string { + if len(data) == 0 { + return "" + } + + // make all cells in a column the same width + max := make([]int, len(data[0])) + for _, row := range data { + for c, cell := range row { + if len(cell) > max[c] { + max[c] = len(cell) + } + } + } + for r, row := range data { + for c, cell := range row { + for s := len(cell); s < max[c]; s++ { + data[r][c] += " " + } + } + } + + // make it smaller if the result is too wide + // only by making the widest column smaller for now + maxWidth := findTermWidth() + if maxWidth != 0 { + width := len(max) - 1 + for _, m := range max { + width += m + } + shortenWith := width - maxWidth + widestColNo, widestColLen := 0, 0 + for i, m := range max { + if m > widestColLen { + widestColNo, widestColLen = i, m + } + } + newTaskColWidth := max[widestColNo] - shortenWith + if newTaskColWidth < 0 { + return "table is too wide to display\n" + } + if newTaskColWidth < max[widestColNo] { + for r, row := range data { + data[r][widestColNo] = row[widestColNo][:newTaskColWidth] + } + } + } + + // print the rows + var output string + for _, row := range data { + // if r%3 == 0 { + // output += fmt.Sprintf("%s", "\x1b[48;5;237m") + // } + for c, col := range row { + output += col + if c != len(row)-1 { + output += " " + } + } + // if r%3 == 0 { + // output += fmt.Sprintf("%s", "\x1b[49m") + // } + output += "\n" + } + + return output +} + +func findTermWidth() int { + cmd := exec.Command("stty", "size") + cmd.Stdin = os.Stdin + out, err := cmd.Output() + if err != nil { + return 0 + } + + s := string(out) + s = strings.TrimSpace(s) + sArr := strings.Split(s, " ") + + width, err := strconv.Atoi(sArr[1]) + if err != nil { + return 0 + } + return width +} diff --git a/plan/storage/memory/localid.go b/plan/storage/memory/localid.go index ece7ba1..59923e8 100644 --- a/plan/storage/memory/localid.go +++ b/plan/storage/memory/localid.go @@ -18,6 +18,19 @@ func NewLocalID() *LocalID { } } +func (ml *LocalID) FindOne(lid int) (string, error) { + ml.mutex.RLock() + defer ml.mutex.RUnlock() + + for id, l := range ml.ids { + if lid == l { + return id, nil + } + } + + return "", storage.ErrNotFound +} + func (ml *LocalID) FindAll() (map[string]int, error) { ml.mutex.RLock() defer ml.mutex.RUnlock() diff --git a/plan/storage/memory/localid_test.go b/plan/storage/memory/localid_test.go index eddaf6f..bd56be1 100644 --- a/plan/storage/memory/localid_test.go +++ b/plan/storage/memory/localid_test.go @@ -54,6 +54,21 @@ func TestLocalID(t *testing.T) { t.Errorf("exp 2, got %v", actLid) } + t.Log("find by local id") + actID, actErr := repo.FindOne(1) + if actErr != nil { + t.Errorf("exp nil, got %v", actErr) + } + if actID != "test" { + t.Errorf("exp test, got %v", actID) + } + + t.Log("unknown local id") + actID, actErr = repo.FindOne(2) + if !errors.Is(actErr, storage.ErrNotFound) { + t.Errorf("exp ErrNotFound, got %v", actErr) + } + actIDs, actErr = repo.FindAll() if actErr != nil { t.Errorf("exp nil, got %v", actErr) diff --git a/plan/storage/sqlite/localid.go b/plan/storage/sqlite/localid.go index f97f1d0..08f8629 100644 --- a/plan/storage/sqlite/localid.go +++ b/plan/storage/sqlite/localid.go @@ -2,6 +2,7 @@ package sqlite import ( "database/sql" + "errors" "fmt" "go-mod.ewintr.nl/planner/plan/storage" @@ -11,6 +12,23 @@ type LocalID struct { db *sql.DB } +func (l *LocalID) FindOne(lid int) (string, error) { + var id string + err := l.db.QueryRow(` +SELECT id +FROM localids +WHERE local_id = ? +`, lid).Scan(&id) + switch { + case errors.Is(err, sql.ErrNoRows): + return "", storage.ErrNotFound + case err != nil: + return "", fmt.Errorf("%w: %v", ErrSqliteFailure, err) + } + + return id, nil +} + func (l *LocalID) FindAll() (map[string]int, error) { rows, err := l.db.Query(` SELECT id, local_id diff --git a/plan/storage/storage.go b/plan/storage/storage.go index ed7fbff..7e7073c 100644 --- a/plan/storage/storage.go +++ b/plan/storage/storage.go @@ -13,6 +13,7 @@ var ( ) type LocalID interface { + FindOne(lid int) (string, error) FindAll() (map[string]int, error) FindOrNext(id string) (int, error) Next() (int, error)