Utensil's Zettelkasten-style forest of evergreen notes on math and tech. utensil.tngl.sh/forest/
0

Configure Feed

Select the types of activity you want to include in your feed.

Port scripts not committed by agent

author
utensil
date (May 9, 2026, 4:10 PM +0800) commit 46523f90 parent 9c20a36f change-id nylnlzuo
+259
+110
port_interests.py
··· 1 + #!/usr/bin/env -S uv run 2 + # /// script 3 + # requires-python = ">=3.11" 4 + # dependencies = [] 5 + # /// 6 + 7 + """ 8 + Port forest interest nodes (uts-0030 to uts-0165) to garden Markdown files. 9 + Each tree file uses the md macro with a GitHub issue header; content is 10 + extracted and written with YAML frontmatter (title, date, tags, aliases, URL). 11 + """ 12 + 13 + import re 14 + from pathlib import Path 15 + 16 + 17 + def slugify(title: str, max_words: int = 6) -> str: 18 + title = title.lower() 19 + title = re.sub(r"[^\w\s-]", "", title) 20 + title = re.sub(r"\s+", "-", title.strip()) 21 + title = re.sub(r"-+", "-", title).strip("-") 22 + parts = title.split("-") 23 + if len(parts) > max_words: 24 + title = "-".join(parts[:max_words]) 25 + return title or "untitled" 26 + 27 + 28 + def extract_md_content(tree_text: str) -> str: 29 + md_start = tree_text.find("\\md{\n") 30 + if md_start == -1: 31 + return "" 32 + 33 + content = tree_text[md_start + len("\\md{\n"):] 34 + lines = content.split("\n") 35 + 36 + # strip trailing blank lines and closing "}" 37 + while lines and lines[-1].strip() == "": 38 + lines.pop() 39 + if lines and lines[-1].strip() == "}": 40 + lines.pop() 41 + 42 + content = "\n".join(lines) 43 + 44 + # strip the "#### [user] opened issue at [...]:" header + following blank line 45 + content = re.sub( 46 + r"^####[^\n]*opened issue at[^\n]*\n\n?", "", content, flags=re.MULTILINE 47 + ) 48 + return content.strip() 49 + 50 + 51 + def convert(tree_id: str, tree_text: str) -> tuple[str, str]: 52 + title_m = re.search(r"\\title\{([^}]+)\}", tree_text) 53 + title = title_m.group(1) if title_m else tree_id 54 + 55 + date_m = re.search(r"\\date\{([^}]+)\}", tree_text) 56 + date = date_m.group(1) if date_m else "" 57 + 58 + content = extract_md_content(tree_text) 59 + slug = slugify(title) 60 + source = f"https://utensil.github.io/forest/{tree_id}/" 61 + 62 + # escape quotes in title for YAML 63 + title_yaml = title.replace('"', '\\"') 64 + 65 + frontmatter = ( 66 + f'---\n' 67 + f'title: "{title_yaml}"\n' 68 + f'date: {date}\n' 69 + f'tags: [interest]\n' 70 + f'aliases: [{tree_id}]\n' 71 + f'source: "{source}"\n' 72 + f'---' 73 + ) 74 + return slug, f"{frontmatter}\n\n{content}\n" 75 + 76 + 77 + def main() -> None: 78 + trees_dir = Path("/Users/utensil/projects/forest/trees") 79 + output_dir = Path("/Users/utensil/projects/garden-interest-port/content/interests") 80 + output_dir.mkdir(parents=True, exist_ok=True) 81 + 82 + used_slugs: dict[str, str] = {} 83 + converted = 0 84 + 85 + for num in range(30, 166): 86 + tree_id = f"uts-{num:04d}" 87 + tree_file = trees_dir / f"{tree_id}.tree" 88 + if not tree_file.exists(): 89 + print(f" missing: {tree_id}") 90 + continue 91 + 92 + text = tree_file.read_text() 93 + slug, md = convert(tree_id, text) 94 + 95 + # disambiguate slug collisions 96 + base_slug = slug 97 + if slug in used_slugs: 98 + slug = f"{base_slug}-{num}" 99 + used_slugs[slug] = tree_id 100 + 101 + out = output_dir / f"{slug}.md" 102 + out.write_text(md) 103 + print(f" {tree_id} → interests/{slug}.md") 104 + converted += 1 105 + 106 + print(f"\nConverted {converted} nodes → {output_dir}") 107 + 108 + 109 + if __name__ == "__main__": 110 + main()
+149
port_posts.py
··· 1 + #!/usr/bin/env -S uv run 2 + # /// script 3 + # requires-python = ">=3.11" 4 + # dependencies = [] 5 + # /// 6 + 7 + """ 8 + Port forest post nodes (mdnote) to garden Markdown files in content/posts/. 9 + Handles both plain mdnote{title}{content} and mdnote{title}{verb>>>|content>>>} forms. 10 + """ 11 + 12 + import re 13 + from pathlib import Path 14 + 15 + 16 + NODES = [ 17 + "uts-0167", 18 + "uts-016A", 19 + "uts-016B", 20 + "uts-016C", 21 + "uts-016E", 22 + "uts-016J", 23 + "uts-016K", 24 + "uts-016L", 25 + # uts-016M skipped: jj.md already exists in garden with complete content 26 + "uts-016N", 27 + "uts-016O", 28 + "uts-016P", 29 + ] 30 + 31 + 32 + def slugify(title: str, max_words: int = 6) -> str: 33 + title = title.lower() 34 + title = re.sub(r"[^\w\s-]", "", title) 35 + title = re.sub(r"\s+", "-", title.strip()) 36 + title = re.sub(r"-+", "-", title).strip("-") 37 + parts = title.split("-") 38 + if len(parts) > max_words: 39 + title = "-".join(parts[:max_words]) 40 + return title or "untitled" 41 + 42 + 43 + def extract_mdnote(text: str) -> tuple[str, str]: 44 + m = re.search(r"\\mdnote\{([^}]+)\}\{", text) 45 + if not m: 46 + return "", "" 47 + 48 + title = m.group(1) 49 + rest = text[m.end():] 50 + 51 + # verb-wrapped: \verb>>>|...\n>>>} 52 + verb_m = re.match(r"\\verb>>>\|(.*?)>>>\}", rest, re.DOTALL) 53 + if verb_m: 54 + return title, verb_m.group(1).strip() 55 + 56 + # plain content: brace-balanced extraction 57 + depth = 1 58 + i = 0 59 + while i < len(rest) and depth > 0: 60 + if rest[i] == "{": 61 + depth += 1 62 + elif rest[i] == "}": 63 + depth -= 1 64 + i += 1 65 + 66 + return title, rest[: i - 1].strip() 67 + 68 + 69 + def parse_tags(text: str) -> list[str]: 70 + return re.findall(r"\\tag\{([^}]+)\}", text) 71 + 72 + 73 + def parse_meta(text: str, key: str) -> str: 74 + m = re.search(rf"\\meta\{{{re.escape(key)}\}}\{{([^}}]+)\}}", text) 75 + return m.group(1) if m else "" 76 + 77 + 78 + def parse_field(text: str, field: str) -> str: 79 + m = re.search(rf"\\{field}\{{([^}}]+)\}}", text) 80 + return m.group(1) if m else "" 81 + 82 + 83 + def build_frontmatter(title: str, date: str, tags: list[str], aliases: str, external: str) -> str: 84 + lines = ["---", f"title: {title}"] 85 + if date: 86 + lines.append(f"date: {date}") 87 + if len(tags) == 1: 88 + lines.append(f"tag: {tags[0]}") 89 + elif tags: 90 + lines.append("tags:") 91 + for t in tags: 92 + lines.append(f" - {t}") 93 + lines.append(f"aliases: [{aliases}]") 94 + lines.append(f'source: "https://utensil.github.io/forest/{aliases}/"') 95 + if external: 96 + lines.append(f'external: "{external}"') 97 + lines.append("---") 98 + return "\n".join(lines) 99 + 100 + 101 + def convert(tree_id: str, tree_text: str) -> tuple[str, str]: 102 + title, content = extract_mdnote(tree_text) 103 + if not title: 104 + return "", "" 105 + 106 + date = parse_field(tree_text, "date") 107 + tags = parse_tags(tree_text) 108 + external = parse_meta(tree_text, "external") 109 + 110 + slug = slugify(title) 111 + fm = build_frontmatter(title, date, tags, tree_id, external) 112 + return slug, f"{fm}\n\n{content}\n" 113 + 114 + 115 + def main() -> None: 116 + trees_dir = Path("/Users/utensil/projects/forest/trees") 117 + output_dir = Path("/Users/utensil/projects/garden-posts-port/content/posts") 118 + output_dir.mkdir(parents=True, exist_ok=True) 119 + 120 + used_slugs: dict[str, str] = {} 121 + converted = 0 122 + 123 + for tree_id in NODES: 124 + tree_file = trees_dir / f"{tree_id}.tree" 125 + if not tree_file.exists(): 126 + print(f" missing: {tree_id}") 127 + continue 128 + 129 + text = tree_file.read_text() 130 + slug, md = convert(tree_id, text) 131 + if not slug: 132 + print(f" no mdnote: {tree_id}") 133 + continue 134 + 135 + base_slug = slug 136 + if slug in used_slugs: 137 + slug = f"{base_slug}-{tree_id[-3:]}" 138 + used_slugs[slug] = tree_id 139 + 140 + out = output_dir / f"{slug}.md" 141 + out.write_text(md) 142 + print(f" {tree_id} → posts/{slug}.md") 143 + converted += 1 144 + 145 + print(f"\nConverted {converted} nodes → {output_dir}") 146 + 147 + 148 + if __name__ == "__main__": 149 + main()