continue refactoring
This commit is contained in:
parent
ca31e9e1d4
commit
6aaf6cfa91
|
|
@ -1,4 +1,4 @@
|
|||
package tmpl
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
|
@ -0,0 +1,205 @@
|
|||
package metadata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Metadata is the metadata encoded into Denote-style
|
||||
// file names.
|
||||
type Metadata struct {
|
||||
Path string
|
||||
Identifier string
|
||||
Title string
|
||||
Tags []string
|
||||
Extension string
|
||||
}
|
||||
|
||||
type Results []*Metadata
|
||||
|
||||
func (es Results) Bytes() []byte {
|
||||
var buf strings.Builder
|
||||
for _, e := range es {
|
||||
title := e.Title
|
||||
if title == "" {
|
||||
title = "(untitled)"
|
||||
}
|
||||
|
||||
tags := strings.Join(e.Tags, ",")
|
||||
fmt.Fprintf(&buf, "%s | %s | %s\n", e.Identifier, title, tags)
|
||||
}
|
||||
return []byte(buf.String())
|
||||
}
|
||||
|
||||
func (es Results) FromString(data string) (Results, error) {
|
||||
var results Results
|
||||
lines := strings.Split(strings.TrimSpace(data), "\n")
|
||||
// Allow lowercase unicode letters and digits, no spaces
|
||||
tagPattern := regexp.MustCompile(`^([\p{Ll}\p{Nd}]+,)*[\p{Ll}\p{Nd}]+$`)
|
||||
|
||||
for lineNum, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
parts := strings.Split(line, "|")
|
||||
if len(parts) != 3 {
|
||||
return nil, fmt.Errorf("line %d: expected 3 columns, got %d (line: %q)", lineNum+1, len(parts), line)
|
||||
}
|
||||
|
||||
identifier := strings.TrimSpace(parts[0])
|
||||
title := strings.TrimSpace(parts[1])
|
||||
tagsStr := strings.TrimSpace(parts[2])
|
||||
|
||||
if identifier == "" {
|
||||
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) {
|
||||
return nil, fmt.Errorf("line %d: tags must be comma-delimited lowercase unicode words (no spaces): got '%s'", lineNum+1, tagsStr)
|
||||
}
|
||||
tags = strings.Split(tagsStr, ",")
|
||||
} else {
|
||||
tags = []string{}
|
||||
}
|
||||
|
||||
results = append(results, &Metadata{
|
||||
Identifier: identifier,
|
||||
Title: title,
|
||||
Tags: tags,
|
||||
})
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func SlicesEqual(a, b []string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := range a {
|
||||
if a[i] != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type SortBy string
|
||||
|
||||
const (
|
||||
SortById SortBy = "id"
|
||||
SortByDate SortBy = "date"
|
||||
SortByTitle SortBy = "title"
|
||||
)
|
||||
|
||||
type SortOrder int
|
||||
|
||||
const (
|
||||
SortOrderAsc SortOrder = iota
|
||||
SortOrderDesc
|
||||
)
|
||||
|
||||
// Sort organizes a list of notes by sortType and order using metadata.
|
||||
func Sort(md Results, sortType SortBy, order SortOrder) {
|
||||
switch sortType {
|
||||
case SortById, SortByDate:
|
||||
sort.Slice(md, func(i, j int) bool {
|
||||
if order == SortOrderAsc {
|
||||
return md[i].Identifier < md[j].Identifier // Reverse chronological by default
|
||||
} else {
|
||||
return md[i].Identifier > md[j].Identifier // Reverse chronological by default
|
||||
}
|
||||
})
|
||||
case SortByTitle:
|
||||
sort.Slice(md, func(i, j int) bool {
|
||||
if order == SortOrderAsc {
|
||||
return strings.ToLower(md[i].Title) < strings.ToLower(md[j].Title)
|
||||
} else {
|
||||
return strings.ToLower(md[i].Title) > strings.ToLower(md[j].Title)
|
||||
}
|
||||
})
|
||||
default:
|
||||
sort.Slice(md, func(i, j int) bool {
|
||||
return md[i].Identifier > md[j].Identifier // Reverse chronological by default
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ExtractMetadata extracts Denote metadata from a filename
|
||||
func ExtractMetadata(path string) *Metadata {
|
||||
fname := filepath.Base(path)
|
||||
note := &Metadata{Path: path}
|
||||
|
||||
if m := regexp.MustCompile(`^(\d{8}T\d{6})`).FindStringSubmatch(fname); m != nil {
|
||||
note.Identifier = m[1]
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
if m := regexp.MustCompile(`__(.+?)(?:\.|$)`).FindStringSubmatch(fname); m != nil {
|
||||
note.Tags = strings.Split(m[1], "_")
|
||||
}
|
||||
note.Extension = filepath.Ext(fname)
|
||||
|
||||
return note
|
||||
}
|
||||
|
||||
func extractTitle(path string) string {
|
||||
ext := strings.ToLower(filepath.Ext(path))
|
||||
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 {
|
||||
return strings.TrimSpace(m[1])
|
||||
}
|
||||
// Fallback to first heading (lines starting with *)
|
||||
if m := regexp.MustCompile(`(?m)^\*+\s+(.+)$`).FindStringSubmatch(text); 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 {
|
||||
return strings.TrimSpace(strings.Trim(m[1], `"`))
|
||||
}
|
||||
if m := regexp.MustCompile(`(?m)^#\s+(.+)$`).FindStringSubmatch(text); m != nil {
|
||||
return strings.TrimSpace(m[1])
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
|
@ -31,6 +31,7 @@ Notes:
|
|||
package fs
|
||||
|
||||
import (
|
||||
"denote/internal/metadata"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
|
|
@ -38,7 +39,6 @@ import (
|
|||
"path/filepath"
|
||||
"regexp"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
|
@ -47,92 +47,6 @@ import (
|
|||
"9fans.net/go/plan9/client"
|
||||
)
|
||||
|
||||
// Metadata is the metadata encoded into Denote-style
|
||||
// file names.
|
||||
type Metadata struct {
|
||||
Path string
|
||||
Identifier string
|
||||
Title string
|
||||
Tags []string
|
||||
Extension string
|
||||
}
|
||||
type Results []*Metadata
|
||||
|
||||
func (es Results) Bytes() []byte {
|
||||
var buf strings.Builder
|
||||
for _, e := range es {
|
||||
title := e.Title
|
||||
if title == "" {
|
||||
title = "(untitled)"
|
||||
}
|
||||
|
||||
tags := strings.Join(e.Tags, ",")
|
||||
fmt.Fprintf(&buf, "%s | %s | %s\n", e.Identifier, title, tags)
|
||||
}
|
||||
return []byte(buf.String())
|
||||
}
|
||||
|
||||
func (es Results) FromString(data string) (Results, error) {
|
||||
var results Results
|
||||
lines := strings.Split(strings.TrimSpace(data), "\n")
|
||||
// Allow lowercase unicode letters and digits, no spaces
|
||||
tagPattern := regexp.MustCompile(`^([\p{Ll}\p{Nd}]+,)*[\p{Ll}\p{Nd}]+$`)
|
||||
|
||||
for lineNum, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
parts := strings.Split(line, "|")
|
||||
if len(parts) != 3 {
|
||||
return nil, fmt.Errorf("line %d: expected 3 columns, got %d (line: %q)", lineNum+1, len(parts), line)
|
||||
}
|
||||
|
||||
identifier := strings.TrimSpace(parts[0])
|
||||
title := strings.TrimSpace(parts[1])
|
||||
tagsStr := strings.TrimSpace(parts[2])
|
||||
|
||||
if identifier == "" {
|
||||
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) {
|
||||
return nil, fmt.Errorf("line %d: tags must be comma-delimited lowercase unicode words (no spaces): got '%s'", lineNum+1, tagsStr)
|
||||
}
|
||||
tags = strings.Split(tagsStr, ",")
|
||||
} else {
|
||||
tags = []string{}
|
||||
}
|
||||
|
||||
results = append(results, &Metadata{
|
||||
Identifier: identifier,
|
||||
Title: title,
|
||||
Tags: tags,
|
||||
})
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func SlicesEqual(a, b []string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := range a {
|
||||
if a[i] != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Filter matches a given field in a Result to a regular expression
|
||||
type Filter struct {
|
||||
field FilterField
|
||||
|
|
@ -207,7 +121,7 @@ func NewFilter(arg string) (*Filter, error) {
|
|||
}
|
||||
|
||||
// IsMatch checks if a note matches this filter
|
||||
func (f *Filter) IsMatch(n *Metadata) bool {
|
||||
func (f *Filter) IsMatch(n *metadata.Metadata) bool {
|
||||
result := false
|
||||
switch f.field {
|
||||
case FilterDate:
|
||||
|
|
@ -237,46 +151,6 @@ func (f *Filter) IsMatch(n *Metadata) bool {
|
|||
return result
|
||||
}
|
||||
|
||||
type SortBy string
|
||||
|
||||
const (
|
||||
SortById SortBy = "id"
|
||||
SortByDate SortBy = "date"
|
||||
SortByTitle SortBy = "title"
|
||||
)
|
||||
|
||||
type SortOrder int
|
||||
|
||||
const (
|
||||
SortOrderAsc SortOrder = iota
|
||||
SortOrderDesc
|
||||
)
|
||||
|
||||
// Sort organizes a list of notes by sortType and order using metadata.
|
||||
func Sort(md Results, sortType SortBy, order SortOrder) {
|
||||
switch sortType {
|
||||
case SortById, SortByDate:
|
||||
sort.Slice(md, func(i, j int) bool {
|
||||
if order == SortOrderAsc {
|
||||
return md[i].Identifier < md[j].Identifier // Reverse chronological by default
|
||||
} else {
|
||||
return md[i].Identifier > md[j].Identifier // Reverse chronological by default
|
||||
}
|
||||
})
|
||||
case SortByTitle:
|
||||
sort.Slice(md, func(i, j int) bool {
|
||||
if order == SortOrderAsc {
|
||||
return strings.ToLower(md[i].Title) < strings.ToLower(md[j].Title)
|
||||
} else {
|
||||
return strings.ToLower(md[i].Title) > strings.ToLower(md[j].Title)
|
||||
}
|
||||
})
|
||||
default:
|
||||
sort.Slice(md, func(i, j int) bool {
|
||||
return md[i].Identifier > md[j].Identifier // Reverse chronological by default
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// File types in our filesystem
|
||||
const (
|
||||
|
|
@ -312,7 +186,7 @@ var fileNames = []string{"path", "title", "keywords", "extension", "ctl"}
|
|||
|
||||
type server struct {
|
||||
dir string
|
||||
notes Results
|
||||
notes metadata.Results
|
||||
mu sync.RWMutex
|
||||
eventChan chan string
|
||||
eventSubs map[uint64]chan string
|
||||
|
|
@ -320,7 +194,7 @@ type server struct {
|
|||
nextSubID uint64
|
||||
nextSubIDMu sync.Mutex
|
||||
filterQuery string
|
||||
filteredNotes []*Metadata
|
||||
filteredNotes []*metadata.Metadata
|
||||
}
|
||||
|
||||
type connState struct {
|
||||
|
|
@ -339,7 +213,7 @@ type fid struct {
|
|||
var srv *server
|
||||
|
||||
// findNote finds a note by identifier in the in-memory collection
|
||||
func findNote(identifier string) (*Metadata, error) {
|
||||
func findNote(identifier string) (*metadata.Metadata, error) {
|
||||
srv.mu.RLock()
|
||||
defer srv.mu.RUnlock()
|
||||
for _, n := range srv.notes {
|
||||
|
|
@ -359,72 +233,6 @@ func emitEvent(id string, op rune) {
|
|||
}
|
||||
}
|
||||
|
||||
// ExtractMetadata extracts Denote metadata from a filename
|
||||
func ExtractMetadata(path string) *Metadata {
|
||||
fname := filepath.Base(path)
|
||||
note := &Metadata{Path: path}
|
||||
|
||||
if m := regexp.MustCompile(`^(\d{8}T\d{6})`).FindStringSubmatch(fname); m != nil {
|
||||
note.Identifier = m[1]
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
if m := regexp.MustCompile(`__(.+?)(?:\.|$)`).FindStringSubmatch(fname); m != nil {
|
||||
note.Tags = strings.Split(m[1], "_")
|
||||
}
|
||||
note.Extension = filepath.Ext(fname)
|
||||
|
||||
return note
|
||||
}
|
||||
|
||||
func extractTitle(path string) string {
|
||||
ext := strings.ToLower(filepath.Ext(path))
|
||||
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 {
|
||||
return strings.TrimSpace(m[1])
|
||||
}
|
||||
// Fallback to first heading (lines starting with *)
|
||||
if m := regexp.MustCompile(`(?m)^\*+\s+(.+)$`).FindStringSubmatch(text); 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 {
|
||||
return strings.TrimSpace(strings.Trim(m[1], `"`))
|
||||
}
|
||||
if m := regexp.MustCompile(`(?m)^#\s+(.+)$`).FindStringSubmatch(text); m != nil {
|
||||
return strings.TrimSpace(m[1])
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// Getdir returns the denote directory
|
||||
func getdir() string {
|
||||
|
|
@ -432,8 +240,8 @@ func getdir() string {
|
|||
}
|
||||
|
||||
// loadData returns metadata for notes in a directory matching given filters
|
||||
func loadData(filters []*Filter) (Results, error) {
|
||||
var notes Results
|
||||
func loadData(filters []*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
|
||||
|
|
@ -441,7 +249,7 @@ func loadData(filters []*Filter) (Results, error) {
|
|||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
if note := ExtractMetadata(path); note.Identifier != "" {
|
||||
if note := metadata.ExtractMetadata(path); note.Identifier != "" {
|
||||
match := true
|
||||
for _, filt := range filters {
|
||||
if !filt.IsMatch(note) {
|
||||
|
|
@ -462,7 +270,7 @@ func loadData(filters []*Filter) (Results, error) {
|
|||
return notes, nil
|
||||
}
|
||||
|
||||
func AddNote(note *Metadata) error {
|
||||
func AddNote(note *metadata.Metadata) error {
|
||||
if srv == nil {
|
||||
return fmt.Errorf("server not running")
|
||||
}
|
||||
|
|
@ -474,7 +282,7 @@ func AddNote(note *Metadata) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func GetNote(identifier string) (*Metadata, error) {
|
||||
func GetNote(identifier string) (*metadata.Metadata, error) {
|
||||
if srv == nil {
|
||||
return nil, fmt.Errorf("server not running")
|
||||
}
|
||||
|
|
@ -933,7 +741,7 @@ func (s *server) write(cs *connState, fc *plan9.Fcall) *plan9.Fcall {
|
|||
|
||||
// Create metadata entry with temporary path
|
||||
// (actual file will be created by denote package event handler)
|
||||
metadata := &Metadata{
|
||||
meta := &metadata.Metadata{
|
||||
Identifier: identifier,
|
||||
Title: title,
|
||||
Tags: tags,
|
||||
|
|
@ -943,7 +751,7 @@ func (s *server) write(cs *connState, fc *plan9.Fcall) *plan9.Fcall {
|
|||
|
||||
// Add to in-memory state
|
||||
s.mu.Lock()
|
||||
s.notes = append(s.notes, metadata)
|
||||
s.notes = append(s.notes, meta)
|
||||
s.mu.Unlock()
|
||||
|
||||
// Emit event notification
|
||||
|
|
@ -1249,7 +1057,7 @@ func (s *server) getIndexContent() string {
|
|||
}
|
||||
|
||||
// Return filtered notes
|
||||
var filtered Results
|
||||
var filtered metadata.Results
|
||||
for _, note := range s.filteredNotes {
|
||||
filtered = append(filtered, note)
|
||||
}
|
||||
|
|
@ -1334,7 +1142,7 @@ func (s *server) handleFilterCommand(fc *plan9.Fcall, command string) *plan9.Fca
|
|||
}
|
||||
|
||||
// Apply filters to notes
|
||||
var filtered []*Metadata
|
||||
var filtered []*metadata.Metadata
|
||||
for _, note := range s.notes {
|
||||
match := true
|
||||
for _, filt := range filters {
|
||||
|
|
|
|||
|
|
@ -7,9 +7,8 @@ the file name.
|
|||
package sync
|
||||
|
||||
import (
|
||||
"denote/internal/metadata"
|
||||
p9client "denote/internal/p9/client"
|
||||
p9server "denote/internal/p9/server"
|
||||
"denote/internal/tmpl"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
|
@ -112,7 +111,7 @@ func noteExists(identifier string) bool {
|
|||
|
||||
// registerNewNote writes to /new to register a new note (triggers 'n' event)
|
||||
func registerNewNote(path string) error {
|
||||
meta := p9server.ExtractMetadata(path)
|
||||
meta := metadata.ExtractMetadata(path)
|
||||
|
||||
// Format as 'title' tag1,tag2
|
||||
var newInput string
|
||||
|
|
@ -130,7 +129,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 := tmpl.Extract(path)
|
||||
fm, err := metadata.Extract(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -242,7 +241,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 := tmpl.Extract(path)
|
||||
fm, err := metadata.Extract(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -272,7 +271,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 := p9server.ExtractMetadata(path)
|
||||
meta := metadata.ExtractMetadata(path)
|
||||
|
||||
// Write title (triggers 'u' event)
|
||||
titlePath := "n/" + identifier + "/title"
|
||||
|
|
|
|||
46
main.go
46
main.go
|
|
@ -1,10 +1,10 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"denote/internal/metadata"
|
||||
p9client "denote/internal/p9/client"
|
||||
p9server "denote/internal/p9/server"
|
||||
"denote/internal/sync"
|
||||
"denote/internal/tmpl"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
|
|
@ -54,10 +54,10 @@ func buildDenoteFilename(identifier, title string, keywords []string, ext string
|
|||
|
||||
func generateNotePath(dir, title string, keywords []string, fileType string) (string, string) {
|
||||
identifier := generateIdentifier()
|
||||
ext := tmpl.FileExtensions[fileType]
|
||||
ext := metadata.FileExtensions[fileType]
|
||||
filename := buildDenoteFilename(identifier, title, keywords, ext)
|
||||
path := filepath.Join(dir, filename)
|
||||
content := tmpl.Generate(title, keywords, fileType, identifier)
|
||||
content := metadata.Generate(title, keywords, fileType, identifier)
|
||||
return path, content
|
||||
}
|
||||
|
||||
|
|
@ -114,8 +114,8 @@ func read9PFields(f *client.Fsys, identifier string, fields ...string) (map[stri
|
|||
return result, nil
|
||||
}
|
||||
|
||||
func readIndex() (p9server.Results, error) {
|
||||
var results p9server.Results
|
||||
func readIndex() (metadata.Results, error) {
|
||||
var results metadata.Results
|
||||
|
||||
err := p9client.With9P(func(f *client.Fsys) error {
|
||||
indexContent, err := read9PFile(f, "index")
|
||||
|
|
@ -268,14 +268,14 @@ func handleUpdateEvent(f *client.Fsys, identifier string) error {
|
|||
fileType = "txt"
|
||||
}
|
||||
|
||||
fm := &tmpl.FrontMatter{
|
||||
fm := &metadata.FrontMatter{
|
||||
Title: title,
|
||||
Tags: tags,
|
||||
Identifier: identifier,
|
||||
FileType: fileType,
|
||||
}
|
||||
|
||||
if err := tmpl.Update(path, fm); err != nil {
|
||||
if err := metadata.Update(path, fm); err != nil {
|
||||
log.Printf("failed to update front matter for %s: %v", identifier, err)
|
||||
}
|
||||
}
|
||||
|
|
@ -418,7 +418,7 @@ func main() {
|
|||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
p9server.Sort(rs, p9server.SortById, p9server.SortOrderDesc)
|
||||
metadata.Sort(rs, metadata.SortById, metadata.SortOrderDesc)
|
||||
refreshWindow(w, rs)
|
||||
|
||||
// listen for 'n' events to refresh window
|
||||
|
|
@ -575,7 +575,7 @@ func main() {
|
|||
}
|
||||
|
||||
// Parse the window content
|
||||
var emptyResults p9server.Results
|
||||
var emptyResults metadata.Results
|
||||
updated, err := emptyResults.FromString(string(body))
|
||||
if err != nil {
|
||||
log.Printf("failed to parse window: %v", err)
|
||||
|
|
@ -624,8 +624,8 @@ func performSearch(w *acme.Win, searchText string) {
|
|||
args := parseArgs(searchText)
|
||||
|
||||
var filterArgs []string
|
||||
sortBy := p9server.SortById
|
||||
sortOrder := p9server.SortOrderDesc
|
||||
sortBy := metadata.SortById
|
||||
sortOrder := metadata.SortOrderDesc
|
||||
|
||||
for _, arg := range args {
|
||||
if sortSpec, ok := strings.CutPrefix(arg, "sort:"); ok {
|
||||
|
|
@ -633,20 +633,20 @@ func performSearch(w *acme.Win, searchText string) {
|
|||
|
||||
switch parts[0] {
|
||||
case "id", "date":
|
||||
sortBy = p9server.SortById
|
||||
sortBy = metadata.SortById
|
||||
case "title":
|
||||
sortBy = p9server.SortByTitle
|
||||
sortBy = metadata.SortByTitle
|
||||
}
|
||||
|
||||
if len(parts) > 1 {
|
||||
switch parts[1] {
|
||||
case "asc":
|
||||
sortOrder = p9server.SortOrderAsc
|
||||
sortOrder = metadata.SortOrderAsc
|
||||
case "desc":
|
||||
sortOrder = p9server.SortOrderDesc
|
||||
sortOrder = metadata.SortOrderDesc
|
||||
}
|
||||
} else {
|
||||
sortOrder = p9server.SortOrderAsc
|
||||
sortOrder = metadata.SortOrderAsc
|
||||
}
|
||||
} else {
|
||||
filterArgs = append(filterArgs, arg)
|
||||
|
|
@ -668,12 +668,12 @@ func performSearch(w *acme.Win, searchText string) {
|
|||
log.Printf("search error: %v", err)
|
||||
return
|
||||
}
|
||||
p9server.Sort(rs, sortBy, sortOrder)
|
||||
metadata.Sort(rs, sortBy, sortOrder)
|
||||
|
||||
refreshWindow(w, rs)
|
||||
}
|
||||
|
||||
func refreshWindow(w *acme.Win, rs p9server.Results) {
|
||||
func refreshWindow(w *acme.Win, rs metadata.Results) {
|
||||
w.Addr(",")
|
||||
w.Write("data", rs.Bytes())
|
||||
w.Addr("#0")
|
||||
|
|
@ -692,7 +692,7 @@ func refreshWindowWithDefaults(w *acme.Win) {
|
|||
log.Printf("error refreshing: %v", err)
|
||||
return
|
||||
}
|
||||
p9server.Sort(rs, p9server.SortById, p9server.SortOrderDesc)
|
||||
metadata.Sort(rs, metadata.SortById, metadata.SortOrderDesc)
|
||||
refreshWindow(w, rs)
|
||||
}
|
||||
|
||||
|
|
@ -749,14 +749,14 @@ func parseArgs(s string) []string {
|
|||
return args
|
||||
}
|
||||
|
||||
func applyIndexChanges(f *client.Fsys, current, updated p9server.Results) error {
|
||||
func applyIndexChanges(f *client.Fsys, current, updated metadata.Results) error {
|
||||
// Validate entry count matches
|
||||
if len(updated) != len(current) {
|
||||
return fmt.Errorf("entry count mismatch: got %d, expected %d", len(updated), len(current))
|
||||
}
|
||||
|
||||
// Build map of current state
|
||||
currentMap := make(map[string]*p9server.Metadata)
|
||||
currentMap := make(map[string]*metadata.Metadata)
|
||||
for _, m := range current {
|
||||
currentMap[m.Identifier] = m
|
||||
}
|
||||
|
|
@ -770,7 +770,7 @@ func applyIndexChanges(f *client.Fsys, current, updated p9server.Results) error
|
|||
|
||||
// Check if anything changed
|
||||
titleChanged := orig.Title != upd.Title
|
||||
tagsChanged := !p9server.SlicesEqual(orig.Tags, upd.Tags)
|
||||
tagsChanged := !metadata.SlicesEqual(orig.Tags, upd.Tags)
|
||||
|
||||
if titleChanged || tagsChanged {
|
||||
// Write both title and keywords
|
||||
|
|
@ -807,7 +807,7 @@ func watchNoteWindow(win *acme.Win, path string) {
|
|||
win.WriteEvent(e)
|
||||
|
||||
encryptedPath := path + ".gpg"
|
||||
meta := p9server.ExtractMetadata(encryptedPath)
|
||||
meta := metadata.ExtractMetadata(encryptedPath)
|
||||
|
||||
var newInput string
|
||||
if len(meta.Tags) > 0 {
|
||||
|
|
|
|||
Loading…
Reference in New Issue