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 := ui.Spin("Creating note…", func() (*lunatask.Note, error) {
76 return builder.Create(cmd.Context())
77 })
78 if err != nil {
79 return err
80 }
81
82 if note == nil {
83 fmt.Fprintln(cmd.OutOrStdout(), ui.Warning.Render("Note already exists (duplicate source)"))
84
85 return nil
86 }
87
88 fmt.Fprintln(cmd.OutOrStdout(), ui.Success.Render("Created note: "+note.ID))
89
90 return nil
91}
92
93func resolveName(arg string) (string, error) {
94 if arg != "-" {
95 return arg, nil
96 }
97
98 scanner := bufio.NewScanner(os.Stdin)
99 if scanner.Scan() {
100 return strings.TrimSpace(scanner.Text()), nil
101 }
102
103 if err := scanner.Err(); err != nil {
104 return "", fmt.Errorf("reading stdin: %w", err)
105 }
106
107 return "", ErrNoInput
108}
109
110func applyNotebook(cmd *cobra.Command, builder *lunatask.NoteBuilder) error {
111 notebookKey, _ := cmd.Flags().GetString("notebook")
112 if notebookKey == "" {
113 cfg, err := config.Load()
114 if err != nil && !errors.Is(err, config.ErrNotFound) {
115 return err
116 }
117
118 if cfg != nil {
119 notebookKey = cfg.Defaults.Notebook
120 }
121 }
122
123 if notebookKey == "" {
124 return nil
125 }
126
127 cfg, err := config.Load()
128 if err != nil {
129 return err
130 }
131
132 notebook := cfg.NotebookByKey(notebookKey)
133 if notebook == nil {
134 return fmt.Errorf("%w: %s", ErrUnknownNotebook, notebookKey)
135 }
136
137 builder.InNotebook(notebook.ID)
138
139 return nil
140}
141
142func applyContent(cmd *cobra.Command, builder *lunatask.NoteBuilder) error {
143 content, _ := cmd.Flags().GetString("content")
144 if content == "" {
145 return nil
146 }
147
148 resolved, err := resolveContent(content)
149 if err != nil {
150 return err
151 }
152
153 builder.WithContent(resolved)
154
155 return nil
156}
157
158func resolveContent(content string) (string, error) {
159 if content != "-" {
160 return content, nil
161 }
162
163 data, err := os.ReadFile("/dev/stdin")
164 if err != nil {
165 return "", fmt.Errorf("reading stdin: %w", err)
166 }
167
168 return strings.TrimSpace(string(data)), nil
169}
170
171func applySource(cmd *cobra.Command, builder *lunatask.NoteBuilder) {
172 source, _ := cmd.Flags().GetString("source")
173 sourceID, _ := cmd.Flags().GetString("source-id")
174
175 if source != "" && sourceID != "" {
176 builder.FromSource(source, sourceID)
177 }
178}