Update frontmatter on title/keywords/signature change
This commit is contained in:
parent
58911d5753
commit
5d6a94ef42
|
|
@ -650,6 +650,9 @@ func (s *server) dispatchWrite(f *fid, tag uint16) *plan9.Fcall {
|
||||||
if s.callbacks.OnRename != nil {
|
if s.callbacks.OnRename != nil {
|
||||||
s.callbacks.OnRename(noteID)
|
s.callbacks.OnRename(noteID)
|
||||||
}
|
}
|
||||||
|
if err := s.updateFrontMatter(note); err != nil {
|
||||||
|
return errorFcall(fc, err.Error())
|
||||||
|
}
|
||||||
if err := s.renameNote(note); err != nil {
|
if err := s.renameNote(note); err != nil {
|
||||||
return errorFcall(fc, err.Error())
|
return errorFcall(fc, err.Error())
|
||||||
}
|
}
|
||||||
|
|
@ -669,6 +672,9 @@ func (s *server) dispatchWrite(f *fid, tag uint16) *plan9.Fcall {
|
||||||
if s.callbacks.OnRename != nil {
|
if s.callbacks.OnRename != nil {
|
||||||
s.callbacks.OnRename(noteID)
|
s.callbacks.OnRename(noteID)
|
||||||
}
|
}
|
||||||
|
if err := s.updateFrontMatter(note); err != nil {
|
||||||
|
return errorFcall(fc, err.Error())
|
||||||
|
}
|
||||||
if err := s.renameNote(note); err != nil {
|
if err := s.renameNote(note); err != nil {
|
||||||
return errorFcall(fc, err.Error())
|
return errorFcall(fc, err.Error())
|
||||||
}
|
}
|
||||||
|
|
@ -680,6 +686,9 @@ func (s *server) dispatchWrite(f *fid, tag uint16) *plan9.Fcall {
|
||||||
if s.callbacks.OnRename != nil {
|
if s.callbacks.OnRename != nil {
|
||||||
s.callbacks.OnRename(noteID)
|
s.callbacks.OnRename(noteID)
|
||||||
}
|
}
|
||||||
|
if err := s.updateFrontMatter(note); err != nil {
|
||||||
|
return errorFcall(fc, err.Error())
|
||||||
|
}
|
||||||
if err := s.renameNote(note); err != nil {
|
if err := s.renameNote(note); err != nil {
|
||||||
return errorFcall(fc, err.Error())
|
return errorFcall(fc, err.Error())
|
||||||
}
|
}
|
||||||
|
|
@ -1008,25 +1017,53 @@ func (s *server) getFileContent(path string) string {
|
||||||
case "backlinks":
|
case "backlinks":
|
||||||
return s.getBacklinks(noteID)
|
return s.getBacklinks(noteID)
|
||||||
case "body":
|
case "body":
|
||||||
return s.readBody(note.Path)
|
return s.readBody(note)
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// readBody reads the file content at path
|
// readBody reads the file content, normalizing frontmatter tags if needed.
|
||||||
func (s *server) readBody(path string) string {
|
// Malformed tags are fixed in place and the file is renamed if necessary.
|
||||||
if path == "" {
|
func (s *server) readBody(note *metadata.Metadata) string {
|
||||||
|
if note.Path == "" {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
data, err := os.ReadFile(path)
|
data, err := os.ReadFile(note.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
ext := filepath.Ext(note.Path)
|
||||||
|
fm, fileType, _ := frontmatter.Unmarshal(data, ext)
|
||||||
|
if fm != nil && fm.Title != "" {
|
||||||
|
note.Title = fm.Title
|
||||||
|
note.Tags = fm.Tags
|
||||||
|
note.Signature = fm.Signature
|
||||||
|
normalized := frontmatter.NormalizeTags(data, fm.Tags, fileType)
|
||||||
|
if string(normalized) != string(data) {
|
||||||
|
if err := os.WriteFile(note.Path, normalized, 0644); err == nil {
|
||||||
|
data = normalized
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Rename if tags changed the filename
|
||||||
|
s.renameNote(note) //nolint:errcheck
|
||||||
|
}
|
||||||
return string(data)
|
return string(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// renameNote renames the physical file to match current note metadata and updates note.Path.
|
// renameNote renames the physical file to match current note metadata and updates note.Path.
|
||||||
// It is a no-op when the filename is already correct.
|
// It is a no-op when the filename is already correct.
|
||||||
|
func (s *server) updateFrontMatter(note *metadata.Metadata) error {
|
||||||
|
if note.Path == "" || !disk.SupportsFrontMatter(note.Path) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
_, fileType, err := disk.ExtractFrontMatter(note.Path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fm := metadata.NewFrontMatter(note.Title, note.Signature, note.Tags, note.Identifier)
|
||||||
|
return disk.UpdateFrontMatter(note.Path, fm, fileType)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *server) renameNote(note *metadata.Metadata) error {
|
func (s *server) renameNote(note *metadata.Metadata) error {
|
||||||
if note.Path == "" {
|
if note.Path == "" {
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -1045,7 +1082,8 @@ func (s *server) renameNote(note *metadata.Metadata) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeBody writes content to file and syncs frontmatter to metadata
|
// writeBody writes content to file and syncs frontmatter to metadata.
|
||||||
|
// If frontmatter tags are malformed, they are normalized and written back.
|
||||||
func (s *server) writeBody(path, body string, note *metadata.Metadata) error {
|
func (s *server) writeBody(path, body string, note *metadata.Metadata) error {
|
||||||
if path == "" {
|
if path == "" {
|
||||||
return fmt.Errorf("no path")
|
return fmt.Errorf("no path")
|
||||||
|
|
@ -1055,11 +1093,18 @@ func (s *server) writeBody(path, body string, note *metadata.Metadata) error {
|
||||||
}
|
}
|
||||||
// Parse frontmatter and sync to metadata
|
// Parse frontmatter and sync to metadata
|
||||||
ext := filepath.Ext(path)
|
ext := filepath.Ext(path)
|
||||||
fm, _, _ := frontmatter.Unmarshal([]byte(body), ext)
|
fm, fileType, _ := frontmatter.Unmarshal([]byte(body), ext)
|
||||||
if fm != nil && fm.Title != "" {
|
if fm != nil && fm.Title != "" {
|
||||||
note.Title = fm.Title
|
note.Title = fm.Title
|
||||||
note.Tags = fm.Tags
|
note.Tags = fm.Tags
|
||||||
note.Signature = fm.Signature
|
note.Signature = fm.Signature
|
||||||
|
// Write normalized tags back into the file
|
||||||
|
normalized := frontmatter.NormalizeTags([]byte(body), fm.Tags, fileType)
|
||||||
|
if string(normalized) != body {
|
||||||
|
if err := os.WriteFile(path, normalized, 0644); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return s.renameNote(note)
|
return s.renameNote(note)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,13 @@ func Marshal(fm *metadata.FrontMatter, fileType metadata.FileType) []byte {
|
||||||
return []byte(content)
|
return []byte(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
var tagSplitRe = regexp.MustCompile(`[\s,;:]+`)
|
var (
|
||||||
|
tagSplitRe = regexp.MustCompile(`[\s,;:]+`)
|
||||||
|
orgFiletagsRe = regexp.MustCompile(`(?m)^(#\+filetags:[ \t]*).*$`)
|
||||||
|
mdYamlTagsRe = regexp.MustCompile(`(?m)^(tags:[ \t]*).*$`)
|
||||||
|
mdTomlTagsRe = regexp.MustCompile(`(?m)^(tags[ \t]*=[ \t]*).*$`)
|
||||||
|
txtTagsRe = regexp.MustCompile(`(?m)^(tags:[ \t]*).*$`)
|
||||||
|
)
|
||||||
var tagStripRe = regexp.MustCompile(`[^a-z0-9]`)
|
var tagStripRe = regexp.MustCompile(`[^a-z0-9]`)
|
||||||
|
|
||||||
// parseTags parses a raw tag string into normalized tags.
|
// parseTags parses a raw tag string into normalized tags.
|
||||||
|
|
@ -101,6 +107,33 @@ func parseTags(s string) []string {
|
||||||
return tags
|
return tags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NormalizeTags replaces the tags field in content with the normalized,
|
||||||
|
// correctly formatted version for the given file type.
|
||||||
|
// Returns the updated content. If no tags field is found, content is unchanged.
|
||||||
|
func NormalizeTags(content []byte, tags []string, fileType metadata.FileType) []byte {
|
||||||
|
formatted := formatTags(tags, fileType)
|
||||||
|
text := string(content)
|
||||||
|
switch fileType {
|
||||||
|
case metadata.FileTypeOrg:
|
||||||
|
text = orgFiletagsRe.ReplaceAllString(text, "${1}"+formatted)
|
||||||
|
case metadata.FileTypeMdYaml:
|
||||||
|
// Only replace within the --- block
|
||||||
|
yamlRe := regexp.MustCompile(`(?ms)^---\n(.*?)\n---`)
|
||||||
|
text = yamlRe.ReplaceAllStringFunc(text, func(block string) string {
|
||||||
|
return mdYamlTagsRe.ReplaceAllString(block, "${1}"+formatted)
|
||||||
|
})
|
||||||
|
case metadata.FileTypeMdToml:
|
||||||
|
// Only replace within the +++ block
|
||||||
|
tomlRe := regexp.MustCompile(`(?ms)^\+\+\+\n(.*?)\n\+\+\+`)
|
||||||
|
text = tomlRe.ReplaceAllStringFunc(text, func(block string) string {
|
||||||
|
return mdTomlTagsRe.ReplaceAllString(block, "${1}"+formatted)
|
||||||
|
})
|
||||||
|
case metadata.FileTypeTxt:
|
||||||
|
text = txtTagsRe.ReplaceAllString(text, "${1}"+formatted)
|
||||||
|
}
|
||||||
|
return []byte(text)
|
||||||
|
}
|
||||||
|
|
||||||
// Unmarshal extracts front matter from file content.
|
// Unmarshal extracts front matter from file content.
|
||||||
// ext should be the file extension (e.g., ".md", ".org", ".txt").
|
// ext should be the file extension (e.g., ".md", ".org", ".txt").
|
||||||
// Returns the parsed frontmatter and the detected FileType.
|
// Returns the parsed frontmatter and the detected FileType.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue