From 097a5337c8dae113b728823b1ef7bb57befd1f09 Mon Sep 17 00:00:00 2001 From: Brian Read Date: Sat, 9 Aug 2025 15:16:01 +0000 Subject: [PATCH] No longer needed --- mojo_formatter_final.py | 788 ---------------------------------------- 1 file changed, 788 deletions(-) delete mode 100644 mojo_formatter_final.py diff --git a/mojo_formatter_final.py b/mojo_formatter_final.py deleted file mode 100644 index 7aba7d1..0000000 --- a/mojo_formatter_final.py +++ /dev/null @@ -1,788 +0,0 @@ -#!/usr/bin/env python3 -""" -Mojolicious Template Formatter - -This program formats Mojolicious template files to make their structure -easily understandable by humans. It properly indents HTML tags, Mojolicious -commands, helper commands, and Perl constructs. - -Uses perltidy for formatting embedded Perl code and can output perltidy results -to a separate file for inspection. -""" - -import re -import sys -import argparse -import subprocess -import tempfile -import os -import logging -import uuid -import platform - - -# Version information -VERSION = "1.0" -PROGRAM_NAME = "Mojolicious Template Formatter" - -# Configure logging -logging.basicConfig( - level=logging.INFO, - format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', - handlers=[ - logging.StreamHandler(sys.stderr) - ] -) -logger = logging.getLogger('mojo_formatter') - - -def get_python_version(): - """Get the current Python version.""" - return f"{platform.python_version()}" - - -def get_perltidy_version(): - """Get the installed perltidy version.""" - try: - # Run the perltidy command - result = subprocess.run(['perltidy', '-v'], capture_output=True, text=True) - if result.returncode == 0: - # Extract version from stdout - version_match = re.search(r'This is perltidy, (v[\d\.]+)', result.stdout) - if version_match: - return version_match.group(1) - return "Unknown version" - else: - return "Not available" - except Exception: - return "Not installed" - -def log_system_info(): - """Log system information including program version and dependencies.""" - python_version = get_python_version() - perltidy_version = get_perltidy_version() - - logger.info(f"{PROGRAM_NAME} v{VERSION}") - logger.info(f"Running with Python {python_version}") - logger.info(f"Perltidy {perltidy_version}") - -class MojoTemplateFormatter: - """ - A formatter for Mojolicious template files that makes their structure - easily understandable by humans. - """ - - def __init__(self, indent_size=4, perltidy_output_dir=None): - """Initialize the formatter with default settings.""" - self.indent_size = indent_size - self.current_indent = 0 - self.output_lines = [] - self.perl_block_stack = [] - self.html_tag_stack = [] - self.in_form_block = False - self.in_content_block = False - self.remove_blank_lines = True - self.perltidy_output_dir = perltidy_output_dir - self.perltidy_block_count = 0 - - # Patterns for Mojolicious syntax - self.mojo_command_pattern = re.compile(r'^(\s*)(%\s*.*?)$') - self.perl_block_start_pattern = re.compile(r'%\s*(?:if|for|while|unless|begin)\b.*?{') - self.perl_if_pattern = re.compile(r'%\s*if\s*\(.*\)\s*{') - self.content_block_start_pattern = re.compile(r'%\s*content_for\b.*?=>\s*begin\b') - self.form_block_start_pattern = re.compile(r'%=\s*form_for\b.*?=>\s*begin\b') - self.perl_block_end_pattern = re.compile(r'%\s*}') - self.perl_end_pattern = re.compile(r'%\s*end\b') - - # Embedded Perl patterns - self.embedded_perl_start_pattern = re.compile(r'<%') - self.embedded_perl_end_pattern = re.compile(r'%>') - self.mojo_expression_pattern = re.compile(r'<%=?=?\s*(.*?)\s*%>') - self.mojo_code_pattern = re.compile(r'<%\s*(.*?)\s*%>') - self.mojo_comment_pattern = re.compile(r'<%#\s*(.*?)\s*%>') - - # HTML tag patterns - self.html_open_tag_pattern = re.compile(r'<([a-zA-Z][a-zA-Z0-9]*)[^>]*(?') - self.html_close_tag_pattern = re.compile(r'') - self.html_self_closing_tag_pattern = re.compile(r'<([a-zA-Z][a-zA-Z0-9]*)[^>]*/>') - - # Pattern for multiple closing tags on a line - self.multiple_closing_tags_pattern = re.compile(r'(]+>)(\s*)(]+>)') - - # List of tags that shouldn't cause indentation changes - self.non_indenting_tags = ['br', 'hr', 'img', 'input', 'link', 'meta'] - - # List of tags that should have minimal indentation - self.minimal_indent_tags = ['span', 'p'] - - # Pattern for lines with multiple closing tags - self.multiple_close_tags_pattern = re.compile(r']+>.*]+>') - - # Pattern for lines ending with
and closing tags - self.br_with_close_tags_pattern = re.compile(r']*>\s*]+>') - - # Pattern for lines with

