tangled
alpha
login
or
join now
desertthunder.dev
/
noteleaf
cli + tui to publish to leaflet (wip) & manage tasks, notes & watch/read lists ๐
charm
leaflet
readability
golang
29
fork
atom
overview
issues
2
pulls
pipelines
chore: remove dead code
desertthunder.dev
3 months ago
750e5121
aa33ae54
+25
-126
3 changed files
expand all
collapse all
unified
split
cmd
commands.go
internal
handlers
handlers.go
media_utilities.go
+15
-24
cmd/commands.go
···
9
"github.com/stormlightlabs/noteleaf/internal/handlers"
10
)
11
12
-
func parseID(k string, args []string) (int64, error) {
13
-
id, err := strconv.ParseInt(args[0], 10, 64)
14
-
if err != nil {
15
-
return id, fmt.Errorf("invalid %v ID: %s", k, args[0])
16
-
}
17
-
18
-
return id, err
19
-
}
20
-
21
// CommandGroup represents a group of related CLI commands
22
type CommandGroup interface {
23
Create() *cobra.Command
···
203
204
Updates episode tracking and completion status. Can mark individual episodes
205
or complete seasons/series depending on ID format.`,
206
-
Args: cobra.ExactArgs(1),
207
RunE: func(cmd *cobra.Command, args []string) error {
208
return c.handler.MarkWatched(cmd.Context(), args[0])
209
},
···
328
Specify a percentage value between 0 and 100 to indicate how far you've
329
progressed through the book. Automatically updates status to 'reading' if not
330
already set.`,
331
-
Args: cobra.ExactArgs(2),
332
RunE: func(cmd *cobra.Command, args []string) error {
333
progress, err := strconv.Atoi(args[1])
334
if err != nil {
···
345
346
Valid statuses are: queued (not started), reading (in progress), finished
347
(completed), or removed (no longer tracking).`,
348
-
Args: cobra.ExactArgs(2),
349
RunE: func(cmd *cobra.Command, args []string) error {
350
return c.handler.UpdateStatus(cmd.Context(), args[0], args[1])
351
},
···
359
handler *handlers.NoteHandler
360
}
361
362
-
// NewNoteCommand creates a new NoteCommand with the given handler
363
func NewNoteCommand(handler *handlers.NoteHandler) *NoteCommand {
364
return &NoteCommand{handler: handler}
365
}
···
445
446
Shows the note with syntax highlighting, proper formatting, and metadata.
447
Useful for quick viewing without opening an editor.`,
448
-
Args: cobra.ExactArgs(1),
449
RunE: func(cmd *cobra.Command, args []string) error {
450
-
if noteID, err := parseID("note", args); err != nil {
451
return err
452
} else {
453
defer c.handler.Close()
···
464
Uses the editor specified in your noteleaf configuration or the EDITOR
465
environment variable. Changes are automatically saved when you close the
466
editor.`,
467
-
Args: cobra.ExactArgs(1),
468
RunE: func(cmd *cobra.Command, args []string) error {
469
-
if noteID, err := parseID("note", args); err != nil {
470
return err
471
} else {
472
defer c.handler.Close()
···
483
484
Removes both the markdown file and database metadata. This operation cannot be
485
undone. You will be prompted for confirmation before deletion.`,
486
-
Args: cobra.ExactArgs(1),
487
RunE: func(cmd *cobra.Command, args []string) error {
488
-
if noteID, err := parseID("note", args); err != nil {
489
return err
490
} else {
491
defer c.handler.Close()
···
566
567
Shows article title, author, publication date, URL, and a brief content
568
preview. Use 'read' command to view the full article content.`,
569
-
Args: cobra.ExactArgs(1),
570
RunE: func(cmd *cobra.Command, args []string) error {
571
-
if articleID, err := parseID("article", args); err != nil {
572
return err
573
} else {
574
defer c.handler.Close()
···
586
This displays the complete article content using syntax highlighting and proper formatting.`,
587
Args: cobra.ExactArgs(1),
588
RunE: func(cmd *cobra.Command, args []string) error {
589
-
if articleID, err := parseID("article", args); err != nil {
590
return err
591
} else {
592
defer c.handler.Close()
···
604
605
Removes the article metadata from the database and deletes associated markdown
606
and HTML files. This operation cannot be undone.`,
607
-
Args: cobra.ExactArgs(1),
608
RunE: func(cmd *cobra.Command, args []string) error {
609
-
if articleID, err := parseID("article", args); err != nil {
610
return err
611
} else {
612
defer c.handler.Close()
···
9
"github.com/stormlightlabs/noteleaf/internal/handlers"
10
)
11
0
0
0
0
0
0
0
0
0
12
// CommandGroup represents a group of related CLI commands
13
type CommandGroup interface {
14
Create() *cobra.Command
···
194
195
Updates episode tracking and completion status. Can mark individual episodes
196
or complete seasons/series depending on ID format.`,
197
+
Args: cobra.ExactArgs(1),
198
RunE: func(cmd *cobra.Command, args []string) error {
199
return c.handler.MarkWatched(cmd.Context(), args[0])
200
},
···
319
Specify a percentage value between 0 and 100 to indicate how far you've
320
progressed through the book. Automatically updates status to 'reading' if not
321
already set.`,
322
+
Args: cobra.ExactArgs(2),
323
RunE: func(cmd *cobra.Command, args []string) error {
324
progress, err := strconv.Atoi(args[1])
325
if err != nil {
···
336
337
Valid statuses are: queued (not started), reading (in progress), finished
338
(completed), or removed (no longer tracking).`,
339
+
Args: cobra.ExactArgs(2),
340
RunE: func(cmd *cobra.Command, args []string) error {
341
return c.handler.UpdateStatus(cmd.Context(), args[0], args[1])
342
},
···
350
handler *handlers.NoteHandler
351
}
352
353
+
// NewNoteCommand creates a new [NoteCommand] with the given handler
354
func NewNoteCommand(handler *handlers.NoteHandler) *NoteCommand {
355
return &NoteCommand{handler: handler}
356
}
···
436
437
Shows the note with syntax highlighting, proper formatting, and metadata.
438
Useful for quick viewing without opening an editor.`,
439
+
Args: cobra.ExactArgs(1),
440
RunE: func(cmd *cobra.Command, args []string) error {
441
+
if noteID, err := handlers.ParseID(args[0], "note"); err != nil {
442
return err
443
} else {
444
defer c.handler.Close()
···
455
Uses the editor specified in your noteleaf configuration or the EDITOR
456
environment variable. Changes are automatically saved when you close the
457
editor.`,
458
+
Args: cobra.ExactArgs(1),
459
RunE: func(cmd *cobra.Command, args []string) error {
460
+
if noteID, err := handlers.ParseID(args[0], "note"); err != nil {
461
return err
462
} else {
463
defer c.handler.Close()
···
474
475
Removes both the markdown file and database metadata. This operation cannot be
476
undone. You will be prompted for confirmation before deletion.`,
477
+
Args: cobra.ExactArgs(1),
478
RunE: func(cmd *cobra.Command, args []string) error {
479
+
if noteID, err := handlers.ParseID(args[0], "note"); err != nil {
480
return err
481
} else {
482
defer c.handler.Close()
···
557
558
Shows article title, author, publication date, URL, and a brief content
559
preview. Use 'read' command to view the full article content.`,
560
+
Args: cobra.ExactArgs(1),
561
RunE: func(cmd *cobra.Command, args []string) error {
562
+
if articleID, err := handlers.ParseID(args[0], "article"); err != nil {
563
return err
564
} else {
565
defer c.handler.Close()
···
577
This displays the complete article content using syntax highlighting and proper formatting.`,
578
Args: cobra.ExactArgs(1),
579
RunE: func(cmd *cobra.Command, args []string) error {
580
+
if articleID, err := handlers.ParseID(args[0], "article"); err != nil {
581
return err
582
} else {
583
defer c.handler.Close()
···
595
596
Removes the article metadata from the database and deletes associated markdown
597
and HTML files. This operation cannot be undone.`,
598
+
Args: cobra.ExactArgs(1),
599
RunE: func(cmd *cobra.Command, args []string) error {
600
+
if articleID, err := handlers.ParseID(args[0], "article"); err != nil {
601
return err
602
} else {
603
defer c.handler.Close()
+10
internal/handlers/handlers.go
···
6
"io"
7
"os"
8
"path/filepath"
0
9
10
"github.com/stormlightlabs/noteleaf/internal/store"
11
"github.com/stormlightlabs/noteleaf/internal/utils"
12
)
0
0
0
0
0
0
0
0
0
13
14
// Setup initializes the application database and configuration
15
func Setup(ctx context.Context, args []string) error {
···
6
"io"
7
"os"
8
"path/filepath"
9
+
"strconv"
10
11
"github.com/stormlightlabs/noteleaf/internal/store"
12
"github.com/stormlightlabs/noteleaf/internal/utils"
13
)
14
+
15
+
// ParseID converts a string ID to int64
16
+
func ParseID(id string, itemType string) (int64, error) {
17
+
itemID, err := strconv.ParseInt(id, 10, 64)
18
+
if err != nil {
19
+
return 0, fmt.Errorf("invalid %s ID: %s", itemType, id)
20
+
}
21
+
return itemID, nil
22
+
}
23
24
// Setup initializes the application database and configuration
25
func Setup(ctx context.Context, args []string) error {
-102
internal/handlers/media_utilities.go
···
1
-
package handlers
2
-
3
-
import (
4
-
"context"
5
-
"fmt"
6
-
"io"
7
-
"strconv"
8
-
9
-
"github.com/stormlightlabs/noteleaf/internal/models"
10
-
)
11
-
12
-
// MediaPrinter defines how to format a media item for display
13
-
type MediaPrinter[T any] func(item *T)
14
-
15
-
// ListMediaItems is a generic utility for listing media items with status filtering
16
-
func ListMediaItems[T any](
17
-
ctx context.Context,
18
-
status string,
19
-
mediaType string,
20
-
listAll func(ctx context.Context) ([]*T, error),
21
-
listByStatus func(ctx context.Context, status string) ([]*T, error),
22
-
printer MediaPrinter[T],
23
-
) error {
24
-
var items []*T
25
-
var err error
26
-
27
-
if status == "" {
28
-
items, err = listAll(ctx)
29
-
if err != nil {
30
-
return fmt.Errorf("failed to list %s: %w", mediaType, err)
31
-
}
32
-
} else {
33
-
items, err = listByStatus(ctx, status)
34
-
if err != nil {
35
-
return fmt.Errorf("failed to get %s %s: %w", status, mediaType, err)
36
-
}
37
-
}
38
-
39
-
if len(items) == 0 {
40
-
if status == "" {
41
-
fmt.Printf("No %s found\n", mediaType)
42
-
} else {
43
-
fmt.Printf("No %s %s found\n", status, mediaType)
44
-
}
45
-
return nil
46
-
}
47
-
48
-
fmt.Printf("Found %d %s:\n\n", len(items), mediaType)
49
-
for _, item := range items {
50
-
printer(item)
51
-
}
52
-
53
-
return nil
54
-
}
55
-
56
-
// PromptUserChoice prompts the user to select from a list of results
57
-
func PromptUserChoice(reader io.Reader, maxChoices int) (int, error) {
58
-
fmt.Print("\nEnter number to add (1-", maxChoices, "), or 0 to cancel: ")
59
-
60
-
var choice int
61
-
if reader != nil {
62
-
if _, err := fmt.Fscanf(reader, "%d", &choice); err != nil {
63
-
return 0, fmt.Errorf("invalid input")
64
-
}
65
-
} else {
66
-
if _, err := fmt.Scanf("%d", &choice); err != nil {
67
-
return 0, fmt.Errorf("invalid input")
68
-
}
69
-
}
70
-
71
-
if choice == 0 {
72
-
fmt.Println("Cancelled.")
73
-
return 0, nil
74
-
}
75
-
if choice < 1 || choice > maxChoices {
76
-
return 0, fmt.Errorf("invalid choice: %d", choice)
77
-
}
78
-
return choice, nil
79
-
}
80
-
81
-
// ParseID converts a string ID to int64
82
-
func ParseID(id string, itemType string) (int64, error) {
83
-
itemID, err := strconv.ParseInt(id, 10, 64)
84
-
if err != nil {
85
-
return 0, fmt.Errorf("invalid %s ID: %s", itemType, id)
86
-
}
87
-
return itemID, nil
88
-
}
89
-
90
-
// PrintSearchResults displays search results with a type-specific formatter
91
-
func PrintSearchResults[T models.Model](results []*models.Model, formatter func(*models.Model, int)) error {
92
-
if len(results) == 0 {
93
-
fmt.Println("No results found.")
94
-
return nil
95
-
}
96
-
97
-
fmt.Printf("Found %d result(s):\n\n", len(results))
98
-
for i, result := range results {
99
-
formatter(result, i+1)
100
-
}
101
-
return nil
102
-
}
···
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0