changeset 2762:f8e28b0f4c36

add template indentation checks to coding-std.py;
author RogerHaase <haaserd@gmail.com>
date Sun, 24 Aug 2014 08:39:54 -0700
parents 31628ac3ec21
children 161b73a84417
files contrib/pep8/coding_std.py
diffstat 1 files changed, 123 insertions(+), 21 deletions(-) [+]
line wrap: on
line diff
--- a/contrib/pep8/coding_std.py	Sun Aug 24 08:39:32 2014 -0700
+++ b/contrib/pep8/coding_std.py	Sun Aug 24 08:39:54 2014 -0700
@@ -1,5 +1,5 @@
 #!/usr/bin/env python
-# Copyright: 2012 by MoinMoin:RogerHaase
+# Copyright: 2012-2014 by MoinMoin:RogerHaase
 # License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
 
 """
@@ -8,8 +8,13 @@
     - exactly one linefeed at file end, see PEP8
     - DOS line endings on .bat and .cmd files, unix line endings everywhere else
 
+Detect and write informative message:
+    - improper indentation of template files ending with .html suffix
+
 Execute this script from the root directory of the moin2 repository or
-from anywhere within the contrib path.
+from anywhere within the contrib path to process all files in the repo.
+
+Or, pass a directory path on the command line.
 """
 
 import sys
@@ -24,14 +29,21 @@
 
 
 class NoDupsLogger(object):
-    """Suppress duplicate messages."""
+    """
+    A simple report logger that suppresses duplicate headings and messages.
+    """
     def __init__(self):
-        self.seen = set()
+        self.messages = set()
+        self.headings = set()
 
-    def log(self, msg):
-        if msg not in self.seen:
-            print msg
-            self.seen.add(msg)
+    def log(self, heading, message):
+        if heading and heading not in self.headings:
+            print u"\n%s" % heading
+            self.headings.add(heading)
+
+        if message and message not in self.messages:
+            print u"   ", message
+            self.messages.add(message)
 
 
 def directories_to_ignore(starting_dir):
@@ -45,10 +57,97 @@
     return ignore_dirs
 
 
+def calc_indentation(line):
+    """
+    Return tuple (length of indentation, line stripped of leading blanks).
+    """
+    stripped = line.lstrip(' ')
+    indentation = len(line) - len(stripped)
+    return indentation, stripped
+
+
+def check_template_indentation(lines, filename, logger):
+    """
+    Identify non-standard indentation, print messages to assist user in manual correction.
+
+    In simple cases, non-standard indent, non-standard dedent messages tells users which lines to indent.
+    """
+    indent_after = ('{% block ', '{% if ', '{% elif ', '{% else ', '{% for ', '{% macro ',
+                    '{%- block ', '{%- if ', '{%- elif ', '{%- else ', '{%- for ', '{%- macro ', '{{ gen.form.open', )
+    indent_before = ('{% endblock ', '{% endif ', '{% endfor ', '{% endmacro', '</',
+                     '{%- endblock ', '{%- endif ', '{%- endfor ', '{%- endmacro', '{{ gen.form.close', )
+    block_endings = {'{% block': ('{% endblock %}', '{%- endblock %}', '{%- endblock -%}', '{% endblock -%}', ),
+                     '{% if': ('{% endif %}', '{%- endif %}', '{%- endif -%}', '{% endif -%}', ),
+                     '{% elif': ('{% endif %}', '{%- endif %}', '{%- endif -%}', '{% endif -%}', ),
+                     '{% else': ('{% endif %}', '{%- endif %}', '{%- endif -%}', '{% endif -%}', ),
+                     '{% for': ('{% endfor %}', '{%- endfor %}', '{%- endfor -%}', '{% endfor -%}', ),
+                     '{% macro': ('{% endmacro %}', '{%- endmacro %}', '{%- endmacro -%}', '{% endmacro -%}', ),
+                     '{{ gen.form.open': ('{{ gen.form.close }}', ),
+                    }
+    ends = ('{% end', '{%- end')
+
+    for idx, line in enumerate(lines):
+        indentation, stripped = calc_indentation(line)
+
+        if stripped.startswith(indent_after):
+            # we have found the beginning of a block
+            incre = 1
+            try:
+                while lines[idx + incre].strip() == '':
+                    incre += 1
+                next_indentation, next_line = calc_indentation(lines[idx + incre])
+                if next_indentation <= indentation:
+                    # next non-blank line does not have expected indentation
+                    # truncate "{{ gen.form.open(form, ..." to "{{ gen.form.open"; "{%- if ..." to "{% if"
+                    block_start = stripped.replace('-', '').split('(')[0].split(' ')
+                    block_start = ' '.join(block_start[:2])
+                    block_end = block_endings.get(block_start)
+                    if not block_end:
+                        # should never get here, mismatched indent_after and block_endings
+                        logger.log(filename, u"Unexpected block type '%s' discovered at line %d!" % (block_start, idx + 1))
+                        continue
+                    if any(x in stripped for x in block_end):
+                        # found line similar to: {% block ... %}...{% endblock %}
+                        continue
+                    if any(x in lines[idx + incre] for x in block_end):
+                        # found 2 consecutive lines similar to: {% block....\n{% endblock %}
+                        continue
+                    logger.log(filename, u"Non-standard indent after line %d -- not fixed!" % (idx + 1))
+            except IndexError:
+                # should never get here, there is an unclosed block near end of template
+                logger.log(filename, u"End of file reached with open block element at line %d!" % (idx + 1))
+
+        elif stripped.startswith(indent_before):
+            # we have found the end of a block
+            decre = -1
+            while idx + decre >= 0 and lines[idx + decre].strip() == '':
+                decre -= 1
+            if idx + decre < 0:
+                # should never get here; file begins with something like {% endblock %} or </div>
+                logger.log(filename, u"Beginning of file reached searching for block content at line %d!" % (idx + 1))
+                continue
+            prior_indentation, prior_line = calc_indentation(lines[idx + decre])
+            if prior_indentation <= indentation:
+                # prior non-blank line does not have expected indentation
+                if stripped.startswith('</'):
+                    tag_open = stripped.split('>')[0].replace('/', '')  # convert </div> to <div, etc.
+                    if prior_line.startswith(tag_open):
+                        # found lines similar to: <td>...\n</td>
+                        continue
+                if stripped.startswith(ends):
+                    # convert {% endif %} to tuple ('{% if', '{%- if') or similar
+                    block_open = (stripped.split(' %}')[0].replace('end', '').replace('-', ''),)
+                    block_open += ((block_open[0].replace('{%', '{%-')), )
+                    if prior_line.startswith(block_open):
+                        # found lines similar to: {% block...\n{% endblock %}
+                        continue
+                logger.log(filename, u"Non-standard dedent before line %d -- not fixed!" % (idx + 1))
+
+
 def check_files(filename, suffix):
