Jon Hall | e1b478d | 2015-01-12 11:31:56 -0800 | [diff] [blame^] | 1 | #!/usr/bin/python |
| 2 | |
| 3 | """ |
| 4 | Translate from PEP8 Python style to Mininet (i.e. Arista-like) |
| 5 | Python style |
| 6 | |
| 7 | usage: unpep8 < old.py > new.py |
| 8 | |
| 9 | - Reinstates CapWords for methods and instance variables |
| 10 | - Gets rid of triple single quotes |
| 11 | - Eliminates triple quotes on single lines |
| 12 | - Inserts extra spaces to improve readability |
| 13 | - Fixes Doxygen (or doxypy) ugliness |
| 14 | |
| 15 | Does the following translations: |
| 16 | |
| 17 | ClassName.method_name(foo = bar) -> ClassName.methodName( foo=bar ) |
| 18 | |
| 19 | Triple-single-quotes -> triple-double-quotes |
| 20 | |
| 21 | @param foo description -> foo: description |
| 22 | @return description -> returns: description |
| 23 | @author me -> author: me |
| 24 | @todo(me) -> TODO(me) |
| 25 | |
| 26 | Bugs/Limitations: |
| 27 | |
| 28 | - Hack to restore strings is ugly |
| 29 | - Multiline strings get mangled |
| 30 | - Comments are mangled (which is arguably the "right thing" to do, except |
| 31 | that, for example, the left hand sides of the above would get translated!) |
| 32 | - Doesn't eliminate unnecessary backslashes |
| 33 | - Has no opinion on tab size |
| 34 | - complicated indented docstrings get flattened |
| 35 | - We don't (yet) have a filter to generate Doxygen/Doxypy |
| 36 | - Currently leaves indents on blank comment lines |
| 37 | - May lead to namespace collisions (e.g. some_thing and someThing) |
| 38 | |
| 39 | Bob Lantz, rlantz@cs.stanford.edu |
| 40 | 1/24/2010 |
| 41 | """ |
| 42 | |
| 43 | import re, sys |
| 44 | |
| 45 | def fixUnderscoreTriplet( match ): |
| 46 | "Translate a matched triplet of the form a_b to aB." |
| 47 | triplet = match.group() |
| 48 | return triplet[ :-2 ] + triplet[ -1 ].capitalize() |
| 49 | |
| 50 | def reinstateCapWords( text ): |
| 51 | underscoreTriplet = re.compile( r'[A-Za-z0-9]_[A-Za-z0-9]' ) |
| 52 | return underscoreTriplet.sub( fixUnderscoreTriplet, text ) |
| 53 | |
| 54 | def replaceTripleApostrophes( text ): |
| 55 | "Replace triple apostrophes with triple quotes." |
| 56 | return text.replace( "'''", '"""') |
| 57 | |
| 58 | def simplifyTripleQuotes( text ): |
| 59 | "Fix single-line doc strings." |
| 60 | r = re.compile( r'"""([^\"\n]+)"""' ) |
| 61 | return r.sub( r'"\1"', text ) |
| 62 | |
| 63 | def insertExtraSpaces( text ): |
| 64 | "Insert extra spaces inside of parentheses and brackets/curly braces." |
| 65 | lparen = re.compile( r'\((?![\s\)])' ) |
| 66 | text = lparen.sub( r'( ', text ) |
| 67 | rparen = re.compile( r'([^\s\(])(?=\))' ) |
| 68 | text = rparen.sub( r'\1 ', text) |
| 69 | # brackets |
| 70 | lbrack = re.compile( r'\[(?![\s\]])' ) |
| 71 | text = lbrack.sub( r'[ ', text ) |
| 72 | rbrack = re.compile( r'([^\s\[])(?=\])' ) |
| 73 | text = rbrack.sub( r'\1 ', text) |
| 74 | # curly braces |
| 75 | lcurly = re.compile( r'\{(?![\s\}])' ) |
| 76 | text = lcurly.sub( r'{ ', text ) |
| 77 | rcurly = re.compile( r'([^\s\{])(?=\})' ) |
| 78 | text = rcurly.sub( r'\1 ', text) |
| 79 | return text |
| 80 | |
| 81 | def fixDoxygen( text ): |
| 82 | """Translate @param foo to foo:, @return bar to returns: bar, and |
| 83 | @author me to author: me""" |
| 84 | param = re.compile( r'@param (\w+)' ) |
| 85 | text = param.sub( r'\1:', text ) |
| 86 | returns = re.compile( r'@return' ) |
| 87 | text = returns.sub( r'returns:', text ) |
| 88 | author = re.compile( r'@author' ) |
| 89 | text = author.sub( r'author:', text) |
| 90 | # @todo -> TODO |
| 91 | text = text.replace( '@todo', 'TODO' ) |
| 92 | return text |
| 93 | |
| 94 | def removeCommentFirstBlankLine( text ): |
| 95 | "Remove annoying blank lines after first line in comments." |
| 96 | line = re.compile( r'("""[^\n]*\n)\s*\n', re.MULTILINE ) |
| 97 | return line.sub( r'\1', text ) |
| 98 | |
| 99 | def fixArgs( match, kwarg = re.compile( r'(\w+) = ' ) ): |
| 100 | "Replace foo = bar with foo=bar." |
| 101 | return kwarg.sub( r'\1=', match.group() ) |
| 102 | |
| 103 | def fixKeywords( text ): |
| 104 | "Change keyword argumentsfrom foo = bar to foo=bar." |
| 105 | args = re.compile( r'\(([^\)]+)\)', re.MULTILINE ) |
| 106 | return args.sub( fixArgs, text ) |
| 107 | |
| 108 | # Unfortunately, Python doesn't natively support balanced or recursive |
| 109 | # regular expressions. We could use PyParsing, but that opens another can |
| 110 | # of worms. For now, we just have a cheap hack to restore strings, |
| 111 | # so we don't end up accidentally mangling things like messages, search strings, |
| 112 | # and regular expressions. |
| 113 | |
| 114 | def lineIter( text ): |
| 115 | "Simple iterator over lines in text." |
| 116 | for line in text.splitlines(): yield line |
| 117 | |
| 118 | def stringIter( strList ): |
| 119 | "Yield strings in strList." |
| 120 | for s in strList: yield s |
| 121 | |
| 122 | def restoreRegex( regex, old, new ): |
| 123 | "Find regexes in old and restore them into new." |
| 124 | oldStrs = regex.findall( old ) |
| 125 | # Sanity check - count should be the same! |
| 126 | newStrs = regex.findall( new ) |
| 127 | assert len( oldStrs ) == len( newStrs ) |
| 128 | # Replace newStrs with oldStrs |
| 129 | siter = stringIter( oldStrs ) |
| 130 | reps = lambda dummy: siter.next() |
| 131 | return regex.sub( reps, new ) |
| 132 | |
| 133 | # This is a cheap hack, and it may not work 100%, since |
| 134 | # it doesn't handle multiline strings. |
| 135 | # However, it should be mostly harmless... |
| 136 | |
| 137 | def restoreStrings( oldText, newText ): |
| 138 | "Restore strings from oldText into newText, returning result." |
| 139 | oldLines, newLines = lineIter( oldText ), lineIter( newText ) |
| 140 | quoteStrings = re.compile( r'("[^"]*")' ) |
| 141 | tickStrings = re.compile( r"('[^']*')" ) |
| 142 | result = '' |
| 143 | # It would be nice if we could blast the whole file, but for |
| 144 | # now it seems to work line-by-line |
| 145 | for newLine in newLines: |
| 146 | oldLine = oldLines.next() |
| 147 | newLine = restoreRegex( quoteStrings, oldLine, newLine ) |
| 148 | newLine = restoreRegex( tickStrings, oldLine, newLine ) |
| 149 | result += newLine + '\n' |
| 150 | return result |
| 151 | |
| 152 | # This might be slightly controversial, since it uses |
| 153 | # three spaces to line up multiline comments. However, |
| 154 | # I much prefer it. Limitations: if you have deeper |
| 155 | # indents in comments, they will be eliminated. ;-( |
| 156 | |
| 157 | def fixComment( match, |
| 158 | indentExp=re.compile( r'\n([ ]*)(?=[^/s])', re.MULTILINE ), |
| 159 | trailingQuotes=re.compile( r'\s+"""' ) ): |
| 160 | "Re-indent comment, and join trailing quotes." |
| 161 | originalIndent = match.group( 1 ) |
| 162 | comment = match.group( 2 ) |
| 163 | indent = '\n' + originalIndent |
| 164 | # Exception: leave unindented things unindented! |
| 165 | if len( originalIndent ) is not 0: indent += ' ' |
| 166 | comment = indentExp.sub( indent, comment ) |
| 167 | return originalIndent + trailingQuotes.sub( '"""', comment ) |
| 168 | |
| 169 | def fixCommentIndents( text ): |
| 170 | "Fix multiline comment indentation." |
| 171 | comments = re.compile( r'^([ ]*)("""[^"]*""")$', re.MULTILINE ) |
| 172 | return comments.sub( fixComment, text ) |
| 173 | |
| 174 | def removeBogusLinefeeds( text ): |
| 175 | "Remove extra linefeeds at the end of single-line comments." |
| 176 | bogusLfs = re.compile( r'"([^"\n]*)\n"', re.MULTILINE ) |
| 177 | return bogusLfs.sub( '"\1"', text) |
| 178 | |
| 179 | def convertFromPep8( program ): |
| 180 | oldProgram = program |
| 181 | # Program text transforms |
| 182 | # program = reinstateCapWords( program ) # Turning off for now |
| 183 | program = fixKeywords( program ) |
| 184 | program = insertExtraSpaces( program ) |
| 185 | # Undo string damage |
| 186 | program = restoreStrings( oldProgram, program ) |
| 187 | # Docstring transforms |
| 188 | program = replaceTripleApostrophes( program ) |
| 189 | program = simplifyTripleQuotes( program ) |
| 190 | program = fixDoxygen( program ) |
| 191 | # program = fixCommentIndents( program ) # Turning off as it breaks tests in TestON |
| 192 | program = removeBogusLinefeeds( program ) |
| 193 | # Destructive transforms (these can delete lines) |
| 194 | program = removeCommentFirstBlankLine( program ) |
| 195 | return program |
| 196 | |
| 197 | if __name__ == '__main__': |
| 198 | print convertFromPep8( sys.stdin.read() ) |