Coverage for cogapp/cogapp.py: 49.42%

# Coverage for cogapp/cogapp.py : 49.42%

## 486 statements

Hot-keys on this page

r m x p toggle line displays

j k next/prev highlighted chunk

0 (zero) top of page

1 (one) first highlighted chunk

1# coding: utf8

2""" Cog content generation tool.

3 http://nedbatchelder.com/code/cog

4

5 Copyright 2004-2019, Ned Batchelder.

6"""

7

8 from __future__ import absolute_import, print_function

9

10 import copy

11 import getopt

12 import glob

13 import hashlib

14 import linecache

15 import os

16 import re

17 import shlex

18 import sys

19 import traceback

20

21 from .backward import PY3, StringIO, string_types, to_bytes

22

23 __all__ = ['Cog', 'CogUsageError', 'main']

24

25 __version__ = '3.1.0'

26

27 usage = """\

28 cog - generate content with inlined Python code.

29

30 cog [OPTIONS] [INFILE | @FILELIST] ...

31

32 INFILE is the name of an input file, '-' will read from stdin.

33 FILELIST is the name of a text file containing file names or

34 other @FILELISTs.

35

36 OPTIONS:

37 -c Checksum the output to protect it against accidental change.

38 -d Delete the generator code from the output file.

39 -D name=val Define a global string available to your generator code.

40 -e Warn if a file has no cog code in it.

41 -I PATH Add PATH to the list of directories for data files and modules.

42 -n ENCODING Use ENCODING when reading and writing files.

43 -o OUTNAME Write the output to OUTNAME.

44 -p PROLOGUE Prepend the generator source with PROLOGUE. Useful to insert an

45 import line. Example: -p "import math"

46 -r Replace the input file with the output.

47 -s STRING Suffix all generated output lines with STRING.

48 -U Write the output with Unix newlines (only LF line-endings).

49 -w CMD Use CMD if the output file needs to be made writable.

50 A %s in the CMD will be filled with the filename.

51 -x Excise all the generated output without running the generators.

52 -z The end-output marker can be omitted, and is assumed at eof.

53 -v Print the version of cog and exit.

54 --verbosity=VERBOSITY

55 Control the amount of output. 2 (the default) lists all files,

56 1 lists only changed files, 0 lists no files.

57 --markers='START END END-OUTPUT'

58 The patterns surrounding cog inline instructions. Should

59 include three values separated by spaces, the start, end,

60 and end-output markers. Defaults to '[[[cog ]]] [[[end]]]'.

61 -h Print this help.

62"""

63

64# Other package modules

65 from .whiteutils import *

66

67 class CogError(Exception):

68 """ Any exception raised by Cog.

69 """

70 def __init__(self, msg, file='', line=0):

71 if file:

72 Exception.__init__(self, "%s(%d): %s" % (file, line, msg))

73 else:

74 Exception.__init__(self, msg)

75

76 class CogUsageError(CogError):

77 """ An error in usage of command-line arguments in cog.

78 """

79 pass

80

81 class CogInternalError(CogError):

82 """ An error in the coding of Cog. Should never happen.

83 """

84 pass

85

86 class CogGeneratedError(CogError):

87 """ An error raised by a user's cog generator.

88 """

89 pass

90

91 class CogUserException(CogError):

92 """ An exception caught when running a user's cog generator.

93 The argument is the traceback message to print.

94 """

95 pass

96

97 class Redirectable:

98 """ An object with its own stdout and stderr files.

99 """

100 def __init__(self):

101 self.stdout = sys.stdout

102 self.stderr = sys.stderr

103

104 def setOutput(self, stdout=None, stderr=None):

105 """ Assign new files for standard out and/or standard error.

106 """

107 if stdout: 107 ↛ 109line 107 didn't jump to line 109, because the condition on line 107 was never false

108 self.stdout = stdout

109 if stderr: 109 ↛ 110line 109 didn't jump to line 110, because the condition on line 109 was never true

110 self.stderr = stderr

111

112 def prout(self, s, end="\n"):

113 print(s, file=self.stdout, end=end)

114

115 def prerr(self, s, end="\n"):

116 print(s, file=self.stderr, end=end)

117

118

119 class CogGenerator(Redirectable):

120 """ A generator pulled from a source file.

121 """

