Plugins/CheckerPlugins/Pep8/Pep8Fixer.py

changeset 849
996367a89673
child 851
321d29b93238
equal deleted inserted replaced
848:e2fad77b41ba 849:996367a89673
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2011 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a class to fix certain PEP 8 issues.
8 """
9
10 import os
11 import re
12
13 from PyQt4.QtCore import QObject
14
15 from E5Gui import E5MessageBox
16
17 import Utilities
18
19 Pep8FixableIssues = ["W191", "W291", "W292", "W293", "E301", "E303", "E304", "W391", "W603"]
20
21 class Pep8Fixer(QObject):
22 """
23 Class implementing a fixer for certain PEP 8 issues.
24 """
25 def __init__(self, project, filename, sourceLines, fixCodes, inPlace):
26 """
27 Constructor
28
29 @param project reference to the project object (Project)
30 @param filename name of the file to be fixed (string)
31 @param sourceLines list of source lines including eol marker
32 (list of string)
33 @param fixCodes list of codes to be fixed as a comma separated
34 string (string)
35 @param inPlace flag indicating to modify the file in place (boolean)
36 """
37 QObject.__init__(self)
38
39 self.__project = project
40 self.__filename = filename
41 self.__origName = ""
42 self.__source = sourceLines[:] # save a copy
43 self.__fixCodes = [c.strip() for c in fixCodes.split(",") if c.strip()]
44
45 if not inPlace:
46 self.__origName = self.__filename
47 self.__filename = os.path.join(os.path.dirname(self.__filename),
48 "fixed_" + os.path.basename(self.__filename))
49
50 self.__fixes = {
51 "W191" : self.__fixTabs,
52 "W291" : self.__fixWhitespace,
53 "W292" : self.__fixNewline,
54 "W293" : self.__fixWhitespace,
55 "E301" : self.__fixOneBlankLine,
56 "E303" : self.__fixTooManyBlankLines,
57 "E304" : self.__fixBlankLinesAfterDecorator,
58 "W391" : self.__fixTrailingBlankLines,
59 "W603" : self.__fixNotEqual,
60 }
61 self.__modified = False
62 self.__stack = [] # these need to be fixed before the file is saved
63 # but after all inline fixes
64
65 def saveFile(self, encoding):
66 """
67 Public method to save the modified file.
68
69 @param encoding encoding of the source file (string)
70 @return flag indicating success (boolean)
71 """
72 if not self.__modified:
73 # no need to write
74 return True
75
76 # apply deferred fixes
77 self.__finalize()
78
79 txt = "".join(self.__source)
80 try:
81 Utilities.writeEncodedFile(self.__filename, txt, encoding)
82 except (IOError, Utilities.CodingError, UnicodeError) as err:
83 E5MessageBox.critical(self,
84 self.trUtf8("Fix PEP 8 issues"),
85 self.trUtf8(
86 """<p>Could not save the file <b>{0}</b>."""
87 """ Skipping it.</p><p>Reason: {1}</p>""")\
88 .format(self.__filename, str(err))
89 )
90 return False
91
92 return True
93
94 def fixIssue(self, line, pos, message):
95 """
96 Public method to fix the fixable issues.
97
98 @param line line number of issue (integer or string)
99 @param pos character position of issue (integer or string)
100 @param message message text (string)
101 @return flag indicating an applied fix (boolean) and a message for
102 the fix (string)
103 """
104 code = message.split(None, 1)[0].strip()
105
106 if (code in self.__fixCodes or len(self.__fixCodes) == 0) and \
107 code in self.__fixes:
108 res = self.__fixes[code](code, line, pos)
109 if res[0]:
110 self.__modified = True
111 else:
112 res = (False, "")
113
114 return res
115
116 def __finalize(self):
117 """
118 Private method to apply all deferred fixes.
119 """
120 for code, line, pos in reversed(self.__stack):
121 self.__fixes[code](code, line, pos, apply=True)
122
123 def __getEol(self):
124 """
125 Private method to get the applicable eol string.
126
127 @return eol string (string)
128 """
129 if self.__origName:
130 fn = self.__origName
131 else:
132 fn = self.__filename
133
134 if self.__project.isOpen() and self.__project.isProjectFile(fn):
135 eol = self.__project.getLineSeparator()
136 else:
137 eol = Utilities.linesep()
138 return eol
139
140 def __fixTabs(self, code, line, pos):
141 """
142 Private method to fix obsolete tab usage.
143
144 @param code code of the issue (string)
145 @param line line number of the issue (integer)
146 @param pos position inside line (integer)
147 @return flag indicating an applied fix (boolean) and a message for
148 the fix (string)
149 """
150 self.__source[line - 1] = self.__source[line - 1].replace("\t", " ")
151 return (True, self.trUtf8("Tab converted to 4 spaces."))
152
153 def __fixWhitespace(self, code, line, pos):
154 """
155 Private method to fix trailing whitespace.
156
157 @param code code of the issue (string)
158 @param line line number of the issue (integer)
159 @param pos position inside line (integer)
160 @return flag indicating an applied fix (boolean) and a message for
161 the fix (string)
162 """
163 self.__source[line - 1] = re.sub(r'[\t ]*$', "",
164 self.__source[line - 1])
165 return (True, self.trUtf8("Whitespace stripped from end of line."))
166
167 def __fixNewline(self, code, line, pos):
168 """
169 Private method to fix a missing newline at the end of file.
170
171 @param code code of the issue (string)
172 @param line line number of the issue (integer)
173 @param pos position inside line (integer)
174 @return flag indicating an applied fix (boolean) and a message for
175 the fix (string)
176 """
177 self.__source[line - 1] += self.__getEol()
178 return (True, self.trUtf8("newline added to end of file."))
179
180 def __fixTrailingBlankLines(self, code, line, pos):
181 """
182 Private method to fix trailing blank lines.
183
184 @param code code of the issue (string)
185 @param line line number of the issue (integer)
186 @param pos position inside line (integer)
187 @return flag indicating an applied fix (boolean) and a message for
188 the fix (string)
189 """
190 index = line - 1
191 while index:
192 if self.__source[index].strip() == "":
193 del self.__source[index]
194 index -= 1
195 else:
196 break
197 return (True, self.trUtf8(
198 "Superfluous trailing blank lines removed from end of file."))
199
200 def __fixNotEqual(self, code, line, pos):
201 """
202 Private method to fix the not equal notation.
203
204 @param code code of the issue (string)
205 @param line line number of the issue (integer)
206 @param pos position inside line (integer)
207 @return flag indicating an applied fix (boolean) and a message for
208 the fix (string)
209 """
210 self.__source[line - 1] = self.__source[line - 1].replace("<>", "!=")
211 return (True, self.trUtf8("'<>' replaced by '!='."))
212
213 def __fixBlankLinesAfterDecorator(self, code, line, pos, apply=False):
214 """
215 Private method to fix superfluous blank lines after a function
216 decorator.
217
218 @param code code of the issue (string)
219 @param line line number of the issue (integer)
220 @param pos position inside line (integer)
221 @keyparam apply flag indicating, that the fix should be applied
222 (boolean)
223 @return flag indicating an applied fix (boolean) and a message for
224 the fix (string)
225 """
226 if apply:
227 index = line - 2
228 while index:
229 if self.__source[index].strip() == "":
230 del self.__source[index]
231 index -= 1
232 else:
233 break
234 else:
235 self.__stack.append((code, line, pos))
236 return (True, self.trUtf8(
237 "Superfluous blank lines after function decorator removed."))
238
239 def __fixTooManyBlankLines(self, code, line, pos, apply=False):
240 """
241 Private method to fix superfluous blank lines.
242
243 @param code code of the issue (string)
244 @param line line number of the issue (integer)
245 @param pos position inside line (integer)
246 @keyparam apply flag indicating, that the fix should be applied
247 (boolean)
248 @return flag indicating an applied fix (boolean) and a message for
249 the fix (string)
250 """
251 if apply:
252 index = line - 3
253 while index:
254 if self.__source[index].strip() == "":
255 del self.__source[index]
256 index -= 1
257 else:
258 break
259 else:
260 self.__stack.append((code, line, pos))
261 return (True, self.trUtf8("Superfluous blank lines removed."))
262
263 def __fixOneBlankLine(self, code, line, pos, apply=False):
264 """
265 Private method to fix the need for one blank line.
266
267 @param code code of the issue (string)
268 @param line line number of the issue (integer)
269 @param pos position inside line (integer)
270 @keyparam apply flag indicating, that the fix should be applied
271 (boolean)
272 @return flag indicating an applied fix (boolean) and a message for
273 the fix (string)
274 """
275 if apply:
276 self.__source.insert(line - 1, self.__getEol())
277 else:
278 self.__stack.append((code, line, pos))
279 return (True, self.trUtf8("One blank line inserted."))

eric ide

mercurial