From d0d5687bd8db64c44a1ddb4d35a52a9f574fff9a Mon Sep 17 00:00:00 2001 From: Brian Read Date: Sat, 9 Aug 2025 15:15:48 +0000 Subject: [PATCH 1/5] No longer needed --- manus request | 182 -------------------------------------------------- 1 file changed, 182 deletions(-) delete mode 100644 manus request diff --git a/manus request b/manus request deleted file mode 100644 index 63acea9..0000000 --- a/manus request +++ /dev/null @@ -1,182 +0,0 @@ -I am wanting you to create a python3 programm to take as input a Mojolicious Template file and output the same contents, but formatted in a way to make it structure easily understandable by a human. -This involves identifying all html tags, mojolicious command and helper commands and also all perl constructs and indenting them in a such a way that subservient html and other statements are indented like an html prettifier, taking into account that the file may also include perl program statements which also should be properly formatted and indented to show the structure like perltidy. All "%" in the first non space character position in the line should be followed by a space unless followed by a specific mojolicious character such as "=". -you can use any python3 libraries that you see fit. -here is an example of a mojolicious template file which needs re-structuring: -``` -% layout 'default', title => "Sme server 2 - yum_install"; -% content_for 'module' => begin -
- % if (config->{debug} == 1) { -

- %= dumper $c->current_route - %= dumper $yum_datas -

- % } - -

<%= $title%>

- - % if ( $notif ) { -
- %= $notif -
- %} -
- - %= form_for 'yumd' => (method => 'POST') => begin - - %=l 'yum_HEADER_AVAILABLE_SOFTWARE' -

- % if ($c->non_empty('available','group')) { - %=l 'yum_DESC_AVAILABLE_GROUPS' -

- %=l 'yum_LABEL_AVAILABLE_GROUPS' - - % param 'SelectedGroups' => $c->get_names2('updates','group') unless param 'SelectedGroups'; - %= select_field 'SelectedGroups' => $c->get_options2('available', 'group'), class => 'input', multiple => "1" -

- %} -
- % if ($c->non_empty('available','packages')) { - %=l 'yum_DESC_AVAILABLE_PACKAGES' -

- %=l 'yum_LABEL_AVAILABLE_PACKAGES' - - % param 'SelectedPackages' => $c->get_names2('updates','package') unless param 'SelectedPackages'; - %= select_field 'SelectedPackages' => $c->get_options2('available', 'package'), class => 'input', multiple => "1" -

- %} - - %= hidden_field 'trt' => 'INST' -

- - %= submit_button $c->l('yum_INSTALL_SOFTWARE'), class => 'action' - - % end -
-% end - -``` -and here is the same example restructured: -``` -% layout 'default', title => "Sme server 2 - yum_install"; -% content_for 'module' => begin -
- % if (config->{debug} == 1) { -

- %= dumper $c->current_route - %= dumper $yum_datas -

- % } -

<%= $title%>

- % if ( $notif ) {
-
- %= $notif -
- %} -
- %= form_for 'yumd' => (method => 'POST') => begin - %=l 'yum_HEADER_AVAILABLE_SOFTWARE' -

- % if ($c->non_empty('available','group')) { - %=l 'yum_DESC_AVAILABLE_GROUPS' -

- %=l 'yum_LABEL_AVAILABLE_GROUPS' - - % param 'SelectedGroups' => $c->get_names2('updates','group') unless param 'SelectedGroups'; - %= select_field 'SelectedGroups' => $c->get_options2('available', 'group'), class => 'input', multiple => "1" -

- %} -
- % if ($c->non_empty('available','packages')) { - %=l 'yum_DESC_AVAILABLE_PACKAGES' -

- %=l 'yum_LABEL_AVAILABLE_PACKAGES' - - % param 'SelectedPackages' => $c->get_names2('updates','package') unless param 'SelectedPackages'; - %= select_field 'SelectedPackages' => $c->get_options2('available', 'package'), class => 'input', multiple => "1" -

- %} - - %= hidden_field 'trt' => 'INST' -

- - %= submit_button $c->l('yum_INSTALL_SOFTWARE'), class => 'action' - - % end -
-% end -``` -Give me the whole program with the following second example unformatted template file ready to run as a test - -``` -
- % my $btn = l('ADD'); - %= form_for '/domains2' => (method => 'POST') => begin -

-

- % if ( $dom_datas->{trt} eq "ADD" ) { - %=l 'dom_CREATE_TITLE' - % } else { - %=l 'dom_MODIFY_TITLE' - % $btn = l('MODIFY'); - % } -

-

-


- - %=l 'DOMAIN_NAME', class => 'label' - - % if ( $dom_datas->{trt} eq "ADD" ) { - % param 'Domain' => $dom_datas->{domain} unless param 'Domain'; - %= text_field 'Domain', class => 'input' - % } else { - %= hidden_field 'Domain' => $dom_datas->{domain} - %= $dom_datas->{domain}, class => 'data' - % } - -

-


- - %=l 'DESCRIPTION_BRIEF', class => 'label' - - % param 'Description' => $dom_datas->{description} unless param 'Description'; - %= text_field 'Description', class => 'input' - -

-


- %=l 'dom_CONTENT_FIELD_DESCRIPTION' -
- - %= $c->l('dom_CONTENT', ''); - - % param 'Content' => $dom_datas->{content} unless param 'Content'; - %= select_field 'Content', $c->content_options_list(), class => 'input' - -

-


- %=l 'dom_DESC_NAMESERVERS' -
- - %=l 'dom_LABEL_NAMESERVERS', class => 'label' - - % param 'Nameservers' => $dom_datas->{nameservers} unless param 'Nameservers'; - %= select_field 'Nameservers', $c->nameserver_options_list(), class => 'input' - -

-


- %= submit_button "$btn", class => 'action' -

- %= hidden_field 'trt' => $dom_datas->{trt} - %end -
-``` - -Further thoughts: - -Some files have this iosrt of structure are <% -...perl... -%> -The perl needs to be formatted accordingly. - -and Delete all blank lines and put all final tags (where the content is indented) on a line on its own. -Example is

