import os import shutil import yaml import re import sys # Global variable to track errors errorList = {"copies": 0, "modifications": 0, "copyWarnings": 0, "modWarnings": 0} # Initialize results results = {"copies": 0, "modifications": 0} def loadConfig(configPath): """Load YAML configuration.""" try: print(f"Loading configuration from {configPath} ...") with open(configPath, 'r', encoding='utf-8') as file: config = yaml.safe_load(file) # validate configuration if not config: raise ValueError("Empty configuration file") print("Configuration loaded successfully.") return config except FileNotFoundError: print(f"Error: Configuration file not found at ./{configPath}") sys.exit(1) except yaml.YAMLError as e: print(f"Error parsing YAML configuration: {e}") sys.exit(1) except ValueError as e: print(f"Configuration error: {e}") sys.exit(1) def ensureDirectory(path): """Ensure that a directory exists.""" print(f"\nChecking for or creating directory: {path}") os.makedirs(path, exist_ok=True) def copySources(config, destinationDirectory): """Copy files and folders according to copy rules.""" print("Starting source file & folder copy and replace process...") for rule in config.get('copy_rules', []): for source in rule.get('sources', []): try: # Distinguish between sources with explicit target and without if isinstance(source, dict): sourcePath = source['source'] checkTargetPath = source['target'] # Check for absolute paths in target and correct them if necessary if os.path.isabs(checkTargetPath): targetPath = os.path.normpath(os.path.join(destinationDirectory, checkTargetPath.lstrip('./'))) raise ValueError(f"Absolute path incorrect: Corrected {checkTargetPath} to {targetPath}.") else: #targetPath = os.path.join(destinationDirectory, source['target']) # old code targetPath = os.path.normpath(os.path.join(destinationDirectory, checkTargetPath)) else: sourcePath = source #targetPath = os.path.join(destinationDirectory, os.path.basename(sourcePath)) # old code targetPath = os.path.normpath(os.path.join(destinationDirectory, os.path.basename(sourcePath))) # Check if source exists if not os.path.exists(sourcePath): raise FileNotFoundError(f"Source path does not exist: {sourcePath}") # Create target directory ensureDirectory(os.path.dirname(targetPath)) # Copy or replace mode if rule.get('mode') == 'replace' and os.path.exists(targetPath): print(f"Replacing existing file/folder: {targetPath}") # Explicitly handle directory or file deletion if os.path.isdir(targetPath): shutil.rmtree(targetPath) else: os.remove(targetPath) # Update mode if rule.get('mode') == 'update' and os.path.exists(targetPath): print(f"Checking if {sourcePath} is newer than {targetPath}") srcMtime = os.path.getmtime(sourcePath) destMtime = os.path.getmtime(targetPath) if srcMtime <= destMtime: print(f"Skipping {sourcePath}: Destination is up to date") break # Skip this source file/folder elif rule.get('mode') != 'update' and not os.path.exists(sourcePath): print(f"Should update {sourcePath} to {targetPath}, but {sourcePath} is not (yet) existing.") # Copy files or directories if os.path.isdir(sourcePath): if not os.path.exists(targetPath): print(f"Copying directory: {sourcePath} -> {targetPath}") shutil.copytree(sourcePath, targetPath) results['copies'] += 1 else: print(f"Skipping directory copy: {sourcePath} -> {targetPath} (already exists)") errorList['copyWarnings'] += 1 else: if not os.path.exists(targetPath): print(f"Copying file: {sourcePath} -> {targetPath}") shutil.copy2(sourcePath, targetPath) results['copies'] += 1 else: print(f"Skipping file copy: {sourcePath} -> {targetPath} (already exists)") errorList['copyWarnings'] += 1 except FileNotFoundError as e: print(f"Error: {e}") errorList["copies"] += 1 continue except ValueError as e: print(f"Configuration error: {e}") errorList["copies"] += 1 continue except PermissionError: print(f"Error: Permission denied when copying {sourcePath}") errorList["copies"] += 1 continue except OSError as e: print(f"Error copying {sourcePath}: {e}") errorList["copies"] += 1 continue print(f"\nSource file & folder copy/replace process completed with {errorList['copies']} errors and {errorList['copyWarnings']} warnings.\n") def modifyFiles(config, destinationDirectory): """Modify files according to modification rules.""" print("Starting file modification process...") for rule in config.get('modification_rules', []): filePattern = rule.get('file_pattern', '*') print(f"Processing files matching pattern: {filePattern}") # Recursively search the destination directory for root, _, files in os.walk(destinationDirectory): for filename in files: try: if re.match(filePattern, filename): filePath = os.path.join(root, filename) print(f"Modifying file: {filePath}") # Read file content with open(filePath, 'r', encoding='utf-8') as f: content = f.read() # Perform text replacements for insertRule in rule.get('insert_rules', []): if 'after_text' in insertRule: after_text = insertRule['after_text'] insert_text = insertRule['insert_text'] if after_text not in content: raise ValueError(f"Text '{after_text}' not found in file: {filePath}") elif insert_text not in content: print(f" Inserting text after: {after_text}") content = content.replace(after_text, after_text + insert_text) else: print(f" Text already present after: {after_text}") errorList['modWarnings'] += 1 if 'before_text' in insertRule: before_text = insertRule['before_text'] insert_text = insertRule['insert_text'] if before_text not in content: raise ValueError(f"Text '{before_text}' not found in file: {filePath}") elif insert_text not in content: print(f" Inserting text before: {before_text}") content = content.replace(before_text, insert_text + before_text) else: print(f" Text already present before: {before_text}") errorList['modWarnings'] += 1 if 'old_text' in insertRule: old_text = insertRule['old_text'] new_text = insertRule['new_text'] if old_text not in content and new_text not in content: raise ValueError(f"Text '{old_text}' not found in file: {filePath}") elif new_text not in content: print(f" Replacing text: {old_text} -> {new_text}") content = content.replace(old_text, new_text) else: print(f" Text already replaced: {old_text} -> {new_text}") errorList['modWarnings'] += 1 # Write modified contents with open(filePath, 'w', encoding='utf-8') as f: f.write(content) results['modifications'] += 1 except ValueError as e: print(f"Error: {e}") errorList["modifications"] += 1 continue except PermissionError: print(f"Error: Permission denied when modifying {filePath}") errorList["modifications"] += 1 continue except IOError as e: print(f"Error reading/writing file {filePath}: {e}") errorList["modifications"] += 1 continue print(f"\nFile modification process completed with {errorList['modifications']} errors and {errorList['modWarnings']} warnings.\n") def main(): """Main function to execute all operations.""" # Check command-line argument if len(sys.argv) < 2: print("Please provide the path to the configuration file.") sys.exit(1) configPath = sys.argv[1] # Load configuration config = loadConfig(configPath) # Ensure destination directory destinationDirectory = config.get('destination_directory', './web') ensureDirectory(destinationDirectory) # Copy files and folders copySources(config, destinationDirectory) # Modify files modifyFiles(config, destinationDirectory) # Print results print(f'\nTotal successful copies: {results["copies"]}') print(f'Total file modifications: {results["modifications"]}') if errorList["copies"] > 0 or errorList["modifications"] > 0: print("Errors occurred during the process. Check the output for details.") print(f"Total copy errors: {errorList['copies']}") print(f"Total modification errors: {errorList['modifications']}") elif errorList["copyWarnings"] > 0 or errorList["modWarnings"] > 0: print(f"All operations in {destinationDirectory} from {configPath} completed successfully! But there were {errorList['modWarnings'] + errorList['copyWarnings']} warnings. Mybe you should check them.") else: print(f"All operations in {destinationDirectory} from {configPath} completed successfully!") if __name__ == '__main__': main()