Add filename and filename-stem template variables (#91)

pull/92/head
Mickaël Menu 3 years ago committed by GitHub
parent 39467a1b7c
commit b1c69b4765
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
## Unreleased
### Added
* New template variables `filename` and `filename-stem` when formatting notes (e.g. with `zk list --format`) and for the [`fzf-line`](docs/tool-fzf.md) config key.
### Fixed
* [#89](https://github.com/mickael-menu/zk/issues/89) Calling `zk index` from outside the notebook (contributed by [@adamreese](https://github.com/mickael-menu/zk/pull/90)).

@ -2,22 +2,24 @@
The following variables are available in the templates used when formatting notes, for example with `zk list --format <template>`.
| Variable | Type | Description |
|---------------|----------|--------------------------------------------------------------------------|
| `path` | string | File path to the note, relative to the current directory |
| `abs-path` | string | File path to the note, absolute path including the notebook directory |
| `title` | string | Note title |
| `link` | string | Markdown link to the note, relative to the current directory<sup>1</sup> |
| `lead` | string | First paragraph extracted from the note content |
| `body` | string | All of the note content, minus the heading |
| `snippets` | [string] | List of context-sensitive relevant excerpts from the note |
| `raw-content` | string | The full raw content of the note file |
| `word-count` | int | Number of words in the note |
| `tags` | [string] | List of tags found in the note |
| `metadata` | map | YAML frontmatter metadata, e.g. `metadata.description`<sup>2</sup> |
| `created` | date | Date of creation of the note |
| `modified` | date | Last date of modification of the note |
| `checksum` | string | SHA-256 checksum of the note file |
| Variable | Type | Description |
|-----------------|----------|--------------------------------------------------------------------------|
| `filename` | string | Filename of the note, including its extension |
| `filename-stem` | string | Filename of the note without the file extension |
| `path` | string | File path to the note, relative to the current directory |
| `abs-path` | string | File path to the note, absolute path including the notebook directory |
| `title` | string | Note title |
| `link` | string | Markdown link to the note, relative to the current directory<sup>1</sup> |
| `lead` | string | First paragraph extracted from the note content |
| `body` | string | All of the note content, minus the heading |
| `snippets` | [string] | List of context-sensitive relevant excerpts from the note |
| `raw-content` | string | The full raw content of the note file |
| `word-count` | int | Number of words in the note |
| `tags` | [string] | List of tags found in the note |
| `metadata` | map | YAML frontmatter metadata, e.g. `metadata.description`<sup>2</sup> |
| `created` | date | Date of creation of the note |
| `modified` | date | Last date of modification of the note |
| `checksum` | string | SHA-256 checksum of the note file |
1. The format of the generated Markdown links can be customized in the [note format configuration](note-format.md).
2. YAML keys are normalized to lower case.

@ -43,6 +43,8 @@ The following variables are available in the line template.
| Variable | Type | Description |
|-----------------|----------|--------------------------------------------------------------------|
| `filename` | string | Filename of the note, including its extension |
| `filename-stem` | string | Filename of the note without the file extension |
| `path` | string | File path to the note, relative to the notebook root |
| `abs-path` | string | Absolute file path to the note |
| `rel-path` | string | File path to the note, relative to the current directory |

@ -108,19 +108,21 @@ func (f *NoteFilter) Apply(notes []core.ContextualNote) ([]core.ContextualNote,
for i, note := range notes {
context := lineRenderContext{
Path: note.Path,
AbsPath: absPaths[i],
RelPath: relPaths[i],
Title: note.Title,
TitleOrPath: note.Title,
Body: stringsutil.JoinLines(note.Body),
RawContent: stringsutil.JoinLines(note.RawContent),
WordCount: note.WordCount,
Tags: note.Tags,
Metadata: note.Metadata,
Created: note.Created,
Modified: note.Modified,
Checksum: note.Checksum,
Filename: note.Filename(),
FilenameStem: note.FilenameStem(),
Path: note.Path,
AbsPath: absPaths[i],
RelPath: relPaths[i],
Title: note.Title,
TitleOrPath: note.Title,
Body: stringsutil.JoinLines(note.Body),
RawContent: stringsutil.JoinLines(note.RawContent),
WordCount: note.WordCount,
Tags: note.Tags,
Metadata: note.Metadata,
Created: note.Created,
Modified: note.Modified,
Checksum: note.Checksum,
}
if context.TitleOrPath == "" {
context.TitleOrPath = note.Path
@ -157,17 +159,19 @@ func (f *NoteFilter) Apply(notes []core.ContextualNote) ([]core.ContextualNote,
var defaultLineTemplate = `{{style "title" title-or-path}} {{style "understate" body}}`
type lineRenderContext struct {
Path string
AbsPath string `handlebars:"abs-path"`
RelPath string `handlebars:"rel-path"`
Title string
TitleOrPath string `handlebars:"title-or-path"`
Body string
RawContent string `handlebars:"raw-content"`
WordCount int `handlebars:"word-count"`
Tags []string
Metadata map[string]interface{}
Created time.Time
Modified time.Time
Checksum string
Filename string
FilenameStem string `handlebars:"filename-stem"`
Path string
AbsPath string `handlebars:"abs-path"`
RelPath string `handlebars:"rel-path"`
Title string
TitleOrPath string `handlebars:"title-or-path"`
Body string
RawContent string `handlebars:"raw-content"`
WordCount int `handlebars:"word-count"`
Tags []string
Metadata map[string]interface{}
Created time.Time
Modified time.Time
Checksum string
}

@ -1,7 +1,10 @@
package core
import (
"path/filepath"
"time"
"github.com/mickael-menu/zk/internal/util/paths"
)
// NoteID represents the unique ID of a note collection relative to a given
@ -63,6 +66,17 @@ func (n Note) AsMinimalNote() MinimalNote {
}
}
// Filename returns the filename portion of the note path.
func (n Note) Filename() string {
return filepath.Base(n.Path)
}
// FilenameStem returns the filename portion of the note path, excluding its
// file extension.
func (n Note) FilenameStem() string {
return paths.FilenameStem(n.Path)
}
// ContextualNote holds a Note and context-sensitive content snippets.
//
// This is used for example:

@ -34,9 +34,11 @@ func newNoteFormatter(basePath string, template Template, linkFormatter LinkForm
}
return template.Render(noteFormatRenderContext{
Path: path,
AbsPath: absPath,
Title: note.Title,
Filename: note.Filename(),
FilenameStem: note.FilenameStem(),
Path: path,
AbsPath: absPath,
Title: note.Title,
Link: newLazyStringer(func() string {
link, _ := linkFormatter(LinkFormatterContext{
Path: note.Path,
@ -67,21 +69,23 @@ var noteTermRegex = regexp.MustCompile(`<zk:match>(.*?)</zk:match>`)
// noteFormatRenderContext holds the variables available to the note formatting
// templates.
type noteFormatRenderContext struct {
Path string `json:"path"`
AbsPath string `json:"absPath" handlebars:"abs-path"`
Title string `json:"title"`
Link fmt.Stringer `json:"link"`
Lead string `json:"lead"`
Body string `json:"body"`
Snippets []string `json:"snippets"`
RawContent string `json:"rawContent" handlebars:"raw-content"`
WordCount int `json:"wordCount" handlebars:"word-count"`
Tags []string `json:"tags"`
Metadata map[string]interface{} `json:"metadata"`
Created time.Time `json:"created"`
Modified time.Time `json:"modified"`
Checksum string `json:"checksum"`
Env map[string]string `json:"-"`
Filename string `json:"filename"`
FilenameStem string `json:"filenameStem" handlebars:"filename-stem"`
Path string `json:"path"`
AbsPath string `json:"absPath" handlebars:"abs-path"`
Title string `json:"title"`
Link fmt.Stringer `json:"link"`
Lead string `json:"lead"`
Body string `json:"body"`
Snippets []string `json:"snippets"`
RawContent string `json:"rawContent" handlebars:"raw-content"`
WordCount int `json:"wordCount" handlebars:"word-count"`
Tags []string `json:"tags"`
Metadata map[string]interface{} `json:"metadata"`
Created time.Time `json:"created"`
Modified time.Time `json:"modified"`
Checksum string `json:"checksum"`
Env map[string]string `json:"-"`
}
func (c noteFormatRenderContext) Equal(other noteFormatRenderContext) bool {

@ -1,6 +1,7 @@
package core
import (
"path/filepath"
"testing"
"time"
@ -28,7 +29,7 @@ func TestNewNoteFormatter(t *testing.T) {
res, err := formatter(ContextualNote{
Note: Note{
ID: 1,
Path: "note1",
Path: "note1.md",
Title: "Note 1",
Lead: "Lead 1",
Body: "Body 1",
@ -51,7 +52,7 @@ func TestNewNoteFormatter(t *testing.T) {
res, err = formatter(ContextualNote{
Note: Note{
ID: 2,
Path: "dir/note2",
Path: "dir/note2.md",
Title: "Note 2",
Lead: "Lead 2",
Body: "Body 2",
@ -71,16 +72,18 @@ func TestNewNoteFormatter(t *testing.T) {
// Check that the template received the proper contexts
assert.Equal(t, test.template.Contexts, []interface{}{
noteFormatRenderContext{
Path: "note1",
AbsPath: "/notebook/note1",
Title: "Note 1",
Link: opt.NewString("[Note 1](note1)"),
Lead: "Lead 1",
Body: "Body 1",
Snippets: []string{"snippet1", "snippet2"},
RawContent: "Content 1",
WordCount: 1,
Tags: []string{"tag1", "tag2"},
Filename: "note1.md",
FilenameStem: "note1",
Path: "note1.md",
AbsPath: "/notebook/note1.md",
Title: "Note 1",
Link: opt.NewString("[Note 1](note1)"),
Lead: "Lead 1",
Body: "Body 1",
Snippets: []string{"snippet1", "snippet2"},
RawContent: "Content 1",
WordCount: 1,
Tags: []string{"tag1", "tag2"},
Metadata: map[string]interface{}{
"metadata1": "val1",
"metadata2": "val2",
@ -90,20 +93,22 @@ func TestNewNoteFormatter(t *testing.T) {
Checksum: "checksum1",
},
noteFormatRenderContext{
Path: "dir/note2",
AbsPath: "/notebook/dir/note2",
Title: "Note 2",
Link: opt.NewString("[Note 2](dir/note2)"),
Lead: "Lead 2",
Body: "Body 2",
Snippets: []string{},
RawContent: "Content 2",
WordCount: 2,
Tags: []string{},
Metadata: map[string]interface{}{},
Created: date3,
Modified: date4,
Checksum: "checksum2",
Filename: "note2.md",
FilenameStem: "note2",
Path: "dir/note2.md",
AbsPath: "/notebook/dir/note2.md",
Title: "Note 2",
Link: opt.NewString("[Note 2](dir/note2)"),
Lead: "Lead 2",
Body: "Body 2",
Snippets: []string{},
RawContent: "Content 2",
WordCount: 2,
Tags: []string{},
Metadata: map[string]interface{}{},
Created: date3,
Modified: date4,
Checksum: "checksum2",
},
})
}
@ -123,10 +128,12 @@ func TestNoteFormatterMakesPathRelative(t *testing.T) {
assert.Nil(t, err)
assert.Equal(t, test.template.Contexts, []interface{}{
noteFormatRenderContext{
Path: expected,
AbsPath: expectedFull,
Link: opt.NewString("[](" + paths.DropExt(expected) + ")"),
Snippets: []string{},
Filename: filepath.Base(expected),
FilenameStem: paths.FilenameStem(expected),
Path: expected,
AbsPath: expectedFull,
Link: opt.NewString("[](" + paths.DropExt(expected) + ")"),
Snippets: []string{},
},
})
}
@ -154,10 +161,12 @@ func TestNoteFormatterStylesSnippetTerm(t *testing.T) {
assert.Nil(t, err)
assert.Equal(t, test.template.Contexts, []interface{}{
noteFormatRenderContext{
Path: ".",
AbsPath: "/notebook",
Link: opt.NewString("[]()"),
Snippets: []string{expected},
Filename: ".",
FilenameStem: ".",
Path: ".",
AbsPath: "/notebook",
Link: opt.NewString("[]()"),
Snippets: []string{expected},
},
})
}

Loading…
Cancel
Save