1# 2# Copyright 2002-2006 Zuza Software Foundation 3# 4# This file is part of translate. 5# 6# translate is free software; you can redistribute it and/or modify 7# it under the terms of the GNU General Public License as published by 8# the Free Software Foundation; either version 2 of the License, or 9# (at your option) any later version. 10# 11# translate is distributed in the hope that it will be useful, 12# but WITHOUT ANY WARRANTY; without even the implied warranty of 13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14# GNU General Public License for more details. 15# 16# You should have received a copy of the GNU General Public License 17# along with this program; if not, see <http://www.gnu.org/licenses/>. 18 19import fnmatch 20import logging 21import optparse 22import os.path 23import re 24import sys 25import traceback 26from collections import OrderedDict 27from io import BytesIO 28 29from translate import __version__ 30from translate.misc import progressbar 31 32 33class ProgressBar: 34 progress_types = OrderedDict( 35 [ 36 ("dots", progressbar.DotsProgressBar), 37 ("none", progressbar.NoProgressBar), 38 ("bar", progressbar.HashProgressBar), 39 ("names", progressbar.MessageProgressBar), 40 ("verbose", progressbar.VerboseProgressBar), 41 ] 42 ) 43 44 def __init__(self, progress_type, allfiles): 45 """Set up a progress bar appropriate to the progress_type and files.""" 46 if progress_type in ("bar", "verbose"): 47 file_count = len(allfiles) 48 self._progressbar = self.progress_types[progress_type](0, file_count) 49 logger = logging.getLogger(os.path.basename(sys.argv[0])).getChild( 50 "progress" 51 ) 52 logger.setLevel(logging.INFO) 53 logger.propagate = False 54 handler = logging.StreamHandler() 55 handler.setLevel(logging.INFO) 56 handler.setFormatter(logging.Formatter()) 57 logger.addHandler(handler) 58 logger.info("processing %d files...", file_count) 59 else: 60 self._progressbar = self.progress_types[progress_type]() 61 62 def report_progress(self, filename, success): 63 """Show that we are progressing...""" 64 self._progressbar.amount += 1 65 self._progressbar.show(filename) 66 67 68class ManPageOption(optparse.Option): 69 ACTIONS = optparse.Option.ACTIONS + ("manpage",) 70 71 def take_action(self, action, dest, opt, value, values, parser): 72 """take_action that can handle manpage as well as standard actions""" 73 if action == "manpage": 74 parser.print_manpage() 75 sys.exit(0) 76 return super().take_action(action, dest, opt, value, values, parser) 77 78 79class ManHelpFormatter(optparse.HelpFormatter): 80 def __init__( 81 self, indent_increment=0, max_help_position=0, width=80, short_first=1 82 ): 83 super().__init__(indent_increment, max_help_position, width, short_first) 84 85 def format_option_strings(self, option): 86 """Return a comma-separated list of option strings & metavariables.""" 87 if option.takes_value(): 88 metavar = option.metavar or option.dest.upper() 89 metavar = "\\fI%s\\fP" % metavar 90 short_opts = [sopt + metavar for sopt in option._short_opts] 91 long_opts = [lopt + "\\fR=\\fP" + metavar for lopt in option._long_opts] 92 else: 93 short_opts = option._short_opts 94 long_opts = option._long_opts 95 96 if self.short_first: 97 opts = short_opts + long_opts 98 else: 99 opts = long_opts + short_opts 100 101 return "\\fB%s\\fP" % ("\\fR, \\fP".join(opts)) 102 103 104class StdoutWrapper: 105 def __init__(self): 106 self.out = sys.stdout 107 108 def __getattr__(self, name): 109 return getattr(self.out, name) 110 111 def write(self, content): 112 if isinstance(content, bytes): 113 try: 114 self.out.write(content.decode("utf-8")) 115 except UnicodeDecodeError: 116 self.out.write("Unable to write binary content to the terminal") 117 else: 118 self.out.write(content) 119 120 121class RecursiveOptionParser(optparse.OptionParser): 122 """A specialized Option Parser for recursing through directories.""" 123 124 def __init__( 125 self, formats, usetemplates=False, allowmissingtemplate=False, description=None 126 ): 127 """Construct the specialized Option Parser. 128 129 :type formats: Dictionary 130 :param formats: See :meth:`~.RecursiveOptionParser.setformats` 131 for an explanation of the formats parameter. 132 """ 133 134 super().__init__(version="%prog " + __version__.sver, description=description) 135 self.setmanpageoption() 136 self.setprogressoptions() 137 self.seterrorleveloptions() 138 self.setformats(formats, usetemplates) 139 self.passthrough = [] 140 self.allowmissingtemplate = allowmissingtemplate 141 logging.basicConfig(format="%(name)s: %(levelname)s: %(message)s") 142 143 def get_prog_name(self): 144 return os.path.basename(sys.argv[0]) 145 146 def setmanpageoption(self): 147 """creates a manpage option that allows the optionparser to generate a 148 manpage 149 """ 150 manpageoption = ManPageOption( 151 None, 152 "--manpage", 153 dest="manpage", 154 default=False, 155 action="manpage", 156 help="output a manpage based on the help", 157 ) 158 self.define_option(manpageoption) 159 160 def format_manpage(self): 161 """returns a formatted manpage""" 162 result = [] 163 prog = self.get_prog_name() 164 formatprog = lambda x: x.replace("%prog", prog) 165 formatToolkit = lambda x: x.replace("%prog", "Translate Toolkit") 166 result.append('.\\" Autogenerated manpage\n') 167 result.append( 168 '.TH %s 1 "%s" "" "%s"\n' 169 % (prog, formatToolkit(self.version), formatToolkit(self.version)) 170 ) 171 result.append(".SH NAME\n") 172 result.append( 173 "{} \\- {}\n".format( 174 self.get_prog_name(), self.description.split("\n\n")[0] 175 ) 176 ) 177 result.append(".SH SYNOPSIS\n") 178 result.append(".PP\n") 179 usage = "\\fB%prog " 180 usage += " ".join(self.getusageman(option) for option in self.option_list) 181 usage += "\\fP" 182 result.append("%s\n" % formatprog(usage)) 183 description_lines = self.description.split("\n\n")[1:] 184 if description_lines: 185 result.append(".SH DESCRIPTION\n") 186 result.append( 187 "\n\n".join( 188 re.sub(r"\.\. note::", "Note:", l) for l in description_lines 189 ) 190 ) 191 result.append(".SH OPTIONS\n") 192 ManHelpFormatter().store_option_strings(self) 193 result.append(".PP\n") 194 for option in self.option_list: 195 result.append(".TP\n") 196 result.append("%s\n" % str(option).replace("-", r"\-")) 197 result.append("%s\n" % option.help.replace("-", r"\-")) 198 return "".join(result) 199 200 def print_manpage(self, file=None): 201 """outputs a manpage for the program using the help information""" 202 if file is None: 203 file = sys.stdout 204 file.write(self.format_manpage()) 205 206 def set_usage(self, usage=None): 207 """sets the usage string - if usage not given, uses getusagestring for 208 each option 209 """ 210 if usage is None: 211 self.usage = "%prog " + " ".join( 212 self.getusagestring(option) for option in self.option_list 213 ) 214 else: 215 super().set_usage(usage) 216 217 def warning(self, msg, options=None, exc_info=None): 218 """Print a warning message incorporating 'msg' to stderr.""" 219 if options: 220 if options.errorlevel == "traceback": 221 errorinfo = "\n".join( 222 traceback.format_exception(exc_info[0], exc_info[1], exc_info[2]) 223 ) 224 elif options.errorlevel == "exception": 225 errorinfo = "\n".join( 226 traceback.format_exception_only(exc_info[0], exc_info[1]) 227 ) 228 elif options.errorlevel == "message": 229 errorinfo = str(exc_info[1]) 230 else: 231 errorinfo = "" 232 if errorinfo: 233 msg += ": " + errorinfo 234 logging.getLogger(self.get_prog_name()).warning(msg) 235 236 def getusagestring(self, option): 237 """returns the usage string for the given option""" 238 optionstring = "|".join(option._short_opts + option._long_opts) 239 if getattr(option, "optionalswitch", False): 240 optionstring = "[%s]" % optionstring 241 if option.metavar: 242 optionstring += " " + option.metavar 243 if getattr(option, "required", False): 244 return optionstring 245 else: 246 return "[%s]" % optionstring 247 248 def getusageman(self, option): 249 """returns the usage string for the given option""" 250 optionstring = "\\fR|\\fP".join(option._short_opts + option._long_opts) 251 if getattr(option, "optionalswitch", False): 252 optionstring = "\\fR[\\fP%s\\fR]\\fP" % optionstring 253 if option.metavar: 254 optionstring += " \\fI%s\\fP" % option.metavar 255 if getattr(option, "required", False): 256 return optionstring 257 else: 258 return "\\fR[\\fP%s\\fR]\\fP" % optionstring 259 260 def define_option(self, option): 261 """Defines the given option, replacing an existing one of the same 262 short name if neccessary... 263 """ 264 for short_opt in option._short_opts: 265 if self.has_option(short_opt): 266 self.remove_option(short_opt) 267 for long_opt in option._long_opts: 268 if self.has_option(long_opt): 269 self.remove_option(long_opt) 270 self.add_option(option) 271 272 def setformats(self, formats, usetemplates): 273 """Sets the format options using the given format dictionary. 274 275 :type formats: Dictionary or iterable 276 :param formats: The dictionary *keys* should be: 277 278 - Single strings (or 1-tuples) containing an 279 input format (if not *usetemplates*) 280 - Tuples containing an input format and 281 template format (if *usetemplates*) 282 - Formats can be *None* to indicate what to do 283 with standard input 284 285 The dictionary *values* should be tuples of 286 outputformat (string) and processor method. 287 """ 288 289 self.inputformats = [] 290 outputformats = [] 291 templateformats = [] 292 self.outputoptions = {} 293 self.usetemplates = usetemplates 294 if isinstance(formats, dict): 295 formats = formats.items() 296 for formatgroup, outputoptions in formats: 297 if isinstance(formatgroup, str) or formatgroup is None: 298 formatgroup = (formatgroup,) 299 if not isinstance(formatgroup, tuple): 300 raise ValueError("formatgroups must be tuples or None/str/unicode") 301 if len(formatgroup) < 1 or len(formatgroup) > 2: 302 raise ValueError("formatgroups must be tuples of length 1 or 2") 303 if len(formatgroup) == 1: 304 formatgroup += (None,) 305 inputformat, templateformat = formatgroup 306 if not isinstance(outputoptions, tuple) or len(outputoptions) != 2: 307 raise ValueError("output options must be tuples of length 2") 308 outputformat, processor = outputoptions 309 if inputformat not in self.inputformats: 310 self.inputformats.append(inputformat) 311 if outputformat not in outputformats: 312 outputformats.append(outputformat) 313 if templateformat not in templateformats: 314 templateformats.append(templateformat) 315 self.outputoptions[(inputformat, templateformat)] = ( 316 outputformat, 317 processor, 318 ) 319 inputformathelp = self.getformathelp(self.inputformats) 320 inputoption = optparse.Option( 321 "-i", 322 "--input", 323 dest="input", 324 default=None, 325 metavar="INPUT", 326 help="read from INPUT in %s" % (inputformathelp), 327 ) 328 inputoption.optionalswitch = True 329 inputoption.required = True 330 self.define_option(inputoption) 331 excludeoption = optparse.Option( 332 "-x", 333 "--exclude", 334 dest="exclude", 335 action="append", 336 type="string", 337 metavar="EXCLUDE", 338 default=["CVS", ".svn", "_darcs", ".git", ".hg", ".bzr"], 339 help="exclude names matching EXCLUDE from input paths", 340 ) 341 self.define_option(excludeoption) 342 outputformathelp = self.getformathelp(outputformats) 343 outputoption = optparse.Option( 344 "-o", 345 "--output", 346 dest="output", 347 default=None, 348 metavar="OUTPUT", 349 help="write to OUTPUT in %s" % (outputformathelp), 350 ) 351 outputoption.optionalswitch = True 352 outputoption.required = True 353 self.define_option(outputoption) 354 if self.usetemplates: 355 self.templateformats = templateformats 356 templateformathelp = self.getformathelp(self.templateformats) 357 templateoption = optparse.Option( 358 "-t", 359 "--template", 360 dest="template", 361 default=None, 362 metavar="TEMPLATE", 363 help="read from TEMPLATE in %s" % (templateformathelp), 364 ) 365 self.define_option(templateoption) 366 367 def setprogressoptions(self): 368 """Sets the progress options.""" 369 progressoption = optparse.Option( 370 None, 371 "--progress", 372 dest="progress", 373 default="bar", 374 choices=list(ProgressBar.progress_types.keys()), 375 metavar="PROGRESS", 376 help="show progress as: %s" % (", ".join(ProgressBar.progress_types)), 377 ) 378 self.define_option(progressoption) 379 380 def seterrorleveloptions(self): 381 """Sets the errorlevel options.""" 382 self.errorleveltypes = ["none", "message", "exception", "traceback"] 383 errorleveloption = optparse.Option( 384 None, 385 "--errorlevel", 386 dest="errorlevel", 387 default="message", 388 choices=self.errorleveltypes, 389 metavar="ERRORLEVEL", 390 help="show errorlevel as: %s" % (", ".join(self.errorleveltypes)), 391 ) 392 self.define_option(errorleveloption) 393 394 def getformathelp(self, formats): 395 """Make a nice help string for describing formats...""" 396 formats = sorted(f for f in formats if f is not None) 397 if len(formats) == 0: 398 return "" 399 elif len(formats) == 1: 400 return "%s format" % (", ".join(formats)) 401 else: 402 return "%s formats" % (", ".join(formats)) 403 404 def isrecursive(self, fileoption, filepurpose="input"): 405 """Checks if fileoption is a recursive file.""" 406 if fileoption is None: 407 return False 408 elif isinstance(fileoption, list): 409 return True 410 else: 411 return os.path.isdir(fileoption) 412 413 def parse_args(self, args=None, values=None): 414 """Parses the command line options, handling implicit input/output 415 args. 416 """ 417 (options, args) = super().parse_args(args, values) 418 # some intelligent as to what reasonable people might give on the 419 # command line 420 if args and not options.input: 421 if len(args) > 1: 422 options.input = args[:-1] 423 args = args[-1:] 424 else: 425 options.input = args[0] 426 args = [] 427 if args and not options.output: 428 options.output = args[-1] 429 args = args[:-1] 430 if args: 431 self.error( 432 "You have used an invalid combination of --input, --output and freestanding args" 433 ) 434 if isinstance(options.input, list) and len(options.input) == 1: 435 options.input = options.input[0] 436 if options.input is None: 437 self.error( 438 "You need to give an inputfile or use - for stdin ; use --help for full usage instructions" 439 ) 440 elif options.input == "-": 441 options.input = None 442 return (options, args) 443 444 def getpassthroughoptions(self, options): 445 """Get the options required to pass to the filtermethod...""" 446 passthroughoptions = {} 447 for optionname in dir(options): 448 if optionname in self.passthrough: 449 passthroughoptions[optionname] = getattr(options, optionname) 450 return passthroughoptions 451 452 def getoutputoptions(self, options, inputpath, templatepath): 453 """Works out which output format and processor method to use...""" 454 if inputpath: 455 inputbase, inputext = self.splitinputext(inputpath) 456 else: 457 inputext = None 458 if templatepath: 459 templatebase, templateext = self.splittemplateext(templatepath) 460 else: 461 templateext = None 462 if (inputext, templateext) in self.outputoptions: 463 return self.outputoptions[inputext, templateext] 464 elif (inputext, "*") in self.outputoptions: 465 outputformat, fileprocessor = self.outputoptions[inputext, "*"] 466 elif ("*", templateext) in self.outputoptions: 467 outputformat, fileprocessor = self.outputoptions["*", templateext] 468 elif ("*", "*") in self.outputoptions: 469 outputformat, fileprocessor = self.outputoptions["*", "*"] 470 elif (inputext, None) in self.outputoptions: 471 return self.outputoptions[inputext, None] 472 elif (None, templateext) in self.outputoptions: 473 return self.outputoptions[None, templateext] 474 elif ("*", None) in self.outputoptions: 475 outputformat, fileprocessor = self.outputoptions["*", None] 476 elif (None, "*") in self.outputoptions: 477 outputformat, fileprocessor = self.outputoptions[None, "*"] 478 else: 479 if self.usetemplates: 480 if inputext is None: 481 raise ValueError( 482 "don't know what to do with input format (no file extension), no template file" 483 ) 484 elif templateext is None: 485 raise ValueError( 486 "don't know what to do with input format %s, no template file" 487 % (os.extsep + inputext) 488 ) 489 else: 490 raise ValueError( 491 "don't know what to do with input format %s, template format %s" 492 % (os.extsep + inputext, os.extsep + templateext) 493 ) 494 else: 495 raise ValueError( 496 "don't know what to do with input format %s" 497 % (os.extsep + inputext) 498 ) 499 if outputformat == "*": 500 if inputext: 501 outputformat = inputext 502 elif templateext: 503 outputformat = templateext 504 elif ("*", "*") in self.outputoptions: 505 outputformat = None 506 else: 507 if self.usetemplates: 508 raise ValueError( 509 "don't know what to do with input format (no file extension), no template file" 510 ) 511 else: 512 raise ValueError( 513 "don't know what to do with input format (no file extension)" 514 ) 515 return outputformat, fileprocessor 516 517 def getfullinputpath(self, options, inputpath): 518 """Gets the full path to an input file.""" 519 if options.input: 520 return os.path.join(options.input, inputpath) 521 else: 522 return inputpath 523 524 def getfulloutputpath(self, options, outputpath): 525 """Gets the full path to an output file.""" 526 if options.recursiveoutput and options.output: 527 return os.path.join(options.output, outputpath) 528 else: 529 return outputpath 530 531 def getfulltemplatepath(self, options, templatepath): 532 """Gets the full path to a template file.""" 533 if not options.recursivetemplate: 534 return templatepath 535 elif templatepath is not None and self.usetemplates and options.template: 536 return os.path.join(options.template, templatepath) 537 else: 538 return None 539 540 def run(self): 541 """Parses the arguments, and runs recursiveprocess with the resulting 542 options... 543 """ 544 (options, args) = self.parse_args() 545 self.recursiveprocess(options) 546 547 def recursiveprocess(self, options): 548 """Recurse through directories and process files.""" 549 if self.isrecursive(options.input, "input") and getattr( 550 options, "allowrecursiveinput", True 551 ): 552 self.ensurerecursiveoutputdirexists(options) 553 if isinstance(options.input, list): 554 inputfiles = self.recurseinputfilelist(options) 555 else: 556 inputfiles = self.recurseinputfiles(options) 557 else: 558 if options.input: 559 inputfiles = [os.path.basename(options.input)] 560 options.input = os.path.dirname(options.input) 561 else: 562 inputfiles = [options.input] 563 options.recursiveoutput = self.isrecursive( 564 options.output, "output" 565 ) and getattr(options, "allowrecursiveoutput", True) 566 options.recursivetemplate = ( 567 self.usetemplates 568 and self.isrecursive(options.template, "template") 569 and getattr(options, "allowrecursivetemplate", True) 570 ) 571 # sort the input files to preserve the order between runs as much as possible. 572 # this makes for more merge-friendly content in single-output-file mode. 573 inputfiles.sort() 574 progress_bar = ProgressBar(options.progress, inputfiles) 575 for inputpath in inputfiles: 576 try: 577 templatepath = self.gettemplatename(options, inputpath) 578 # If we have a recursive template, but the template doesn't 579 # have this input file, let's drop it. 580 if ( 581 options.recursivetemplate 582 and templatepath is None 583 and not self.allowmissingtemplate 584 ): 585 self.warning( 586 f"No template at {templatepath}. Skipping {inputpath}." 587 ) 588 continue 589 outputformat, fileprocessor = self.getoutputoptions( 590 options, inputpath, templatepath 591 ) 592 fullinputpath = self.getfullinputpath(options, inputpath) 593 fulltemplatepath = self.getfulltemplatepath(options, templatepath) 594 outputpath = self.getoutputname(options, inputpath, outputformat) 595 fulloutputpath = self.getfulloutputpath(options, outputpath) 596 if options.recursiveoutput and outputpath: 597 self.checkoutputsubdir(options, os.path.dirname(outputpath)) 598 except Exception: 599 self.warning( 600 "Couldn't handle input file %s" % inputpath, options, sys.exc_info() 601 ) 602 continue 603 try: 604 success = self.processfile( 605 fileprocessor, 606 options, 607 fullinputpath, 608 fulloutputpath, 609 fulltemplatepath, 610 ) 611 except Exception: 612 self.warning( 613 "Error processing: input %s, output %s, template %s" 614 % (fullinputpath, fulloutputpath, fulltemplatepath), 615 options, 616 sys.exc_info(), 617 ) 618 success = False 619 progress_bar.report_progress(inputpath, success) 620 del progress_bar 621 622 def ensurerecursiveoutputdirexists(self, options): 623 if not self.isrecursive(options.output, "output"): 624 if not options.output: 625 self.error(optparse.OptionValueError("No output directory given")) 626 try: 627 self.warning("Output directory does not exist. Attempting to create") 628 os.mkdir(options.output) 629 except OSError: 630 self.error( 631 optparse.OptionValueError( 632 "Output directory does not exist, attempt to create failed" 633 ) 634 ) 635 636 def openinputfile(self, options, fullinputpath): 637 """Opens the input file.""" 638 if fullinputpath is None: 639 return sys.stdin 640 return open(fullinputpath, "rb") 641 642 def openoutputfile(self, options, fulloutputpath): 643 """Opens the output file.""" 644 if fulloutputpath is None: 645 return StdoutWrapper() 646 return open(fulloutputpath, "wb") 647 648 def opentempoutputfile(self, options, fulloutputpath): 649 """Opens a temporary output file.""" 650 return BytesIO() 651 652 def finalizetempoutputfile(self, options, outputfile, fulloutputpath): 653 """Write the temp outputfile to its final destination.""" 654 outputfile.seek(0, 0) 655 outputstring = outputfile.read() 656 outputfile = self.openoutputfile(options, fulloutputpath) 657 outputfile.write(outputstring) 658 outputfile.close() 659 660 def opentemplatefile(self, options, fulltemplatepath): 661 """Opens the template file (if required).""" 662 if fulltemplatepath is not None: 663 if os.path.isfile(fulltemplatepath): 664 return open(fulltemplatepath, "rb") 665 else: 666 self.warning("missing template file %s" % fulltemplatepath) 667 return None 668 669 def processfile( 670 self, fileprocessor, options, fullinputpath, fulloutputpath, fulltemplatepath 671 ): 672 """Process an individual file.""" 673 inputfile = self.openinputfile(options, fullinputpath) 674 if fulloutputpath and fulloutputpath in (fullinputpath, fulltemplatepath): 675 outputfile = self.opentempoutputfile(options, fulloutputpath) 676 tempoutput = True 677 else: 678 outputfile = self.openoutputfile(options, fulloutputpath) 679 tempoutput = False 680 templatefile = self.opentemplatefile(options, fulltemplatepath) 681 passthroughoptions = self.getpassthroughoptions(options) 682 if fileprocessor(inputfile, outputfile, templatefile, **passthroughoptions): 683 if tempoutput: 684 self.warning("writing to temporary output...") 685 self.finalizetempoutputfile(options, outputfile, fulloutputpath) 686 return True 687 else: 688 # remove the file if it is a file (could be stdout etc) 689 if fulloutputpath and os.path.isfile(fulloutputpath): 690 outputfile.close() 691 os.unlink(fulloutputpath) 692 return False 693 694 def mkdir(self, parent, subdir): 695 """Makes a subdirectory (recursively if neccessary).""" 696 if not os.path.isdir(parent): 697 raise ValueError( 698 "cannot make child directory %r if parent %r does not exist" 699 % (subdir, parent) 700 ) 701 currentpath = parent 702 subparts = subdir.split(os.sep) 703 for part in subparts: 704 currentpath = os.path.join(currentpath, part) 705 if not os.path.isdir(currentpath): 706 os.mkdir(currentpath) 707 708 def checkoutputsubdir(self, options, subdir): 709 """Checks to see if subdir under options.output needs to be created, 710 creates if neccessary. 711 """ 712 fullpath = os.path.join(options.output, subdir) 713 if not os.path.isdir(fullpath): 714 self.mkdir(options.output, subdir) 715 716 def isexcluded(self, options, inputpath): 717 """Checks if this path has been excluded.""" 718 basename = os.path.basename(inputpath) 719 for excludename in options.exclude: 720 if fnmatch.fnmatch(basename, excludename): 721 return True 722 return False 723 724 def recurseinputfilelist(self, options): 725 """Use a list of files, and find a common base directory for them.""" 726 # find a common base directory for the files to do everything 727 # relative to 728 commondir = os.path.dirname(os.path.commonprefix(options.input)) 729 inputfiles = [] 730 for inputfile in options.input: 731 if self.isexcluded(options, inputfile): 732 continue 733 if inputfile.startswith(commondir + os.sep): 734 inputfiles.append(inputfile.replace(commondir + os.sep, "", 1)) 735 else: 736 inputfiles.append(inputfile.replace(commondir, "", 1)) 737 options.input = commondir 738 return inputfiles 739 740 def recurseinputfiles(self, options): 741 """Recurse through directories and return files to be processed.""" 742 dirstack = [""] 743 join = os.path.join 744 inputfiles = [] 745 while dirstack: 746 top = dirstack.pop(-1) 747 names = os.listdir(join(options.input, top)) 748 dirs = [] 749 for name in names: 750 inputpath = join(top, name) 751 if self.isexcluded(options, inputpath): 752 continue 753 fullinputpath = self.getfullinputpath(options, inputpath) 754 # handle directories... 755 if os.path.isdir(fullinputpath): 756 dirs.append(inputpath) 757 elif os.path.isfile(fullinputpath): 758 if not self.isvalidinputname(name): 759 # only handle names that match recognized input 760 # file extensions 761 continue 762 inputfiles.append(inputpath) 763 # make sure the directories are processed next time round. 764 dirs.reverse() 765 dirstack.extend(dirs) 766 return inputfiles 767 768 def splitext(self, pathname): 769 """Splits *pathname* into name and ext, and removes the extsep. 770 771 :param pathname: A file path 772 :type pathname: string 773 :return: root, ext 774 :rtype: tuple 775 """ 776 root, ext = os.path.splitext(pathname) 777 ext = ext.replace(os.extsep, "", 1) 778 return (root, ext) 779 780 def splitinputext(self, inputpath): 781 """Splits an *inputpath* into name and extension.""" 782 return self.splitext(inputpath) 783 784 def splittemplateext(self, templatepath): 785 """Splits a *templatepath* into name and extension.""" 786 return self.splitext(templatepath) 787 788 def templateexists(self, options, templatepath): 789 """Returns whether the given template exists...""" 790 fulltemplatepath = self.getfulltemplatepath(options, templatepath) 791 return os.path.isfile(fulltemplatepath) 792 793 def gettemplatename(self, options, inputname): 794 """Gets an output filename based on the input filename.""" 795 if not self.usetemplates: 796 return None 797 if not inputname or not options.recursivetemplate: 798 return options.template 799 inputbase, inputext = self.splitinputext(inputname) 800 if options.template: 801 for inputext1, templateext1 in self.outputoptions: 802 if inputext == inputext1: 803 if templateext1: 804 templatepath = inputbase + os.extsep + templateext1 805 if self.templateexists(options, templatepath): 806 return templatepath 807 if "*" in self.inputformats: 808 for inputext1, templateext1 in self.outputoptions: 809 if (inputext == inputext1) or (inputext1 == "*"): 810 if templateext1 == "*": 811 templatepath = inputname 812 if self.templateexists(options, templatepath): 813 return templatepath 814 elif templateext1: 815 templatepath = inputbase + os.extsep + templateext1 816 if self.templateexists(options, templatepath): 817 return templatepath 818 return None 819 820 def getoutputname(self, options, inputname, outputformat): 821 """Gets an output filename based on the input filename.""" 822 if not inputname or not options.recursiveoutput: 823 return options.output 824 inputbase, inputext = self.splitinputext(inputname) 825 outputname = inputbase 826 if outputformat: 827 outputname += os.extsep + outputformat 828 return outputname 829 830 def isvalidinputname(self, inputname): 831 """Checks if this is a valid input filename.""" 832 inputbase, inputext = self.splitinputext(inputname) 833 return (inputext in self.inputformats) or ("*" in self.inputformats) 834