122 def __init__(self, options=None):

123 Redirectable.__init__(self)

124 self.markers = []

125 self.lines = []

126 self.options = options or CogOptions()

127

128 def parseMarker(self, l):

129 self.markers.append(l)

130

131 def parseLine(self, l):

132 self.lines.append(l.strip('\n'))

133

134 def getCode(self):

135 """ Extract the executable Python code from the generator.

136 """

137 # If the markers and lines all have the same prefix

138 # (end-of-line comment chars, for example),

139 # then remove it from all the lines.

140 prefIn = commonPrefix(self.markers + self.lines)

141 if prefIn:

142 self.markers = [ l.replace(prefIn, '', 1) for l in self.markers ]

143 self.lines = [ l.replace(prefIn, '', 1) for l in self.lines ]

144

145 return reindentBlock(self.lines, '')

146

147 def evaluate(self, cog, globals, fname):

148 # figure out the right whitespace prefix for the output

149 prefOut = whitePrefix(self.markers)

150

151 intext = self.getCode()

152 if not intext:

153 return ''

154

155 prologue = "import " + cog.cogmodulename + " as cog\n"

156 if self.options.sPrologue: 156 ↛ 157line 156 didn't jump to line 157, because the condition on line 156 was never true

157 prologue += self.options.sPrologue + '\n'

158 code = compile(prologue + intext, str(fname), 'exec')

159

160 # Make sure the "cog" module has our state.

161 cog.cogmodule.msg = self.msg

162 cog.cogmodule.out = self.out

163 cog.cogmodule.outl = self.outl

164 cog.cogmodule.error = self.error

165

166 self.outstring = ''

167 try:

168 eval(code, globals)

169 except CogError: 169 ↛ 170line 169 didn't jump to line 170, because the exception caught by line 169 didn't happen

170 raise

171 except:

172 typ, err, tb = sys.exc_info()

173 frames = (tuple(fr) for fr in traceback.extract_tb(tb.tb_next))

174 frames = find_cog_source(frames, prologue)

175 msg = "".join(traceback.format_list(frames))

176 msg += "{}: {}".format(typ.__name__, err)

177 raise CogUserException(msg)

178

179 # We need to make sure that the last line in the output

180 # ends with a newline, or it will be joined to the

181 # end-output line, ruining cog's idempotency.

182 if self.outstring and self.outstring[-1] != '\n':

183 self.outstring += '\n'

184

185 return reindentBlock(self.outstring, prefOut)

186

187 def msg(self, s):

188 self.prout("Message: "+s)

189

190 def out(self, sOut='', dedent=False, trimblanklines=False):

191 """ The cog.out function.

192 """

193 if trimblanklines and ('\n' in sOut):

194 lines = sOut.split('\n')

195 if lines[0].strip() == '':

196 del lines[0]

197 if lines and lines[-1].strip() == '':

198 del lines[-1]

199 sOut = '\n'.join(lines)+'\n'

200 if dedent:

201 sOut = reindentBlock(sOut)

202 self.outstring += sOut

203

204 def outl(self, sOut='', **kw):

205 """ The cog.outl function.

206 """

207 self.out(sOut, **kw)

208 self.out('\n')

209

210 def error(self, msg='Error raised by cog generator.'):

211 """ The cog.error function.

212 Instead of raising standard python errors, cog generators can use

213 this function. It will display the error without a scary Python

214 traceback.

215 """

216 raise CogGeneratedError(msg)

217

218

219 class NumberedFileReader:

220 """ A decorator for files that counts the readline()'s called.

221 """

222 def __init__(self, f):

223 self.f = f

224 self.n = 0

225

226 def readline(self):

227 l = self.f.readline()

228 if l:

229 self.n += 1

230 return l

231

232 def linenumber(self):

233 return self.n

234

235

236 class CogOptions:

237 """ Options for a run of cog.

238 """

239 def __init__(self):

240 # Defaults for argument values.

241 self.args = []

242 self.includePath = []

243 self.defines = {}

244 self.bShowVersion = False

245 self.sMakeWritableCmd = None

246 self.bReplace = False

247 self.bNoGenerate = False

248 self.sOutputName = None

249 self.bWarnEmpty = False

250 self.bHashOutput = False

251 self.bDeleteCode = False

252 self.bEofCanBeEnd = False

