11import re
22import os
3+ import frontmatter
34from importlib import import_module
45from shutil import copytree , copy
56from 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
0 commit comments