blob: 94e956ec66fe0dbfae54162d781854c02edb8aec [file] [log] [blame]
Jon Halle1b478d2015-01-12 11:31:56 -08001#!/usr/bin/python
2
3"""
4Translate from PEP8 Python style to Mininet (i.e. Arista-like)
5Python style
6
7usage: 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
15Does the following translations:
16
17ClassName.method_name(foo = bar) -> ClassName.methodName( foo=bar )
18
19Triple-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
26Bugs/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
39Bob Lantz, rlantz@cs.stanford.edu
401/24/2010
41"""
42
43import re, sys
44
45def 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
50def reinstateCapWords( text ):
51 underscoreTriplet = re.compile( r'[A-Za-z0-9]_[A-Za-z0-9]' )
52 return underscoreTriplet.sub( fixUnderscoreTriplet, text )
53
54def replaceTripleApostrophes( text ):
55 "Replace triple apostrophes with triple quotes."
56 return text.replace( "'''", '"""')
57
58def simplifyTripleQuotes( text ):
59 "Fix single-line doc strings."
60 r = re.compile( r'"""([^\"\n]+)"""' )
61 return r.sub( r'"\1"', text )
62
63def 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
81def 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
94def 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
99def fixArgs( match, kwarg = re.compile( r'(\w+) = ' ) ):
100 "Replace foo = bar with foo=bar."
101 return kwarg.sub( r'\1=', match.group() )
102
103def 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
114def lineIter( text ):
115 "Simple iterator over lines in text."
116 for line in text.splitlines(): yield line
117
118def stringIter( strList ):
119 "Yield strings in strList."
120 for s in strList: yield s
121
122def 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
137def 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
157def 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
169def fixCommentIndents( text ):
170 "Fix multiline comment indentation."
171 comments = re.compile( r'^([ ]*)("""[^"]*""")$', re.MULTILINE )
172 return comments.sub( fixComment, text )
173
174def 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
179def convertFromPep8( program ):
180 oldProgram = program
181 # Program text transforms
kelvin-onlabd3b64892015-01-20 13:26:24 -0800182 program = reinstateCapWords( program ) # Turning off for now
Jon Halle1b478d2015-01-12 11:31:56 -0800183 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
197if __name__ == '__main__':
198 print convertFromPep8( sys.stdin.read() )