253 self.sSuffix = None

254 self.bNewlines = False

255 self.sBeginSpec = '[[[cog'

256 self.sEndSpec = ']]]'

257 self.sEndOutput = '[[[end]]]'

258 self.sEncoding = "utf-8"

259 self.verbosity = 2

260 self.sPrologue = ''

261

262 def __eq__(self, other):

263 """ Comparison operator for tests to use.

264 """

265 return self.__dict__ == other.__dict__

266

267 def clone(self):

268 """ Make a clone of these options, for further refinement.

269 """

270 return copy.deepcopy(self)

271

272 def addToIncludePath(self, dirs):

273 """ Add directories to the include path.

274 """

275 dirs = dirs.split(os.pathsep)

276 self.includePath.extend(dirs)

277

278 def parseArgs(self, argv):

279 # Parse the command line arguments.

280 try:

281 opts, self.args = getopt.getopt(

282 argv,

283 'cdD:eI:n:o:rs:p:Uvw:xz',

284 [

285 'markers=',

286 'verbosity=',

287 ]

288 )

289 except getopt.error as msg:

290 raise CogUsageError(msg)

291

292 # Handle the command line arguments.

293 for o, a in opts:

294 if o == '-c':

295 self.bHashOutput = True

296 elif o == '-d':

297 self.bDeleteCode = True

298 elif o == '-D':

299 if a.count('=') < 1:

300 raise CogUsageError("-D takes a name=value argument")

301 name, value = a.split('=', 1)

302 self.defines[name] = value

303 elif o == '-e':

304 self.bWarnEmpty = True

305 elif o == '-I':

306 self.addToIncludePath(os.path.abspath(a))

307 elif o == '-n':

308 self.sEncoding = a

309 elif o == '-o':

310 self.sOutputName = a

311 elif o == '-r':

312 self.bReplace = True

313 elif o == '-s':

314 self.sSuffix = a

315 elif o == '-p':

316 self.sPrologue = a

317 elif o == '-U':

318 self.bNewlines = True

319 elif o == '-v':

320 self.bShowVersion = True

321 elif o == '-w':

322 self.sMakeWritableCmd = a

323 elif o == '-x':

324 self.bNoGenerate = True

325 elif o == '-z':

326 self.bEofCanBeEnd = True

327 elif o == '--markers':

328 self._parse_markers(a)

329 elif o == '--verbosity':

330 self.verbosity = int(a)

331 else:

332 # Since getopt.getopt is given a list of possible flags,

333 # this is an internal error.

334 raise CogInternalError("Don't understand argument %s" % o)

335

336 def _parse_markers(self, val):

337 try:

338 self.sBeginSpec, self.sEndSpec, self.sEndOutput = val.split(' ')

339 except ValueError:

340 raise CogUsageError(

341 '--markers requires 3 values separated by spaces, could not parse %r' % val

342 )

343

344 def validate(self):

345 """ Does nothing if everything is OK, raises CogError's if it's not.

346 """

347 if self.bReplace and self.bDeleteCode:

348 raise CogUsageError("Can't use -d with -r (or you would delete all your source!)")

349

350 if self.bReplace and self.sOutputName:

351 raise CogUsageError("Can't use -o with -r (they are opposites)")

352

353

354 class Cog(Redirectable):

355 """ The Cog engine.

356 """

357 def __init__(self):

358 Redirectable.__init__(self)

359 self.options = CogOptions()

360 self._fixEndOutputPatterns()

361 self.cogmodulename = "cog"

362 self.createCogModule()

363

364 def _fixEndOutputPatterns(self):

365 end_output = re.escape(self.options.sEndOutput)

366 self.reEndOutput = re.compile(end_output + r'(?P<hashsect> *\(checksum: (?P<hash>[a-f0-9]+)\))')

367 self.sEndFormat = self.options.sEndOutput + ' (checksum: %s)'

368

369 def showWarning(self, msg):

370 self.prout("Warning: "+msg)

371

372 def isBeginSpecLine(self, s):

373 return self.options.sBeginSpec in s

374

375 def isEndSpecLine(self, s):

376 return self.options.sEndSpec in s and not self.isEndOutputLine(s)

377

378 def isEndOutputLine(self, s):

379 return self.options.sEndOutput in s

380

381 def createCogModule(self):

382 """ Make a cog "module" object so that imported Python modules

383 can say "import cog" and get our state.

384 """

385 class DummyModule(object):

386 """Modules don't have to be anything special, just an object will do."""

387 pass

388 self.cogmodule = DummyModule()

389 self.cogmodule.path = []

390

391 def openOutputFile(self, fname):

392 """ Open an output file, taking all the details into account.

393 """

394 opts = {}

395 mode = "w"

396 if PY3:

397 opts['encoding'] = self.options.sEncoding

398 if self.options.bNewlines:

399 if PY3:

400 opts['newline'] = "\n"

401 else:

402 mode = "wb"

403 fdir = os.path.dirname(fname)

404 if os.path.dirname(fdir) and not os.path.exists(fdir):

405 os.makedirs(fdir)

406 return open(fname, mode, **opts)

407

408 def openInputFile(self, fname):

409 """ Open an input file. """

410 if fname == "-":

411 return sys.stdin

412 else:

413 opts = {}

414 if PY3:

415 opts['encoding'] = self.options.sEncoding

416 return open(fname, "r", **opts)

417

418 def processFile(self, fIn, fOut, fname=None, globals=None):

419 """ Process an input file object to an output file object.

420 fIn and fOut can be file objects, or file names.

421 """

422

423 sFileIn = fname or ''

424 sFileOut = fname or ''

425 fInToClose = fOutToClose = None

426 # Convert filenames to files.

427 if isinstance(fIn, string_types): 427 ↛ 429line 427 didn't jump to line 429, because the condition on line 427 was never true

428 # Open the input file.

429 sFileIn = fIn

430 fIn = fInToClose = self.openInputFile(fIn)

431 if isinstance(fOut, string_types): 431 ↛ 433line 431 didn't jump to line 433, because the condition on line 431 was never true

432 # Open the output file.

433 sFileOut = fOut

434 fOut = fOutToClose = self.openOutputFile(fOut)

435

436 try:

437 fIn = NumberedFileReader(fIn)

438

439 bSawCog = False

440

441 self.cogmodule.inFile = sFileIn

442 self.cogmodule.outFile = sFileOut

443 self.cogmodulename = 'cog_' + hashlib.md5(sFileOut.encode()).hexdigest()

444 sys.modules[self.cogmodulename] = self.cogmodule

445 # if "import cog" explicitly done in code by user, note threading will cause clashes.

446 sys.modules['cog'] = self.cogmodule

447

448 # The globals dict we'll use for this file.

449 if globals is None: 449 ↛ 453line 449 didn't jump to line 453, because the condition on line 449 was never false

450 globals = {}

451

452 # If there are any global defines, put them in the globals.

453 globals.update(self.options.defines)

454

455 # loop over generator chunks

456 l = fIn.readline()

457 while l:

458 # Find the next spec begin

459 while l and not self.isBeginSpecLine(l):

460 if self.isEndSpecLine(l): 460 ↛ 461line 460 didn't jump to line 461, because the condition on line 460 was never true

461 raise CogError("Unexpected '%s'" % self.options.sEndSpec,

462 file=sFileIn, line=fIn.linenumber())

463 if self.isEndOutputLine(l): 463 ↛ 464line 463 didn't jump to line 464, because the condition on line 463 was never true

464 raise CogError("Unexpected '%s'" % self.options.sEndOutput,

465 file=sFileIn, line=fIn.linenumber())

466 fOut.write(l)

467 l = fIn.readline()

468 if not l:

469 break

470 if not self.options.bDeleteCode: 470 ↛ 474line 470 didn't jump to line 474, because the condition on line 470 was never false

471 fOut.write(l)

472

473 # l is the begin spec

474 gen = CogGenerator(options=self.options)

475 gen.setOutput(stdout=self.stdout)

476 gen.parseMarker(l)

477 firstLineNum = fIn.linenumber()

478 self.cogmodule.firstLineNum = firstLineNum

479

480 # If the spec begin is also a spec end, then process the single

481 # line of code inside.

482 if self.isEndSpecLine(l):

483 beg = l.find(self.options.sBeginSpec)

484 end = l.find(self.options.sEndSpec)

485 if beg > end:

486 raise CogError("Cog code markers inverted",

487 file=sFileIn, line=firstLineNum)

