Skip to content

Commit 2154d4c

Browse files
render synced vault notes from templates
1 parent 900ff99 commit 2154d4c

3 files changed

Lines changed: 114 additions & 57 deletions

File tree

packages/vault-sync/discogs.py

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -64,24 +64,23 @@ def release_url(info):
6464
return f"https://www.discogs.com/release/{info['id']}"
6565

6666

67-
def album_note(title, year, artists, cover_file, url, lp_date):
68-
artist_block = "artist:\n" + "".join(f' - "[[{a}]]"\n' for a in artists)
69-
cover_line = f'cover: "[[{cover_file}]]"\n' if cover_file else "cover:\n"
70-
lp_line = f'lp: "[[{lp_date}]]"\n' if lp_date else "lp:\n"
71-
return (
72-
"---\n"
73-
"categories:\n"
74-
' - "[[Albums]]"\n'
75-
f"{artist_block}"
76-
f"{cover_line}"
77-
f"discogs: {url}\n"
78-
f"{lp_line}"
79-
f"year: {year}\n"
80-
"---\n"
67+
def album_note(template_path, year, artists, cover_file, url, lp_date):
68+
"""Render an Album note from the vault's Album Template."""
69+
return vaultlib.render_template(
70+
template_path,
71+
{
72+
"artist": [f'"[[{a}]]"' for a in artists],
73+
"cover": f'"[[{cover_file}]]"' if cover_file else "",
74+
"discogs": url,
75+
"lp": f'"[[{lp_date}]]"' if lp_date else "",
76+
"year": year,
77+
},
8178
)
8279

8380

84-
def sync_release(item, references, attachments, token, lp_date, index):
81+
def sync_release(
82+
item, notes, attachments, album_template, artist_template, token, lp_date, index
83+
):
8584
"""Create or update a single album note. ``lp_date`` is the owned-on date
8685
(collection) or None (wantlist). ``index`` is the {discogs-url: path} dedup
8786
map, updated in place when a note is created. Returns 'new', 'updated', or
@@ -101,7 +100,7 @@ def sync_release(item, references, attachments, token, lp_date, index):
101100
return "updated"
102101
return None
103102

104-
path, base = vaultlib.unique_note_path(references, title, year)
103+
path, base = vaultlib.unique_note_path(notes, title, year)
105104

106105
cover_file = None
107106
cover = info.get("cover_image")
@@ -119,10 +118,10 @@ def sync_release(item, references, attachments, token, lp_date, index):
119118
cover_file = None
120119

121120
for a in artists:
122-
vaultlib.ensure_person_note(references, a, "Artists")
121+
vaultlib.ensure_person_note(notes, a, artist_template)
123122

124123
vaultlib.write_note(
125-
path, album_note(title, year, artists, cover_file, url, lp_date)
124+
path, album_note(album_template, year, artists, cover_file, url, lp_date)
126125
)
127126
index[url.rstrip("/")] = path
128127
kind = "owned" if lp_date else "wishlist"
@@ -133,6 +132,9 @@ def sync_release(item, references, attachments, token, lp_date, index):
133132
def main(username, token, vault, include_wantlist):
134133
notes = vault
135134
attachments = os.path.join(vault, "Attachments")
135+
templates = os.path.join(vault, "Templates")
136+
album_template = os.path.join(templates, "Album Template.md")
137+
artist_template = os.path.join(templates, "Artist Template.md")
136138
os.makedirs(notes, exist_ok=True)
137139
os.makedirs(attachments, exist_ok=True)
138140

@@ -145,15 +147,31 @@ def main(username, token, vault, include_wantlist):
145147
for item in paginate(collection, token, "releases"):
146148
date_added = (item.get("date_added") or "")[:10] # ISO ts -> YYYY-MM-DD
147149
result = sync_release(
148-
item, notes, attachments, token, date_added or None, index
150+
item,
151+
notes,
152+
attachments,
153+
album_template,
154+
artist_template,
155+
token,
156+
date_added or None,
157+
index,
149158
)
150159
created += result == "new"
151160
updated += result == "updated"
152161

153162
if include_wantlist:
154163
wantlist = urljoin(BASE_URL, f"/users/{username}/wants")
155164
for item in paginate(wantlist, token, "wants"):
156-
result = sync_release(item, notes, attachments, token, None, index)
165+
result = sync_release(
166+
item,
167+
notes,
168+
attachments,
169+
album_template,
170+
artist_template,
171+
token,
172+
None,
173+
index,
174+
)
157175
created += result == "new"
158176

159177
print(f"discogs: {created} new, {updated} updated")

packages/vault-sync/letterboxd.py

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -84,27 +84,27 @@ def scrape_directors(film_url):
8484
return [d["name"] for d in data.get("director", []) if d.get("name")]
8585

8686

87-
def movie_note(title, year, poster_file, directors, url, liked, watched):
88-
directors_block = "director:\n" + "".join(f' - "[[{d}]]"\n' for d in directors)
89-
cover_line = f'cover: "[[{poster_file}]]"\n' if poster_file else "cover:\n"
90-
return (
91-
"---\n"
92-
"categories:\n"
93-
' - "[[Movies]]"\n'
94-
f"{cover_line}"
95-
f"{directors_block}"
96-
f"letterboxd: {url}\n"
97-
f"liked: {'true' if liked else 'false'}\n"
98-
"watched:\n"
99-
f' - "[[{watched}]]"\n'
100-
f"year: {year}\n"
101-
"---\n"
87+
def movie_note(template_path, year, poster_file, directors, url, liked, watched):
88+
"""Render a Movie note from the vault's Movie Template."""
89+
return vaultlib.render_template(
90+
template_path,
91+
{
92+
"cover": f'"[[{poster_file}]]"' if poster_file else "",
93+
"director": [f'"[[{d}]]"' for d in directors],
94+
"letterboxd": url,
95+
"liked": "true" if liked else "false",
96+
"watched": [f'"[[{watched}]]"'],
97+
"year": year,
98+
},
10299
)
103100

104101

105102
def main(username, vault):
106103
notes = vault
107104
attachments = os.path.join(vault, "Attachments")
105+
templates = os.path.join(vault, "Templates")
106+
movie_template = os.path.join(templates, "Movie Template.md")
107+
director_template = os.path.join(templates, "Director Template.md")
108108
os.makedirs(notes, exist_ok=True)
109109
os.makedirs(attachments, exist_ok=True)
110110

@@ -139,12 +139,12 @@ def main(username, vault):
139139

140140
directors = scrape_directors(entry["url"])
141141
for d in directors:
142-
vaultlib.ensure_person_note(notes, d, "Directors")
142+
vaultlib.ensure_person_note(notes, d, director_template)
143143

144144
vaultlib.write_note(
145145
path,
146146
movie_note(
147-
entry["title"],
147+
movie_template,
148148
entry["year"],
149149
poster_file,
150150
directors,

packages/vault-sync/vaultlib.py

Lines changed: 59 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,61 @@ def set_scalar_if_empty(path, key, value):
159159
return False
160160

161161

162+
def render_template(template_path, fields=None):
163+
"""Render a vault template while preserving its static frontmatter and body.
164+
165+
``fields`` maps frontmatter keys to either scalar strings or lists of YAML
166+
list-item strings. Existing values for those keys are replaced; absent keys
167+
are added before the closing frontmatter fence. This lets the vault's own
168+
templates control note layout, defaults, and body content.
169+
"""
170+
with open(template_path, "r", encoding="utf-8") as f:
171+
lines = f.read().splitlines(keepends=True)
172+
if not lines or lines[0].rstrip("\n") != "---":
173+
raise ValueError(f"template has no leading frontmatter fence: {template_path}")
174+
fm_end = next(
175+
(i for i in range(1, len(lines)) if lines[i].rstrip("\n") == "---"), None
176+
)
177+
if fm_end is None:
178+
raise ValueError(f"template has no closing frontmatter fence: {template_path}")
179+
180+
fields = fields or {}
181+
rendered = [lines[0]]
182+
seen = set()
183+
i = 1
184+
while i < fm_end:
185+
line = lines[i]
186+
match = re.match(r"^([A-Za-z][A-Za-z0-9_-]*):( |$)", line.rstrip("\n"))
187+
if not match or match.group(1) not in fields:
188+
rendered.append(line)
189+
i += 1
190+
continue
191+
192+
key = match.group(1)
193+
value = fields[key]
194+
seen.add(key)
195+
if isinstance(value, list):
196+
rendered.append(f"{key}:\n")
197+
rendered.extend(f" - {item}\n" for item in value)
198+
i += 1
199+
while i < fm_end and lines[i].startswith(" - "):
200+
i += 1
201+
else:
202+
rendered.append(f"{key}: {value}\n")
203+
i += 1
204+
205+
for key, value in fields.items():
206+
if key in seen:
207+
continue
208+
if isinstance(value, list):
209+
rendered.append(f"{key}:\n")
210+
rendered.extend(f" - {item}\n" for item in value)
211+
else:
212+
rendered.append(f"{key}: {value}\n")
213+
rendered.extend(lines[fm_end:])
214+
return "".join(rendered)
215+
216+
162217
def write_note(path, text):
163218
"""Create a note. Never overwrites an existing file."""
164219
if os.path.exists(path):
@@ -177,26 +232,10 @@ def download(url, dest, headers=None):
177232
f.write(data)
178233

179234

180-
def ensure_person_note(references_dir, name, type_link):
181-
"""Create the thin People note a ``director``/``artist`` link points at, if
182-
it doesn't exist — so the Director/Artist Base views resolve. Mirrors the
183-
Director/Artist templates: categories [[People]] + a types link, with the
184-
matching embedded Base view in the body."""
235+
def ensure_person_note(notes_dir, name, template_path):
236+
"""Create a linked person note from its vault template if it is missing."""
185237
base = sanitize(name)
186-
path = os.path.join(references_dir, f"{base}.md")
238+
path = os.path.join(notes_dir, f"{base}.md")
187239
if os.path.exists(path):
188240
return
189-
section = "Movies" if type_link == "Directors" else "Albums"
190-
view = "Director" if type_link == "Directors" else "Artist"
191-
text = (
192-
"---\n"
193-
"categories:\n"
194-
' - "[[People]]"\n'
195-
"types:\n"
196-
f' - "[[{type_link}]]"\n'
197-
"---\n"
198-
f"## {section}\n\n"
199-
f"![[{section}.base#{view}]]\n"
200-
)
201-
with open(path, "w", encoding="utf-8") as f:
202-
f.write(text)
241+
write_note(path, render_template(template_path))

0 commit comments

Comments
 (0)