-    """Delete trailing blanks,
-        force a single linefeed at file end,
-        force line ending to be \r\n for bat files and \n for all others."""
+    """
+    Delete trailing blanks, single linefeed at file end, line ending to be \r\n for bat files and \n for all others.
+    """
     suffix = suffix.lower()
     if suffix in WIN_SUFFIXES:
         line_end = "\r\n"
@@ -59,32 +158,35 @@
     with open(filename, "rb") as f:
         lines = f.readlines()
 
+    if filename.endswith('.html'):
+        check_template_indentation(lines, filename, logger)
+
     # now look at file end and get rid of all whitespace-only lines there:
     while lines:
         if not lines[-1].strip():
             del lines[-1]
-            logger.log(u"%s was changed to remove empty lines at eof" % filename)
+            logger.log(filename, u"Empty lines at eof removed.")
         else:
             break
 
     with open(filename, "wb") as f:
-        for line in lines:
-            length_line = len(line)
+        for idx, line in enumerate(lines):
+            line_length = len(line)
             line = line.replace('\t', '    ')
-            if len(line) != length_line:
-                logger.log(u"%s was changed to replace tab characters with 4 spaces" % filename)
+            if len(line) != line_length:
+                logger.log(filename, u"Tab characters replaced with 4 spaces.")
             pep8_line = line.rstrip() + line_end
             f.write(pep8_line)
             # if line was changed, issue warning once for each type of change
             if suffix in WIN_SUFFIXES and not line.endswith("\r\n"):
-                logger.log(u"%s was changed to DOS line endings" % filename)
+                logger.log(filename, u"Line endings changed to DOS style.")
             elif suffix not in WIN_SUFFIXES and line.endswith("\r\n"):
-                logger.log(u"%s was changed to Unix line endings" % filename)
+                logger.log(filename, u"Line endings changed to Unix style.")
             elif pep8_line != line:
                 if len(pep8_line) < len(line):
-                    logger.log(u"%s was changed to remove trailing blanks" % filename)
+                    logger.log(filename, u"Trailing blanks removed.")
                 else:
-                    logger.log(u"%s was changed to add end of line character at end of file" % filename)
+                    logger.log(filename, u"End of line character added at end of file.")
 
 
 def file_picker(starting_dir):
@@ -110,5 +212,5 @@
     else:
         starting_dir = os.path.abspath(os.path.dirname(__file__))
         starting_dir = starting_dir.split(os.sep + 'contrib')[0]
-    NoDupsLogger().log(u"Starting directory is %s" % starting_dir)
+    NoDupsLogger().log(u"Starting directory is %s" % starting_dir, None)
     file_picker(starting_dir)