and others \ No newline at end of file From 097a5337c8dae113b728823b1ef7bb57befd1f09 Mon Sep 17 00:00:00 2001 From: Brian Read Date: Sat, 9 Aug 2025 15:16:01 +0000 Subject: [PATCH 2/5] 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 From a5079ec3860f254d6a3a726c5f184f0281601e01 Mon Sep 17 00:00:00 2001 From: Brian Read Date: Sat, 9 Aug 2025 15:16:15 +0000 Subject: [PATCH 3/5] To be replaced --- README.md | 128 ------------------------------------------------------ 1 file changed, 128 deletions(-) delete mode 100644 README.md diff --git a/README.md b/README.md deleted file mode 100644 index 6506155..0000000 --- a/README.md +++ /dev/null @@ -1,128 +0,0 @@ -# Mojolicious Template Formatter - -This Python 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. - -## Features - -- **HTML Tag Indentation**: Properly indents HTML tags and nested elements -- **Mojolicious Command Formatting**: Formats Mojolicious command lines (% lines) with proper spacing -- **Perl Code Block Formatting**: Intelligently handles Perl code blocks and their nesting -- **Special Syntax Handling**: Special handling for Mojolicious-specific syntax (form_for, content_for blocks) -- **Embedded Perl Formatting**: Uses perltidy to format embedded Perl code blocks -- **Smart Tag Handling**: Special handling for self-closing tags, minimal indentation for span/p tags -- **Customizable Indentation**: Configurable indentation size (default: 4 spaces) -- **Perltidy Output Files**: Option to save original and formatted Perl code to separate files - -## Installation - -No installation is required. The formatter is a standalone Python script that can be run directly. - -### Requirements - -- Python 3.6 or higher -- perltidy (for Perl code formatting) - -To install perltidy: - -```bash -sudo apt-get install perltidy -``` - -## Usage - -### Basic Usage - -```bash -./mojo_formatter_final_fixed8.py input_file.mojo > output_file.mojo -``` - -### With Custom Indentation - -```bash -./mojo_formatter_final_fixed8.py --indent 2 input_file.mojo > output_file.mojo -``` - -### With Perltidy Output Files - -```bash -./mojo_formatter_final_fixed8.py --perltidy-output-dir=/path/to/output/dir input_file.mojo > output_file.mojo -``` - -### With Debug Logging - -```bash -./mojo_formatter_final_fixed8.py --debug input_file.mojo > output_file.mojo -``` - -## How It Works - -The formatter processes Mojolicious template files in several passes: - -1. **Embedded Perl Processing**: Extracts and formats embedded Perl code using perltidy -2. **Line-by-Line Processing**: Processes each line based on its type (HTML or Mojolicious command) -3. **Post-Processing**: Handles special cases like multiple closing tags on a single line -4. **Duplicate Tag Cleanup**: Normalizes and removes duplicate closing tags - -## Special Features - -### Smart HTML Tag Handling - -- Non-indenting tags like `
`, `
`, ``, etc. don't cause indentation changes -- Minimal indentation for `` and `

