org/internal/parser/writer.go

211 lines
4.9 KiB
Go

package parser
import (
"bufio"
"fmt"
"os"
"strings"
"github.com/rwejlgaard/org/internal/model"
)
// Save writes the org file back to disk
func Save(orgFile *model.OrgFile) error {
// Check if this is a multi-file org (directory-based)
// In multi-file mode, top-level items have SourceFile set and represent files
isMultiFile := false
if len(orgFile.Items) > 0 && orgFile.Items[0].SourceFile != "" {
isMultiFile = true
}
if isMultiFile {
return saveMultiFile(orgFile)
}
// Single file mode
file, err := os.Create(orgFile.Path)
if err != nil {
return err
}
defer file.Close()
writer := bufio.NewWriter(file)
defer writer.Flush()
for _, item := range orgFile.Items {
if err := writeItem(writer, item); err != nil {
return err
}
}
return nil
}
// saveMultiFile saves items back to their individual source files
func saveMultiFile(orgFile *model.OrgFile) error {
// Group items by source file
fileItems := make(map[string][]*model.Item)
for _, fileItem := range orgFile.Items {
if fileItem.SourceFile == "" {
continue
}
// The children of this file item are the actual items to save
fileItems[fileItem.SourceFile] = fileItem.Children
}
// Save each file
for filePath, items := range fileItems {
if err := saveItemsToFile(filePath, items); err != nil {
return err
}
}
return nil
}
// saveItemsToFile writes a list of items to a specific file
func saveItemsToFile(filePath string, items []*model.Item) error {
file, err := os.Create(filePath)
if err != nil {
return err
}
defer file.Close()
writer := bufio.NewWriter(file)
defer writer.Flush()
for _, item := range items {
// Decrement level since we're saving to individual files
decrementedItem := decrementItemLevelForSave(item)
if err := writeItem(writer, decrementedItem); err != nil {
return err
}
}
return nil
}
// decrementItemLevelForSave creates a copy of an item with decremented levels for saving
func decrementItemLevelForSave(item *model.Item) *model.Item {
copied := *item
copied.Level--
copiedChildren := make([]*model.Item, len(item.Children))
for i, child := range item.Children {
copiedChildren[i] = decrementItemLevelForSave(child)
}
copied.Children = copiedChildren
return &copied
}
// writeItem recursively writes an item and its children
func writeItem(writer *bufio.Writer, item *model.Item) error {
// Write heading
stars := strings.Repeat("*", item.Level)
line := stars
if item.State != model.StateNone {
line += " " + string(item.State)
}
if item.Priority != model.PriorityNone {
line += " [#" + string(item.Priority) + "]"
}
line += " " + item.Title
// Add tags if present
if len(item.Tags) > 0 {
line += " :" + strings.Join(item.Tags, ":") + ":"
}
line += "\n"
if _, err := writer.WriteString(line); err != nil {
return err
}
// Write scheduling info if not already in notes
hasScheduled := false
hasDeadline := false
hasLogbook := false
hasProperties := false
for _, note := range item.Notes {
if strings.Contains(note, "SCHEDULED:") {
hasScheduled = true
}
if strings.Contains(note, "DEADLINE:") {
hasDeadline = true
}
if strings.Contains(note, ":LOGBOOK:") {
hasLogbook = true
}
if strings.Contains(note, ":PROPERTIES:") {
hasProperties = true
}
}
if item.Scheduled != nil && !hasScheduled {
scheduledLine := fmt.Sprintf("SCHEDULED: <%s>\n", FormatOrgDate(*item.Scheduled))
if _, err := writer.WriteString(scheduledLine); err != nil {
return err
}
}
if item.Deadline != nil && !hasDeadline {
deadlineLine := fmt.Sprintf("DEADLINE: <%s>\n", FormatOrgDate(*item.Deadline))
if _, err := writer.WriteString(deadlineLine); err != nil {
return err
}
}
// Write effort in :PROPERTIES: drawer if not already in notes
if item.Effort != "" && !hasProperties {
if _, err := writer.WriteString(":PROPERTIES:\n"); err != nil {
return err
}
effortLine := fmt.Sprintf(":EFFORT: %s\n", item.Effort)
if _, err := writer.WriteString(effortLine); err != nil {
return err
}
if _, err := writer.WriteString(":END:\n"); err != nil {
return err
}
}
// Write clock entries in :LOGBOOK: drawer if not already in notes
if len(item.ClockEntries) > 0 && !hasLogbook {
if _, err := writer.WriteString(":LOGBOOK:\n"); err != nil {
return err
}
for _, entry := range item.ClockEntries {
clockLine := fmt.Sprintf("CLOCK: [%s]", formatClockTimestamp(entry.Start))
if entry.End != nil {
clockLine += fmt.Sprintf("--[%s]", formatClockTimestamp(*entry.End))
}
clockLine += "\n"
if _, err := writer.WriteString(clockLine); err != nil {
return err
}
}
if _, err := writer.WriteString(":END:\n"); err != nil {
return err
}
}
// Write notes
for _, note := range item.Notes {
if _, err := writer.WriteString(note + "\n"); err != nil {
return err
}
}
// Write children
for _, child := range item.Children {
if err := writeItem(writer, child); err != nil {
return err
}
}
return nil
}