#!/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))