package ui import ( "fmt" "os" "os/exec" "strings" "time" gemlog "git.travisshears.com/travisshears/gemlog-cli/gemlog" tea "github.com/charmbracelet/bubbletea" "gopkg.in/yaml.v3" ) type Frontmatter struct { Title string `yaml:"title"` Date string `yaml:"date"` Slug string `yaml:"slug"` // Tags []string `yaml:"tags"` } // TODO: add edit command func EditPostCMD(config *gemlog.Config, id string, rev string) tea.Cmd { return func() tea.Msg { gemlogEntry, err := gemlog.ReadGemlogEntry(config, id) if err != nil { return ErrorMsg{fmt.Errorf("failed to read gemlog entry: %w", err)} } // Create a temporary file tmpFile, err := os.CreateTemp("/tmp", "gemlog-*.md") if err != nil { return ErrorMsg{fmt.Errorf("failed to create temporary file: %w", err)} } frontmatter := Frontmatter{ Title: gemlogEntry.Title, Date: gemlogEntry.Date.Format("2006-01-02"), Slug: gemlogEntry.Slug, // Tags: []string{}, // or nil if empty is ok } yamlData, err := yaml.Marshal(&frontmatter) if err != nil { return ErrorMsg{fmt.Errorf("failed to marshal frontmatter: %w", err)} } initialContent := append(yamlData, []byte("---\n"+gemlogEntry.Gemtxt)...) if _, err := tmpFile.Write(initialContent); err != nil { tmpFile.Close() os.Remove(tmpFile.Name()) return ErrorMsg{fmt.Errorf("failed to write initial content: %w", err)} } tmpFile.Close() // Get the editor from environment variable, default to vim editor := os.Getenv("EDITOR") if editor == "" { editor = "vim" } // Create the command to open the editor with the temp file c := exec.Command(editor, tmpFile.Name()) //nolint:gosec // Return tea.ExecProcess which will suspend the TUI and run the editor return tea.ExecProcess(c, func(err error) tea.Msg { defer os.Remove(tmpFile.Name()) // Clean up the temp file if err != nil { return ErrorMsg{fmt.Errorf("editor command failed: %w", err)} } // Read the contents of the file after editing content, readErr := os.ReadFile(tmpFile.Name()) if readErr != nil { return ErrorMsg{fmt.Errorf("failed to read file contents: %w", readErr)} } gemlogEntry, err := parsePost(string(content)) if err != nil { return ErrorMsg{fmt.Errorf("failed to parse post: %w", err)} } if err := gemlog.UpdateGemlogEntry(config, &gemlogEntry, id, rev); err != nil { return ErrorMsg{fmt.Errorf("failed to save gemlog entry: %w", err)} } // Return success with the content return Notification(fmt.Sprintf("Post updated: \ngemini://travisshears.com/gemlog/%s\n\n", gemlogEntry.Slug)) })() } } func WritePostCMD(config *gemlog.Config) tea.Cmd { return func() tea.Msg { // Create a temporary file tmpFile, err := os.CreateTemp("/tmp", "gemlog-*.md") if err != nil { return ErrorMsg{fmt.Errorf("failed to create temporary file: %w", err)} } if _, err := tmpFile.Write([]byte(defaultTemplate)); err != nil { tmpFile.Close() os.Remove(tmpFile.Name()) return ErrorMsg{fmt.Errorf("failed to write initial content: %w", err)} } tmpFile.Close() // Get the editor from environment variable, default to vim editor := os.Getenv("EDITOR") if editor == "" { editor = "vim" } // Create the command to open the editor with the temp file c := exec.Command(editor, tmpFile.Name()) //nolint:gosec // Return tea.ExecProcess which will suspend the TUI and run the editor return tea.ExecProcess(c, func(err error) tea.Msg { defer os.Remove(tmpFile.Name()) // Clean up the temp file if err != nil { return ErrorMsg{fmt.Errorf("editor command failed: %w", err)} } // Read the contents of the file after editing content, readErr := os.ReadFile(tmpFile.Name()) if readErr != nil { return ErrorMsg{fmt.Errorf("failed to read file contents: %w", readErr)} } gemlogEntry, err := parsePost(string(content)) if err != nil { return ErrorMsg{fmt.Errorf("failed to parse post: %w", err)} } if err := gemlog.SaveGemlogEntry(config, &gemlogEntry); err != nil { return ErrorMsg{fmt.Errorf("failed to save gemlog entry: %w", err)} } // Return success with the content return Notification(fmt.Sprintf("Post created: \ngemini://travisshears.com/gemlog/%s\n\n", gemlogEntry.Slug)) })() } } func parsePost(post string) (gemlog.GemlogEntry, error) { // split post on new lines lines := strings.Split(post, "\n") // Find the separator line "---" separatorIndex := -1 for i, line := range lines { if strings.TrimSpace(line) == "---" { separatorIndex = i break } } if separatorIndex == -1 { return gemlog.GemlogEntry{}, fmt.Errorf("no frontmatter separator '---' found") } // Extract frontmatter and body frontmatterLines := lines[:separatorIndex] bodyLines := lines[separatorIndex+1:] // Parse frontmatter YAML frontmatterYAML := strings.Join(frontmatterLines, "\n") var frontmatter Frontmatter if err := yaml.Unmarshal([]byte(frontmatterYAML), &frontmatter); err != nil { return gemlog.GemlogEntry{}, fmt.Errorf("failed to parse frontmatter: %w", err) } // Parse date date, err := time.Parse("2006-01-02", strings.TrimSpace(frontmatter.Date)) if err != nil { return gemlog.GemlogEntry{}, fmt.Errorf("failed to parse date: %w", err) } // Parse tags (comma-separated) // var tags []string // if frontmatter.Tags != "" { // tagParts := strings.Split(frontmatter.Tags, ",") // for _, tag := range tagParts { // trimmed := strings.TrimSpace(tag) // if trimmed != "" { // tags = append(tags, trimmed) // } // } // } // Join body lines body := strings.Join(bodyLines, "\n") return gemlog.GemlogEntry{ Title: frontmatter.Title, Date: date, Slug: frontmatter.Slug, // Tags: tags, Gemtxt: body, }, nil }