` tags (half the normal indentation) -- Special handling for lines with multiple closing tags - -### Embedded Perl Formatting - -The formatter uses perltidy to format embedded Perl code blocks (enclosed in `<%` and `%>` tags). This ensures that your Perl code follows consistent formatting rules. - -### Perltidy Output Files - -When using the `--perltidy-output-dir` option, the formatter saves both the original and formatted Perl code for each embedded Perl block to separate files: - -- `perl_block_N_original.pl`: The original Perl code before formatting -- `perl_block_N_formatted.pl`: The formatted Perl code after perltidy processing - -## Example - -### Input - -```perl -

-<% -# This is a test of pure Perl code with minimal indentation -if ($status) { - $c->desktopBackupRecordStatus($backup_rec, 'pre-backup', $status); - return ($c->l('bac_OPERATION_STATUS_REPORT').$c->l('bac_ERR_PRE_BACKUP')); -} - -my $clvl = $c->stash('compressionlevel'); -my $cmd = "/bin/tar --create --file=- --directory / @{$c->stash('exclude')} " - . "@{$c->stash('directories')} | /usr/bin/gzip $clvl "; -%> -
-``` - -### Output - -```perl -
- <% - # This is a test of pure Perl code with minimal indentation - if ($status) { - $c->desktopBackupRecordStatus($backup_rec, 'pre-backup', $status); - return ($c->l('bac_OPERATION_STATUS_REPORT') . $c->l('bac_ERR_PRE_BACKUP')); - } - my $clvl = $c->stash('compressionlevel'); - my $cmd = "/bin/tar --create --file=- --directory / @{$c->stash('exclude')} " - . "@{$c->stash('directories')} | /usr/bin/gzip $clvl "; - %> -
-``` - -## Troubleshooting - -If you encounter issues with perltidy, the formatter will fall back to a simple indentation-based formatter for Perl code. Enable debug logging with the `--debug` flag to see detailed information about the formatting process. - -## License - -This software is provided as-is, without any warranties or conditions of any kind. \ No newline at end of file From bc2cbd18c6216bfe73c913b4f61fcefdb01e617d Mon Sep 17 00:00:00 2001 From: Brian Read Date: Sat, 9 Aug 2025 15:28:34 +0000 Subject: [PATCH 4/5] New version --- README.md | 205 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..58e24ea --- /dev/null +++ b/README.md @@ -0,0 +1,205 @@ +# mojofmt + +Formatter for Mojolicious Embedded Perl templates (.ep, .htm.ep, .html.ep) + +mojofmt formats HTML and Mojolicious EP templates without breaking embedded Perl. It understands line directives (% ...), inline tags (<% ... %>), raw HTML blocks, and can reformat multi-line Perl blocks inside <% ... %> using perltidy (with a safe fallback if perltidy isn’t available). + +## Features + +- Indents HTML structure and Mojolicious line directives consistently +- Preserves chomp markers (<%- ... -%>) and does not alter newline semantics +- Formats inline EP tags: + - <%= ... %> expressions (keeps them single-line) + - <% ... %> one-line statements +- Re-formats extended multi-line Perl blocks between lines with only <% and %> (or chomped variants), using perltidy or a naive brace-aware indenter +- Treats pre/script/style/textarea content as opaque (unchanged) +- Optional spacing normalization inside <% %> delimiters +- Optional aggressive spacing after common Perl keywords (--perl-keyword-spacing) +- End-of-line normalization (lf, crlf, or preserve) +- CLI with --write, --check, --diff, --out, --stdin/--stdout +- Self-test mode to sanity-check behavior and perltidy availability + +## Requirements + +- Python 3.8+ (3.11+ recommended) +- Optional but recommended: perltidy (Perl::Tidy) + - Debian/Ubuntu: apt-get install perltidy + - CPAN: cpanm Perl::Tidy + - mojofmt will fall back to a simple brace-based indenter for extended blocks if perltidy is absent or fails + +## Install + +- Clone this repository +- Make the script executable and put it on your PATH, or run it in place + +``` +chmod +x mojofmt.py +./mojofmt.py --version +``` + +## Usage + +Basic formatting to stdout: +- ./mojofmt.py path/to/template.html.ep +- cat file.ep | ./mojofmt.py --stdin --stdout + +Write changes in place (creates a .bak backup): +- ./mojofmt.py -w templates/ + +Check mode (exit 1 if any file would change): +- ./mojofmt.py --check templates/ + +Show a diff: +- ./mojofmt.py --diff path/to/file.ep + +Write output to a separate file: +- ./mojofmt.py -o formatted.ep path/to/file.ep +- cat file.ep | ./mojofmt.py --stdin -o formatted.ep + +Control indentation (spaces per level): +- ./mojofmt.py --indent 4 file.ep + +Normalize EOLs: +- ./mojofmt.py --eol lf file.ep +- ./mojofmt.py --eol crlf file.ep +- ./mojofmt.py --eol preserve file.ep + +Perl spacing knobs: +- Add spacing after common Perl keywords: ./mojofmt.py --perl-keyword-spacing file.ep +- Pass a specific perltidy binary: ./mojofmt.py --perltidy /usr/bin/perltidy file.ep +- See perltidy status: ./mojofmt.py --self-test + +Increase logging: +- ./mojofmt.py --log-level debug file.ep +- ./mojofmt.py --verbose file.ep (shorthand for info) + +## How it works + +- HTML indentation: + - Tracks opening/closing tags; avoids indenting void/self-closing tags + - pre/script/style/textarea bodies are untouched +- Mojolicious directives: + - Lines starting with % are indented relative to HTML and Perl block depth + - begin/end and { ... } braces adjust indentation depth +- Inline EP tags on a line: + - <%= ... %> expressions are normalized via perltidy in an expression-safe wrapper + - <% ... %> one-line statements are normalized via perltidy (or left as-is if perltidy is missing) + - Optional normalization of spaces inside <% %> delimiters can be disabled with --no-space-in-delims +- Extended multi-line Perl blocks: + - Detected when <% (or <%-) is on a line by itself, and %> (or -%>) is on a line by itself + - The inner Perl is dedented, wrapped in do { ... } and run through perltidy; if that fails or perltidy is missing, a brace-aware fallback indenter is used + - Inner lines are re-indented to match the opening/closing delimiter’s indentation +- EOL normalization: + - Input CRLF/CR are normalized internally; output can be forced to lf/crlf or preserve the original + +## Examples + +Before: +``` +
    +% for my $i (1..3) { +
  • <%= $i%>
  • +%} +
