Hugoifier
fix(decapify): remove duplicate os import, scan content subdirs recursively Closes #5
Commit
71088c1abd457372533ee5ba2a48c13cffa44f050b844b4b38b54999d6621089
Parent
e0420f1afbfd864…
1 file changed
+24
-17
+24
-17
| --- src/utils/decapify.py | ||
| +++ src/utils/decapify.py | ||
| @@ -11,19 +11,16 @@ | ||
| 11 | 11 | import re |
| 12 | 12 | import yaml |
| 13 | 13 | |
| 14 | 14 | from config import call_ai |
| 15 | 15 | |
| 16 | -logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') | |
| 17 | - | |
| 18 | 16 | DECAP_CDN = "https://unpkg.com/decap-cms@^3.0.0/dist/decap-cms.js" |
| 19 | 17 | |
| 20 | 18 | # 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 | |
| 25 | 22 | |
| 26 | 23 | |
| 27 | 24 | def decapify( |
| 28 | 25 | site_dir: str, |
| 29 | 26 | cms_name: str = None, |
| @@ -126,46 +123,54 @@ | ||
| 126 | 123 | with open(config_path, 'w') as f: |
| 127 | 124 | yaml.dump(config, f, default_flow_style=False, allow_unicode=True, sort_keys=False) |
| 128 | 125 | |
| 129 | 126 | logging.info(f"Wrote Decap CMS config to {config_path}") |
| 130 | 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 | + | |
| 131 | 138 | |
| 132 | 139 | def _build_collections(content_dir: str) -> list: |
| 133 | 140 | """ |
| 134 | 141 | 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) | |
| 138 | 144 | """ |
| 139 | 145 | if not os.path.isdir(content_dir): |
| 140 | 146 | return [_default_pages_collection()] |
| 141 | 147 | |
| 142 | 148 | collections = [] |
| 143 | - seen_blog_like = False | |
| 144 | 149 | |
| 145 | 150 | entries = sorted(os.listdir(content_dir)) |
| 146 | 151 | for entry in entries: |
| 147 | 152 | subdir = os.path.join(content_dir, entry) |
| 148 | 153 | if not os.path.isdir(subdir): |
| 149 | 154 | continue |
| 150 | 155 | |
| 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')) | |
| 153 | 159 | |
| 154 | 160 | 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]) | |
| 157 | 163 | collections.append({ |
| 158 | 164 | 'name': entry, |
| 159 | 165 | 'label': entry.replace('-', ' ').title(), |
| 160 | 166 | 'folder': f'content/{entry}', |
| 161 | 167 | 'create': True, |
| 162 | 168 | 'slug': '{{slug}}', |
| 163 | 169 | 'fields': fields, |
| 164 | 170 | }) |
| 165 | - seen_blog_like = True | |
| 166 | - elif '_index.md' in md_files: | |
| 171 | + elif has_index: | |
| 167 | 172 | # File collection (single page) |
| 168 | 173 | fields = _infer_fields_for_file(os.path.join(subdir, '_index.md')) |
| 169 | 174 | collections.append({ |
| 170 | 175 | 'name': entry, |
| 171 | 176 | 'label': entry.replace('-', ' ').title(), |
| @@ -183,11 +188,13 @@ | ||
| 183 | 188 | return collections |
| 184 | 189 | |
| 185 | 190 | |
| 186 | 191 | def _infer_fields_for_folder(subdir: str, md_files: list) -> list: |
| 187 | 192 | """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) | |
| 189 | 196 | frontmatter = _parse_frontmatter(sample) |
| 190 | 197 | |
| 191 | 198 | fields = [] |
| 192 | 199 | field_map = { |
| 193 | 200 | 'title': {'label': 'Title', 'name': 'title', 'widget': 'string'}, |
| 194 | 201 |
| --- 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 |