Skip to content

Commit

Permalink
Allow for YAML metadata in templates (#655)
Browse files Browse the repository at this point in the history
* Add a frontmatter metadata parser

* Use the template metadata to determine the filepath to use

* Document template metadata

* Add name and description template metadata attributes

These are displayed in the template picker

* Document name and description template metadata attributes
  • Loading branch information
movermeyer authored Jun 4, 2021
1 parent 2f9507d commit d4623a2
Show file tree
Hide file tree
Showing 6 changed files with 566 additions and 29 deletions.
Binary file added docs/assets/images/template-picker-annotated.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
107 changes: 107 additions & 0 deletions docs/features/note-templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,110 @@ In addition, you can also use variables provided by Foam:
| `FOAM_TITLE` | The title of the note. If used, Foam will prompt you to enter a title for the note. |

**Note:** neither the defaulting feature (eg. `${variable:default}`) nor the format feature (eg. `${variable/(.*)/${1:/upcase}/}`) (available to other variables) are available for these Foam-provided variables.

## Metadata

Templates can also contain metadata about the templates themselves. The metadata is defined in YAML "Frontmatter" blocks within the templates.

| Name | Description |
| ------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `filepath` | The filepath to use when creating the new note. If the filepath is a relative filepath, it is relative to the current workspace. |
| `name` | A human readable name to show in the template picker. |
| `description` | A human readable description to show in the template picker. |

Foam-specific variables (e.g. `$FOAM_TITLE`) can be used within template metadata. However, VS Code snippet variables are ([currently](https://github.com/foambubble/foam/pull/655)) not supported.

### `filepath` attribute

The `filepath` metadata attribute allows you to define a relative or absolute filepath to use when creating a note using the template.
If the filepath is a relative filepath, it is relative to the current workspace.

#### Example of relative `filepath`

For example, `filepath` can be used to customize `.foam/templates/new-note.md`, overriding the default `Foam: Create New Note` behaviour of opening the file in the same directory as the active file:

```yaml
---
# This will create the note in the "journal" subdirectory of the current workspace,
# regardless of which file is the active file.
foam_template:
filepath: 'journal/$FOAM_TITLE.md'
---
```

#### Example of absolute `filepath`

`filepath` can be an absolute filepath, so that the notes get created in the same location, regardless of which file or workspace the editor currently has open.
The format of an absolute filepath may vary depending on the filesystem used.

```yaml
---
foam_template:
# Unix / MacOS filesystems
filepath: '/Users/john.smith/foam/journal/$FOAM_TITLE.md'

# Windows filesystems
filepath: 'C:\Users\john.smith\Documents\foam\journal\$FOAM_TITLE.md'
---
```

### `name` and `description` attributes

These attributes provide a human readable name and description to be shown in the template picker (e.g. When a user uses the `Foam: Create New Note From Template` command):

![Template Picker annotated with attributes](../assets/images/template-picker-annotated.png)

### Adding template metadata to an existing YAML Frontmatter block

If your template already has a YAML Frontmatter block, you can add the Foam template metadata to it.

#### Limitations

Foam only supports adding the template metadata to *YAML* Frontmatter blocks. If the existing Frontmatter block uses some other format (e.g. JSON), you will have to add the template metadata to its own YAML Frontmatter block.

Further, the template metadata must be provided as a [YAML block mapping](https://yaml.org/spec/1.2/spec.html#id2798057), with the attributes placed on the lines immediately following the `foam_template` line:

```yaml
---
existing_frontmatter: "Existing Frontmatter block"
foam_template: # this is a YAML "Block" mapping ("Flow" mappings aren't supported)
name: My Note Template # Attributes must be on the lines immediately following `foam_template`
description: This is my note template
filepath: `journal/$FOAM_TITLE.md`
---
This is the rest of the template
```

Due to the technical limitations of parsing the complex YAML format, unless the metadata is provided this specific form, Foam is unable to correctly remove the template metadata before creating the resulting note.

If this limitation proves inconvenient to you, please let us know. We may be able to extend our parsing capabilities to cover your use case. In the meantime, you can add the template metadata without this limitation by providing it in its own YAML Frontmatter block.

### Adding template metadata to its own YAML Frontmatter block

You can add the template metadata to its own YAML Frontmatter block at the start of the template:

```yaml
---
foam_template:
name: My Note Template
description: This is my note template
filepath: `journal/$FOAM_TITLE.md`
---
This is the rest of the template
```

If the note already has a Frontmatter block, a Foam-specific Frontmatter block can be added to the start of the template. The Foam-specific Frontmatter block must always be placed at the very beginning of the file, and only whitespace can separate the two Frontmatter blocks.

```yaml
---
foam_template:
name: My Note Template
description: This is my note template
filepath: `journal/$FOAM_TITLE.md`
---

---
existing_frontmatter: "Existing Frontmatter block"
---
This is the rest of the template
```
58 changes: 51 additions & 7 deletions packages/foam-vscode/src/features/create-from-template.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { SnippetString, window } from 'vscode';
import { window, workspace } from 'vscode';
import {
resolveFoamVariables,
resolveFoamTemplateVariables,
substituteFoamVariables,
determineDefaultFilepath,
} from './create-from-template';
import path from 'path';
import { isWindows } from '../utils';
import { URI } from 'foam-core';

describe('substituteFoamVariables', () => {
test('Does nothing if no Foam-specific variables are used', () => {
Expand Down Expand Up @@ -100,8 +104,8 @@ describe('resolveFoamTemplateVariables', () => {
`;

const expectedMap = new Map<string, string>();
const expectedSnippet = new SnippetString(input);
const expected = [expectedMap, expectedSnippet];
const expectedString = input;
const expected = [expectedMap, expectedString];

expect(await resolveFoamTemplateVariables(input)).toEqual(expected);
});
Expand All @@ -115,8 +119,8 @@ describe('resolveFoamTemplateVariables', () => {
`;

const expectedMap = new Map<string, string>();
const expectedSnippet = new SnippetString(input);
const expected = [expectedMap, expectedSnippet];
const expectedString = input;
const expected = [expectedMap, expectedString];

expect(await resolveFoamTemplateVariables(input)).toEqual(expected);
});
Expand All @@ -139,11 +143,51 @@ describe('resolveFoamTemplateVariables', () => {
const expectedMap = new Map<string, string>();
expectedMap.set('FOAM_TITLE', foamTitle);

const expectedSnippet = new SnippetString(expectedOutput);
const expected = [expectedMap, expectedSnippet];
const expected = [expectedMap, expectedOutput];

expect(
await resolveFoamTemplateVariables(input, new Set(['FOAM_TITLE']))
).toEqual(expected);
});
});

describe('determineDefaultFilepath', () => {
test('Absolute filepath metadata is unchanged', () => {
const absolutePath = isWindows
? 'c:\\absolute_path\\journal\\My Note Title.md'
: '/absolute_path/journal/My Note Title.md';

const resolvedValues = new Map<string, string>();
const templateMetadata = new Map<string, string>();
templateMetadata.set('filepath', absolutePath);

const resultFilepath = determineDefaultFilepath(
resolvedValues,
templateMetadata
);

expect(URI.toFsPath(resultFilepath)).toMatch(absolutePath);
});

test('Relative filepath metadata is appended to current directory', () => {
const relativePath = isWindows
? 'journal\\My Note Title.md'
: 'journal/My Note Title.md';

const resolvedValues = new Map<string, string>();
const templateMetadata = new Map<string, string>();
templateMetadata.set('filepath', relativePath);

const resultFilepath = determineDefaultFilepath(
resolvedValues,
templateMetadata
);

const expectedPath = path.join(
workspace.workspaceFolders[0].uri.fsPath,
relativePath
);

expect(URI.toFsPath(resultFilepath)).toMatch(expectedPath);
});
});
Loading

0 comments on commit d4623a2

Please sign in to comment.