architectural cleanup, fix frontmatter bugs
This commit is contained in:
parent
c4187f54f9
commit
603cd31002
|
|
@ -2,8 +2,6 @@ package metadata
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
|
@ -74,14 +72,11 @@ type FrontMatter struct {
|
|||
FileType string // org, md-yaml, md-toml, txt
|
||||
}
|
||||
|
||||
// Extract extracts front matter from a file
|
||||
func Extract(path string) (*FrontMatter, error) {
|
||||
ext := strings.ToLower(filepath.Ext(path))
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
text := string(data)
|
||||
// ParseFrontMatter extracts front matter from file content.
|
||||
// ext should be the file extension (e.g., ".md", ".org", ".txt").
|
||||
func ParseFrontMatter(content string, ext string) (*FrontMatter, error) {
|
||||
ext = strings.ToLower(ext)
|
||||
text := content
|
||||
|
||||
fm := &FrontMatter{}
|
||||
|
||||
|
|
@ -155,13 +150,10 @@ func Extract(path string) (*FrontMatter, error) {
|
|||
return fm, nil
|
||||
}
|
||||
|
||||
// Update updates front matter in a file
|
||||
func Update(path string, fm *FrontMatter) error {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
text := string(data)
|
||||
// UpdateFrontMatter updates front matter in file content and returns the new content.
|
||||
// originalContent is the current file content, fm is the new front matter to apply.
|
||||
func UpdateFrontMatter(originalContent string, fm *FrontMatter) (string, error) {
|
||||
text := originalContent
|
||||
|
||||
dateStr := time.Now().Format("2006-01-02 Mon 15:04")
|
||||
keywordsStr := FormatTags(fm.Tags, fm.FileType)
|
||||
|
|
@ -219,10 +211,10 @@ func Update(path string, fm *FrontMatter) error {
|
|||
newText = newFrontMatter + text
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unsupported file type: %s", fm.FileType)
|
||||
return "", fmt.Errorf("unsupported file type: %s", fm.FileType)
|
||||
}
|
||||
|
||||
return os.WriteFile(path, []byte(newText), 0644)
|
||||
return newText, nil
|
||||
}
|
||||
|
||||
// Generate generates front matter content for given parameters
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package metadata
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
|
|
@ -59,10 +58,6 @@ func (es Results) FromString(data string) (Results, error) {
|
|||
return nil, fmt.Errorf("line %d: identifier cannot be empty", lineNum+1)
|
||||
}
|
||||
|
||||
if strings.Contains(title, "|") {
|
||||
return nil, fmt.Errorf("line %d: title cannot contain '|'", lineNum+1)
|
||||
}
|
||||
|
||||
var tags []string
|
||||
if tagsStr != "" {
|
||||
if !tagPattern.MatchString(tagsStr) {
|
||||
|
|
@ -136,8 +131,10 @@ func Sort(md Results, sortType SortBy, order SortOrder) {
|
|||
}
|
||||
}
|
||||
|
||||
// ExtractMetadata extracts Denote metadata from a filename
|
||||
func ExtractMetadata(path string) *Metadata {
|
||||
// ParseFilename extracts Denote metadata from a filename only (no file I/O).
|
||||
// Returns metadata with Path, Identifier, Title (from filename), and Tags.
|
||||
// Caller should use ExtractTitleFromContent to get title from file content.
|
||||
func ParseFilename(path string) *Metadata {
|
||||
fname := filepath.Base(path)
|
||||
note := &Metadata{Path: path}
|
||||
|
||||
|
|
@ -146,17 +143,8 @@ func ExtractMetadata(path string) *Metadata {
|
|||
}
|
||||
|
||||
// Extract title from filename
|
||||
filenameTitle := ""
|
||||
if m := regexp.MustCompile(`--([^_\.]+)`).FindStringSubmatch(fname); m != nil {
|
||||
filenameTitle = strings.ReplaceAll(m[1], "-", " ")
|
||||
}
|
||||
|
||||
// Try to get title from file content, fall back to filename
|
||||
fileContentTitle := extractTitle(path)
|
||||
if fileContentTitle != "" {
|
||||
note.Title = fileContentTitle
|
||||
} else {
|
||||
note.Title = filenameTitle
|
||||
note.Title = strings.ReplaceAll(m[1], "-", " ")
|
||||
}
|
||||
|
||||
if m := regexp.MustCompile(`__(.+?)(?:\.|$)`).FindStringSubmatch(fname); m != nil {
|
||||
|
|
@ -166,35 +154,32 @@ func ExtractMetadata(path string) *Metadata {
|
|||
return note
|
||||
}
|
||||
|
||||
func extractTitle(path string) string {
|
||||
ext := strings.ToLower(filepath.Ext(strings.TrimSuffix(path, ".gpg")))
|
||||
// ExtractTitleFromContent extracts the title from file content.
|
||||
// ext should be the file extension (e.g., ".md", ".org", ".txt").
|
||||
// Returns empty string if no title found or unsupported extension.
|
||||
func ExtractTitleFromContent(content string, ext string) string {
|
||||
ext = strings.ToLower(ext)
|
||||
if ext != ".org" && ext != ".md" && ext != ".txt" {
|
||||
return ""
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
text := string(data)
|
||||
|
||||
// Try org-mode #+title: first, then fall back to first heading
|
||||
if ext == ".org" {
|
||||
if m := regexp.MustCompile(`(?m)^#\+title:\s*(.+)$`).FindStringSubmatch(text); m != nil {
|
||||
if m := regexp.MustCompile(`(?m)^#\+title:\s*(.+)$`).FindStringSubmatch(content); m != nil {
|
||||
return strings.TrimSpace(m[1])
|
||||
}
|
||||
// Fallback to first heading (lines starting with *)
|
||||
if m := regexp.MustCompile(`(?m)^\*+\s+(.+)$`).FindStringSubmatch(text); m != nil {
|
||||
if m := regexp.MustCompile(`(?m)^\*+\s+(.+)$`).FindStringSubmatch(content); m != nil {
|
||||
return strings.TrimSpace(m[1])
|
||||
}
|
||||
}
|
||||
|
||||
// Try markdown YAML front matter title: first, then fall back to # header
|
||||
if ext == ".md" {
|
||||
if m := regexp.MustCompile(`(?ms)^---\n.*?^title:\s*(.+?)$.*?^---`).FindStringSubmatch(text); m != nil {
|
||||
if m := regexp.MustCompile(`(?ms)^---\n.*?^title:\s*(.+?)$.*?^---`).FindStringSubmatch(content); m != nil {
|
||||
return strings.TrimSpace(strings.Trim(m[1], `"`))
|
||||
}
|
||||
if m := regexp.MustCompile(`(?m)^#\s+(.+)$`).FindStringSubmatch(text); m != nil {
|
||||
if m := regexp.MustCompile(`(?m)^#\s+(.+)$`).FindStringSubmatch(content); m != nil {
|
||||
return strings.TrimSpace(m[1])
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
// Package client provides 9P client helpers for connecting to the denote server.
|
||||
// Package client provides pure 9P client I/O operations for the denote server.
|
||||
// This package contains NO business logic - only read/write primitives.
|
||||
package client
|
||||
|
||||
import (
|
||||
"denote/internal/metadata"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
|
@ -79,65 +79,3 @@ func ReadFields(f *client.Fsys, identifier string, fields ...string) (map[string
|
|||
return result, nil
|
||||
}
|
||||
|
||||
func ReadIndex() (metadata.Results, error) {
|
||||
var results metadata.Results
|
||||
|
||||
err := With9P(func(f *client.Fsys) error {
|
||||
indexContent, err := ReadFile(f, "index")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read index: %w", err)
|
||||
}
|
||||
|
||||
results, err = results.FromString(indexContent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Populate Path for each note
|
||||
for _, note := range results {
|
||||
fields, err := ReadFields(f, note.Identifier, "path")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read fields for %s: %w", note.Identifier, err)
|
||||
}
|
||||
note.Path = fields["path"]
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func SetFilter(filterQuery string) error {
|
||||
return With9P(func(f *client.Fsys) error {
|
||||
var cmd string
|
||||
if filterQuery == "" {
|
||||
cmd = "filter"
|
||||
} else {
|
||||
cmd = "filter " + filterQuery
|
||||
}
|
||||
return WriteFile(f, "ctl", cmd)
|
||||
})
|
||||
}
|
||||
|
||||
func IdentifierToPath(identifier string) (string, error) {
|
||||
if err := SetFilter(fmt.Sprintf("date:%s", identifier)); err != nil {
|
||||
return "", fmt.Errorf("failed to set filter: %w", err)
|
||||
}
|
||||
defer SetFilter("")
|
||||
|
||||
notes, err := ReadIndex()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read index: %w", err)
|
||||
}
|
||||
|
||||
if len(notes) == 0 {
|
||||
return "", fmt.Errorf("no note found with identifier %s", identifier)
|
||||
}
|
||||
|
||||
return notes[0].Path, nil
|
||||
}
|
||||
|
|
@ -35,7 +35,6 @@ import (
|
|||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
|
|
@ -77,7 +76,6 @@ const (
|
|||
var fileNames = []string{"path", "title", "keywords", "ctl"}
|
||||
|
||||
type server struct {
|
||||
dir string
|
||||
notes metadata.Results
|
||||
mu sync.RWMutex
|
||||
eventChan chan string
|
||||
|
|
@ -127,58 +125,15 @@ func emitEvent(id string, op rune) {
|
|||
|
||||
|
||||
// Getdir returns the denote directory
|
||||
func getdir() string {
|
||||
return fmt.Sprintf("%s/doc", os.Getenv("HOME"))
|
||||
}
|
||||
|
||||
// loadData returns metadata for notes in a directory matching given filters
|
||||
func loadData(filters []*metadata.Filter) (metadata.Results, error) {
|
||||
var notes metadata.Results
|
||||
err := filepath.Walk(getdir(), func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
if note := metadata.ExtractMetadata(path); note.Identifier != "" {
|
||||
match := true
|
||||
for _, filt := range filters {
|
||||
if !filt.IsMatch(note) {
|
||||
match = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if match {
|
||||
notes = append(notes, note)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return notes, nil
|
||||
}
|
||||
|
||||
|
||||
|
||||
// StartServer starts the 9P fileserver in the background
|
||||
func StartServer() error {
|
||||
// StartServer starts the 9P fileserver in the background with pre-loaded metadata.
|
||||
// initialData should contain all notes to be served - typically loaded by sync.LoadAll().
|
||||
func StartServer(initialData metadata.Results) error {
|
||||
if srv != nil {
|
||||
return fmt.Errorf("server already running")
|
||||
}
|
||||
|
||||
// Load all notes
|
||||
notes, err := loadData([]*metadata.Filter{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load notes: %w", err)
|
||||
}
|
||||
|
||||
srv = &server{
|
||||
dir: getdir(),
|
||||
notes: notes,
|
||||
notes: initialData,
|
||||
eventChan: make(chan string, 100),
|
||||
eventSubs: make(map[uint64]chan string),
|
||||
}
|
||||
|
|
@ -669,6 +624,13 @@ func (s *server) write(cs *connState, fc *plan9.Fcall) *plan9.Fcall {
|
|||
break
|
||||
}
|
||||
}
|
||||
// Also remove from filtered notes
|
||||
for i, n := range s.filteredNotes {
|
||||
if n.Identifier == noteID {
|
||||
s.filteredNotes = append(s.filteredNotes[:i], s.filteredNotes[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,115 @@
|
|||
package sync
|
||||
|
||||
import (
|
||||
"denote/internal/metadata"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// SupportsFrontMatter returns true if the file extension supports frontmatter.
|
||||
func SupportsFrontMatter(path string) bool {
|
||||
ext := strings.ToLower(filepath.Ext(path))
|
||||
return ext == ".org" || ext == ".md" || ext == ".txt"
|
||||
}
|
||||
|
||||
// ExtractMetadata extracts metadata from a file (combines filename and content parsing).
|
||||
// This is the I/O wrapper around metadata's pure functions.
|
||||
func ExtractMetadata(path string) (*metadata.Metadata, error) {
|
||||
// Parse filename (no I/O)
|
||||
note := metadata.ParseFilename(path)
|
||||
|
||||
// Check if we should try to read file content for title
|
||||
ext := strings.ToLower(filepath.Ext(path))
|
||||
// Don't read encrypted files or unsupported types
|
||||
if ext == ".gpg" || (ext != ".org" && ext != ".md" && ext != ".txt") {
|
||||
return note, nil
|
||||
}
|
||||
|
||||
// Read file content
|
||||
content, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
// If we can't read the file, just use filename metadata
|
||||
return note, nil
|
||||
}
|
||||
|
||||
// Try to extract title from content
|
||||
if title := metadata.ExtractTitleFromContent(string(content), ext); title != "" {
|
||||
note.Title = title
|
||||
}
|
||||
|
||||
return note, nil
|
||||
}
|
||||
|
||||
// ExtractFrontMatter reads a file and parses its front matter.
|
||||
func ExtractFrontMatter(path string) (*metadata.FrontMatter, error) {
|
||||
ext := strings.ToLower(filepath.Ext(path))
|
||||
|
||||
// Don't try to parse front matter from encrypted files
|
||||
if ext == ".gpg" {
|
||||
return &metadata.FrontMatter{}, nil
|
||||
}
|
||||
|
||||
content, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read file: %w", err)
|
||||
}
|
||||
|
||||
return metadata.ParseFrontMatter(string(content), ext)
|
||||
}
|
||||
|
||||
// UpdateFrontMatter updates the front matter in a file.
|
||||
func UpdateFrontMatter(path string, fm *metadata.FrontMatter) error {
|
||||
// Only update frontmatter for supported file types
|
||||
if !SupportsFrontMatter(path) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read current content
|
||||
content, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read file: %w", err)
|
||||
}
|
||||
|
||||
// Update front matter (pure function)
|
||||
newContent, err := metadata.UpdateFrontMatter(string(content), fm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update front matter: %w", err)
|
||||
}
|
||||
|
||||
// Write back to file
|
||||
return os.WriteFile(path, []byte(newContent), 0644)
|
||||
}
|
||||
|
||||
// LoadAll walks a directory and extracts metadata from all denote files.
|
||||
func LoadAll(dir string) (metadata.Results, error) {
|
||||
var notes metadata.Results
|
||||
|
||||
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
note, err := ExtractMetadata(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Only include files with valid identifiers
|
||||
if note.Identifier != "" {
|
||||
notes = append(notes, note)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to walk directory: %w", err)
|
||||
}
|
||||
|
||||
return notes, nil
|
||||
}
|
||||
|
|
@ -61,9 +61,11 @@ func WatchAcmeLog() {
|
|||
exists := noteExists(identifier)
|
||||
|
||||
if exists {
|
||||
// Existing note: sync front matter (triggers 'u' event)
|
||||
if err := syncFrontMatter(ev.Name, identifier); err != nil {
|
||||
log.Printf("sync: failed to sync %s: %v", ev.Name, err)
|
||||
// Existing note: sync front matter if supported (triggers 'u' event)
|
||||
if SupportsFrontMatter(ev.Name) {
|
||||
if err := syncFrontMatter(ev.Name, identifier); err != nil {
|
||||
log.Printf("sync: failed to sync %s: %v", ev.Name, err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// New note: register with 9P (triggers 'n' event)
|
||||
|
|
@ -111,7 +113,10 @@ func noteExists(identifier string) bool {
|
|||
|
||||
// registerNewNote writes to /new to register a new note (triggers 'n' event)
|
||||
func registerNewNote(path string) error {
|
||||
meta := metadata.ExtractMetadata(path)
|
||||
meta, err := ExtractMetadata(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Format as 'title' tag1,tag2
|
||||
var newInput string
|
||||
|
|
@ -129,7 +134,7 @@ func registerNewNote(path string) error {
|
|||
// syncFrontMatter reads the file's front matter and writes it to 9P metadata
|
||||
func syncFrontMatter(path, identifier string) error {
|
||||
// Extract front matter from file
|
||||
fm, err := metadata.Extract(path)
|
||||
fm, err := ExtractFrontMatter(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -201,7 +206,7 @@ func SyncAll() error {
|
|||
continue
|
||||
}
|
||||
|
||||
// Read path and extension
|
||||
// Read path
|
||||
pathFid, err := f.Open("n/"+identifier+"/path", 0)
|
||||
if err != nil {
|
||||
log.Printf("sync: failed to read path for %s: %v", identifier, err)
|
||||
|
|
@ -212,18 +217,17 @@ func SyncAll() error {
|
|||
pathFid.Close()
|
||||
path := strings.TrimSpace(string(pathBuf[:n]))
|
||||
|
||||
extFid, err := f.Open("n/"+identifier+"/extension", 0)
|
||||
if err != nil {
|
||||
log.Printf("sync: failed to read extension for %s: %v", identifier, err)
|
||||
// Check if file exists on filesystem
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
// File was deleted, emit 'd' event to clean up metadata
|
||||
if err := p9client.WriteFile(f, "n/"+identifier+"/ctl", "d"); err != nil {
|
||||
log.Printf("sync: failed to emit delete event for %s: %v", identifier, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
extBuf := make([]byte, 8192)
|
||||
n, _ = extFid.Read(extBuf)
|
||||
extFid.Close()
|
||||
ext := strings.TrimSpace(string(extBuf[:n]))
|
||||
|
||||
// Route to appropriate sync function
|
||||
if ext == ".org" || ext == ".md" || ext == ".txt" {
|
||||
// Route to appropriate sync function based on file type
|
||||
if SupportsFrontMatter(path) {
|
||||
if err := syncDenoteFile(f, path, identifier); err != nil {
|
||||
log.Printf("sync: failed to sync denote file %s: %v", identifier, err)
|
||||
}
|
||||
|
|
@ -241,7 +245,7 @@ func SyncAll() error {
|
|||
// syncDenoteFile syncs a text file with front matter
|
||||
func syncDenoteFile(f *client.Fsys, path, identifier string) error {
|
||||
// Extract front matter from file
|
||||
fm, err := metadata.Extract(path)
|
||||
fm, err := ExtractFrontMatter(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -271,7 +275,7 @@ func syncDenoteFile(f *client.Fsys, path, identifier string) error {
|
|||
// syncNonDenoteFile syncs a binary file (metadata from filename)
|
||||
func syncNonDenoteFile(f *client.Fsys, path, identifier string) error {
|
||||
// Extract metadata from filename
|
||||
meta := metadata.ExtractMetadata(path)
|
||||
meta := metadata.ParseFilename(path)
|
||||
|
||||
// Write title (triggers 'u' event)
|
||||
titlePath := "n/" + identifier + "/title"
|
||||
|
|
|
|||
158
main.go
158
main.go
|
|
@ -77,6 +77,42 @@ func renameNote(oldPath, title string, keywords []string, identifier string) (st
|
|||
return newPath, nil
|
||||
}
|
||||
|
||||
// Helper functions for 9P operations
|
||||
|
||||
// readIndex reads and parses the index from 9P server.
|
||||
func readIndex(f *client.Fsys) (metadata.Results, error) {
|
||||
indexContent, err := p9client.ReadFile(f, "index")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read index: %w", err)
|
||||
}
|
||||
|
||||
var results metadata.Results
|
||||
results, err = results.FromString(indexContent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Populate Path for each note
|
||||
for _, note := range results {
|
||||
path, err := p9client.ReadFile(f, "n/"+note.Identifier+"/path")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read path for %s: %w", note.Identifier, err)
|
||||
}
|
||||
note.Path = path
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// setFilter sets or clears the filter on the 9P server.
|
||||
func setFilter(f *client.Fsys, filterQuery string) error {
|
||||
cmd := "filter"
|
||||
if filterQuery != "" {
|
||||
cmd = "filter " + filterQuery
|
||||
}
|
||||
return p9client.WriteFile(f, "ctl", cmd)
|
||||
}
|
||||
|
||||
// Event handling
|
||||
|
||||
func eventListener() {
|
||||
|
|
@ -121,6 +157,10 @@ func eventListener() {
|
|||
if err := handleNewEvent(f, identifier); err != nil {
|
||||
log.Printf("error handling new for %s: %v", identifier, err)
|
||||
}
|
||||
case "d":
|
||||
if err := handleDeleteEvent(identifier); err != nil {
|
||||
log.Printf("error handling delete for %s: %v", identifier, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -133,7 +173,7 @@ func eventListener() {
|
|||
}
|
||||
|
||||
func handleUpdateEvent(f *client.Fsys, identifier string) error {
|
||||
fields, err := p9client.ReadFields(f, identifier, "title", "keywords", "path", "extension")
|
||||
fields, err := p9client.ReadFields(f, identifier, "title", "keywords", "path")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -141,7 +181,6 @@ func handleUpdateEvent(f *client.Fsys, identifier string) error {
|
|||
title := fields["title"]
|
||||
keywords := fields["keywords"]
|
||||
path := fields["path"]
|
||||
ext := fields["extension"]
|
||||
|
||||
var tags []string
|
||||
if keywords != "" {
|
||||
|
|
@ -150,9 +189,8 @@ func handleUpdateEvent(f *client.Fsys, identifier string) error {
|
|||
}
|
||||
}
|
||||
|
||||
isDenoteFile := ext == ".org" || ext == ".md" || ext == ".txt"
|
||||
|
||||
if isDenoteFile {
|
||||
if sync.SupportsFrontMatter(path) {
|
||||
ext := strings.ToLower(filepath.Ext(path))
|
||||
var fileType string
|
||||
switch ext {
|
||||
case ".org":
|
||||
|
|
@ -170,12 +208,13 @@ func handleUpdateEvent(f *client.Fsys, identifier string) error {
|
|||
FileType: fileType,
|
||||
}
|
||||
|
||||
if err := metadata.Update(path, fm); err != nil {
|
||||
if err := sync.UpdateFrontMatter(path, fm); err != nil {
|
||||
log.Printf("failed to update front matter for %s: %v", identifier, err)
|
||||
}
|
||||
}
|
||||
|
||||
// After updating content, check if a rename is needed
|
||||
ext := strings.ToLower(filepath.Ext(strings.TrimSuffix(path, ".gpg")))
|
||||
dir := filepath.Dir(path)
|
||||
if dir == "." {
|
||||
dir = denoteDir
|
||||
|
|
@ -284,13 +323,42 @@ func main() {
|
|||
args := os.Args[1:]
|
||||
if len(args) == 1 {
|
||||
if identifier, ok := strings.CutPrefix(args[0], "denote:"); ok {
|
||||
path, err := p9client.IdentifierToPath(identifier)
|
||||
// Find note by identifier using 9P
|
||||
var notePath string
|
||||
err := p9client.With9P(func(f *client.Fsys) error {
|
||||
// Set filter to find the identifier
|
||||
if err := p9client.WriteFile(f, "ctl", "filter date:"+identifier); err != nil {
|
||||
return err
|
||||
}
|
||||
defer p9client.WriteFile(f, "ctl", "filter") // Clear filter
|
||||
|
||||
// Read index
|
||||
indexContent, err := p9client.ReadFile(f, "index")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Parse index
|
||||
var results metadata.Results
|
||||
results, err = results.FromString(indexContent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(results) == 0 {
|
||||
return fmt.Errorf("no note found with identifier %s", identifier)
|
||||
}
|
||||
|
||||
// Read path for first result
|
||||
notePath, err = p9client.ReadFile(f, "n/"+results[0].Identifier+"/path")
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error finding note: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println(path)
|
||||
if err := exec.Command("plumb", path).Run(); err != nil {
|
||||
fmt.Println(notePath)
|
||||
if err := exec.Command("plumb", notePath).Run(); err != nil {
|
||||
log.Printf("failed to plumb identifier: %v", err)
|
||||
}
|
||||
return
|
||||
|
|
@ -303,8 +371,14 @@ func main() {
|
|||
}
|
||||
}
|
||||
|
||||
// 9p server startup
|
||||
if err := p9server.StartServer(); err != nil {
|
||||
// Load metadata from filesystem
|
||||
notes, err := sync.LoadAll(denoteDir)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "warning: failed to load notes: %v\n", err)
|
||||
}
|
||||
|
||||
// 9p server startup with pre-loaded data
|
||||
if err := p9server.StartServer(notes); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "warning: failed to start fileserver: %v\n", err)
|
||||
}
|
||||
|
||||
|
|
@ -332,10 +406,15 @@ func main() {
|
|||
}
|
||||
|
||||
// get initial results (clear any filter, read index)
|
||||
if err := p9client.SetFilter(""); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
rs, err := p9client.ReadIndex()
|
||||
var rs metadata.Results
|
||||
err = p9client.With9P(func(f *client.Fsys) error {
|
||||
if err := setFilter(f, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
var err error
|
||||
rs, err = readIndex(f)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
|
@ -505,11 +584,15 @@ func main() {
|
|||
|
||||
// Get current results and apply changes
|
||||
// Read unfiltered index
|
||||
if err := p9client.SetFilter(""); err != nil {
|
||||
log.Printf("failed to clear filter: %v", err)
|
||||
break
|
||||
}
|
||||
current, err := p9client.ReadIndex()
|
||||
var current metadata.Results
|
||||
err = p9client.With9P(func(f *client.Fsys) error {
|
||||
if err := setFilter(f, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
var err error
|
||||
current, err = readIndex(f)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("failed to get current results: %v", err)
|
||||
break
|
||||
|
|
@ -577,14 +660,16 @@ func performSearch(w *acme.Win, searchText string) {
|
|||
// Build filter query string for ctl
|
||||
filterQuery := strings.Join(filterArgs, " ")
|
||||
|
||||
// Set filter on server
|
||||
if err := p9client.SetFilter(filterQuery); err != nil {
|
||||
log.Printf("failed to set filter: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Read filtered index
|
||||
rs, err := p9client.ReadIndex()
|
||||
// Set filter and read index
|
||||
var rs metadata.Results
|
||||
err := p9client.With9P(func(f *client.Fsys) error {
|
||||
if err := setFilter(f, filterQuery); err != nil {
|
||||
return err
|
||||
}
|
||||
var err error
|
||||
rs, err = readIndex(f)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("search error: %v", err)
|
||||
return
|
||||
|
|
@ -603,12 +688,15 @@ func refreshWindow(w *acme.Win, rs metadata.Results) {
|
|||
}
|
||||
|
||||
func refreshWindowWithDefaults(w *acme.Win) {
|
||||
// Clear filter and read index
|
||||
if err := p9client.SetFilter(""); err != nil {
|
||||
log.Printf("error clearing filter: %v", err)
|
||||
return
|
||||
}
|
||||
rs, err := p9client.ReadIndex()
|
||||
var rs metadata.Results
|
||||
err := p9client.With9P(func(f *client.Fsys) error {
|
||||
if err := setFilter(f, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
var err error
|
||||
rs, err = readIndex(f)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("error refreshing: %v", err)
|
||||
return
|
||||
|
|
@ -728,7 +816,7 @@ func watchNoteWindow(win *acme.Win, path string) {
|
|||
win.WriteEvent(e)
|
||||
|
||||
encryptedPath := path + ".gpg"
|
||||
meta := metadata.ExtractMetadata(encryptedPath)
|
||||
meta := metadata.ParseFilename(encryptedPath)
|
||||
|
||||
var newInput string
|
||||
if len(meta.Tags) > 0 {
|
||||
|
|
|
|||
Loading…
Reference in New Issue