488 else:

489 sCode = l[beg+len(self.options.sBeginSpec):end].strip()

490 gen.parseLine(sCode)

491 else:

492 # Deal with an ordinary code block.

493 l = fIn.readline()

494

495 # Get all the lines in the spec

496 while l and not self.isEndSpecLine(l):

497 if self.isBeginSpecLine(l): 497 ↛ 498line 497 didn't jump to line 498, because the condition on line 497 was never true

498 raise CogError("Unexpected '%s'" % self.options.sBeginSpec,

499 file=sFileIn, line=fIn.linenumber())

500 if self.isEndOutputLine(l): 500 ↛ 501line 500 didn't jump to line 501, because the condition on line 500 was never true

501 raise CogError("Unexpected '%s'" % self.options.sEndOutput,

502 file=sFileIn, line=fIn.linenumber())

503 if not self.options.bDeleteCode: 503 ↛ 505line 503 didn't jump to line 505, because the condition on line 503 was never false

504 fOut.write(l)

505 gen.parseLine(l)

506 l = fIn.readline()

507 if not l: 507 ↛ 508line 507 didn't jump to line 508, because the condition on line 507 was never true

508 raise CogError(

509 "Cog block begun but never ended.",

510 file=sFileIn, line=firstLineNum)

511

512 if not self.options.bDeleteCode: 512 ↛ 514line 512 didn't jump to line 514, because the condition on line 512 was never false

513 fOut.write(l)

514 gen.parseMarker(l)

515

516 l = fIn.readline()

517

518 # Eat all the lines in the output section. While reading past

519 # them, compute the md5 hash of the old output.

520 previous = ""

521 hasher = hashlib.md5()

522 while l and not self.isEndOutputLine(l):

523 if self.isBeginSpecLine(l): 523 ↛ 524line 523 didn't jump to line 524, because the condition on line 523 was never true

524 raise CogError("Unexpected '%s'" % self.options.sBeginSpec,

525 file=sFileIn, line=fIn.linenumber())

526 if self.isEndSpecLine(l): 526 ↛ 527line 526 didn't jump to line 527, because the condition on line 526 was never true

527 raise CogError("Unexpected '%s'" % self.options.sEndSpec,

528 file=sFileIn, line=fIn.linenumber())

529 previous += l

530 hasher.update(to_bytes(l))

531 l = fIn.readline()

532 curHash = hasher.hexdigest()

533

534 if not l and not self.options.bEofCanBeEnd: 534 ↛ 536line 534 didn't jump to line 536, because the condition on line 534 was never true

535 # We reached end of file before we found the end output line.

536 raise CogError("Missing '%s' before end of file." % self.options.sEndOutput,

537 file=sFileIn, line=fIn.linenumber())

538

539 # Make the previous output available to the current code

540 self.cogmodule.previous = previous

541

542 # Write the output of the spec to be the new output if we're

543 # supposed to generate code.

544 hasher = hashlib.md5()

545 if not self.options.bNoGenerate: 545 ↛ 551line 545 didn't jump to line 551, because the condition on line 545 was never false

546 sFile = "<cog %s:%d>" % (sFileIn, firstLineNum)

547 sGen = gen.evaluate(cog=self, globals=globals, fname=sFile)

548 sGen = self.suffixLines(sGen)

549 hasher.update(to_bytes(sGen))

550 fOut.write(sGen)

551 newHash = hasher.hexdigest()

552

553 bSawCog = True

554

555 # Write the ending output line

556 hashMatch = self.reEndOutput.search(l)

557 if self.options.bHashOutput: 557 ↛ 558line 557 didn't jump to line 558, because the condition on line 557 was never true

558 if hashMatch:

559 oldHash = hashMatch.groupdict()['hash']

560 if oldHash != curHash:

561 raise CogError("Output has been edited! Delete old checksum to unprotect.",

562 file=sFileIn, line=fIn.linenumber())

563 # Create a new end line with the correct hash.

564 endpieces = l.split(hashMatch.group(0), 1)

565 else:

566 # There was no old hash, but we want a new hash.

567 endpieces = l.split(self.options.sEndOutput, 1)

568 l = (self.sEndFormat % newHash).join(endpieces)

569 else:

570 # We don't want hashes output, so if there was one, get rid of

