Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

templates with dynamic variable reference #3810

Open
2 of 9 tasks
dboeckenhoff opened this issue Nov 25, 2022 · 5 comments
Open
2 of 9 tasks

templates with dynamic variable reference #3810

dboeckenhoff opened this issue Nov 25, 2022 · 5 comments
Labels
type.enhancement Extension to a previously shipped feature. Minor functionality update. type.feature New functionality

Comments

@dboeckenhoff
Copy link

Please select if your request is either something new or an enhancement

  • Enhancement of an existing Feature.
  • Request of a new feature.

Please select the area your request applies to. (Multiple selections are Possible. You can leave blank if you're not sure.)

  • Workspace - VSCode workspace, vaults, Intellisense/autocomplete, Dendron settings
  • Lookup - Dendron's Lookup Command
  • Views - Dendron Preview, Tree View, Side Panels in the UI
  • Schema - Dendron Schemas
  • Pod - Data import from / export to Dendron
  • Publish - External Site Publish
  • Markdown - Markdown features, such as syntax support and features

Is your feature request related to a problem? Please describe

At the moment it is (to my understanding) not possible to apply a template that produces a text with the link e.g. {{fm.title}} to be able to later change title dynamically

Describe the solution you'd like

It would be very powerful, if we could escape {} brakets in templates so it is rendered correctly into the text after applying the template.

Describe alternatives you've considered

Additional context

@github-actions github-actions bot added status.triage-needed type.enhancement Extension to a previously shipped feature. Minor functionality update. type.feature New functionality labels Nov 25, 2022
@dboeckenhoff
Copy link
Author

Alternatively this would be solved by a merge mechanism on re-application of the same template (git like) which would be nice either way because this would allow to change a template and update the notes created with it.

@hikchoi
Copy link
Contributor

hikchoi commented Nov 28, 2022

hey @dboeckenhoff,

we support handlebars syntax for templates: https://wiki.dendron.so/notes/1mu1qb1vilhqr7tlatwyqxm/

Would this achieve what you are trying to do?

@dboeckenhoff
Copy link
Author

dboeckenhoff commented Nov 29, 2022 via email

@dboeckenhoff
Copy link
Author

See: this issue

@dboeckenhoff
Copy link
Author

dboeckenhoff commented Jan 6, 2023

I have spent some time writing a python script using jinja to allow more powerful templating:

FILE: apply_template.py (lives in dendron workspace)

import argparse
import collections.abc
import yaml
from jinja2 import Environment, FileSystemLoader


FMSEP = "---\n"
notes_dir = "notes/"

parser = argparse.ArgumentParser()
parser.add_argument("path")
args = parser.parse_args()

path = notes_dir + args.path



def update(d, u, gaps_only=False):
    for k, v in u.items():
        if isinstance(v, collections.abc.Mapping):
            d[k] = update(d.get(k, {}), v, gaps_only=gaps_only)
        else:
            if gaps_only and k in d:
                continue
            d[k] = v
    return d


class Frontmatter:
    def __init__(self, dict_):
        # import pdb; pdb.set_trace()
        for key, value in dict_.items():
            if isinstance(value, dict):
                value = Frontmatter(value)
            keys = key.split(".")
            first = keys[0]
            key = ".".join(keys[1:])
            if key:
                if hasattr(self, key):
                    raise NotImplementedError()
                else:
                    setattr(self, first, Frontmatter({key: value}))
            else:
                setattr(self, first, value)
                

class DotDict(dict):
    """
    a dictionary that supports dot notation 
    as well as dictionary access notation 
    usage: d = DotDict() or d = DotDict({'val1':'first'})
    set attributes: d.val2 = 'second' or d['val2'] = 'second'
    get attributes: d.val2 or d['val2']
    """
    __getattr__ = dict.__getitem__
    __setattr__ = dict.__setitem__
    __delattr__ = dict.__delitem__
                
                
def get_yaml(file_):
    pointer = file_.tell()
    if file_.readline() != FMSEP:
        file_.seek(pointer)
        return ''
    header = ""
    while True:
        line = file_.readline()
        if line == FMSEP:
            return header
        header += line


def load_dendron_md(path: str) -> dict:
    """
    Return the front matter section (between "---") read with yaml

    Args:
        path (str): path to dendron note
    """
    with open(path) as file_:
        config = yaml.load(get_yaml(file_), Loader=yaml.FullLoader)
        content = file_.read()
    return config, content


def save_dendron_md(path: str, front_matter: dict, content: str):
    with open(path, "w") as file_:
        file_.write(FMSEP)
        file_.write(yaml.dump(front_matter))
        file_.write(FMSEP)
        file_.write(content)


environment = Environment(loader=FileSystemLoader(notes_dir))
fm_dict, main_content = load_dendron_md(path)

content = ""
for template_path in fm_dict["templates"]:
    template_path = f"template.{template_path}.md"
    template = environment.get_template(template_path)

    fm_template_dict, _ = load_dendron_md(notes_dir + template_path)
    update(fm_dict, fm_template_dict, gaps_only=True)
    
    front_matter = DotDict(fm_dict)
    template_content = template.render(
        fm=front_matter
    )
    template_content = template_content.split(FMSEP)[2]
    content += template_content
content += main_content
save_dendron_md(path, fm_dict, content)

Templates are remembered using the "templates" variable in the frontmatter of a note:

FILE: notes/dummy-person.md

---
id: 8x87qm5up7u7linpof376dr
title: Dummy Person
desc: ''
updated: 1673016779971
created: 1673016756941
templates:
- person.developer
- person.diagnostician
---

## MyNotes

with the two example templates

FILE: notes/templates.person.developer.md

---
id: y3jky2vfggyio96i8wwdt1o
title: Developer
desc: ''
updated: 1673013780932
created: 1672993521263
person:
  developer:
    codes:
    - link.name.of.note.referencing.the.code.without.extension
    - other.code.note
---

## Codes

{% for code in fm.person.developer.codes %}
* [[{{ code }}]]
{% endfor%

and

FILE: notes/templates.person.diagnostician.md

---
id: 5ghznjuo2tku10ovm8wjjek
title: Diagnostician
desc: ''
updated: 1673013344878
created: 1673011604692
person:
  diagnostician:
    diagnostics:
    - link.name.of.note.referencing.the.diagnostic.without.extension
    - other.code.note
---

## Diagnostics

{% for diagnostic in fm.person.diagnostician.diagnostics %}
* [[{{ diagnostic }}]]
{% endfor%}

Then you can run the python script (better would be a dendron command later obviously) to (re-)apply the templates at once. I do it via a launch.json in vscode automatically from the node.

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Dendron: Apply Template",
            "type": "python",
            "request": "launch",
            "program": "apply_template.py",
            "args": ["${fileBasename}"],
            "console": "integratedTerminal",
            "justMyCode": true,
            "cwd": "${workspaceFolder}",
        }
    ]

This is just an example use-case of how things can work and I made it compatible with the current syntax. I am using this for now. A nice improvement would be to work with diffs to avoid duplicating the content of the templates but that is too much work for the current stage. Curious to hear your opinions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type.enhancement Extension to a previously shipped feature. Minor functionality update. type.feature New functionality
Projects
None yet
Development

No branches or pull requests

2 participants