Hugoifier

fix(decapify): remove duplicate os import, scan content subdirs recursively Closes #5

lmata 2026-03-12 21:51 trunk
Commit 71088c1abd457372533ee5ba2a48c13cffa44f050b844b4b38b54999d6621089
1 file changed +24 -17
--- src/utils/decapify.py
+++ src/utils/decapify.py
@@ -11,19 +11,16 @@
1111
import re
1212
import yaml
1313
1414
from config import call_ai
1515
16
-logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
17
-
1816
DECAP_CDN = "https://unpkg.com/decap-cms@^3.0.0/dist/decap-cms.js"
1917
2018
# Whitelabel defaults — override via decapify() kwargs or env vars
21
-import os as _os
22
-DEFAULT_CMS_NAME = _os.getenv('CMS_NAME', 'Content Manager')
23
-DEFAULT_CMS_LOGO = _os.getenv('CMS_LOGO_URL', '') # URL or empty
24
-DEFAULT_CMS_COLOR = _os.getenv('CMS_COLOR', '#2e3748') # top-bar background
19
+DEFAULT_CMS_NAME = os.getenv('CMS_NAME', 'Content Manager')
20
+DEFAULT_CMS_LOGO = os.getenv('CMS_LOGO_URL', '') # URL or empty
21
+DEFAULT_CMS_COLOR = os.getenv('CMS_COLOR', '#2e3748') # top-bar background
2522
2623
2724
def decapify(
2825
site_dir: str,
2926
cms_name: str = None,
@@ -126,46 +123,54 @@
126123
with open(config_path, 'w') as f:
127124
yaml.dump(config, f, default_flow_style=False, allow_unicode=True, sort_keys=False)
128125
129126
logging.info(f"Wrote Decap CMS config to {config_path}")
130127
128
+
129
+def _collect_md_files(dirpath: str) -> list:
130
+ """Recursively collect all .md files under dirpath (excluding _index.md)."""
131
+ found = []
132
+ for root, dirs, files in os.walk(dirpath):
133
+ for f in files:
134
+ if f.endswith('.md') and f != '_index.md':
135
+ found.append(os.path.join(root, f))
136
+ return found
137
+
131138
132139
def _build_collections(content_dir: str) -> list:
133140
"""
134141
Inspect content/ to build Decap CMS collections.
135
- - Subdirs with multiple .md files → folder collection (e.g. blog)
136
- - Subdirs with a single _index.md → file collection (e.g. about, contact)
137
- - Top-level _index.md → homepage file entry
142
+ - Subdirs with any .md files (at any depth) → folder collection (e.g. blog)
143
+ - Subdirs with only a top-level _index.md → file collection (e.g. about, contact)
138144
"""
139145
if not os.path.isdir(content_dir):
140146
return [_default_pages_collection()]
141147
142148
collections = []
143
- seen_blog_like = False
144149
145150
entries = sorted(os.listdir(content_dir))
146151
for entry in entries:
147152
subdir = os.path.join(content_dir, entry)
148153
if not os.path.isdir(subdir):
149154
continue
150155
151
- md_files = [f for f in os.listdir(subdir) if f.endswith('.md')]
152
- non_index = [f for f in md_files if f != '_index.md']
156
+ # Collect all .md files at any depth (excluding _index.md)
157
+ non_index = _collect_md_files(subdir)
158
+ has_index = os.path.exists(os.path.join(subdir, '_index.md'))
153159
154160
if non_index:
155
- # Folder collection (blog, posts, etc.)
156
- fields = _infer_fields_for_folder(subdir, non_index)
161
+ # Folder collection (blog, posts, etc.) — use shallowest sample for field inference
162
+ fields = _infer_fields_for_folder(subdir, [os.path.relpath(f, subdir) for f in non_index])
157163
collections.append({
158164
'name': entry,
159165
'label': entry.replace('-', ' ').title(),
160166
'folder': f'content/{entry}',
161167
'create': True,
162168
'slug': '{{slug}}',
163169
'fields': fields,
164170
})
165
- seen_blog_like = True
166
- elif '_index.md' in md_files:
171
+ elif has_index:
167172
# File collection (single page)
168173
fields = _infer_fields_for_file(os.path.join(subdir, '_index.md'))
169174
collections.append({
170175
'name': entry,
171176
'label': entry.replace('-', ' ').title(),
@@ -183,11 +188,13 @@
183188
return collections
184189
185190
186191
def _infer_fields_for_folder(subdir: str, md_files: list) -> list:
187192
"""Read a sample .md file and extract frontmatter keys as fields."""
188
- sample = os.path.join(subdir, md_files[0])
193
+ # md_files may be relative paths (from _collect_md_files); resolve to absolute
194
+ first = md_files[0]
195
+ sample = first if os.path.isabs(first) else os.path.join(subdir, first)
189196
frontmatter = _parse_frontmatter(sample)
190197
191198
fields = []
192199
field_map = {
193200
'title': {'label': 'Title', 'name': 'title', 'widget': 'string'},
194201
--- src/utils/decapify.py
+++ src/utils/decapify.py
@@ -11,19 +11,16 @@
11 import re
12 import yaml
13
14 from config import call_ai
15
16 logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
17
18 DECAP_CDN = "https://unpkg.com/decap-cms@^3.0.0/dist/decap-cms.js"
19
20 # Whitelabel defaults — override via decapify() kwargs or env vars
21 import os as _os
22 DEFAULT_CMS_NAME = _os.getenv('CMS_NAME', 'Content Manager')
23 DEFAULT_CMS_LOGO = _os.getenv('CMS_LOGO_URL', '') # URL or empty
24 DEFAULT_CMS_COLOR = _os.getenv('CMS_COLOR', '#2e3748') # top-bar background
25
26
27 def decapify(
28 site_dir: str,
29 cms_name: str = None,
@@ -126,46 +123,54 @@
126 with open(config_path, 'w') as f:
127 yaml.dump(config, f, default_flow_style=False, allow_unicode=True, sort_keys=False)
128
129 logging.info(f"Wrote Decap CMS config to {config_path}")
130
 
 
 
 
 
 
 
 
 
 
131
132 def _build_collections(content_dir: str) -> list:
133 """
134 Inspect content/ to build Decap CMS collections.
135 - Subdirs with multiple .md files → folder collection (e.g. blog)
136 - Subdirs with a single _index.md → file collection (e.g. about, contact)
137 - Top-level _index.md → homepage file entry
138 """
139 if not os.path.isdir(content_dir):
140 return [_default_pages_collection()]
141
142 collections = []
143 seen_blog_like = False
144
145 entries = sorted(os.listdir(content_dir))
146 for entry in entries:
147 subdir = os.path.join(content_dir, entry)
148 if not os.path.isdir(subdir):
149 continue
150
151 md_files = [f for f in os.listdir(subdir) if f.endswith('.md')]
152 non_index = [f for f in md_files if f != '_index.md']
 
153
154 if non_index:
155 # Folder collection (blog, posts, etc.)
156 fields = _infer_fields_for_folder(subdir, non_index)
157 collections.append({
158 'name': entry,
159 'label': entry.replace('-', ' ').title(),
160 'folder': f'content/{entry}',
161 'create': True,
162 'slug': '{{slug}}',
163 'fields': fields,
164 })
165 seen_blog_like = True
166 elif '_index.md' in md_files:
167 # File collection (single page)
168 fields = _infer_fields_for_file(os.path.join(subdir, '_index.md'))
169 collections.append({
170 'name': entry,
171 'label': entry.replace('-', ' ').title(),
@@ -183,11 +188,13 @@
183 return collections
184
185
186 def _infer_fields_for_folder(subdir: str, md_files: list) -> list:
187 """Read a sample .md file and extract frontmatter keys as fields."""
188 sample = os.path.join(subdir, md_files[0])
 
 
189 frontmatter = _parse_frontmatter(sample)
190
191 fields = []
192 field_map = {
193 'title': {'label': 'Title', 'name': 'title', 'widget': 'string'},
194
--- src/utils/decapify.py
+++ src/utils/decapify.py
@@ -11,19 +11,16 @@
11 import re
12 import yaml
13
14 from config import call_ai
15
 
 
16 DECAP_CDN = "https://unpkg.com/decap-cms@^3.0.0/dist/decap-cms.js"
17
18 # Whitelabel defaults — override via decapify() kwargs or env vars
19 DEFAULT_CMS_NAME = os.getenv('CMS_NAME', 'Content Manager')
20 DEFAULT_CMS_LOGO = os.getenv('CMS_LOGO_URL', '') # URL or empty
21 DEFAULT_CMS_COLOR = os.getenv('CMS_COLOR', '#2e3748') # top-bar background
 
22
23
24 def decapify(
25 site_dir: str,
26 cms_name: str = None,
@@ -126,46 +123,54 @@
123 with open(config_path, 'w') as f:
124 yaml.dump(config, f, default_flow_style=False, allow_unicode=True, sort_keys=False)
125
126 logging.info(f"Wrote Decap CMS config to {config_path}")
127
128
129 def _collect_md_files(dirpath: str) -> list:
130 """Recursively collect all .md files under dirpath (excluding _index.md)."""
131 found = []
132 for root, dirs, files in os.walk(dirpath):
133 for f in files:
134 if f.endswith('.md') and f != '_index.md':
135 found.append(os.path.join(root, f))
136 return found
137
138
139 def _build_collections(content_dir: str) -> list:
140 """
141 Inspect content/ to build Decap CMS collections.
142 - Subdirs with any .md files (at any depth) → folder collection (e.g. blog)
143 - Subdirs with only a top-level _index.md → file collection (e.g. about, contact)
 
144 """
145 if not os.path.isdir(content_dir):
146 return [_default_pages_collection()]
147
148 collections = []
 
149
150 entries = sorted(os.listdir(content_dir))
151 for entry in entries:
152 subdir = os.path.join(content_dir, entry)
153 if not os.path.isdir(subdir):
154 continue
155
156 # Collect all .md files at any depth (excluding _index.md)
157 non_index = _collect_md_files(subdir)
158 has_index = os.path.exists(os.path.join(subdir, '_index.md'))
159
160 if non_index:
161 # Folder collection (blog, posts, etc.) — use shallowest sample for field inference
162 fields = _infer_fields_for_folder(subdir, [os.path.relpath(f, subdir) for f in non_index])
163 collections.append({
164 'name': entry,
165 'label': entry.replace('-', ' ').title(),
166 'folder': f'content/{entry}',
167 'create': True,
168 'slug': '{{slug}}',
169 'fields': fields,
170 })
171 elif has_index:
 
172 # File collection (single page)
173 fields = _infer_fields_for_file(os.path.join(subdir, '_index.md'))
174 collections.append({
175 'name': entry,
176 'label': entry.replace('-', ' ').title(),
@@ -183,11 +188,13 @@
188 return collections
189
190
191 def _infer_fields_for_folder(subdir: str, md_files: list) -> list:
192 """Read a sample .md file and extract frontmatter keys as fields."""
193 # md_files may be relative paths (from _collect_md_files); resolve to absolute
194 first = md_files[0]
195 sample = first if os.path.isabs(first) else os.path.join(subdir, first)
196 frontmatter = _parse_frontmatter(sample)
197
198 fields = []
199 field_map = {
200 'title': {'label': 'Title', 'name': 'title', 'widget': 'string'},
201

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button