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