add.go

  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}