#!/usr/bin/python # -*- test-case-name: Test.test_stylin -*- """ Stylin.py Convert code to your style. Usage: Stylin.py Source.py [Dest.py] If Dest.py isn't specified, stylin will overwrite the source file. The output file still works 99.9% of the time. Occasionally, nested quotes will confuse Stylin. You'll most likely want to go over the output to fix any remaining formatting errors. Copyright (C) 2007 Mike Handverger This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. """ import sys # configure functionality prefs = dict(indent = 4, indenttype = "spaces", tabsize = 8, eol = "\n", operators = ["+", "-", "*", "/", "!", "%", "<", ">", "&"], singlespace = [], doublespace = ["def"], triplespace = ["class"], ignoredocstringsfor = ["__", " "], needdocstrings = ["def", "class"]) def readFile(): """ Read the whole file and return a list of its lines """ f = open(filename, "r") lines = f.readlines() f.close() return lines class Indent: """ Stores an indent value and is able to compare it with others """ def __init__(self, tabs=-1, spaces=-1): self.tabs = tabs self.spaces = spaces def getFrom(self, line): """ Sets us to be the indent from this line """ tabs = 0 spaces = 0 for c in line: if c == " ": spaces += 1 elif c == "\t": tabs += 1 else: self.tabs = tabs self.spaces = spaces return self self.tabs = -1 self.spaces = -1 return self def computeIndentLevel(self): """ Returns the total indent level or -1 for blank line """ if (self.tabs, self.spaces) == ( - 1, -1): return -1 return self.tabs * prefs['tabsize'] + self.spaces def __cmp__(self, other): return self.computeIndentLevel() - other.computeIndentLevel() def __repr__(self): return "" % (self.tabs, self.spaces) def reIndent(line, indent): """ Reindent line so it matches indent """ nl = line.lstrip() if nl == '': return nl return "\t" * indent.tabs + " " * indent.spaces + nl def fixSpaces(line, quotes=""): """ Make sure there are spaces before and after operators except = in keyword args """ l = "" isFunctionDef = line.lstrip().startswith("def ") if line.lstrip().startswith("#"): return line, quotes # print "line", line for x in range(len(line)): c = line[x] if x > 0: cm1 = line[x - 1] else: cm1 = "" if x + 1 < len(line): cp1 = line[x + 1] else: cp1 = "" if x + 2 < len(line): cp2 = line[x + 2] else: cp2 = "" if (len(quotes) < 2 and ((c == '"' and cp1 + cp2 != '""') or (c == "'" and cp1 + cp2 != "''"))): if quotes == c: quotes = "" # print "set single quotes to nothing" elif not quotes: quotes = c # print "set quotes", quotes elif c + cp1 + cp2 == '"""' or c + cp1 + cp2 == "'''": if quotes == c + cp1 + cp2: quotes = "" # print "set triple quotes to nothing" elif not quotes: quotes = c + cp1 + cp2 # print "set quotes", quotes if quotes: l += c elif (c == "=" and cm1 != "=" and not cm1 in prefs['operators'] and cm1 != " " and not isFunctionDef): l += " =" if cp1 != "=" and cp1 != " ": l += " " elif c == "=" and cm1 in prefs['operators'] and cp1 != " ": l += "= " elif (c in prefs['operators'] and cm1 != " " and cm1 != "*" and cp1 != "*" and cm1 != "="): l += " " + c if cp1 != " " and cp1 != "=": l += " " elif c == "," and cp1 != " ": if cm1 == " ": l = l[:-1] l += ", " elif c == "=" and cp1 != " " and cp1 != "=" and not isFunctionDef: l += c + " " else: l += c if quotes != '"""' and quotes != "'''" and quotes: # print "Mismatched quotes!", line return l, "!" return l, quotes def writeFile(name, newlines): """ Writes l (a list of lines) to a file called name """ f = open(outfile, "w") for l in newlines: f.write(l)# + prefs['eol']) f.close() def checkNewSpacing(line, indent, spacing): """ Check if line contains a keyword we should use for adding blank lines """ for type in ["singlespace", "doublespace", "triplespace"]: for cmd in prefs.get(type, []): if line.lstrip().startswith(cmd + " "): spacing[type].append(indent) def startsWithQuotes(line): """ Returns true if line starts with some form of quotes. """ if line.strip().startswith("'") or line.strip().startswith('"'): return True return False def endsWithQuotes(line): """ Returns true if line ends with some form of quotes. """ if line.strip().endswith("'") or line.strip().endswith('"'): return True return False def isDocStringRequired(line, lastline): """ Returns 1 if a docstring is required, 2 if one isn't required but if one exists, we should reformat and returns 0 if no docstring is required """ ndsdocstring = 0 for cm in prefs['needdocstrings']: if lastline.strip().startswith(cm + " "): for nodoc in prefs['ignoredocstringsfor']: if nodoc in lastline: # signifies that we should reformat if there is one, but not create one ndsdocstring = 2 break else: ndsdocstring = 1 return ndsdocstring def applyDocString(ndsdocstring, line, newline, newlines, currentlevel, quotes): """ Format existing docstring or add a properly formatted one """ # check for proper quotes management here! if startsWithQuotes(line): #we have a docstring if endsWithQuotes(line) and line.strip().strip("'\"") != "": # it's one line newline, q = fixSpaces(reIndent('"""'+prefs['eol'], Indent(0, prefs['indent']*(currentlevel))), "") l2, q = fixSpaces(reIndent(line.strip().strip("'\"") + prefs['eol'], Indent(0, prefs['indent']*(currentlevel))),"") newlines.append(newline) newlines.append(l2) else: # multiline if not line.strip().lstrip("'\"") == "": l1, quotes = fixSpaces(reIndent('"""'+prefs['eol'], Indent(0, prefs['indent'] * (currentlevel))), "") newline, q = fixSpaces(reIndent(line.lstrip().lstrip("'\""), Indent(0, prefs['indent'] * (currentlevel))), '"""') newlines.append(l1) else: if ndsdocstring != 2: # need to add one l1, q = fixSpaces(reIndent('"""' + prefs['eol'], Indent(0, prefs['indent'] * (currentlevel))), "") l2, q = fixSpaces(reIndent('You need to add a docstring here!' + prefs['eol'], Indent(0, prefs['indent'] * (currentlevel))), "") newlines.append(l1) newlines.append(l2) newlines.append(l1) return newline, quotes def applyBlankLines(indent, spacing): """ If our new indent matches the indent we need to apply some blank lines at, then return how many lines """ doublespace = spacing["doublespace"] triplespace = spacing["triplespace"] addlines = 0 for x in range(len(doublespace)): if indent <= doublespace[x]: addlines = 2 if x == 0: spacing["doublespace"] = [] else: spacing["doublespace"] = doublespace[:x] break for x in range(len(triplespace)): if indent <= triplespace[x]: addlines = 3 if x == 0: spacing["triplespace"] = [] else: spacing["triplespace"] = triplespace[:x] break return addlines def addLines(cnt, lines): """ Add cnt blank lines to lines """ for x in range(cnt): lines.append(prefs['eol']) def checkNextIndent(lineno, lines, lastindents, spacing): """ Check the next non blank line's indent and add lines if we need to """ line = lines[lineno] addlines = 0 for y in range(lineno + 1, len(lines)): ln = lines[y] if ln.strip() != "": indent = Indent().getFrom(line) if indent < lastindents[-1]: addlines = applyBlankLines(indent, spacing) if not addlines: addlines = checkSingleSpace(indent, spacing) return addlines# - (y - (lineno + 1)) return addlines def checkSingleSpace(indent, spacing): """ Check to see if we need to add a single space """ addlines = 0 singlespace = spacing["singlespace"] for x in range(len(singlespace)): i = singlespace[x] if indent <= i: addlines = 1 if x == 0: spacing['singlespace'] = [] else: spacing['singlespace'] = singlespace[:x] break return addlines def processLines(lines): """ This function does all the stylizing of lines """ newlines = [] currentlevel = 0 lastindents = [Indent(0, 0)] spacing = dict(singlespace=[], doublespace=[], triplespace=[]) quotes = "" skipblank = 0 addlines = 0 lastline = "" for lineno in range(len(lines)): line = lines[lineno] if lineno > 0: lastline = lines[lineno - 1] if line.strip() != "": skipblank = 0 indent = Indent().getFrom(line) checkNewSpacing(line, indent, spacing) if indent > lastindents[ - 1]: currentlevel += 1 lastindents.append(indent) elif indent < lastindents[ - 1]: for x in range(len(lastindents)): if indent == lastindents[x]: currentlevel = x lastindents = lastindents[:x + 1] break addlines = applyBlankLines(indent, spacing) newline, quotes = fixSpaces(reIndent(line, Indent(0, prefs['indent'] * currentlevel)), quotes) ndsdocstring = isDocStringRequired(line, lastline) if ndsdocstring or lineno == 0: newline, quotes = applyDocString(ndsdocstring, line, newline, newlines, currentlevel, quotes) if addlines: addLines(addlines, newlines) skipblank = 1 addlines = 0 newlines.append(newline.rstrip(" ")) addlines = checkSingleSpace(indent, spacing) if addlines: skipblank = 1 addLines(addlines, newlines) elif not skipblank: addlines = checkNextIndent(lineno, lines, lastindents, spacing) addLines(addlines, newlines) if addlines: skipblank = 1 return newlines if __name__ == "__main__": # grab the filenames filename = sys.argv[1] if len(sys.argv) > 2: outfile = sys.argv[2] else: outfile = filename lines = readFile() newlines = processLines(lines) writeFile(outfile, newlines)