cli + tui to publish to leaflet (wip) & manage tasks, notes & watch/read lists 馃崈
charm
leaflet
readability
golang
1package main
2
3import (
4 "context"
5 "fmt"
6 "os"
7 "strings"
8
9 "github.com/charmbracelet/fang"
10 "github.com/charmbracelet/log"
11 "github.com/spf13/cobra"
12 "github.com/stormlightlabs/noteleaf/internal/handlers"
13 "github.com/stormlightlabs/noteleaf/internal/store"
14 "github.com/stormlightlabs/noteleaf/internal/ui"
15 "github.com/stormlightlabs/noteleaf/internal/utils"
16)
17
18// App represents the main CLI application
19type App struct {
20 db *store.Database
21 config *store.Config
22}
23
24// NewApp creates a new CLI application instance
25func NewApp() (*App, error) {
26 db, err := store.NewDatabase()
27 if err != nil {
28 return nil, fmt.Errorf("failed to initialize database: %w", err)
29 }
30
31 if config, err := store.LoadConfig(); err != nil {
32 return nil, fmt.Errorf("failed to load configuration: %w", err)
33 } else {
34 return &App{db, config}, nil
35 }
36}
37
38// Close cleans up application resources
39func (app *App) Close() error {
40 if app.db != nil {
41 return app.db.Close()
42 }
43 return nil
44}
45
46func statusCmd() *cobra.Command {
47 return &cobra.Command{
48 Use: "status",
49 Short: "Show application status and configuration",
50 RunE: func(cmd *cobra.Command, args []string) error {
51 return handlers.Status(cmd.Context(), args)
52 },
53 }
54}
55
56func resetCmd() *cobra.Command {
57 return &cobra.Command{
58 Use: "reset",
59 Short: "Reset the application (removes all data)",
60 RunE: func(cmd *cobra.Command, args []string) error {
61 return handlers.Reset(cmd.Context(), args)
62 },
63 }
64}
65
66func rootCmd() *cobra.Command {
67 root := &cobra.Command{
68 Use: "noteleaf",
69 Long: ui.Georgia.ColoredInViewport(),
70 Short: "A TaskWarrior-inspired CLI with notes, media queues and reading lists",
71 RunE: func(c *cobra.Command, args []string) error {
72 if len(args) == 0 {
73 return c.Help()
74 }
75
76 output := strings.Join(args, " ")
77 fmt.Println(output)
78 return nil
79 },
80 }
81
82 root.SetHelpCommand(&cobra.Command{Hidden: true})
83 cobra.EnableCommandSorting = false
84
85 root.AddGroup(&cobra.Group{ID: "core", Title: "Core Commands:"})
86 root.AddGroup(&cobra.Group{ID: "management", Title: "Management Commands:"})
87 return root
88}
89
90func setupCmd() *cobra.Command {
91 handler, err := handlers.NewSeedHandler()
92 if err != nil {
93 log.Fatalf("failed to instantiate seed handler: %v", err)
94 }
95
96 root := &cobra.Command{
97 Use: "setup",
98 Short: "Initialize and manage application setup",
99 RunE: func(c *cobra.Command, args []string) error {
100 return handlers.Setup(c.Context(), args)
101 },
102 }
103
104 seedCmd := &cobra.Command{
105 Use: "seed",
106 Short: "Populate database with test data",
107 Long: "Add sample tasks, books, and notes to the database for testing and demonstration purposes",
108 RunE: func(c *cobra.Command, args []string) error {
109 force, _ := c.Flags().GetBool("force")
110 return handler.Seed(c.Context(), force)
111 },
112 }
113 seedCmd.Flags().BoolP("force", "f", false, "Clear existing data and re-seed")
114
115 root.AddCommand(seedCmd)
116 return root
117}
118
119func confCmd() *cobra.Command {
120 return &cobra.Command{
121 Use: "config [key] [value]",
122 Short: "Manage configuration",
123 Args: cobra.ExactArgs(2),
124 RunE: func(c *cobra.Command, args []string) error {
125 key, value := args[0], args[1]
126 fmt.Printf("Setting config %s = %s\n", key, value)
127 return nil
128 },
129 }
130}
131
132func main() {
133 logger := utils.NewLogger("info", "text")
134 utils.Logger = logger
135
136 app, err := NewApp()
137 if err != nil {
138 logger.Fatal("Failed to initialize application", "error", err)
139 }
140 defer app.Close()
141
142 taskHandler, err := handlers.NewTaskHandler()
143 if err != nil {
144 log.Fatalf("failed to create task handler: %v", err)
145 }
146
147 movieHandler, err := handlers.NewMovieHandler()
148 if err != nil {
149 log.Fatalf("failed to create movie handler: %v", err)
150 }
151
152 tvHandler, err := handlers.NewTVHandler()
153 if err != nil {
154 log.Fatalf("failed to create TV handler: %v", err)
155 }
156
157 noteHandler, err := handlers.NewNoteHandler()
158 if err != nil {
159 log.Fatalf("failed to create note handler: %v", err)
160 }
161
162 bookHandler, err := handlers.NewBookHandler()
163 if err != nil {
164 log.Fatalf("failed to create book handler: %v", err)
165 }
166
167 articleHandler, err := handlers.NewArticleHandler()
168 if err != nil {
169 log.Fatalf("failed to create article handler: %v", err)
170 }
171
172 root := rootCmd()
173
174 coreGroups := []CommandGroup{
175 NewTaskCommand(taskHandler),
176 NewNoteCommand(noteHandler),
177 NewArticleCommand(articleHandler),
178 }
179
180 for _, group := range coreGroups {
181 cmd := group.Create()
182 cmd.GroupID = "core"
183 root.AddCommand(cmd)
184 }
185
186 mediaCmd := &cobra.Command{Use: "media", Short: "Manage media queues (books, movies, TV shows)"}
187 mediaCmd.GroupID = "core"
188 mediaCmd.AddCommand(NewMovieCommand(movieHandler).Create())
189 mediaCmd.AddCommand(NewTVCommand(tvHandler).Create())
190 mediaCmd.AddCommand(NewBookCommand(bookHandler).Create())
191 root.AddCommand(mediaCmd)
192
193 mgmt := []func() *cobra.Command{statusCmd, confCmd, setupCmd, resetCmd}
194 for _, cmdFunc := range mgmt {
195 cmd := cmdFunc()
196 cmd.GroupID = "management"
197 root.AddCommand(cmd)
198 }
199
200 opts := []fang.Option{
201 fang.WithVersion("0.1.0"),
202 fang.WithoutCompletions(),
203 fang.WithColorSchemeFunc(ui.NoteleafColorScheme),
204 }
205
206 if err := fang.Execute(context.Background(), root, opts...); err != nil {
207 os.Exit(1)
208 }
209}