1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
2//
3// SPDX-License-Identifier: AGPL-3.0-or-later
4
5package note
6
7import (
8 "bufio"
9 "errors"
10 "fmt"
11 "os"
12 "strings"
13
14 "git.secluded.site/go-lunatask"
15 "git.secluded.site/lune/internal/client"
16 "git.secluded.site/lune/internal/completion"
17 "git.secluded.site/lune/internal/config"
18 "git.secluded.site/lune/internal/ui"
19 "github.com/spf13/cobra"
20)
21
22// ErrUnknownNotebook indicates the specified notebook key was not found in config.
23var ErrUnknownNotebook = errors.New("unknown notebook key")
24
25// ErrNoInput indicates no input was provided on stdin.
26var ErrNoInput = errors.New("no input provided on stdin")
27
28// AddCmd creates a new note. Exported for potential use by shortcuts.
29var AddCmd = &cobra.Command{
30 Use: "add [NAME]",
31 Short: "Create a new note",
32 Long: `Create a new note in Lunatask.
33
34The note name is optional. Use flags to set additional properties.
35Use "-" as content flag value to read from stdin.`,
36 RunE: runAdd,
37}
38
39func init() {
40 AddCmd.Flags().StringP("notebook", "b", "", "Notebook key (from config)")
41 AddCmd.Flags().StringP("content", "c", "", "Note content (use - for stdin)")
42 AddCmd.Flags().String("source", "", "Source identifier")
43 AddCmd.Flags().String("source-id", "", "Source ID")
44
45 _ = AddCmd.RegisterFlagCompletionFunc("notebook", completion.Notebooks)
46}
47
48func runAdd(cmd *cobra.Command, args []string) error {
49 apiClient, err := client.New()
50 if err != nil {
51 return err
52 }
53
54 builder := apiClient.NewNote()
55
56 if len(args) > 0 {
57 name, err := resolveName(args[0])
58 if err != nil {
59 return err
60 }
61
62 builder.WithName(name)
63 }
64
65 if err := applyNotebook(cmd, builder); err != nil {
66 return err
67 }
68
69 if err := applyContent(cmd, builder); err != nil {
70 return err
71 }
72
73 applySource(cmd, builder)
74
75 note, err := builder.Create(cmd.Context())
76 if err != nil {
77 return err
78 }
79
80 if note == nil {
81 fmt.Fprintln(cmd.OutOrStdout(), ui.Warning.Render("Note already exists (duplicate source)"))
82
83 return nil
84 }
85
86 fmt.Fprintln(cmd.OutOrStdout(), ui.Success.Render("Created note: "+note.ID))
87
88 return nil
89}
90
91func resolveName(arg string) (string, error) {
92 if arg != "-" {
93 return arg, nil
94 }
95
96 scanner := bufio.NewScanner(os.Stdin)
97 if scanner.Scan() {
98 return strings.TrimSpace(scanner.Text()), nil
99 }
100
101 if err := scanner.Err(); err != nil {
102 return "", fmt.Errorf("reading stdin: %w", err)
103 }
104
105 return "", ErrNoInput
106}
107
108func applyNotebook(cmd *cobra.Command, builder *lunatask.NoteBuilder) error {
109 notebookKey, _ := cmd.Flags().GetString("notebook")
110 if notebookKey == "" {
111 cfg, err := config.Load()
112 if err != nil && !errors.Is(err, config.ErrNotFound) {
113 return err
114 }
115
116 if cfg != nil {
117 notebookKey = cfg.Defaults.Notebook
118 }
119 }
120
121 if notebookKey == "" {
122 return nil
123 }
124
125 cfg, err := config.Load()
126 if err != nil {
127 return err
128 }
129
130 notebook := cfg.NotebookByKey(notebookKey)
131 if notebook == nil {
132 return fmt.Errorf("%w: %s", ErrUnknownNotebook, notebookKey)
133 }
134
135 builder.InNotebook(notebook.ID)
136
137 return nil
138}
139
140func applyContent(cmd *cobra.Command, builder *lunatask.NoteBuilder) error {
141 content, _ := cmd.Flags().GetString("content")
142 if content == "" {
143 return nil
144 }
145
146 resolved, err := resolveContent(content)
147 if err != nil {
148 return err
149 }
150
151 builder.WithContent(resolved)
152
153 return nil
154}
155
156func resolveContent(content string) (string, error) {
157 if content != "-" {
158 return content, nil
159 }
160
161 data, err := os.ReadFile("/dev/stdin")
162 if err != nil {
163 return "", fmt.Errorf("reading stdin: %w", err)
164 }
165
166 return strings.TrimSpace(string(data)), nil
167}
168
169func applySource(cmd *cobra.Command, builder *lunatask.NoteBuilder) {
170 source, _ := cmd.Flags().GetString("source")
171 sourceID, _ := cmd.Flags().GetString("source-id")
172
173 if source != "" && sourceID != "" {
174 builder.FromSource(source, sourceID)
175 }
176}