cli + tui to publish to leaflet (wip) & manage tasks, notes & watch/read lists 馃崈
charm leaflet readability golang
29
fork

Configure Feed

Select the types of activity you want to include in your feed.

at 2a281596f9d6660d4ecd8152b65c6add2959d05c 269 lines 7.3 kB view raw
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 "github.com/stormlightlabs/noteleaf/tools" 17) 18 19var ( 20 newTaskHandler = handlers.NewTaskHandler 21 newMovieHandler = handlers.NewMovieHandler 22 newTVHandler = handlers.NewTVHandler 23 newNoteHandler = handlers.NewNoteHandler 24 newBookHandler = handlers.NewBookHandler 25 newArticleHandler = handlers.NewArticleHandler 26 newPublicationHandler = handlers.NewPublicationHandler 27 exc = fang.Execute 28) 29 30// App represents the main CLI application 31type App struct { 32 db *store.Database 33 config *store.Config 34} 35 36// NewApp creates a new CLI application instance ([App]) 37func NewApp() (*App, error) { 38 db, err := store.NewDatabase() 39 if err != nil { 40 return nil, fmt.Errorf("failed to initialize database: %w", err) 41 } 42 43 config, err := store.LoadConfig() 44 if err != nil { 45 return nil, fmt.Errorf("failed to load configuration: %w", err) 46 } 47 48 return &App{db, config}, nil 49} 50 51// Close cleans up application resources 52func (app *App) Close() error { 53 if app.db != nil { 54 return app.db.Close() 55 } 56 return nil 57} 58 59func statusCmd() *cobra.Command { 60 return &cobra.Command{ 61 Use: "status", 62 Short: "Show application status and configuration", 63 Long: `Display comprehensive application status information. 64 65Shows database location, configuration file path, data directories, and current 66settings. Use this command to verify your noteleaf installation and diagnose 67configuration issues.`, 68 RunE: func(cmd *cobra.Command, args []string) error { 69 return handlers.Status(cmd.Context(), args, cmd.OutOrStdout()) 70 }, 71 } 72} 73 74func resetCmd() *cobra.Command { 75 return &cobra.Command{ 76 Use: "reset", 77 Short: "Reset the application (removes all data)", 78 Long: `Remove all application data and return to initial state. 79 80This command deletes the database, all media files, notes, and articles. The 81configuration file is preserved. Use with caution as this operation cannot be 82undone. You will be prompted for confirmation before deletion proceeds.`, 83 RunE: func(cmd *cobra.Command, args []string) error { 84 return handlers.Reset(cmd.Context(), args) 85 }, 86 } 87} 88 89func rootCmd() *cobra.Command { 90 root := &cobra.Command{ 91 Use: "noteleaf", 92 Short: "A TaskWarrior-inspired CLI with notes, media queues and reading lists", 93 Long: `noteleaf - personal information manager for the command line 94 95A comprehensive CLI tool for managing tasks, notes, articles, and media queues. 96Inspired by TaskWarrior, noteleaf combines todo management with reading lists, 97watch queues, and a personal knowledge base. 98 99Core features include hierarchical tasks with dependencies, recurring tasks, 100time tracking, markdown notes with tags, article archiving, and media queue 101management for books, movies, and TV shows.`, 102 RunE: func(c *cobra.Command, args []string) error { 103 if len(args) == 0 { 104 return c.Help() 105 } 106 107 output := strings.Join(args, " ") 108 fmt.Fprintln(c.OutOrStdout(), output) 109 return nil 110 }, 111 } 112 113 root.SetHelpCommand(&cobra.Command{Hidden: true}) 114 cobra.EnableCommandSorting = false 115 116 root.AddGroup(&cobra.Group{ID: "core", Title: "Core:"}) 117 root.AddGroup(&cobra.Group{ID: "management", Title: "Manage:"}) 118 return root 119} 120 121func setupCmd() *cobra.Command { 122 handler, err := handlers.NewSeedHandler() 123 if err != nil { 124 log.Fatalf("failed to instantiate seed handler: %v", err) 125 } 126 127 root := &cobra.Command{ 128 Use: "setup", 129 Short: "Initialize and manage application setup", 130 Long: `Initialize noteleaf for first use. 131 132Creates the database, configuration file, and required data directories. Run 133this command after installing noteleaf or when setting up a new environment. 134Safe to run multiple times as it will skip existing resources.`, 135 RunE: func(c *cobra.Command, args []string) error { 136 return handlers.Setup(c.Context(), args) 137 }, 138 } 139 140 seedCmd := &cobra.Command{ 141 Use: "seed", 142 Short: "Populate database with test data", 143 Long: "Add sample tasks, books, and notes to the database for testing and demonstration purposes", 144 RunE: func(c *cobra.Command, args []string) error { 145 force, _ := c.Flags().GetBool("force") 146 return handler.Seed(c.Context(), force) 147 }, 148 } 149 seedCmd.Flags().BoolP("force", "f", false, "Clear existing data and re-seed") 150 151 root.AddCommand(seedCmd) 152 return root 153} 154 155func confCmd() *cobra.Command { 156 handler, err := handlers.NewConfigHandler() 157 if err != nil { 158 log.Fatalf("failed to create config handler: %v", err) 159 } 160 return NewConfigCommand(handler).Create() 161} 162 163func run() int { 164 logger := utils.NewLogger("info", "text") 165 utils.Logger = logger 166 167 app, err := NewApp() 168 if err != nil { 169 logger.Error("Failed to initialize application", "error", err) 170 return 1 171 } 172 defer app.Close() 173 174 taskHandler, err := newTaskHandler() 175 if err != nil { 176 log.Error("failed to create task handler", "err", err) 177 return 1 178 } 179 180 movieHandler, err := newMovieHandler() 181 if err != nil { 182 log.Error("failed to create movie handler", "err", err) 183 return 1 184 } 185 186 tvHandler, err := newTVHandler() 187 if err != nil { 188 log.Error("failed to create TV handler", "err", err) 189 return 1 190 } 191 192 noteHandler, err := newNoteHandler() 193 if err != nil { 194 log.Error("failed to create note handler", "err", err) 195 return 1 196 } 197 198 bookHandler, err := newBookHandler() 199 if err != nil { 200 log.Error("failed to create book handler", "err", err) 201 return 1 202 } 203 204 articleHandler, err := newArticleHandler() 205 if err != nil { 206 log.Error("failed to create article handler", "err", err) 207 return 1 208 } 209 210 publicationHandler, err := newPublicationHandler() 211 if err != nil { 212 log.Error("failed to create publication handler", "err", err) 213 return 1 214 } 215 216 root := rootCmd() 217 218 coreGroups := []CommandGroup{ 219 NewTaskCommand(taskHandler), 220 NewNoteCommand(noteHandler), 221 NewPublicationCommand(publicationHandler), 222 NewArticleCommand(articleHandler), 223 } 224 225 for _, group := range coreGroups { 226 cmd := group.Create() 227 cmd.GroupID = "core" 228 root.AddCommand(cmd) 229 } 230 231 mediaCmd := &cobra.Command{ 232 Use: "media", 233 Short: "Manage media queues (books, movies, TV shows)", 234 Long: `Track and manage reading lists and watch queues. 235 236Organize books, movies, and TV shows you want to consume. Search external 237databases to add items, track reading/watching progress, and maintain a 238history of completed media.`, 239 } 240 mediaCmd.GroupID = "core" 241 mediaCmd.AddCommand(NewMovieCommand(movieHandler).Create()) 242 mediaCmd.AddCommand(NewTVCommand(tvHandler).Create()) 243 mediaCmd.AddCommand(NewBookCommand(bookHandler).Create()) 244 root.AddCommand(mediaCmd) 245 246 mgmt := []func() *cobra.Command{statusCmd, confCmd, setupCmd, resetCmd} 247 for _, cmdFunc := range mgmt { 248 cmd := cmdFunc() 249 cmd.GroupID = "management" 250 root.AddCommand(cmd) 251 } 252 253 root.AddCommand(tools.NewDocGenCommand(root)) 254 255 opts := []fang.Option{ 256 fang.WithVersion("0.1.0"), 257 fang.WithoutCompletions(), 258 fang.WithColorSchemeFunc(ui.NoteleafColorScheme), 259 } 260 261 if err := exc(context.Background(), root, opts...); err != nil { 262 return 1 263 } 264 return 0 265} 266 267func main() { 268 os.Exit(run()) 269}