pattern or variations - self.br_span_p_pattern = re.compile(r']*>\s*\s*

') - - # Pattern for lines with
followed by whitespace and

- self.br_span_space_p_pattern = re.compile(r']*>\s*\s+

') - - def format(self, content): - """ - Format the given Mojolicious template content. - - Args: - content (str): The content of the Mojolicious template file. - - Returns: - str: The formatted content. - """ - logger.info("Starting formatting process") - - # First pass: process embedded Perl blocks - logger.info("Processing embedded Perl blocks") - content = self._preprocess_embedded_perl(content) - - lines = content.splitlines() - self.output_lines = [] - self.current_indent = 0 - self.perl_block_stack = [] - self.html_tag_stack = [] - self.in_form_block = False - self.in_content_block = False - - logger.info("Processing lines for HTML and Mojolicious commands") - i = 0 - while i < len(lines): - line = lines[i] - i += 1 - - # Skip empty lines if remove_blank_lines is enabled - if not line.strip(): - if not self.remove_blank_lines: - self.output_lines.append('') - continue - - # Process the line based on its type - if self._is_mojo_command_line(line): - self._process_mojo_command_line(line) - else: - self._process_html_line(line) - - # Second pass: handle closing tags on separate lines - logger.info("Post-processing closing tags") - self._postprocess_closing_tags() - - logger.info("Formatting complete") - return '\n'.join(self.output_lines) - - def _preprocess_embedded_perl(self, content): - """ - Preprocess embedded Perl blocks to format the Perl code inside using perltidy. - - Args: - content (str): The content to preprocess. - - Returns: - str: The preprocessed content. - """ - # Find all embedded Perl blocks - pattern = re.compile(r'<%\s*(.*?)\s*%>', re.DOTALL) - - def format_perl_code(match): - perl_code = match.group(1) - if not perl_code.strip(): - logger.debug("Empty Perl block found") - return f"<%\n%>" - - # Format the Perl code by adding indentation - lines = perl_code.splitlines() - if len(lines) <= 1: - # For single-line Perl, just clean up spacing - logger.debug("Single-line Perl block found") - return f"<% {perl_code.strip()} %>" - - # For multi-line Perl, use perltidy - self.perltidy_block_count += 1 - block_id = self.perltidy_block_count - logger.info(f"Found multi-line Perl block #{block_id} with {len(lines)} lines") - logger.debug(f"Original Perl code (block #{block_id}):\n{perl_code}") - - formatted_perl = self._run_perltidy(perl_code, block_id) - - # If perltidy fails, fall back to our simple formatter - if formatted_perl is None: - logger.warning(f"Perltidy failed for block #{block_id}, falling back to simple formatter") - formatted_lines = [] - current_indent = self.indent_size - - for line in lines: - if not line.strip(): - continue # Skip empty lines - - stripped = line.lstrip() - - # Check if this line decreases indentation (closing brace at start) - if stripped.startswith('}') or stripped.startswith(');'): - current_indent = max(self.indent_size, current_indent - self.indent_size) - - # Add the line with proper indentation - if stripped.startswith('#'): - # For comments, use the current indentation - formatted_lines.append(' ' * current_indent + stripped) - else: - formatted_lines.append(' ' * current_indent + stripped) - - # Check if this line increases indentation for the next line - if (stripped.endswith('{') or - stripped.endswith('({') or - stripped.endswith('sub {') or - stripped.endswith('= {') or - stripped.endswith('=> {') or - (stripped.endswith('(') and not stripped.startswith(')'))): - current_indent += self.indent_size - - # Special case for closing parentheses that decrease indentation - if stripped.endswith(');') and not stripped.startswith('('): - current_indent = max(self.indent_size, current_indent - self.indent_size) - - # Join the formatted lines with newlines - formatted_perl = '\n'.join(formatted_lines) - else: - logger.info(f"Perltidy successfully formatted block #{block_id}") - logger.debug(f"Perltidy formatted code (block #{block_id}):\n{formatted_perl}") - - # Note: No space between % and > in the closing tag - # IMPORTANT: Preserve the exact perltidy formatting - return f"<%\n{formatted_perl}\n%>" - - # Replace all embedded Perl blocks with formatted versions - logger.info("Searching for embedded Perl blocks") - result = pattern.sub(format_perl_code, content) - logger.info(f"Embedded Perl block processing complete, found {self.perltidy_block_count} blocks") - return result - - def _run_perltidy(self, perl_code, block_id): - """ - Run perltidy on the given Perl code. - - Args: - perl_code (str): The Perl code to format. - block_id (int): Identifier for this Perl block. - - Returns: - str: The formatted Perl code, or None if perltidy fails. - """ - try: - logger.info(f"Running perltidy on Perl block #{block_id}") - - # Create temporary files for input and output - with tempfile.NamedTemporaryFile(mode='w+', delete=False) as input_file: - input_file.write(perl_code) - input_file_path = input_file.name - logger.debug(f"Created temporary input file for block #{block_id}: {input_file_path}") - - output_file_path = input_file_path + '.tidy' - - # Run perltidy with our desired options - cmd = [ - 'perltidy', - '-i=' + str(self.indent_size), # Set indentation size - '-ci=' + str(self.indent_size), # Set continuation indentation - '-l=120', # Line length - '-pt=2', # Parenthesis tightness - '-bt=2', # Brace tightness - '-sbt=2', # Square bracket tightness - '-ce', # Cuddled else - '-nbl', # No blank lines before comments - '-nsfs', # No space for semicolon - input_file_path, # Input file - '-o', output_file_path # Output file - ] - - logger.debug(f"Executing perltidy command for block #{block_id}: {' '.join(cmd)}") - - # Execute perltidy - result = subprocess.run(cmd, capture_output=True, text=True) - - # Check if perltidy succeeded - if result.returncode != 0: - logger.error(f"Perltidy failed for block #{block_id} with return code {result.returncode}") - logger.error(f"Stderr: {result.stderr}") - return None - - # Read the formatted code - if os.path.exists(output_file_path): - with open(output_file_path, 'r') as output_file: - formatted_code = output_file.read() - logger.info(f"Perltidy output file size for block #{block_id}: {len(formatted_code)} bytes") - - # If requested, save the perltidy output to a separate file - if self.perltidy_output_dir: - self._save_perltidy_output(perl_code, formatted_code, block_id) - else: - logger.error(f"Perltidy output file not found for block #{block_id}: {output_file_path}") - return None - - # Clean up temporary files - logger.debug(f"Cleaning up temporary files for block #{block_id}") - os.unlink(input_file_path) - if os.path.exists(output_file_path): - os.unlink(output_file_path) - - return formatted_code.strip() - - except Exception as e: - logger.exception(f"Error running perltidy for block #{block_id}: {e}") - return None - - def _save_perltidy_output(self, original_code, formatted_code, block_id): - """ - Save the original and formatted Perl code to separate files for inspection. - - Args: - original_code (str): The original Perl code. - formatted_code (str): The formatted Perl code. - block_id (int): Identifier for this Perl block. - """ - try: - # Create the output directory if it doesn't exist - os.makedirs(self.perltidy_output_dir, exist_ok=True) - - # Create filenames for the original and formatted code - original_file = os.path.join(self.perltidy_output_dir, f"perl_block_{block_id}_original.pl") - formatted_file = os.path.join(self.perltidy_output_dir, f"perl_block_{block_id}_formatted.pl") - - # Write the original code to a file - with open(original_file, 'w') as f: - f.write(original_code) - - # Write the formatted code to a file - with open(formatted_file, 'w') as f: - f.write(formatted_code) - - logger.info(f"Saved perltidy input/output for block #{block_id} to {original_file} and {formatted_file}") - - except Exception as e: - logger.exception(f"Error saving perltidy output for block #{block_id}: {e}") - - def _postprocess_closing_tags(self): - """ - Postprocess the output lines to put closing tags on separate lines. - """ - logger.info("Post-processing closing tags") - result_lines = [] - i = 0 - - # Track if we're inside an embedded Perl block - in_perl_block = False - - while i < len(self.output_lines): - line = self.output_lines[i] - - # Check if we're entering an embedded Perl block - if line.strip() == '<%': - in_perl_block = True - result_lines.append(line) - i += 1 - continue - - # Check if we're exiting an embedded Perl block - if line.strip() == '%>': - in_perl_block = False - result_lines.append(line) - i += 1 - continue - - # If we're inside an embedded Perl block, don't modify the line - if in_perl_block: - result_lines.append(line) - i += 1 - continue - - # Check for multiple closing tags - if self.multiple_closing_tags_pattern.search(line): - logger.debug(f"Found multiple closing tags in line: {line}") - # Split the line at each closing tag - parts = [] - current = line - - while self.multiple_closing_tags_pattern.search(current): - match = self.multiple_closing_tags_pattern.search(current) - first_tag = match.group(1) - whitespace = match.group(2) - second_tag = match.group(3) - - # Split at the second tag - before_second = current[:match.start(3)] - after_second = current[match.end(3):] - - # Add the part before the second tag - parts.append(before_second) - - # Update current to be the second tag and everything after - current = second_tag + after_second - - # Add the last part - if current: - parts.append(current) - - # Add all parts as separate lines - base_indent = len(line) - len(line.lstrip()) - for j, part in enumerate(parts): - # For closing tags, reduce indentation - if j > 0 and part.strip().startswith(' 1 and stripped[1] not in ['=', '#', '%']: - if not stripped[1].isspace(): - stripped = '%' + ' ' + stripped[1:] - - # Check for content block start - if self.content_block_start_pattern.search(stripped): - logger.debug("Found content block start") - indent = ' ' * self.current_indent - formatted_line = indent + stripped - self.output_lines.append(formatted_line) - self.in_content_block = True - self.current_indent += self.indent_size - return - - # Check for form block start - if self.form_block_start_pattern.search(stripped): - logger.debug("Found form block start") - indent = ' ' * self.current_indent - formatted_line = indent + stripped - self.output_lines.append(formatted_line) - self.in_form_block = True - self.current_indent += self.indent_size - return - - # Handle Perl block opening - if self.perl_block_start_pattern.search(stripped): - logger.debug("Found Perl block start") - indent = ' ' * self.current_indent - formatted_line = indent + stripped - self.output_lines.append(formatted_line) - - # Track the block with its opening indentation level - self.perl_block_stack.append(self.current_indent) - self.current_indent += self.indent_size - return - - # Handle Perl block closing with } - if self.perl_block_end_pattern.search(stripped): - logger.debug("Found Perl block end with }") - if self.perl_block_stack: - # Pop the indentation level from the stack - self.current_indent = self.perl_block_stack.pop() - - # Apply the indentation to the closing brace (same as opening) - indent = ' ' * self.current_indent - formatted_line = indent + stripped - self.output_lines.append(formatted_line) - else: - # If no block stack, just use current indentation - indent = ' ' * self.current_indent - formatted_line = indent + stripped - self.output_lines.append(formatted_line) - return - - # Handle Perl block closing with end - if self.perl_end_pattern.search(stripped): - logger.debug("Found Perl block end with 'end'") - if self.in_form_block and not self.perl_block_stack: - self.in_form_block = False - self.current_indent = max(0, self.current_indent - self.indent_size) - elif self.in_content_block and not self.perl_block_stack: - self.in_content_block = False - self.current_indent = max(0, self.current_indent - self.indent_size) - elif self.perl_block_stack: - # Pop the indentation level from the stack - self.current_indent = self.perl_block_stack.pop() - - # Apply the indentation to the end statement - indent = ' ' * self.current_indent - formatted_line = indent + stripped - self.output_lines.append(formatted_line) - return - - # Regular Mojolicious command line - indent = ' ' * self.current_indent - formatted_line = indent + stripped - self.output_lines.append(formatted_line) - - def _process_html_line(self, line): - """ - Process an HTML line. - - Args: - line (str): The HTML line to process. - """ - # Special handling for embedded Perl blocks - if line.strip().startswith('<%'): - # For embedded Perl blocks, don't modify the indentation - # Just add the line as is to preserve perltidy formatting - self.output_lines.append(line) - return - - # Special handling for Perl block closing tag - if line.strip() == '%>': - # For the closing tag, don't add any space after % - self.output_lines.append('%>') - return - - stripped = line.lstrip() - logger.debug(f"Processing HTML line: {stripped[:30]}...") - - # Special handling for lines with

