package cmd import ( "fmt" "strings" "time" "github.com/lerko/nib/internal/db" "github.com/lerko/nib/internal/display" "github.com/spf13/cobra" ) var ( lsTag string lsDate string lsMonth string lsFrom string lsTo string lsLimit int lsAll bool ) var lsCmd = &cobra.Command{ Use: "ls", Short: "list entities in stream order", RunE: runLs, } func init() { lsCmd.Flags().StringVar(&lsTag, "tag", "", "filter by tag") lsCmd.Flags().StringVar(&lsDate, "date", "", "filter by date (YYYY-MM-DD)") lsCmd.Flags().StringVar(&lsMonth, "month", "", "filter by month (YYYY-MM)") lsCmd.Flags().StringVar(&lsFrom, "from", "", "start date (YYYY-MM-DD)") lsCmd.Flags().StringVar(&lsTo, "to", "", "end date (YYYY-MM-DD)") lsCmd.Flags().IntVar(&lsLimit, "limit", 0, "max entities to show (default 50)") lsCmd.Flags().BoolVar(&lsAll, "all", false, "include deleted entities") } func runLs(_ *cobra.Command, _ []string) error { store, err := openStore() if err != nil { return err } defer store.Close() p := db.DefaultListParams() p.IncludeDeleted = lsAll if lsTag != "" { p.Tag = &lsTag } if lsLimit > 0 { p.Limit = lsLimit } hasDateFilter := false if lsDate != "" { p.Date = &lsDate hasDateFilter = true } if lsMonth != "" { t, err := time.Parse("2006-01", lsMonth) if err != nil { return fmt.Errorf("bad --month format, use YYYY-MM") } from := t.Format("2006-01-02") to := t.AddDate(0, 1, -1).Format("2006-01-02") p.From = &from p.To = &to hasDateFilter = true } if lsFrom != "" { if _, err := time.Parse("2006-01-02", lsFrom); err != nil { return fmt.Errorf("bad --from format, use YYYY-MM-DD") } p.From = &lsFrom hasDateFilter = true } if lsTo != "" { if _, err := time.Parse("2006-01-02", lsTo); err != nil { return fmt.Errorf("bad --to format, use YYYY-MM-DD") } p.To = &lsTo hasDateFilter = true } if !hasDateFilter { since := time.Now().UTC().Add(-48 * time.Hour) p.Since = &since } entities, err := store.List(p) if err != nil { return err } if len(entities) == 0 { return nil } groups := groupByDate(entities) for _, g := range groups { fmt.Printf("── %s ──\n", g.label) for _, e := range g.entities { printEntity(e) } fmt.Println() } return nil } type dateGroup struct { label string entities []*db.Entity } func groupByDate(entities []*db.Entity) []dateGroup { var groups []dateGroup var current *dateGroup for _, e := range entities { label := formatDateLabel(e.CreatedAt) if current == nil || current.label != label { if current != nil { groups = append(groups, *current) } current = &dateGroup{label: label} } current.entities = append(current.entities, e) } if current != nil { groups = append(groups, *current) } return groups } func formatDateLabel(t time.Time) string { return strings.ToLower(t.Format("Jan 2")) } func printEntity(e *db.Entity) { glyph := display.DisplayGlyph(e.Glyph, e.CardType) shortID := display.FormatID(e.ID) label := e.Body if e.Title != nil { label = *e.Title } var line strings.Builder fmt.Fprintf(&line, "%s %-40s", glyph, label) if e.TimeAnchor != nil { fmt.Fprintf(&line, " @%-5s", *e.TimeAnchor) } else { line.WriteString(" ") } var tagStr string for _, tag := range e.Tags { tagStr += " #" + tag } if tagStr != "" { fmt.Fprintf(&line, " %-16s", strings.TrimSpace(tagStr)) } else { line.WriteString(" ") } fmt.Fprintf(&line, " %s", shortID) if e.UseCount > 0 { fmt.Fprintf(&line, " (%d×)", e.UseCount) } fmt.Println(line.String()) }