1
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
"""
2
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
AI-powered post-conversion enhancements for Hugo sites.
3
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
4
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Individual commands:
5
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
hugoifier generate <path> --prompt "..." or --from-file example.md
6
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
hugoifier seo <path>
7
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
hugoifier alt-text <path>
8
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
9
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Meta command:
10
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
hugoifier enhance <path> (runs seo + alt-text)
11
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
"""
12
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
13
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
import json
14
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
import logging
15
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
import os
16
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
import re
17
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
18
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
import yaml
19
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
20
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
from ..config import call_ai
21
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
22
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
SYSTEM = "You are an expert Hugo site developer, SEO specialist, and content strategist."
23
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
24
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
25
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# ---------------------------------------------------------------------------
26
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Meta command
27
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# ---------------------------------------------------------------------------
28
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
29
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
def enhance(site_dir: str) -> str:
30
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
"""Run all non-destructive enhancements (SEO + alt-text)."""
31
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
context = _read_site_context(site_dir)
32
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
results = []
33
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
results.append(seo(site_dir, context=context))
34
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
results.append(alt_text(site_dir, context=context))
35
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
return "\n".join(results)
36
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
37
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
38
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# ---------------------------------------------------------------------------
39
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Content generation
40
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# ---------------------------------------------------------------------------
41
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
42
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
def generate(
43
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
site_dir: str,
44
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
prompt: str = None,
45
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
from_file: str = None,
46
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
) -> str:
47
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
"""Generate new Hugo content pages using AI."""
48
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
context = _read_site_context(site_dir)
49
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
50
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Build the instruction
51
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
if from_file:
52
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
with open(from_file, 'r', errors='replace') as f:
53
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
example = f.read()
54
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
instruction = (
55
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
f"Use the following file as a style and structure example. "
56
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
f"Generate 2-3 new content pages that follow the same format and tone.\n\n"
57
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
f"Example file:\n{example[:5000]}"
58
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
)
59
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
elif prompt:
60
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
instruction = prompt
61
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
else:
62
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
instruction = (
63
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
"Generate 2-3 new content pages that fit this site's theme and purpose. "
64
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
"Create pages for sections that exist in the navigation but are missing content."
65
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
)
66
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
67
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
ai_prompt = f"""You are generating content for a Hugo website.
68
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
69
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Site title: {context['title']}
70
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Site description: {context['description']}
71
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Existing content sections: {', '.join(context['content_sections']) or 'none'}
72
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
73
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Sample existing content:
74
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
{context['sample_content'][:3000]}
75
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
76
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Instruction: {instruction}
77
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
78
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Return a JSON object mapping relative file paths (under content/) to full markdown files.
79
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Each file MUST start with YAML frontmatter (--- delimiters) including: title, date, description.
80
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
IMPORTANT: Always quote title and description values in frontmatter with double quotes to handle colons and special characters.
81
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Example: {{"blog/my-first-post.md": "---\\ntitle: \\"My First Post\\"\\ndate: 2026-03-17\\ndescription: \\"A great post about things\\"\\n---\\n\\nContent here..."}}
82
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
83
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Return ONLY valid JSON, no explanation."""
84
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
85
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
response = call_ai(ai_prompt, SYSTEM, max_tokens=8192)
86
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
files = _parse_ai_json(response)
87
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
88
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
if not files:
89
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
return "Could not generate content — AI response was not valid JSON"
90
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
91
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
content_dir = os.path.join(site_dir, 'content')
92
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
written = []
93
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
for rel_path, content in files.items():
94
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
content = _fix_frontmatter_quoting(content)
95
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
dest = os.path.join(content_dir, rel_path)
96
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
os.makedirs(os.path.dirname(dest), exist_ok=True)
97
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
with open(dest, 'w') as f:
98
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
f.write(content)
99
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
written.append(rel_path)
100
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
logging.info(f"Generated {rel_path}")
101
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
102
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
return f"Generated {len(written)} content pages: {written}"
103
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
104
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
105
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# ---------------------------------------------------------------------------
106
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# SEO optimization
107
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# ---------------------------------------------------------------------------
108
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
109
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
def seo(site_dir: str, context: dict = None) -> str:
110
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
"""Add missing meta descriptions to content + OG tags to baseof."""
111
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
if context is None:
112
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
context = _read_site_context(site_dir)
113
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
114
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
results = []
115
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
results.append(_seo_descriptions(site_dir, context))
116
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
results.append(_seo_og_tags(site_dir))
117
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
return "\n".join(r for r in results if r)
118
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
119
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
120
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
def _seo_descriptions(site_dir: str, context: dict) -> str:
121
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
"""Add missing meta descriptions to content files."""
122
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
content_dir = os.path.join(site_dir, 'content')
123
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
if not os.path.isdir(content_dir):
124
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
return "No content/ directory found"
125
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
126
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Find files missing descriptions
127
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
missing = []
128
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
for root, dirs, files in os.walk(content_dir):
129
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
for f in files:
130
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
if not f.endswith('.md'):
131
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
continue
132
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
path = os.path.join(root, f)
133
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
fm = _parse_frontmatter(path)
134
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
if not fm.get('description'):
135
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
title = fm.get('title', f)
136
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
body = _read_body(path)
137
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
missing.append((path, title, body[:500]))
138
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
139
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
if not missing:
140
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
return "All content files already have descriptions"
141
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
142
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Batch AI call — up to 10 at a time
143
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
updated = 0
144
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
for batch in _chunks(missing, 10):
145
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
items = "\n".join(
146
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
f'- "{title}": {excerpt[:200]}' for _, title, excerpt in batch
147
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
)
148
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
ai_prompt = f"""Generate concise SEO meta descriptions (1-2 sentences, under 160 chars) for these Hugo content pages.
149
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
150
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Site: {context['title']}
151
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
152
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Pages:
153
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
{items}
154
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
155
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Return a JSON object mapping the exact title to the description.
156
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Return ONLY valid JSON."""
157
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
158
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
try:
159
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
response = call_ai(ai_prompt, SYSTEM, max_tokens=2048)
160
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
descriptions = _parse_ai_json(response)
161
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
if not descriptions:
162
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
continue
163
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
164
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
for path, title, _ in batch:
165
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
desc = descriptions.get(title)
166
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
if desc:
167
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
_update_frontmatter(path, {'description': desc})
168
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
updated += 1
169
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
except Exception as e:
170
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
logging.warning(f"SEO description batch failed: {e}")
171
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
172
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
return f"Added meta descriptions to {updated} content files"
173
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
174
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
175
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
def _seo_og_tags(site_dir: str) -> str:
176
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
"""Add Open Graph tags to baseof.html if missing."""
177
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
baseof = _find_baseof(site_dir)
178
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
if not baseof:
179
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
return "No baseof.html found"
180
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
181
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
with open(baseof, 'r') as f:
182
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
html = f.read()
183
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
184
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
if 'og:title' in html:
185
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
return "OG tags already present in baseof.html"
186
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
187
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
og_block = '''
188
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
<!-- Open Graph -->
189
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
<meta property="og:title" content="{{ .Title }}" />
190
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
<meta property="og:description" content="{{ with .Description }}{{ . }}{{ else }}{{ .Site.Params.description }}{{ end }}" />
191
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
<meta property="og:type" content="{{ if .IsPage }}article{{ else }}website{{ end }}" />
192
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
<meta property="og:url" content="{{ .Permalink }}" />
193
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
{{ with .Params.image }}<meta property="og:image" content="{{ . | absURL }}" />{{ end }}'''
194
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
195
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Insert before </head>
196
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
html = html.replace('</head>', og_block + '\n</head>')
197
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
with open(baseof, 'w') as f:
198
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
f.write(html)
199
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
200
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
return f"Added OG tags to {os.path.relpath(baseof, site_dir)}"
201
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
202
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
203
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# ---------------------------------------------------------------------------
204
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Image alt text
205
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# ---------------------------------------------------------------------------
206
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
207
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
def alt_text(site_dir: str, context: dict = None) -> str:
208
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
"""Generate alt text for images missing it in templates."""
209
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
if context is None:
210
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
context = _read_site_context(site_dir)
211
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
212
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Find all template files
213
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
templates = []
214
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
for search_dir in [
215
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
os.path.join(site_dir, 'layouts'),
216
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
*_glob_dirs(site_dir, 'themes/*/layouts'),
217
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
]:
218
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
if not os.path.isdir(search_dir):
219
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
continue
220
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
for root, dirs, files in os.walk(search_dir):
221
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
for f in files:
222
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
if f.endswith('.html'):
223
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
templates.append(os.path.join(root, f))
224
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
225
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Find images with empty or missing alt text
226
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
missing = []
227
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
img_pattern = re.compile(r'<img\b([^>]*)/?>', re.DOTALL)
228
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
alt_pattern = re.compile(r'alt\s*=\s*["\']([^"\']*)["\']')
229
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
230
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
for tpl_path in templates:
231
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
with open(tpl_path, 'r', errors='replace') as f:
232
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
content = f.read()
233
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
for m in img_pattern.finditer(content):
234
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
attrs = m.group(1)
235
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
alt_match = alt_pattern.search(attrs)
236
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Skip if has meaningful alt text (including Hugo template vars)
237
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
if alt_match and alt_match.group(1).strip():
238
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
continue
239
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Extract src for context
240
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
src_match = re.search(r'src\s*=\s*["\']([^"\']+)["\']', attrs)
241
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
src = src_match.group(1) if src_match else 'unknown'
242
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Get surrounding context
243
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
start = max(0, m.start() - 100)
244
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
end = min(len(content), m.end() + 100)
245
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
ctx = content[start:end]
246
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
missing.append((tpl_path, m.group(0), src, ctx))
247
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
248
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
if not missing:
249
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
return "All images already have alt text"
250
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
251
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Batch AI call
252
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
items = "\n".join(
253
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
f'- src="{src}" context: {ctx[:150]}' for _, _, src, ctx in missing[:20]
254
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
)
255
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
ai_prompt = f"""Generate descriptive alt text for these images on a Hugo site called "{context['title']}".
256
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
257
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Images:
258
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
{items}
259
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
260
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Return a JSON object mapping the src value to a short descriptive alt text string.
261
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
For images with Hugo template src attributes, use a Hugo template for the alt text too.
262
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
Return ONLY valid JSON."""
263
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
264
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
try:
265
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
response = call_ai(ai_prompt, SYSTEM, max_tokens=2048)
266
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
alts = _parse_ai_json(response)
267
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
except Exception as e:
268
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
return f"Alt text generation failed: {e}"
269
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
270
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
if not alts:
271
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
return "Could not parse alt text suggestions from AI"
272
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
273
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
updated_files = set()
274
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
for tpl_path, img_tag, src, _ in missing:
275
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
suggested = alts.get(src)
276
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
if not suggested:
277
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
continue
278
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
with open(tpl_path, 'r') as f:
279
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
content = f.read()
280
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
281
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
safe_alt = suggested.replace('"', '"')
282
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
if 'alt=' in img_tag:
283
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
new_tag = re.sub(r'alt\s*=\s*["\'][^"\']*["\']', f'alt="{safe_alt}"', img_tag)
284
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
else:
285
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
new_tag = img_tag.replace('<img ', f'<img alt="{safe_alt}" ', 1)
286
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
287
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
content = content.replace(img_tag, new_tag, 1)
288
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
with open(tpl_path, 'w') as f:
289
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
f.write(content)
290
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
updated_files.add(tpl_path)
291
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
292
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
return f"Added alt text to {len(updated_files)} template files"
293
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
294
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
295
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# ---------------------------------------------------------------------------
296
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Helpers
297
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# ---------------------------------------------------------------------------
298
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
299
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
def _read_site_context(site_dir: str) -> dict:
300
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
"""Read basic site info for AI context."""
301
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
context = {
302
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
'title': 'My Hugo Site',
303
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
'description': '',
304
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
'content_sections': [],
305
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
'sample_content': '',
306
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
}
307
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
308
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Read hugo.toml
309
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
for config_name in ('hugo.toml', 'config.toml'):
310
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
config_path = os.path.join(site_dir, config_name)
311
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
if os.path.exists(config_path):
312
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
with open(config_path, 'r') as f:
313
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
config_text = f.read()
314
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
title_match = re.search(r'^title\s*=\s*"([^"]*)"', config_text, re.MULTILINE)
315
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
if title_match:
316
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
context['title'] = title_match.group(1)
317
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
desc_match = re.search(r'description\s*=\s*"([^"]*)"', config_text, re.MULTILINE)
318
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
if desc_match:
319
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
context['description'] = desc_match.group(1)
320
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
break
321
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
322
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Read content sections
323
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
content_dir = os.path.join(site_dir, 'content')
324
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
if os.path.isdir(content_dir):
325
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
context['content_sections'] = [
326
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
d for d in os.listdir(content_dir)
327
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
if os.path.isdir(os.path.join(content_dir, d))
328
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
]
329
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
330
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Sample content
331
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
samples = []
332
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
for root, dirs, files in os.walk(content_dir):
333
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
for f in files:
334
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
if f.endswith('.md') and len(samples) < 3:
335
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
path = os.path.join(root, f)
336
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
with open(path, 'r', errors='replace') as fh:
337
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
samples.append(fh.read()[:1000])
338
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
context['sample_content'] = "\n---\n".join(samples)
339
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
340
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
return context
341
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
342
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
343
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
def _parse_frontmatter(md_path: str) -> dict:
344
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
"""Parse YAML frontmatter from a .md file."""
345
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
try:
346
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
with open(md_path, 'r', errors='replace') as f:
347
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
content = f.read()
348
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
match = re.match(r'^---\n(.*?)\n---', content, re.DOTALL)
349
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
if match:
350
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
return yaml.safe_load(match.group(1)) or {}
351
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
except Exception:
352
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
pass
353
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
return {}
354
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
355
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
356
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
def _read_body(md_path: str) -> str:
357
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
"""Read the body content (after frontmatter) of a .md file."""
358
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
try:
359
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
with open(md_path, 'r', errors='replace') as f:
360
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
content = f.read()
361
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
match = re.match(r'^---\n.*?\n---\n?(.*)', content, re.DOTALL)
362
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
return match.group(1).strip() if match else content
363
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
except Exception:
364
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
return ""
365
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
366
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
367
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
def _update_frontmatter(md_path: str, updates: dict):
368
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
"""Add/update keys in a markdown file's YAML frontmatter."""
369
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
with open(md_path, 'r', errors='replace') as f:
370
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
content = f.read()
371
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
372
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
match = re.match(r'^(---\n)(.*?)(\n---\n?)(.*)', content, re.DOTALL)
373
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
if not match:
374
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
return
375
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
376
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
fm_text = match.group(2)
377
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
body = match.group(4)
378
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
379
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
fm = yaml.safe_load(fm_text) or {}
380
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
fm.update(updates)
381
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
382
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
new_fm = yaml.dump(fm, default_flow_style=False, allow_unicode=True, sort_keys=False)
383
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
with open(md_path, 'w') as f:
384
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
f.write(f"---\n{new_fm}---\n{body}")
385
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
386
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
387
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
def _find_baseof(site_dir: str) -> str | None:
388
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
"""Find baseof.html in the site."""
389
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
candidates = [
390
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
os.path.join(site_dir, 'layouts', '_default', 'baseof.html'),
391
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
]
392
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Also check themes
393
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
themes_dir = os.path.join(site_dir, 'themes')
394
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
if os.path.isdir(themes_dir):
395
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
for theme in os.listdir(themes_dir):
396
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
candidates.append(
397
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
os.path.join(themes_dir, theme, 'layouts', '_default', 'baseof.html')
398
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
)
399
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
for c in candidates:
400
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
if os.path.isfile(c):
401
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
return c
402
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
return None
403
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
404
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
405
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
def _glob_dirs(base: str, pattern: str) -> list:
406
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
"""Simple glob for directory patterns like 'themes/*/layouts'."""
407
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
parts = pattern.split('*')
408
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
if len(parts) != 2:
409
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
return []
410
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
prefix = os.path.join(base, parts[0])
411
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
suffix = parts[1]
412
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
if not os.path.isdir(prefix):
413
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
return []
414
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
return [
415
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
os.path.join(prefix, d, suffix.lstrip('/'))
416
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
for d in os.listdir(prefix)
417
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
if os.path.isdir(os.path.join(prefix, d))
418
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
]
419
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
420
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
421
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
def _parse_ai_json(response: str) -> dict | None:
422
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
"""Extract JSON dict from AI response, stripping markdown fences."""
423
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
stripped = re.sub(r'```(?:json)?\s*', '', response)
424
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
stripped = re.sub(r'```\s*$', '', stripped.strip())
425
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
try:
426
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
result = json.loads(stripped)
427
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
if isinstance(result, dict):
428
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
return result
429
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
except json.JSONDecodeError:
430
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
pass
431
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
match = re.search(r'\{.*\}', stripped, re.DOTALL)
432
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
if match:
433
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
try:
434
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
result = json.loads(match.group(0))
435
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
if isinstance(result, dict):
436
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
return result
437
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
except json.JSONDecodeError:
438
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
pass
439
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
return None
440
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
441
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
442
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
def _fix_frontmatter_quoting(content: str) -> str:
443
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
"""Quote YAML frontmatter values that contain colons (Hugo/YAML safety)."""
444
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
match = re.match(r'^(---\n)(.*?)(\n---)', content, re.DOTALL)
445
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
if not match:
446
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
return content
447
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
fm_lines = match.group(2).split('\n')
448
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
fixed = []
449
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
for line in fm_lines:
450
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
kv = re.match(r'^(\w+):\s+(.+)$', line)
451
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
if kv:
452
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
key, val = kv.group(1), kv.group(2)
453
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
# Quote if value contains a colon and isn't already quoted
454
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
if ':' in val and not (val.startswith('"') or val.startswith("'")):
455
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
val = f'"{val}"'
456
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
fixed.append(f'{key}: {val}')
457
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
else:
458
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
fixed.append(line)
459
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
return f"---\n{chr(10).join(fixed)}\n---{content[match.end():]}"
460
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
461
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
462
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
def _chunks(lst, n):
463
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
"""Yield successive n-sized chunks from list."""
464
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
for i in range(0, len(lst), n):
465
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!
yield lst[i:i + n]
466
{ copied = false; pop = false }, 1000)" :class="copied && 'copied'">
Copy link Copied!