10 __doc__ =
"Performs hard-coded substitutions on all files in a directory."
19 def ANSIcode(content):
return "\x1B[" + content +
"m"
34 if not options
or not options.UseColor:
return str(msg)
35 return options.Colors.get(category,
"") + str(msg) + ANSIReset
52 ContextClass.__init__(self)
63 def SetLine(self, line_no): self.line_no = line_no
74 def __str__(self):
return "<no substitution>"
76 def __call__(self, s, context =
None):
return s
84 def __init__(self, match, replacement, exceptions = []):
85 SubstitutionClass.__init__(self)
91 def __str__(self):
return self.regex.pattern
95 if pattern.search(s)
is not None:
return s
96 return self.regex.sub(self.
repl, s)
99 return "%r -> %r (regex)" % (self.regex.pattern, self.
repl)
106 SubstitutionClass.__init__(self)
115 if pattern.search(s)
is not None:
return s
116 if self.regex.match(s):
return []
121 return "%r (remove)" % (self.regex.pattern, )
127 def __init__(self, match, replacement, exceptions = []):
128 SubstitutionClass.__init__(self)
138 if pattern
in s:
return s
143 return "%r -> %r (literal)" % (self.
pattern, self.
repl)
149 def __init__(self, match, message, exceptions = []):
150 SubstitutionClass.__init__(self)
151 if hasattr(match,
'search'):
156 self.
regex = re.compile(match)
165 if pattern.search(s)
is not None:
return s
166 match = self.regex.search(s)
167 if match
is not None:
168 msg = match.expand(self.
msg)
171 "From line '%s': %s", s,
176 "From %s: %s\n => %s",
180 logging.debug(
"(pattern: %r on %r)", self.regex.pattern, s)
186 return "%r -> %r (warning)" % (self.
pattern, self.
msg)
195 """Supported keyword arguments: "options"
205 for pattern
in self.
patterns: pattern.SetOptions(options)
210 try: self.options.Colors.update(colors)
211 except AttributeError: self.options.Colors = colors
214 def Color(self, msg, category):
return Colorize(msg, category, self.
options)
217 pattern.SetOptions(self.
options)
218 self.patterns.append(pattern)
223 if not pattern.endswith(
'$'): pattern +=
"$"
224 match = re.compile(pattern)
225 self.file_filters.append(match)
253 def AddWord(self, word, repl, exceptions = []):
268 if pattern.match(FilePath)
is None:
continue
276 """Returns the very same string if the new line is the same as the old one
277 or a list of lines to replace line with
279 if line
is None:
return line
282 new_line =
subst(line, context)
283 if new_line
is line:
continue
285 msg =
" pattern '%s' matched" % subst
286 if context
is not None: msg +=
" at %s" % context
288 if isinstance(new_line, str):
289 msg +=
"\n OLD| " + self.
Color(line.rstrip(
'\n'),
'old')
290 msg +=
"\n NEW| %s" % self.
Color(new_line.rstrip(
'\n'),
'new')
292 msg +=
"\n DEL| %s" % self.
Color(line.rstrip(
'\n'),
'old')
294 msg +=
"\n OLD| " + self.
Color(line.rstrip(
'\n'),
'old')
296 msg +=
"\n NEW| %s" % self.
Color(l.rstrip(
'\n'),
'new')
298 self.options.LogMsg(msg)
302 if not isinstance(new_line, str):
return new_line
310 """Returns whether substitutions were performed"""
316 if not self.
MatchFile(FilePath):
return False
320 context.SetOptions(self.
options)
323 SourceFile =
open(FilePath,
'r')
324 for iLine, line
in enumerate(SourceFile):
325 context.SetLine(iLine + 1)
331 if isinstance(new_line, str):
332 Content.append(new_line)
334 Content.extend(new_line)
341 logging.debug(
"No changes in '%s'.", FilePath)
343 logging.debug(
"%d lines changed in '%s'.", nChanges, FilePath)
345 if self.options.DoIt:
347 OutputFile = ProcessorClass.CreateTempFile(FilePath)
348 OutputPath = OutputFile.name
350 OutputFile.write(
"".
join(Content))
352 shutil.copymode(FilePath, OutputPath)
355 shutil.move(OutputPath, FilePath)
363 for FilePath
in files:
370 """Returns the number of files processor actually acted on"""
372 if os.path.isdir(DirPath):
373 for dirpath, dirnames, filenames
in os.walk(DirPath):
375 = [ os.path.join(dirpath, filename)
for filename
in filenames ]
378 logging.debug(
" processor '%s' changed %d files in '%s'",
379 self.
name, nChanged, dirpath
385 ApplyChangesMsg =
"changed" if self.options.DoIt
else "would change"
386 logging.info(
"Processor '%s' %s %d files in '%s'",
387 self.
name, ApplyChangesMsg, nActions, DirPath
392 ApplyChangesMsg =
"changed" if self.options.DoIt
else "would change"
393 logging.info(
"Processor '%s' %s file '%s'",
394 self.
name, ApplyChangesMsg, DirPath
406 "Processor '%s' applies %d substitutions" % (self, len(self.
patterns))
409 try: output.append(
" " + subst.Describe())
410 except AttributeError:
411 output.append(
" " + str(subst))
413 output.append(
" " + repr(subst))
420 TempPath = os.path.join(
421 tempfile.gettempdir(),
422 tempfile.gettempprefix() +
"-" + os.path.basename(FilePath) +
".tmp"
424 TempFile =
open(TempPath,
'w')
443 for processor
in self: processor.SetOptions(options)
446 for processor
in self: processor.SetColors(**colors)
449 if ProcessorNames
is None:
return
451 for ProcessorName
in ProcessorNames:
453 if Processor.name != ProcessorName:
continue
454 selected.append(Processor)
458 (
"Unknown processor '%s' selected" % ProcessorName)
465 ApplyChangesMsg =
"changed" if self.options.DoIt
else "would be changed"
467 for processor
in self: nChanged += processor.ProcessDir(DirPath)
468 logging.info(
"%d file %s under '%s'", nChanged, ApplyChangesMsg, DirPath)
473 self.processors.append(processor)
478 output = [
"There are %d processors in queue" % len(self) ]
479 for processor
in self:
480 output.extend(processor.Describe())
490 return ProcessorsList.Global.AddProcessor(processor)
497 format=
"%(levelname)s: %(message)s"
506 parser = argparse.ArgumentParser(description=__doc__)
508 parser.add_argument(
"InputDirs", nargs=
"*", action=
"store",
509 help=
"input directories [current]")
511 parser.add_argument(
'--doit', dest=
"DoIt", action=
'store_true',
512 help=
"perform the substitutions [%(default)s]")
514 parser.add_argument(
'--verbose',
'-v', dest=
"DoVerbose", action=
'store_true',
515 help=
"shows all the changes on screen [%(default)s]")
516 parser.add_argument(
'--debug', dest=
"DoDebug", action=
'store_true',
517 help=
"enables debug messages on screen")
518 parser.add_argument(
'--color',
'-U', dest=
"UseColor", action=
'store_true',
519 help=
"enables coloured output [%(default)s]")
521 parser.add_argument(
'--list', dest=
"DoList", action=
'store_true',
522 help=
"just prints the hard-coded substitutions for each processor")
523 parser.add_argument(
'--only', dest=
"SelectedProcessors", action=
'append',
524 help=
"executes only the processors with the specified name (see --list)")
525 parser.add_argument(
'--version', action=
'version',
526 version=
'%(prog)s ' + __version__)
528 arguments = parser.parse_args()
531 LoggingSetup(logging.DEBUG
if arguments.DoDebug
else logging.INFO)
533 if arguments.DoVerbose: arguments.LogMsg = logging.info
534 else: arguments.LogMsg = logging.debug
536 Processors = ProcessorsList.Global
538 Processors.SetOptions(arguments)
539 Processors.SetColors(
540 old=ANSIRed, new=ANSIGreen, source=ANSIWhite, line_no=ANSIMagenta,
543 if arguments.SelectedProcessors:
544 Processors.SelectProcessors(arguments.SelectedProcessors)
547 logging.info(
"\n".
join(Processors.Describe()))
552 if not arguments.InputDirs: arguments.InputDirs = [
'.' ]
554 for InputPath
in arguments.InputDirs:
555 Processors.ProcessDir(InputPath)
562 if __name__ ==
"__main__":
569 subst.AddPattern (
r"[^\w]",
r"_" )
570 subst.AddSimplePattern(
"A",
"a")
def Colorize
Library code.
auto enumerate(Iterables &&...iterables)
Range-for loop helper tracking the number of iteration.
S join(S const &sep, Coll const &s)
Returns a concatenation of strings in s separated by sep.
open(RACETRACK) or die("Could not open file $RACETRACK for writing")