Skip to content

Commit 52dd7d8

Browse files
committed
update: partial copy
1 parent 249b786 commit 52dd7d8

File tree

2 files changed

+108
-20
lines changed

2 files changed

+108
-20
lines changed

foliant/backends/base.py

Lines changed: 107 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import re
22
import os
3+
import frontmatter
34
from importlib import import_module
45
from shutil import copytree, copy
56
from pathlib import Path
@@ -90,8 +91,9 @@ def apply_preprocessor(self, preprocessor: str or dict):
9091
@staticmethod
9192
def partial_copy(
9293
source: Union[str, Path, List[Union[str, Path]]],
94+
project_path: Union[str, Path],
95+
root: Union[str, Path],
9396
destination: Union[str, Path],
94-
root: Union[str, Path]
9597
) -> None:
9698
"""
9799
Copies files, a list of files,
@@ -116,6 +118,97 @@ def _extract_first_header(file_path):
116118
return match.group(0)
117119
return None
118120

121+
def _modify_markdown_file(
122+
file_path: Union[str, Path],
123+
dst_file_path: Union[str, Path],
124+
not_build: bool = True,
125+
remove_content: bool = True,
126+
keep_first_header: bool = True,
127+
create_frontmatter: bool = True,
128+
dry_run: bool = False,
129+
):
130+
"""
131+
Modify a Markdown file's frontmatter and content according to specified parameters.
132+
Uses python-frontmatter package for reliable frontmatter handling.
133+
134+
Args:
135+
file_path: Path to the Markdown file
136+
not_build: Value for not_build field (None means don't modify)
137+
remove_content: Whether to remove the content body
138+
keep_first_header: Keep first H1 when removing content
139+
create_frontmatter: Create frontmatter if missing
140+
dry_run: Preview changes without writing
141+
142+
Returns:
143+
Tuple of (modified: bool, new_content: str)
144+
145+
Examples:
146+
# Basic usage - add not_build: true
147+
modified, content = modify_markdown_file("post.md")
148+
149+
# Remove content but keep first header
150+
modify_markdown_file("post.md", remove_content=True, keep_first_header=True)
151+
152+
# Dry run to preview changes
153+
modified, new_content = modify_markdown_file("post.md", dry_run=True)
154+
"""
155+
try:
156+
file_path = Path(file_path)
157+
content = file_path.read_text(encoding='utf-8')
158+
159+
# Parse document with python-frontmatter
160+
post = frontmatter.loads(content)
161+
original_content = post.content
162+
changes_made = False
163+
164+
# Modify frontmatter if requested
165+
if not_build is not None:
166+
if post.get('not_build') != not_build:
167+
post['not_build'] = not_build
168+
changes_made = True
169+
170+
# Handle content modifications
171+
if remove_content and original_content.strip():
172+
new_content = ''
173+
if keep_first_header:
174+
# Find first H1 header using regex
175+
h1_match = re.search(r'^#\s+.+$', original_content, flags=re.MULTILINE)
176+
if h1_match:
177+
new_content = h1_match.group(0) + '\n'
178+
179+
if post.content != new_content:
180+
post.content = new_content
181+
changes_made = True
182+
183+
# Create frontmatter if missing and requested
184+
if not has_frontmatter(post) and create_frontmatter and (not_build is not None or changes_made):
185+
changes_made = True # Adding frontmatter counts as a change
186+
187+
# Return original if no changes
188+
if not changes_made:
189+
return (False, content)
190+
191+
# Serialize back to text
192+
output = frontmatter.dumps(post)
193+
if not has_frontmatter(post) and create_frontmatter:
194+
output = f"---\n{output}" # Ensure proper YAML fences
195+
196+
# Dry run check
197+
if dry_run:
198+
return (True, output)
199+
200+
# Write changes
201+
dst_file_path.write_text(output, encoding='utf-8')
202+
return (True, output)
203+
204+
except Exception as e:
205+
print(f"Error processing {file_path}: {str(e)}")
206+
return (False, content)
207+
208+
def has_frontmatter(post: frontmatter.Post) -> bool:
209+
"""Check if post has existing frontmatter using python-frontmatter internals"""
210+
return hasattr(post, 'metadata') and (post.metadata or hasattr(post, 'fm'))
211+
119212
def _find_referenced_images(file_path: Path) -> Set[Path]:
120213
"""Finds all image files referenced in the given file."""
121214
image_paths = set()
@@ -141,13 +234,10 @@ def _copy_files_without_content(src_dir: str, dst_dir: str):
141234
dst_file_path = Path(os.path.join(dst_dir, dirs, file_name))
142235
dst_file_path.parent.mkdir(parents=True, exist_ok=True)
143236
if file_name.endswith('.md'):
144-
header = _extract_first_header(src_file_path)
145-
if header:
146-
with open(dst_file_path, 'w', encoding='utf-8') as dst_file:
147-
dst_file.write(header + '\n')
148-
else:
149-
if Path(src_file_path).suffix.lower() not in image_extensions:
150-
copy(src_file_path, dst_file_path)
237+
_modify_markdown_file(src_file_path, dst_file_path)
238+
# else:
239+
# if Path(src_file_path).suffix.lower() not in image_extensions:
240+
# copy(src_file_path, dst_file_path)
151241

152242
def _copy_files_recursive(files_to_copy: List):
153243
"""Recursively copies files and their dependencies."""
@@ -188,16 +278,14 @@ def _copy_files_recursive(files_to_copy: List):
188278

189279
# Basic logic
190280
_copy_files_without_content(root_path, destination_path)
191-
192281
if isinstance(source, str) and ',' in source:
193282
source = source.split(',')
194283
if isinstance(source, list):
195284
files_to_copy = []
196285
for item in source:
197-
item_path = Path(item)
198-
if not item_path.exists():
199-
raise FileNotFoundError(f"Source '{item}' not found.")
200-
files_to_copy.append(item_path)
286+
item_path = Path(project_path, item)
287+
if item_path.exists():
288+
files_to_copy.append(item_path)
201289
else:
202290
if isinstance(source, str):
203291
source_path = Path(source)
@@ -207,10 +295,8 @@ def _copy_files_recursive(files_to_copy: List):
207295
if isinstance(source, str) and ('*' in source or '?' in source or '[' in source):
208296
files_to_copy = [Path(file) for file in glob(source, recursive=True)]
209297
else:
210-
if not source_path.exists():
211-
raise FileNotFoundError(f"Source '{source_path}' not found.")
212-
files_to_copy = [source_path]
213-
298+
if source_path.exists():
299+
files_to_copy = [source_path]
214300
_copy_files_recursive(files_to_copy)
215301

216302
def preprocess_and_make(self, target: str) -> str:
@@ -223,10 +309,11 @@ def preprocess_and_make(self, target: str) -> str:
223309
'''
224310

225311
src_path = self.project_path / self.config['src_dir']
226-
multiprojectcache_dir = os.path.join(self.project_path, '.multiprojectcache')
312+
# multiprojectcache_dir = os.path.join(self.project_path, '.multiprojectcache')
227313

228-
if self.context['only_partial'] and not os.path.isdir(multiprojectcache_dir):
229-
self.partial_copy(self.context['only_partial'], self.working_dir, src_path)
314+
if self.context['only_partial']:
315+
# if os.path.isdir(multiprojectcache_dir) and target == "pre":
316+
self.partial_copy(self.context['only_partial'], self.project_path, src_path, self.working_dir)
230317
else:
231318
copytree(src_path, self.working_dir)
232319

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ python = "^3.9"
1515
pyyaml = "6.0.2"
1616
cliar = "^1.3.5"
1717
prompt_toolkit = "^3.0.50"
18+
python-frontmatter = ">=1.1.0"
1819

1920
[tool.poetry.group.dev.dependencies]
2021
pytest = "^8.3.5"

0 commit comments

Comments
 (0)