package output import ( "encoding/json" "fmt" "io" "os" "reflect" "text/tabwriter" "git.franklin.lab/steve.cliff/pcli/model" ) func Print(data any, format string, w io.Writer) error { if format == "table" { return printTable(data, w) } return printJSON(data, w) } func printJSON(data any, w io.Writer) error { envelope := model.Envelope{ Data: data, Error: nil, } encoder := json.NewEncoder(w) encoder.SetIndent("", " ") return encoder.Encode(envelope) } func PrintError(err error, format string, w io.Writer) error { if format == "table" { fmt.Fprintf(os.Stderr, "Error: %s\n", err.Error()) return nil } errMsg := err.Error() envelope := model.Envelope{ Data: nil, Error: &errMsg, } encoder := json.NewEncoder(w) encoder.SetIndent("", " ") return encoder.Encode(envelope) } func PrintErrorWithCommand(err error, format string, w io.Writer, command string) error { if format == "table" { fmt.Fprintf(os.Stderr, "Error in %s command: %s\n", command, err.Error()) return nil } errMsg := fmt.Sprintf("Error in %s command: %s", command, err.Error()) envelope := model.Envelope{ Data: nil, Error: &errMsg, } encoder := json.NewEncoder(w) encoder.SetIndent("", " ") return encoder.Encode(envelope) } func printTable(data any, w io.Writer) error { if data == nil { return nil } tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0) defer tw.Flush() v := reflect.ValueOf(data) if v.Kind() == reflect.Slice { if v.Len() == 0 { return nil } elemType := v.Index(0).Type() switch elemType.Name() { case "Project": return printProjectTable(data.([]model.Project), tw) case "Board": return printBoardTable(data.([]model.Board), tw) case "Card": return printCardTable(data.([]model.Card), tw) case "CardWithList": return printCardWithListTable(data.([]model.CardWithList), tw) case "Comment": return printCommentTable(data.([]model.Comment), tw) case "TaskList": return printTaskListTable(data.([]model.TaskList), tw) case "Task": return printTaskTable(data.([]model.Task), tw) case "Label": return printLabelTable(data.([]model.Label), tw) case "List": return printListTable(data.([]model.List), tw) case "Action": return printActionTable(data.([]model.Action), tw) default: return fmt.Errorf("unsupported slice type for table output: %s", elemType.Name()) } } switch data := data.(type) { case *model.Project: return printProjectTable([]model.Project{*data}, tw) case *model.Board: return printBoardTable([]model.Board{*data}, tw) case *model.Card: return printCardTable([]model.Card{*data}, tw) case *model.CardDetail: return printCardDetailTable(data, tw) case *model.Comment: return printCommentTable([]model.Comment{*data}, tw) case *model.TaskList: return printTaskListTable([]model.TaskList{*data}, tw) case *model.Task: return printTaskTable([]model.Task{*data}, tw) case *model.Label: return printLabelTable([]model.Label{*data}, tw) case *model.List: return printListTable([]model.List{*data}, tw) case model.StatusSummary: return printStatusTable(data, tw) case *model.StatusSummary: return printStatusTable(*data, tw) default: return fmt.Errorf("unsupported type for table output: %T", data) } } func printProjectTable(projects []model.Project, tw *tabwriter.Writer) error { fmt.Fprintln(tw, "ID\tNAME\tDESCRIPTION\tHIDDEN") for _, p := range projects { desc := "" if p.Description != nil { desc = *p.Description } fmt.Fprintf(tw, "%s\t%s\t%s\t%v\n", p.ID, p.Name, desc, p.IsHidden) } return nil } func printBoardTable(boards []model.Board, tw *tabwriter.Writer) error { fmt.Fprintln(tw, "ID\tNAME\tPROJECT_ID\tDEFAULT_VIEW") for _, b := range boards { fmt.Fprintf(tw, "%s\t%s\t%s\t%s\n", b.ID, b.Name, b.ProjectID, b.DefaultView) } return nil } func printCardTable(cards []model.Card, tw *tabwriter.Writer) error { fmt.Fprintln(tw, "ID\tNAME\tLIST_ID\tTYPE\tCLOSED") for _, c := range cards { fmt.Fprintf(tw, "%s\t%s\t%s\t%s\t%v\n", c.ID, c.Name, c.ListID, c.Type, c.IsClosed) } return nil } func printCardWithListTable(cards []model.CardWithList, tw *tabwriter.Writer) error { fmt.Fprintln(tw, "ID\tNAME\tLIST\tTYPE\tCLOSED") for _, c := range cards { fmt.Fprintf(tw, "%s\t%s\t%s\t%s\t%v\n", c.ID, c.Name, c.ListName, c.Type, c.IsClosed) } return nil } func printCardDetailTable(card *model.CardDetail, tw *tabwriter.Writer) error { fmt.Fprintln(tw, "ID\tNAME\tLIST_ID\tTYPE\tCLOSED") fmt.Fprintf(tw, "%s\t%s\t%s\t%s\t%v\n", card.ID, card.Name, card.ListID, card.Type, card.IsClosed) if len(card.TaskLists) > 0 { fmt.Fprintln(tw) fmt.Fprintln(tw, "TASK_LIST_ID\tTASK_LIST_NAME\tPOSITION") for _, tl := range card.TaskLists { fmt.Fprintf(tw, "%s\t%s\t%.0f\n", tl.ID, tl.Name, tl.Position) } } if len(card.Tasks) > 0 { fmt.Fprintln(tw) fmt.Fprintln(tw, "TASK_ID\tTASK_NAME\tTASK_LIST_ID\tCOMPLETED") for _, t := range card.Tasks { fmt.Fprintf(tw, "%s\t%s\t%s\t%v\n", t.ID, t.Name, t.TaskListID, t.IsCompleted) } } return nil } func printCommentTable(comments []model.Comment, tw *tabwriter.Writer) error { fmt.Fprintln(tw, "ID\tCARD_ID\tTEXT\tCREATED_AT") for _, c := range comments { createdAt := "" if c.CreatedAt != nil { createdAt = *c.CreatedAt } fmt.Fprintf(tw, "%s\t%s\t%s\t%s\n", c.ID, c.CardID, c.Text, createdAt) } return nil } func printTaskListTable(taskLists []model.TaskList, tw *tabwriter.Writer) error { fmt.Fprintln(tw, "ID\tNAME\tCARD_ID\tPOSITION") for _, tl := range taskLists { fmt.Fprintf(tw, "%s\t%s\t%s\t%.0f\n", tl.ID, tl.Name, tl.CardID, tl.Position) } return nil } func printTaskTable(tasks []model.Task, tw *tabwriter.Writer) error { fmt.Fprintln(tw, "ID\tNAME\tTASK_LIST_ID\tCOMPLETED") for _, t := range tasks { fmt.Fprintf(tw, "%s\t%s\t%s\t%v\n", t.ID, t.Name, t.TaskListID, t.IsCompleted) } return nil } func printLabelTable(labels []model.Label, tw *tabwriter.Writer) error { fmt.Fprintln(tw, "ID\tNAME\tCOLOR\tBOARD_ID") for _, l := range labels { name := "" if l.Name != nil { name = *l.Name } fmt.Fprintf(tw, "%s\t%s\t%s\t%s\n", l.ID, name, l.Color, l.BoardID) } return nil } func printListTable(lists []model.List, tw *tabwriter.Writer) error { fmt.Fprintln(tw, "ID\tNAME\tTYPE\tBOARD_ID\tPOSITION\tCOLOR") for _, l := range lists { name := "" if l.Name != nil { name = *l.Name } color := "" if l.Color != nil { color = *l.Color } position := "" if l.Position != nil { position = fmt.Sprintf("%.0f", *l.Position) } fmt.Fprintf(tw, "%s\t%s\t%s\t%s\t%s\t%s\n", l.ID, name, l.Type, l.BoardID, position, color) } return nil } func printActionTable(actions []model.Action, tw *tabwriter.Writer) error { fmt.Fprintln(tw, "ID\tTYPE\tCARD_ID\tCREATED_AT") for _, a := range actions { createdAt := "" if a.CreatedAt != nil { createdAt = *a.CreatedAt } fmt.Fprintf(tw, "%s\t%s\t%s\t%s\n", a.ID, a.Type, a.CardID, createdAt) } return nil } func printStatusTable(summary model.StatusSummary, tw *tabwriter.Writer) error { // Print total board count fmt.Fprintf(tw, "%d boards\n\n", summary.TotalBoards) // Print each board with its lists for i, board := range summary.Boards { fmt.Fprintf(tw, "Board: %s\n", board.Name) fmt.Fprintln(tw, "LIST\tCARDS") for _, list := range board.Lists { cardsText := fmt.Sprintf("%d", list.OpenCards) if list.ClosedCards > 0 { cardsText += fmt.Sprintf(" (%d closed)", list.ClosedCards) } fmt.Fprintf(tw, "%s\t%s\n", list.Name, cardsText) } // Add blank line between boards (except last one) if i < len(summary.Boards)-1 { fmt.Fprintln(tw) } } return nil }