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

@ -43,6 +43,8 @@ The following variables are available in the line template.
| Variable | Type | Description | | 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 | | `path` | string | File path to the note, relative to the notebook root |
| `abs-path` | string | Absolute file path to the note | | `abs-path` | string | Absolute file path to the note |
| `rel-path` | string | File path to the note, relative to the current directory | | `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 { for i, note := range notes {
context := lineRenderContext{ context := lineRenderContext{
Path: note.Path, Filename: note.Filename(),
AbsPath: absPaths[i], FilenameStem: note.FilenameStem(),
RelPath: relPaths[i], Path: note.Path,
Title: note.Title, AbsPath: absPaths[i],
TitleOrPath: note.Title, RelPath: relPaths[i],
Body: stringsutil.JoinLines(note.Body), Title: note.Title,
RawContent: stringsutil.JoinLines(note.RawContent), TitleOrPath: note.Title,
WordCount: note.WordCount, Body: stringsutil.JoinLines(note.Body),
Tags: note.Tags, RawContent: stringsutil.JoinLines(note.RawContent),
Metadata: note.Metadata, WordCount: note.WordCount,
Created: note.Created, Tags: note.Tags,
Modified: note.Modified, Metadata: note.Metadata,
Checksum: note.Checksum, Created: note.Created,
Modified: note.Modified,
Checksum: note.Checksum,
} }
if context.TitleOrPath == "" { if context.TitleOrPath == "" {
context.TitleOrPath = note.Path 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}}` var defaultLineTemplate = `{{style "title" title-or-path}} {{style "understate" body}}`
type lineRenderContext struct { type lineRenderContext struct {
Path string Filename string
AbsPath string `handlebars:"abs-path"` FilenameStem string `handlebars:"filename-stem"`
RelPath string `handlebars:"rel-path"` Path string
Title string AbsPath string `handlebars:"abs-path"`
TitleOrPath string `handlebars:"title-or-path"` RelPath string `handlebars:"rel-path"`
Body string Title string
RawContent string `handlebars:"raw-content"` TitleOrPath string `handlebars:"title-or-path"`
WordCount int `handlebars:"word-count"` Body string
Tags []string RawContent string `handlebars:"raw-content"`
Metadata map[string]interface{} WordCount int `handlebars:"word-count"`
Created time.Time Tags []string
Modified time.Time Metadata map[string]interface{}
Checksum string Created time.Time
Modified time.Time
Checksum string
} }

@ -1,7 +1,10 @@
package core package core
import ( import (
"path/filepath"
"time" "time"
"github.com/mickael-menu/zk/internal/util/paths"
) )
// NoteID represents the unique ID of a note collection relative to a given // 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. // ContextualNote holds a Note and context-sensitive content snippets.
// //
// This is used for example: // This is used for example:

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

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

Loading…
Cancel
Save