+``` + +After: +``` +
    + % for my $i (1 .. 3) { +
  • <%= $i %>
  • + % } +
+``` + +Extended Perl block: +``` +<% +my $x=1; +if($x){ +say"hi"; +} +%> +``` + +Becomes (with --indent 4): +``` +<% +my $x = 1; +if ($x) { + say "hi"; +} +%> +``` + +## Options + +- -w, --write: Overwrite files in place (writes a .bak backup) +- -o, --out FILE: Write formatted output to FILE (single input or --stdin) +- --check: Exit non-zero if any file would change +- --diff: Print unified diff +- --stdin / --stdout: Pipe mode +- --perltidy PATH: Path to perltidy executable +- --indent N: Indent width (spaces; default 2) +- --eol lf|crlf|preserve: End-of-line handling (default lf) +- --no-space-in-delims: Do not normalize spacing inside <% %> delimiters +- --perl-keyword-spacing: Aggressively insert a space after common Perl keywords +- --self-test: Run internal sanity checks and perltidy probe +- --log-level error|info|debug: Logging verbosity (or use --verbose) +- --version: Print version + +## File selection + +By default, mojofmt processes: +- .ep +- .htm.ep +- .html.ep + +Directories are walked recursively; only matching files are formatted. + +## Tips and caveats + +- perltidy recommended: For best results on complex Perl inside templates, install perltidy. mojofmt falls back to a brace-aware indenter for extended blocks, but won’t do token-level Perl formatting without perltidy. +- Extended block detection: Only triggers when the opening <% (or <%-) and closing %> (or -%>) are on their own lines. Inline <% ... %> on the same line are handled by the inline path. +- Raw blocks: Content inside pre/script/style/textarea is not changed. +- Chomp markers: Left/right chomps (<%- and -%>) are preserved and not moved. +- Idempotent: Running mojofmt repeatedly should not keep changing the file (self-test checks this). +- EOLs: Use --eol preserve to retain original line endings. + +## Troubleshooting + +- perltidy non-zero exit N in debug logs: + - mojofmt wraps extended blocks in do { ... } for perltidy; if it still fails, run perltidy manually on the wrapper to see the error. + - Ensure perltidy is on PATH or pass --perltidy /path/to/perltidy. +- Extended block didn’t reformat: + - Confirm the delimiters are on their own lines (no code on the <% / %> lines). + - Run with --log-level debug to see whether perltidy or the naive indenter handled the block. +- Spaces around Perl keywords: + - Off by default. Enable with --perl-keyword-spacing if you want if(...)->if (...), my$->my $, return(...)->return (...), etc. + +## Development + +Run self-tests: +- ./mojofmt.py --self-test +- Use --log-level debug for detailed logs + +Format from stdin to stdout: +- python3 mojofmt.py --stdin --stdout < in.ep > out.ep + +Generate a diff without writing: +- python3 mojofmt.py --diff path/to/file.html.ep + +## Contributing + +- Open an issue or pull request with a clear description and a minimal repro template +- Please include before/after snippets and your command-line flags +- If you modify formatting rules, add/adjust a self-test where possible + +## License + +See LICENSE file in this repository. If you don’t have one yet, consider MIT or Apache-2.0. + +## Acknowledgments + +- Mojolicious and Mojo::Template for the EP syntax +- Perl::Tidy for robust Perl formatting \ No newline at end of file From 59bfbde88e2686d25b7f6a14692814da8f440e05 Mon Sep 17 00:00:00 2001 From: Brian Read Date: Sat, 9 Aug 2025 15:29:07 +0000 Subject: [PATCH 5/5] Update README.md --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 58e24ea..4c18b98 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -# mojofmt - +# Mojolicious Template Formatter Formatter for Mojolicious Embedded Perl templates (.ep, .htm.ep, .html.ep) mojofmt formats HTML and Mojolicious EP templates without breaking embedded Perl. It understands line directives (% ...), inline tags (<% ... %>), raw HTML blocks, and can reformat multi-line Perl blocks inside <% ... %> using perltidy (with a safe fallback if perltidy isn’t available).