pattern or variations - if self.br_span_p_pattern.search(stripped) or self.br_span_space_p_pattern.search(stripped): - logger.debug("Found

pattern") - # Find the base indentation level for this paragraph - base_indent = 0 - for i in range(len(self.html_tag_stack)): - if i < len(self.html_tag_stack) and self.html_tag_stack[i].lower() == 'p': - base_indent = i * self.indent_size - break - - # If we couldn't find a p tag, use the current indentation minus some offset - if base_indent == 0: - base_indent = max(0, self.current_indent - (2 * self.indent_size)) - - # Format the line with the base indentation - indent = ' ' * base_indent - - # Preserve the original spacing between and

if it exists - if self.br_span_space_p_pattern.search(stripped): - # Replace
with
but keep the spacing before

- parts = re.split(r'(\s+

)', stripped) - if len(parts) >= 3: - formatted_line = indent + parts[0] + parts[1] - else: - formatted_line = indent + stripped - else: - formatted_line = indent + stripped - - self.output_lines.append(formatted_line) - - # Update the tag stack to reflect the closing tags - if 'span' in self.html_tag_stack: - self.html_tag_stack.remove('span') - if 'p' in self.html_tag_stack: - self.html_tag_stack.remove('p') - - # Adjust current indentation - self.current_indent = base_indent - - return - - # Special handling for lines with
and closing tags - if self.br_with_close_tags_pattern.search(stripped): - logger.debug("Found
with closing tags") - # Find appropriate indentation level - indent_level = self.current_indent - for tag in self.html_close_tag_pattern.findall(stripped): - if tag.lower() in self.minimal_indent_tags and tag.lower() in [t.lower() for t in self.html_tag_stack]: - indent_level = max(0, indent_level - (self.indent_size // 2)) - - indent = ' ' * indent_level - formatted_line = indent + stripped - self.output_lines.append(formatted_line) - - # Update the tag stack to reflect the closing tags - close_tags = self.html_close_tag_pattern.findall(stripped) - for tag in close_tags: - if tag.lower() in [t.lower() for t in self.html_tag_stack]: - self.html_tag_stack.remove(tag) - - return - - # Skip indentation changes for lines with only non-indenting tags - if self._contains_only_non_indenting_tags(stripped): - logger.debug("Found line with only non-indenting tags") - indent = ' ' * self.current_indent - formatted_line = indent + stripped - self.output_lines.append(formatted_line) - return - - # Special handling for lines with multiple closing tags - if self.multiple_close_tags_pattern.search(stripped): - logger.debug("Found line with multiple closing tags") - # Count the number of closing tags - close_count = len(self.html_close_tag_pattern.findall(stripped)) - # Reduce indentation once for the whole line - if close_count > 1 and self.html_tag_stack: - for _ in range(min(close_count, len(self.html_tag_stack))): - tag = self.html_tag_stack.pop() - if tag.lower() not in self.non_indenting_tags: - self.current_indent = max(0, self.current_indent - self.indent_size) - - indent = ' ' * self.current_indent - formatted_line = indent + stripped - self.output_lines.append(formatted_line) - return - - # Check for closing tags first to adjust indentation before adding the line - close_tags = self.html_close_tag_pattern.findall(stripped) - for tag in close_tags: - if tag.lower() in self.non_indenting_tags: - continue - - if tag.lower() in [t.lower() for t in self.html_tag_stack]: - self.html_tag_stack.remove(tag) - - # Use smaller indentation for minimal indent tags - if tag.lower() in self.minimal_indent_tags: - self.current_indent = max(0, self.current_indent - (self.indent_size // 2)) - else: - self.current_indent = max(0, self.current_indent - self.indent_size) - - # Apply current indentation - indent = ' ' * self.current_indent - formatted_line = indent + stripped - self.output_lines.append(formatted_line) - - # Check for opening tags to adjust indentation for next lines - open_tags = self.html_open_tag_pattern.findall(stripped) - self_closing_tags = self.html_self_closing_tag_pattern.findall(stripped) - - # Add only non-self-closing tags to the stack (excluding special tags) - for tag in open_tags: - if tag.lower() in self.non_indenting_tags: - continue - - if tag not in self_closing_tags: - self.html_tag_stack.append(tag) - - # Use smaller indentation for minimal indent tags - if tag.lower() in self.minimal_indent_tags: - self.current_indent += (self.indent_size // 2) - else: - self.current_indent += self.indent_size - - def _contains_only_non_indenting_tags(self, line): - """ - Check if the line contains only non-indenting tags. - - Args: - line (str): The line to check. - - Returns: - bool: True if the line contains only non-indenting tags, False otherwise. - """ - # Check for
tags - for tag in self.non_indenting_tags: - pattern = re.compile(f'<{tag}[^>]*>') - if pattern.search(line): - # If the line has a non-indenting tag and not much else - other_content = re.sub(f']*>', '', line).strip() - if not other_content or other_content == '' or other_content == '

' or '' in other_content: - return True - - return False - - -def format_mojolicious_template(content, indent_size=4, remove_blank_lines=True, log_level=logging.INFO, perltidy_output_dir=None): - """ - Format a Mojolicious template. - - Args: - content (str): The content of the Mojolicious template. - indent_size (int): Number of spaces to use for indentation. - remove_blank_lines (bool): Whether to remove blank lines. - log_level (int): Logging level to use. - perltidy_output_dir (str): Directory to save perltidy input/output files. - - Returns: - str: The formatted content. - """ - # Set the logging level - logger.setLevel(log_level) - - formatter = MojoTemplateFormatter(indent_size, perltidy_output_dir) - formatter.remove_blank_lines = remove_blank_lines - return formatter.format(content) - - -def main(): - """Main function to run the formatter.""" - parser = argparse.ArgumentParser(description='Format Mojolicious template files.') - parser.add_argument('input_file', nargs='?', type=argparse.FileType('r'), - default=sys.stdin, help='Input file (default: stdin)') - parser.add_argument('output_file', nargs='?', type=argparse.FileType('w'), - default=sys.stdout, help='Output file (default: stdout)') - parser.add_argument('--indent', type=int, default=4, - help='Number of spaces to use for indentation (default: 4)') - parser.add_argument('--keep-blank-lines', action='store_true', - help='Keep blank lines in the output (default: remove blank lines)') - parser.add_argument('--log-level', choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'], - default='INFO', help='Set the logging level (default: INFO)') - parser.add_argument('--perltidy-output-dir', type=str, - help='Directory to save perltidy input/output files for inspection') - args = parser.parse_args() - - # Set the log level based on the command-line argument - log_level = getattr(logging, args.log_level) - logger.setLevel(log_level) - - # Log program and version information - log_system_info() - - logger.info(f"Starting formatter with indent={args.indent}, keep_blank_lines={args.keep_blank_lines}, log_level={args.log_level}") - if args.perltidy_output_dir: - logger.info(f"Perltidy output will be saved to: {args.perltidy_output_dir}") - - content = args.input_file.read() - logger.info(f"Read {len(content)} bytes from input") - - formatted_content = format_mojolicious_template( - content, - args.indent, - remove_blank_lines=not args.keep_blank_lines, - log_level=log_level, - perltidy_output_dir=args.perltidy_output_dir - ) - - logger.info(f"Writing {len(formatted_content)} bytes to output") - args.output_file.write(formatted_content) - logger.info("Formatting complete") - - -if __name__ == '__main__': - main() \ No newline at end of file