|
91515c0…
|
lmata
|
1 |
""" |
|
91515c0…
|
lmata
|
2 |
Full end-to-end pipeline: detect → copy → configure → decap. |
|
91515c0…
|
lmata
|
3 |
|
|
91515c0…
|
lmata
|
4 |
For already-Hugo themes: assembles a clean, standalone site. |
|
91515c0…
|
lmata
|
5 |
For raw HTML themes: calls hugoify first, then assembles. |
|
91515c0…
|
lmata
|
6 |
""" |
|
91515c0…
|
lmata
|
7 |
|
|
91515c0…
|
lmata
|
8 |
import logging |
|
91515c0…
|
lmata
|
9 |
import os |
|
91515c0…
|
lmata
|
10 |
import shutil |
|
91515c0…
|
lmata
|
11 |
from pathlib import Path |
|
91515c0…
|
lmata
|
12 |
|
|
91515c0…
|
lmata
|
13 |
from .decapify import decapify |
|
77c5e9a…
|
noreply
|
14 |
from .hugoify import hugoify_html, hugoify_nextjs |
|
77c5e9a…
|
noreply
|
15 |
from .theme_finder import find_hugo_theme, find_nextjs_app, find_raw_html_files |
|
91515c0…
|
lmata
|
16 |
from .theme_patcher import patch_config, patch_theme |
|
91515c0…
|
lmata
|
17 |
|
|
91515c0…
|
lmata
|
18 |
|
|
91515c0…
|
lmata
|
19 |
def complete( |
|
91515c0…
|
lmata
|
20 |
input_path: str, |
|
91515c0…
|
lmata
|
21 |
output_dir: str = None, |
|
91515c0…
|
lmata
|
22 |
cms_name: str = None, |
|
91515c0…
|
lmata
|
23 |
cms_logo: str = None, |
|
91515c0…
|
lmata
|
24 |
cms_color: str = None, |
|
91515c0…
|
lmata
|
25 |
) -> str: |
|
91515c0…
|
lmata
|
26 |
""" |
|
91515c0…
|
lmata
|
27 |
Run the full pipeline for a theme. |
|
91515c0…
|
lmata
|
28 |
|
|
91515c0…
|
lmata
|
29 |
Args: |
|
91515c0…
|
lmata
|
30 |
input_path: Path to a theme directory (from themes/) or raw HTML dir. |
|
91515c0…
|
lmata
|
31 |
output_dir: Where to write the output site. Defaults to output/{theme-name}. |
|
91515c0…
|
lmata
|
32 |
cms_name: Whitelabel CMS name for Decap admin UI. |
|
91515c0…
|
lmata
|
33 |
cms_logo: Whitelabel logo URL for Decap admin UI. |
|
91515c0…
|
lmata
|
34 |
cms_color: Whitelabel top-bar color for Decap admin UI. |
|
91515c0…
|
lmata
|
35 |
|
|
91515c0…
|
lmata
|
36 |
Returns: |
|
91515c0…
|
lmata
|
37 |
Path to the generated site, or error message. |
|
91515c0…
|
lmata
|
38 |
""" |
|
91515c0…
|
lmata
|
39 |
logging.info(f"Starting pipeline for {input_path} ...") |
|
91515c0…
|
lmata
|
40 |
|
|
91515c0…
|
lmata
|
41 |
branding = {'cms_name': cms_name, 'cms_logo': cms_logo, 'cms_color': cms_color} |
|
91515c0…
|
lmata
|
42 |
info = find_hugo_theme(input_path) |
|
91515c0…
|
lmata
|
43 |
|
|
91515c0…
|
lmata
|
44 |
if info: |
|
91515c0…
|
lmata
|
45 |
return _assemble_hugo_site(info, output_dir, branding) |
|
77c5e9a…
|
noreply
|
46 |
|
|
77c5e9a…
|
noreply
|
47 |
# Next.js path (check before raw HTML since Next.js projects may contain .html files) |
|
77c5e9a…
|
noreply
|
48 |
nextjs_info = find_nextjs_app(input_path) |
|
77c5e9a…
|
noreply
|
49 |
if nextjs_info: |
|
77c5e9a…
|
noreply
|
50 |
return _convert_nextjs(input_path, nextjs_info, output_dir, branding) |
|
77c5e9a…
|
noreply
|
51 |
|
|
77c5e9a…
|
noreply
|
52 |
# Raw HTML path |
|
77c5e9a…
|
noreply
|
53 |
html_files = find_raw_html_files(input_path) |
|
77c5e9a…
|
noreply
|
54 |
if not html_files: |
|
77c5e9a…
|
noreply
|
55 |
raise ValueError(f"No Hugo theme, Next.js app, or HTML files found in {input_path}") |
|
77c5e9a…
|
noreply
|
56 |
return _convert_raw_html(input_path, html_files, output_dir, branding) |
|
91515c0…
|
lmata
|
57 |
|
|
91515c0…
|
lmata
|
58 |
|
|
91515c0…
|
lmata
|
59 |
# --------------------------------------------------------------------------- |
|
91515c0…
|
lmata
|
60 |
# Hugo theme path |
|
91515c0…
|
lmata
|
61 |
# --------------------------------------------------------------------------- |
|
91515c0…
|
lmata
|
62 |
|
|
91515c0…
|
lmata
|
63 |
def _assemble_hugo_site(info: dict, output_dir: str = None, branding: dict = None) -> str: |
|
91515c0…
|
lmata
|
64 |
theme_dir = info['theme_dir'] |
|
91515c0…
|
lmata
|
65 |
example_site = info['example_site'] |
|
91515c0…
|
lmata
|
66 |
theme_name = info['theme_name'] |
|
91515c0…
|
lmata
|
67 |
|
|
91515c0…
|
lmata
|
68 |
if output_dir is None: |
|
91515c0…
|
lmata
|
69 |
output_dir = str(Path(__file__).parents[2] / 'output' / theme_name) |
|
91515c0…
|
lmata
|
70 |
|
|
91515c0…
|
lmata
|
71 |
logging.info(f"Building site at {output_dir} ...") |
|
91515c0…
|
lmata
|
72 |
os.makedirs(output_dir, exist_ok=True) |
|
91515c0…
|
lmata
|
73 |
|
|
91515c0…
|
lmata
|
74 |
# 1. Copy theme files → themes/{theme_name}/ |
|
91515c0…
|
lmata
|
75 |
dest_theme = os.path.join(output_dir, 'themes', theme_name) |
|
91515c0…
|
lmata
|
76 |
_copy_dir(theme_dir, dest_theme, exclude={'exampleSite', '__MACOSX', '.DS_Store'}) |
|
91515c0…
|
lmata
|
77 |
logging.info(f"Copied theme to {dest_theme}") |
|
91515c0…
|
lmata
|
78 |
patch_theme(dest_theme) |
|
91515c0…
|
lmata
|
79 |
|
|
91515c0…
|
lmata
|
80 |
# 2. Copy exampleSite content/static/data → site root |
|
91515c0…
|
lmata
|
81 |
if example_site: |
|
91515c0…
|
lmata
|
82 |
for subdir in ('content', 'data', 'i18n'): |
|
91515c0…
|
lmata
|
83 |
src = os.path.join(example_site, subdir) |
|
91515c0…
|
lmata
|
84 |
if os.path.isdir(src): |
|
91515c0…
|
lmata
|
85 |
_copy_dir(src, os.path.join(output_dir, subdir)) |
|
91515c0…
|
lmata
|
86 |
logging.info(f"Copied {subdir}/ from exampleSite") |
|
91515c0…
|
lmata
|
87 |
|
|
91515c0…
|
lmata
|
88 |
# Static: merge exampleSite/static into output/static |
|
91515c0…
|
lmata
|
89 |
src_static = os.path.join(example_site, 'static') |
|
91515c0…
|
lmata
|
90 |
if os.path.isdir(src_static): |
|
91515c0…
|
lmata
|
91 |
_copy_dir(src_static, os.path.join(output_dir, 'static')) |
|
91515c0…
|
lmata
|
92 |
logging.info("Copied static/ from exampleSite") |
|
91515c0…
|
lmata
|
93 |
|
|
91515c0…
|
lmata
|
94 |
# Write hugo.toml from exampleSite config |
|
91515c0…
|
lmata
|
95 |
config_toml = _find_config(example_site) |
|
91515c0…
|
lmata
|
96 |
if config_toml: |
|
91515c0…
|
lmata
|
97 |
_write_hugo_toml(config_toml, output_dir, theme_name) |
|
91515c0…
|
lmata
|
98 |
else: |
|
91515c0…
|
lmata
|
99 |
_write_minimal_hugo_toml(output_dir, theme_name) |
|
91515c0…
|
lmata
|
100 |
else: |
|
91515c0…
|
lmata
|
101 |
_write_minimal_hugo_toml(output_dir, theme_name) |
|
91515c0…
|
lmata
|
102 |
# Create minimal content/_index.md |
|
91515c0…
|
lmata
|
103 |
content_dir = os.path.join(output_dir, 'content') |
|
91515c0…
|
lmata
|
104 |
os.makedirs(content_dir, exist_ok=True) |
|
91515c0…
|
lmata
|
105 |
index_md = os.path.join(content_dir, '_index.md') |
|
91515c0…
|
lmata
|
106 |
if not os.path.exists(index_md): |
|
91515c0…
|
lmata
|
107 |
with open(index_md, 'w') as f: |
|
91515c0…
|
lmata
|
108 |
f.write('---\ntitle: Home\n---\n') |
|
91515c0…
|
lmata
|
109 |
|
|
91515c0…
|
lmata
|
110 |
# 3. Generate Decap CMS config |
|
91515c0…
|
lmata
|
111 |
b = branding or {} |
|
91515c0…
|
lmata
|
112 |
decapify( |
|
91515c0…
|
lmata
|
113 |
output_dir, |
|
91515c0…
|
lmata
|
114 |
cms_name=b.get('cms_name'), cms_logo=b.get('cms_logo'), cms_color=b.get('cms_color'), |
|
91515c0…
|
lmata
|
115 |
) |
|
91515c0…
|
lmata
|
116 |
|
|
91515c0…
|
lmata
|
117 |
logging.info(f"Done. Site ready at: {output_dir}") |
|
91515c0…
|
lmata
|
118 |
logging.info(f"Run: cd {output_dir} && hugo serve") |
|
91515c0…
|
lmata
|
119 |
return output_dir |
|
91515c0…
|
lmata
|
120 |
|
|
91515c0…
|
lmata
|
121 |
|
|
91515c0…
|
lmata
|
122 |
# --------------------------------------------------------------------------- |
|
77c5e9a…
|
noreply
|
123 |
# Next.js path |
|
77c5e9a…
|
noreply
|
124 |
# --------------------------------------------------------------------------- |
|
77c5e9a…
|
noreply
|
125 |
|
|
77c5e9a…
|
noreply
|
126 |
def _convert_nextjs( |
|
77c5e9a…
|
noreply
|
127 |
input_path: str, nextjs_info: dict, output_dir: str = None, branding: dict = None |
|
77c5e9a…
|
noreply
|
128 |
) -> str: |
|
77c5e9a…
|
noreply
|
129 |
app_dir = nextjs_info['app_dir'] |
|
77c5e9a…
|
noreply
|
130 |
theme_name = nextjs_info.get('app_name', os.path.basename(os.path.abspath(input_path))) |
|
77c5e9a…
|
noreply
|
131 |
|
|
77c5e9a…
|
noreply
|
132 |
if output_dir is None: |
|
77c5e9a…
|
noreply
|
133 |
output_dir = str(Path(__file__).parents[2] / 'output' / theme_name) |
|
77c5e9a…
|
noreply
|
134 |
|
|
77c5e9a…
|
noreply
|
135 |
logging.info(f"Converting Next.js app: {theme_name}") |
|
77c5e9a…
|
noreply
|
136 |
|
|
77c5e9a…
|
noreply
|
137 |
# Convert: capture rendered HTML if dev server running, else AI fallback |
|
77c5e9a…
|
noreply
|
138 |
hugo_layouts = hugoify_nextjs(nextjs_info) |
|
77c5e9a…
|
noreply
|
139 |
|
|
77c5e9a…
|
noreply
|
140 |
os.makedirs(output_dir, exist_ok=True) |
|
77c5e9a…
|
noreply
|
141 |
|
|
77c5e9a…
|
noreply
|
142 |
# Extract captured CSS if present (from rendered HTML capture) |
|
77c5e9a…
|
noreply
|
143 |
captured_css = hugo_layouts.pop('_captured_css', {}) |
|
77c5e9a…
|
noreply
|
144 |
|
|
77c5e9a…
|
noreply
|
145 |
# Write converted layouts |
|
77c5e9a…
|
noreply
|
146 |
theme_layouts_dir = os.path.join(output_dir, 'themes', theme_name, 'layouts') |
|
77c5e9a…
|
noreply
|
147 |
os.makedirs(os.path.join(theme_layouts_dir, '_default'), exist_ok=True) |
|
77c5e9a…
|
noreply
|
148 |
os.makedirs(os.path.join(theme_layouts_dir, 'partials'), exist_ok=True) |
|
77c5e9a…
|
noreply
|
149 |
|
|
77c5e9a…
|
noreply
|
150 |
for filename, content in hugo_layouts.items(): |
|
77c5e9a…
|
noreply
|
151 |
# Fix common AI mistake: partial "partials/X.html" → partial "X.html" |
|
77c5e9a…
|
noreply
|
152 |
if isinstance(content, str): |
|
77c5e9a…
|
noreply
|
153 |
content = content.replace('partial "partials/', 'partial "') |
|
77c5e9a…
|
noreply
|
154 |
dest = os.path.join(theme_layouts_dir, filename) |
|
77c5e9a…
|
noreply
|
155 |
os.makedirs(os.path.dirname(dest), exist_ok=True) |
|
77c5e9a…
|
noreply
|
156 |
with open(dest, 'w') as f: |
|
77c5e9a…
|
noreply
|
157 |
f.write(content) |
|
77c5e9a…
|
noreply
|
158 |
|
|
77c5e9a…
|
noreply
|
159 |
# Copy public/ assets to theme static/ |
|
77c5e9a…
|
noreply
|
160 |
public_dir = os.path.join(app_dir, 'public') |
|
77c5e9a…
|
noreply
|
161 |
theme_static = os.path.join(output_dir, 'themes', theme_name, 'static') |
|
77c5e9a…
|
noreply
|
162 |
if os.path.isdir(public_dir): |
|
77c5e9a…
|
noreply
|
163 |
_copy_dir(public_dir, theme_static) |
|
77c5e9a…
|
noreply
|
164 |
logging.info("Copied public/ assets to static/") |
|
77c5e9a…
|
noreply
|
165 |
|
|
77c5e9a…
|
noreply
|
166 |
# Write captured CSS (from rendered HTML capture) |
|
77c5e9a…
|
noreply
|
167 |
css_dest = os.path.join(theme_static, 'css') |
|
77c5e9a…
|
noreply
|
168 |
os.makedirs(css_dest, exist_ok=True) |
|
77c5e9a…
|
noreply
|
169 |
for css_name, css_content in captured_css.items(): |
|
77c5e9a…
|
noreply
|
170 |
with open(os.path.join(css_dest, css_name), 'w') as f: |
|
77c5e9a…
|
noreply
|
171 |
f.write(css_content) |
|
77c5e9a…
|
noreply
|
172 |
logging.info(f"Wrote captured CSS: {css_name}") |
|
77c5e9a…
|
noreply
|
173 |
|
|
77c5e9a…
|
noreply
|
174 |
# Also copy source CSS files (globals.css etc.) |
|
77c5e9a…
|
noreply
|
175 |
for css_file in nextjs_info.get('css_files', []): |
|
77c5e9a…
|
noreply
|
176 |
if os.path.isfile(css_file): |
|
77c5e9a…
|
noreply
|
177 |
shutil.copy2(css_file, os.path.join(css_dest, os.path.basename(css_file))) |
|
77c5e9a…
|
noreply
|
178 |
logging.info("Copied CSS files") |
|
77c5e9a…
|
noreply
|
179 |
|
|
77c5e9a…
|
noreply
|
180 |
_write_minimal_hugo_toml(output_dir, theme_name) |
|
3d0f0d4…
|
lmata
|
181 |
_write_default_layouts(output_dir, theme_name) |
|
77c5e9a…
|
noreply
|
182 |
|
|
77c5e9a…
|
noreply
|
183 |
# Create minimal content |
|
77c5e9a…
|
noreply
|
184 |
content_dir = os.path.join(output_dir, 'content') |
|
77c5e9a…
|
noreply
|
185 |
os.makedirs(content_dir, exist_ok=True) |
|
77c5e9a…
|
noreply
|
186 |
with open(os.path.join(content_dir, '_index.md'), 'w') as f: |
|
77c5e9a…
|
noreply
|
187 |
f.write('---\ntitle: Home\n---\n') |
|
77c5e9a…
|
noreply
|
188 |
|
|
77c5e9a…
|
noreply
|
189 |
b = branding or {} |
|
77c5e9a…
|
noreply
|
190 |
decapify( |
|
77c5e9a…
|
noreply
|
191 |
output_dir, |
|
77c5e9a…
|
noreply
|
192 |
cms_name=b.get('cms_name'), cms_logo=b.get('cms_logo'), cms_color=b.get('cms_color'), |
|
77c5e9a…
|
noreply
|
193 |
) |
|
77c5e9a…
|
noreply
|
194 |
|
|
77c5e9a…
|
noreply
|
195 |
logging.info(f"Done. Site ready at: {output_dir}") |
|
77c5e9a…
|
noreply
|
196 |
logging.info(f"Run: cd {output_dir} && hugo serve") |
|
77c5e9a…
|
noreply
|
197 |
return output_dir |
|
77c5e9a…
|
noreply
|
198 |
|
|
77c5e9a…
|
noreply
|
199 |
|
|
77c5e9a…
|
noreply
|
200 |
# --------------------------------------------------------------------------- |
|
91515c0…
|
lmata
|
201 |
# Raw HTML path |
|
91515c0…
|
lmata
|
202 |
# --------------------------------------------------------------------------- |
|
91515c0…
|
lmata
|
203 |
|
|
91515c0…
|
lmata
|
204 |
def _convert_raw_html( |
|
91515c0…
|
lmata
|
205 |
input_path: str, html_files: list, output_dir: str = None, branding: dict = None |
|
91515c0…
|
lmata
|
206 |
) -> str: |
|
91515c0…
|
lmata
|
207 |
theme_name = os.path.basename(os.path.abspath(input_path)) |
|
91515c0…
|
lmata
|
208 |
|
|
91515c0…
|
lmata
|
209 |
if output_dir is None: |
|
91515c0…
|
lmata
|
210 |
output_dir = str(Path(__file__).parents[2] / 'output' / theme_name) |
|
91515c0…
|
lmata
|
211 |
|
|
91515c0…
|
lmata
|
212 |
logging.info(f"Converting raw HTML theme: {theme_name}") |
|
91515c0…
|
lmata
|
213 |
|
|
77c5e9a…
|
noreply
|
214 |
# Direct HTML extraction — use the actual HTML as-is, no AI reinterpretation |
|
91515c0…
|
lmata
|
215 |
main_html = _pick_main_html(html_files) |
|
77c5e9a…
|
noreply
|
216 |
logging.info(f"Extracting {main_html} ...") |
|
91515c0…
|
lmata
|
217 |
hugo_layouts = hugoify_html(main_html) |
|
91515c0…
|
lmata
|
218 |
|
|
91515c0…
|
lmata
|
219 |
os.makedirs(output_dir, exist_ok=True) |
|
91515c0…
|
lmata
|
220 |
|
|
91515c0…
|
lmata
|
221 |
# Write converted layouts |
|
91515c0…
|
lmata
|
222 |
theme_layouts_dir = os.path.join(output_dir, 'themes', theme_name, 'layouts') |
|
91515c0…
|
lmata
|
223 |
os.makedirs(os.path.join(theme_layouts_dir, '_default'), exist_ok=True) |
|
91515c0…
|
lmata
|
224 |
os.makedirs(os.path.join(theme_layouts_dir, 'partials'), exist_ok=True) |
|
91515c0…
|
lmata
|
225 |
|
|
91515c0…
|
lmata
|
226 |
for filename, content in hugo_layouts.items(): |
|
91515c0…
|
lmata
|
227 |
dest = os.path.join(theme_layouts_dir, filename) |
|
91515c0…
|
lmata
|
228 |
os.makedirs(os.path.dirname(dest), exist_ok=True) |
|
91515c0…
|
lmata
|
229 |
with open(dest, 'w') as f: |
|
91515c0…
|
lmata
|
230 |
f.write(content) |
|
91515c0…
|
lmata
|
231 |
|
|
77c5e9a…
|
noreply
|
232 |
# Copy ALL static assets from the HTML theme directory |
|
77c5e9a…
|
noreply
|
233 |
theme_static = os.path.join(output_dir, 'themes', theme_name, 'static') |
|
77c5e9a…
|
noreply
|
234 |
for item in os.listdir(input_path): |
|
77c5e9a…
|
noreply
|
235 |
src = os.path.join(input_path, item) |
|
77c5e9a…
|
noreply
|
236 |
if os.path.isdir(src) and item not in ('__MACOSX', '.git', 'node_modules'): |
|
77c5e9a…
|
noreply
|
237 |
_copy_dir(src, os.path.join(theme_static, item)) |
|
77c5e9a…
|
noreply
|
238 |
elif os.path.isfile(src) and not item.endswith('.html'): |
|
77c5e9a…
|
noreply
|
239 |
# Copy non-HTML files (images, fonts, etc.) to static root |
|
77c5e9a…
|
noreply
|
240 |
os.makedirs(theme_static, exist_ok=True) |
|
77c5e9a…
|
noreply
|
241 |
shutil.copy2(src, os.path.join(theme_static, item)) |
|
91515c0…
|
lmata
|
242 |
|
|
91515c0…
|
lmata
|
243 |
_write_minimal_hugo_toml(output_dir, theme_name) |
|
3d0f0d4…
|
lmata
|
244 |
_write_default_layouts(output_dir, theme_name) |
|
91515c0…
|
lmata
|
245 |
|
|
91515c0…
|
lmata
|
246 |
# Create minimal content |
|
91515c0…
|
lmata
|
247 |
content_dir = os.path.join(output_dir, 'content') |
|
91515c0…
|
lmata
|
248 |
os.makedirs(content_dir, exist_ok=True) |
|
91515c0…
|
lmata
|
249 |
with open(os.path.join(content_dir, '_index.md'), 'w') as f: |
|
91515c0…
|
lmata
|
250 |
f.write('---\ntitle: Home\n---\n') |
|
91515c0…
|
lmata
|
251 |
|
|
91515c0…
|
lmata
|
252 |
b = branding or {} |
|
91515c0…
|
lmata
|
253 |
decapify( |
|
91515c0…
|
lmata
|
254 |
output_dir, |
|
91515c0…
|
lmata
|
255 |
cms_name=b.get('cms_name'), cms_logo=b.get('cms_logo'), cms_color=b.get('cms_color'), |
|
91515c0…
|
lmata
|
256 |
) |
|
91515c0…
|
lmata
|
257 |
|
|
91515c0…
|
lmata
|
258 |
logging.info(f"Done. Site ready at: {output_dir}") |
|
91515c0…
|
lmata
|
259 |
return output_dir |
|
91515c0…
|
lmata
|
260 |
|
|
91515c0…
|
lmata
|
261 |
|
|
91515c0…
|
lmata
|
262 |
# --------------------------------------------------------------------------- |
|
91515c0…
|
lmata
|
263 |
# Helpers |
|
91515c0…
|
lmata
|
264 |
# --------------------------------------------------------------------------- |
|
91515c0…
|
lmata
|
265 |
|
|
91515c0…
|
lmata
|
266 |
def _copy_dir(src: str, dest: str, exclude: set = None): |
|
91515c0…
|
lmata
|
267 |
"""Copy src → dest, skipping excluded names.""" |
|
91515c0…
|
lmata
|
268 |
exclude = exclude or set() |
|
91515c0…
|
lmata
|
269 |
if not os.path.isdir(src): |
|
91515c0…
|
lmata
|
270 |
return |
|
91515c0…
|
lmata
|
271 |
os.makedirs(dest, exist_ok=True) |
|
91515c0…
|
lmata
|
272 |
for item in os.listdir(src): |
|
91515c0…
|
lmata
|
273 |
if item in exclude or item.startswith('._'): |
|
91515c0…
|
lmata
|
274 |
continue |
|
91515c0…
|
lmata
|
275 |
s = os.path.join(src, item) |
|
91515c0…
|
lmata
|
276 |
d = os.path.join(dest, item) |
|
91515c0…
|
lmata
|
277 |
if os.path.isdir(s): |
|
91515c0…
|
lmata
|
278 |
_copy_dir(s, d, exclude) |
|
91515c0…
|
lmata
|
279 |
else: |
|
91515c0…
|
lmata
|
280 |
shutil.copy2(s, d) |
|
91515c0…
|
lmata
|
281 |
|
|
91515c0…
|
lmata
|
282 |
|
|
91515c0…
|
lmata
|
283 |
def _find_config(example_site: str) -> str | None: |
|
91515c0…
|
lmata
|
284 |
"""Find config.toml or hugo.toml in exampleSite.""" |
|
91515c0…
|
lmata
|
285 |
for name in ('hugo.toml', 'config.toml'): |
|
91515c0…
|
lmata
|
286 |
p = os.path.join(example_site, name) |
|
91515c0…
|
lmata
|
287 |
if os.path.exists(p): |
|
91515c0…
|
lmata
|
288 |
return p |
|
91515c0…
|
lmata
|
289 |
# config/_default/config.toml pattern |
|
91515c0…
|
lmata
|
290 |
p = os.path.join(example_site, 'config', '_default', 'config.toml') |
|
91515c0…
|
lmata
|
291 |
if os.path.exists(p): |
|
91515c0…
|
lmata
|
292 |
return p |
|
91515c0…
|
lmata
|
293 |
return None |
|
91515c0…
|
lmata
|
294 |
|
|
91515c0…
|
lmata
|
295 |
|
|
91515c0…
|
lmata
|
296 |
def _write_hugo_toml(source_config: str, output_dir: str, theme_name: str): |
|
91515c0…
|
lmata
|
297 |
"""Copy source config to hugo.toml, ensuring theme = theme_name and modern key names.""" |
|
91515c0…
|
lmata
|
298 |
import re |
|
91515c0…
|
lmata
|
299 |
with open(source_config, 'r') as f: |
|
91515c0…
|
lmata
|
300 |
content = f.read() |
|
91515c0…
|
lmata
|
301 |
|
|
91515c0…
|
lmata
|
302 |
# Suppress noisy but harmless warnings from example content |
|
91515c0…
|
lmata
|
303 |
if 'ignoreLogs' not in content: |
|
91515c0…
|
lmata
|
304 |
content += "\nignorelogs = ['warning-goldmark-raw-html']\n" |
|
91515c0…
|
lmata
|
305 |
|
|
91515c0…
|
lmata
|
306 |
# Ensure theme is set correctly |
|
91515c0…
|
lmata
|
307 |
if re.search(r'^theme\s*=', content, re.MULTILINE): |
|
91515c0…
|
lmata
|
308 |
content = re.sub(r'^theme\s*=.*$', f'theme = "{theme_name}"', content, flags=re.MULTILINE) |
|
91515c0…
|
lmata
|
309 |
else: |
|
91515c0…
|
lmata
|
310 |
content = f'theme = "{theme_name}"\n' + content |
|
91515c0…
|
lmata
|
311 |
|
|
91515c0…
|
lmata
|
312 |
dest = os.path.join(output_dir, 'hugo.toml') |
|
91515c0…
|
lmata
|
313 |
with open(dest, 'w') as f: |
|
91515c0…
|
lmata
|
314 |
f.write(content) |
|
91515c0…
|
lmata
|
315 |
patch_config(dest) |
|
91515c0…
|
lmata
|
316 |
logging.info("Wrote hugo.toml") |
|
91515c0…
|
lmata
|
317 |
|
|
91515c0…
|
lmata
|
318 |
|
|
91515c0…
|
lmata
|
319 |
def _write_minimal_hugo_toml(output_dir: str, theme_name: str): |
|
91515c0…
|
lmata
|
320 |
dest = os.path.join(output_dir, 'hugo.toml') |
|
91515c0…
|
lmata
|
321 |
safe_name = theme_name.replace('"', '') |
|
91515c0…
|
lmata
|
322 |
title = safe_name.replace('-', ' ').title() |
|
91515c0…
|
lmata
|
323 |
with open(dest, 'w') as f: |
|
91515c0…
|
lmata
|
324 |
f.write(f'''baseURL = "http://localhost:1313/" |
|
91515c0…
|
lmata
|
325 |
languageCode = "en-us" |
|
91515c0…
|
lmata
|
326 |
title = "{title}" |
|
91515c0…
|
lmata
|
327 |
theme = "{safe_name}" |
|
91515c0…
|
lmata
|
328 |
''') |
|
91515c0…
|
lmata
|
329 |
logging.info("Wrote minimal hugo.toml") |
|
3d0f0d4…
|
lmata
|
330 |
|
|
3d0f0d4…
|
lmata
|
331 |
|
|
3d0f0d4…
|
lmata
|
332 |
def _write_default_layouts(output_dir: str, theme_name: str): |
|
3d0f0d4…
|
lmata
|
333 |
"""Write single.html and list.html if they don't already exist.""" |
|
3d0f0d4…
|
lmata
|
334 |
layouts_dir = os.path.join(output_dir, 'themes', theme_name, 'layouts', '_default') |
|
3d0f0d4…
|
lmata
|
335 |
os.makedirs(layouts_dir, exist_ok=True) |
|
3d0f0d4…
|
lmata
|
336 |
|
|
3d0f0d4…
|
lmata
|
337 |
single = os.path.join(layouts_dir, 'single.html') |
|
3d0f0d4…
|
lmata
|
338 |
if not os.path.exists(single): |
|
3d0f0d4…
|
lmata
|
339 |
with open(single, 'w') as f: |
|
3d0f0d4…
|
lmata
|
340 |
f.write('''{{ define "main" }} |
|
3d0f0d4…
|
lmata
|
341 |
<div style="max-width:48rem;margin:0 auto;padding:3rem 1.5rem"> |
|
3d0f0d4…
|
lmata
|
342 |
<a href="/" style="color:#515be3;font-size:0.875rem;display:inline-block;margin-bottom:2rem">← Back to home</a> |
|
3d0f0d4…
|
lmata
|
343 |
<h1 style="font-size:2.25rem;font-weight:700;margin-bottom:1rem">{{ .Title }}</h1> |
|
3d0f0d4…
|
lmata
|
344 |
{{ with .Params.description }}<p style="color:#9ca3af;font-size:1.125rem;margin-bottom:1.5rem">{{ . }}</p>{{ end }} |
|
3d0f0d4…
|
lmata
|
345 |
{{ with .Date }}<time style="color:#6b7280;font-size:0.875rem;display:block;margin-bottom:2.5rem">{{ .Format "January 2, 2006" }}</time>{{ end }} |
|
3d0f0d4…
|
lmata
|
346 |
<article>{{ .Content }}</article> |
|
3d0f0d4…
|
lmata
|
347 |
</div> |
|
3d0f0d4…
|
lmata
|
348 |
{{ end }} |
|
3d0f0d4…
|
lmata
|
349 |
''') |
|
3d0f0d4…
|
lmata
|
350 |
logging.info("Wrote default single.html") |
|
3d0f0d4…
|
lmata
|
351 |
|
|
3d0f0d4…
|
lmata
|
352 |
list_html = os.path.join(layouts_dir, 'list.html') |
|
3d0f0d4…
|
lmata
|
353 |
if not os.path.exists(list_html): |
|
3d0f0d4…
|
lmata
|
354 |
with open(list_html, 'w') as f: |
|
3d0f0d4…
|
lmata
|
355 |
f.write('''{{ define "main" }} |
|
3d0f0d4…
|
lmata
|
356 |
<div style="max-width:48rem;margin:0 auto;padding:3rem 1.5rem"> |
|
3d0f0d4…
|
lmata
|
357 |
<a href="/" style="color:#515be3;font-size:0.875rem;display:inline-block;margin-bottom:2rem">← Back to home</a> |
|
3d0f0d4…
|
lmata
|
358 |
<h1 style="font-size:2.25rem;font-weight:700;margin-bottom:3rem">{{ .Title }}</h1> |
|
3d0f0d4…
|
lmata
|
359 |
{{ range .Pages }} |
|
3d0f0d4…
|
lmata
|
360 |
<a href="{{ .Permalink }}" style="display:block;padding:1.5rem;border-radius:1rem;border:1px solid #374151;margin-bottom:1.5rem;text-decoration:none;color:inherit"> |
|
3d0f0d4…
|
lmata
|
361 |
<h2 style="font-size:1.25rem;font-weight:600;margin-bottom:0.5rem">{{ .Title }}</h2> |
|
3d0f0d4…
|
lmata
|
362 |
{{ with .Params.description }}<p style="color:#9ca3af">{{ . }}</p>{{ end }} |
|
3d0f0d4…
|
lmata
|
363 |
{{ with .Date }}<time style="color:#6b7280;font-size:0.875rem;margin-top:0.75rem;display:block">{{ .Format "January 2, 2006" }}</time>{{ end }} |
|
3d0f0d4…
|
lmata
|
364 |
</a> |
|
3d0f0d4…
|
lmata
|
365 |
{{ end }} |
|
3d0f0d4…
|
lmata
|
366 |
</div> |
|
3d0f0d4…
|
lmata
|
367 |
{{ end }} |
|
3d0f0d4…
|
lmata
|
368 |
''') |
|
3d0f0d4…
|
lmata
|
369 |
logging.info("Wrote default list.html") |
|
91515c0…
|
lmata
|
370 |
|
|
91515c0…
|
lmata
|
371 |
|
|
91515c0…
|
lmata
|
372 |
def _pick_main_html(html_files: list) -> str: |
|
91515c0…
|
lmata
|
373 |
"""Pick the most likely 'main' HTML file (index.html or first one).""" |
|
91515c0…
|
lmata
|
374 |
for f in html_files: |
|
91515c0…
|
lmata
|
375 |
if os.path.basename(f).lower() in ('index.html', 'home.html', 'main.html'): |
|
91515c0…
|
lmata
|
376 |
return f |
|
91515c0…
|
lmata
|
377 |
return html_files[0] |