diff options
-rwxr-xr-x | tools/hacking.py | 175 | ||||
-rwxr-xr-x | tools/run-pep8 | 13 |
2 files changed, 185 insertions, 3 deletions
diff --git a/tools/hacking.py b/tools/hacking.py new file mode 100755 index 00000000..d0c27d25 --- /dev/null +++ b/tools/hacking.py @@ -0,0 +1,175 @@ +#!/usr/bin/env python +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2012, Cloudscaling +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""cloudinit HACKING file compliance testing (based off of nova hacking.py) + +built on top of pep8.py +""" + +import inspect +import logging +import os +import re +import sys +import tokenize +import warnings + +import pep8 + +# Don't need this for testing +logging.disable('LOG') + +# N1xx comments +# N2xx except +# N3xx imports +# N4xx docstrings +# N[5-9]XX (future use) + +DOCSTRING_TRIPLE = ['"""', "'''"] +VERBOSE_MISSING_IMPORT = False +_missingImport = set([]) + + +def import_normalize(line): + # convert "from x import y" to "import x.y" + # handle "from x import y as z" to "import x.y as z" + split_line = line.split() + if (line.startswith("from ") and "," not in line and + split_line[2] == "import" and split_line[3] != "*" and + split_line[1] != "__future__" and + (len(split_line) == 4 or + (len(split_line) == 6 and split_line[4] == "as"))): + return "import %s.%s" % (split_line[1], split_line[3]) + else: + return line + + +def cloud_import_alphabetical(physical_line, line_number, lines): + """Check for imports in alphabetical order. + + HACKING guide recommendation for imports: + imports in human alphabetical order + N306 + """ + # handle import x + # use .lower since capitalization shouldn't dictate order + split_line = import_normalize(physical_line.strip()).lower().split() + split_previous = import_normalize(lines[line_number - 2] + ).strip().lower().split() + # with or without "as y" + length = [2, 4] + if (len(split_line) in length and len(split_previous) in length and + split_line[0] == "import" and split_previous[0] == "import"): + if split_line[1] < split_previous[1]: + return (0, "N306: imports not in alphabetical order (%s, %s)" + % (split_previous[1], split_line[1])) + + +def cloud_docstring_start_space(physical_line): + """Check for docstring not start with space. + + HACKING guide recommendation for docstring: + Docstring should not start with space + N401 + """ + pos = max([physical_line.find(i) for i in DOCSTRING_TRIPLE]) # start + if (pos != -1 and len(physical_line) > pos + 1): + if (physical_line[pos + 3] == ' '): + return (pos, "N401: one line docstring should not start with" + " a space") + + +def cloud_todo_format(physical_line): + """Check for 'TODO()'. + + HACKING guide recommendation for TODO: + Include your name with TODOs as in "#TODO(termie)" + N101 + """ + pos = physical_line.find('TODO') + pos1 = physical_line.find('TODO(') + pos2 = physical_line.find('#') # make sure it's a comment + if (pos != pos1 and pos2 >= 0 and pos2 < pos): + return pos, "N101: Use TODO(NAME)" + + +def cloud_docstring_one_line(physical_line): + """Check one line docstring end. + + HACKING guide recommendation for one line docstring: + A one line docstring looks like this and ends in a period. + N402 + """ + pos = max([physical_line.find(i) for i in DOCSTRING_TRIPLE]) # start + end = max([physical_line[-4:-1] == i for i in DOCSTRING_TRIPLE]) # end + if (pos != -1 and end and len(physical_line) > pos + 4): + if (physical_line[-5] != '.'): + return pos, "N402: one line docstring needs a period" + + +def cloud_docstring_multiline_end(physical_line): + """Check multi line docstring end. + + HACKING guide recommendation for docstring: + Docstring should end on a new line + N403 + """ + pos = max([physical_line.find(i) for i in DOCSTRING_TRIPLE]) # start + if (pos != -1 and len(physical_line) == pos): + print physical_line + if (physical_line[pos + 3] == ' '): + return (pos, "N403: multi line docstring end on new line") + + + +current_file = "" + + +def readlines(filename): + """Record the current file being tested.""" + pep8.current_file = filename + return open(filename).readlines() + + +def add_cloud(): + """Monkey patch pep8 for cloud-init guidelines. + + Look for functions that start with cloud_ + and add them to pep8 module. + + Assumes you know how to write pep8.py checks + """ + for name, function in globals().items(): + if not inspect.isfunction(function): + continue + if name.startswith("cloud_"): + exec("pep8.%s = %s" % (name, name)) + +if __name__ == "__main__": + # NOVA based 'hacking.py' error codes start with an N + pep8.ERRORCODE_REGEX = re.compile(r'[EWN]\d{3}') + add_cloud() + pep8.current_file = current_file + pep8.readlines = readlines + try: + pep8._main() + finally: + if len(_missingImport) > 0: + print >> sys.stderr, ("%i imports missing in this test environment" + % len(_missingImport)) + diff --git a/tools/run-pep8 b/tools/run-pep8 index e7707985..ea46c117 100755 --- a/tools/run-pep8 +++ b/tools/run-pep8 @@ -12,17 +12,24 @@ if [ $# -eq 0 ]; then done else files=( "$@" ); -fi +fi + +if [ -f 'hacking.py' ] +then + base=`pwd` +else + base=`pwd`/tools/ +fi cmd=( - pep8 + ${base}/hacking.py --ignore=E501 # Line too long (these are caught by pylint) "${files[@]}" ) -echo -e "\nRunning pep8:" +echo -e "\nRunning 'cloudinit' pep8:" echo "${cmd[@]}" "${cmd[@]}" |