571 # it.

572 if hashMatch: 572 ↛ 573line 572 didn't jump to line 573, because the condition on line 572 was never true

573 l = l.replace(hashMatch.groupdict()['hashsect'], '', 1)

574

575 if not self.options.bDeleteCode: 575 ↛ 577line 575 didn't jump to line 577, because the condition on line 575 was never false

576 fOut.write(l)

577 l = fIn.readline()

578

579 if not bSawCog and self.options.bWarnEmpty: 579 ↛ 580line 579 didn't jump to line 580, because the condition on line 579 was never true

580 self.showWarning("no cog code found in %s" % sFileIn)

581 finally:

582 if fInToClose: 582 ↛ 583line 582 didn't jump to line 583, because the condition on line 582 was never true

583 fInToClose.close()

584 if fOutToClose: 584 ↛ 585line 584 didn't jump to line 585, because the condition on line 584 was never true

585 fOutToClose.close()

586

587

588 # A regex for non-empty lines, used by suffixLines.

589 reNonEmptyLines = re.compile(r"^\s*\S+.*$", re.MULTILINE)

590

591 def suffixLines(self, text):

592 """ Add suffixes to the lines in text, if our options desire it.

593 text is many lines, as a single string.

594 """

595 if self.options.sSuffix: 595 ↛ 597line 595 didn't jump to line 597, because the condition on line 595 was never true

596 # Find all non-blank lines, and add the suffix to the end.

597 repl = r"\g<0>" + self.options.sSuffix.replace('\\', '\\\\')

598 text = self.reNonEmptyLines.sub(repl, text)

599 return text

600

601 def processString(self, sInput, fname=None):

602 """ Process sInput as the text to cog.

603 Return the cogged output as a string.

604 """

605 fOld = StringIO(sInput)

606 fNew = StringIO()

607 self.processFile(fOld, fNew, fname=fname)

608 return fNew.getvalue()

609

610 def replaceFile(self, sOldPath, sNewText):

611 """ Replace file sOldPath with the contents sNewText

612 """

613 if not os.access(sOldPath, os.W_OK):

614 # Need to ensure we can write.

615 if self.options.sMakeWritableCmd:

616 # Use an external command to make the file writable.

617 cmd = self.options.sMakeWritableCmd.replace('%s', sOldPath)

618 self.stdout.write(os.popen(cmd).read())

619 if not os.access(sOldPath, os.W_OK):

620 raise CogError("Couldn't make %s writable" % sOldPath)

621 else:

622 # Can't write!

623 raise CogError("Can't overwrite %s" % sOldPath)

624 f = self.openOutputFile(sOldPath)

625 f.write(sNewText)

626 f.close()

627

628 def saveIncludePath(self):

629 self.savedInclude = self.options.includePath[:]

630 self.savedSysPath = sys.path[:]

631

632 def restoreIncludePath(self):

633 self.options.includePath = self.savedInclude

634 self.cogmodule.path = self.options.includePath

635 sys.path = self.savedSysPath

636

637 def addToIncludePath(self, includePath):

638 self.cogmodule.path.extend(includePath)

639 sys.path.extend(includePath)

640

641 def processOneFile(self, sFile):

642 """ Process one filename through cog.

643 """

644

645 self.saveIncludePath()

646 bNeedNewline = False

647

648 try:

649 self.addToIncludePath(self.options.includePath)

650 # Since we know where the input file came from,

651 # push its directory onto the include path.

652 self.addToIncludePath([os.path.dirname(sFile)])

653

654 # How we process the file depends on where the output is going.

655 if self.options.sOutputName:

656 self.processFile(sFile, self.options.sOutputName, sFile)

657 elif self.options.bReplace:

658 # We want to replace the cog file with the output,

659 # but only if they differ.

660 if self.options.verbosity >= 2:

661 self.prout("Cogging %s" % sFile, end="")

662 bNeedNewline = True

663

664 try:

665 fOldFile = self.openInputFile(sFile)

666 sOldText = fOldFile.read()

667 fOldFile.close()

668 sNewText = self.processString(sOldText, fname=sFile)

669 if sOldText != sNewText:

670 if self.options.verbosity >= 1:

671 if self.options.verbosity < 2:

672 self.prout("Cogging %s" % sFile, end="")

673 self.prout(" (changed)")

674 bNeedNewline = False

675 self.replaceFile(sFile, sNewText)

676 finally:

677 # The try-finally block is so we can print a partial line

678 # with the name of the file, and print (changed) on the

679 # same line, but also make sure to break the line before

680 # any traceback.

681 if bNeedNewline:

682 self.prout("")

683 else:

684 self.processFile(sFile, self.stdout, sFile)

685 finally:

686 self.restoreIncludePath()

687

688 def processWildcards(self, sFile):

689 files = glob.glob(sFile)

690 if files:

691 for sMatchingFile in files:

692 self.processOneFile(sMatchingFile)

693 else:

694 self.processOneFile(sFile)

695

696 def processFileList(self, sFileList):

697 """ Process the files in a file list.

698 """

699 flist = self.openInputFile(sFileList)

700 lines = flist.readlines()

701 flist.close()

702 for l in lines:

703 # Use shlex to parse the line like a shell.

704 lex = shlex.shlex(l, posix=True)

705 lex.whitespace_split = True

706 lex.commenters = '#'

707 # No escapes, so that backslash can be part of the path

708 lex.escape = ''

709 args = list(lex)

710 if args:

711 self.processArguments(args)

712

713 def processArguments(self, args):

714 """ Process one command-line.

715 """

716 saved_options = self.options

717 self.options = self.options.clone()

718

719 self.options.parseArgs(args[1:])

720 self.options.validate()

721

722 if args[0][0] == '@':

723 if self.options.sOutputName:

724 raise CogUsageError("Can't use -o with @file")

725 self.processFileList(args[0][1:])

726 else:

727 self.processWildcards(args[0])

728

729 self.options = saved_options

730

731 def callableMain(self, argv):

732 """ All of command-line cog, but in a callable form.

733 This is used by main.

734 argv is the equivalent of sys.argv.

735 """

736 argv = argv[1:]

737

738 # Provide help if asked for anywhere in the command line.

739 if '-?' in argv or '-h' in argv:

740 self.prerr(usage, end="")

741 return

742

743 self.options.parseArgs(argv)

744 self.options.validate()

745 self._fixEndOutputPatterns()

746

747 if self.options.bShowVersion:

748 self.prout("Cog version %s" % __version__)

749 return

750

751 if self.options.args:

752 for a in self.options.args:

753 self.processArguments([a])

754 else:

755 raise CogUsageError("No files to process")

756

757 def main(self, argv):

758 """ Handle the command-line execution for cog.

759 """

760

761 try:

762 self.callableMain(argv)

763 return 0

764 except CogUsageError as err:

765 self.prerr(err)

766 self.prerr("(for help use -?)")

767 return 2

768 except CogGeneratedError as err:

769 self.prerr("Error: %s" % err)

770 return 3

771 except CogUserException as err:

772 self.prerr("Traceback (most recent call last):")

773 self.prerr(err.args[0])

774 return 4

775 except CogError as err:

776 self.prerr(err)

777 return 1

778

779

780 def find_cog_source(frame_summary, prologue):

781 """Find cog source lines in a frame summary list, for printing tracebacks.

782

783 Arguments:

784 frame_summary: a list of 4-item tuples, as returned by traceback.extract_tb.

785 prologue: the text of the code prologue.

786

787 Returns

788 A list of 4-item tuples, updated to correct the cog entries.

789

790 """

791 prolines = prologue.splitlines()

792 for filename, lineno, funcname, source in frame_summary:

793 if not source: 793 ↛ 805line 793 didn't jump to line 805, because the condition on line 793 was never false

794 m = re.search(r"^<cog ([^:]+):(\d+)>$", filename)

795 if m: 795 ↛ 796line 795 didn't jump to line 796, because the condition on line 795 was never true

796 if lineno <= len(prolines):

797 filename = '<prologue>'

798 source = prolines[lineno-1]

799 lineno -= 1 # Because "import cog" is the first line in the prologue

800 else:

801 filename, coglineno = m.groups()

802 coglineno = int(coglineno)

803 lineno += coglineno - len(prolines)

804 source = linecache.getline(filename, lineno).strip()

805 yield filename, lineno, funcname, source

806

807

808 def main():

809 """Main function for entry_points to use."""

810 return Cog().main(sys.argv)