Coverage for cogapp/cogapp.py: 49.34%

# Coverage for cogapp/cogapp.py : 49.34%

## 485 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 imp

15 import linecache

16 import os

17 import re

18 import shlex

19 import sys

20 import traceback

21

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

23

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

25

26 __version__ = '3.0.0'

27

28 usage = """\

29 cog - generate content with inlined Python code.

30

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

32

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

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

35 other @FILELISTs.

36

37 OPTIONS:

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

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

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

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

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

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

44 -o OUTNAME Write the output to OUTNAME.

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

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

47 -r Replace the input file with the output.

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

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

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

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

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

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

54 -v Print the version of cog and exit.

55 --verbosity=VERBOSITY

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

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

58 --markers='START END END-OUTPUT'

59 The patterns surrounding cog inline instructions. Should

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

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

62 -h Print this help.

63"""

64

65# Other package modules

66 from .whiteutils import *

67

68 class CogError(Exception):

69 """ Any exception raised by Cog.

70 """

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

72 if file:

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

74 else:

75 Exception.__init__(self, msg)

76

77 class CogUsageError(CogError):

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

79 """

80 pass

81

82 class CogInternalError(CogError):

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

84 """

85 pass

86

87 class CogGeneratedError(CogError):

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

89 """

90 pass

91

92 class CogUserException(CogError):

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

94 The argument is the traceback message to print.

95 """

96 pass

97

98 class Redirectable:

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

100 """

101 def __init__(self):

102 self.stdout = sys.stdout

103 self.stderr = sys.stderr

104

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

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

107 """

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

109 self.stdout = stdout

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

111 self.stderr = stderr

112

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

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

115

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

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

118

119

120 class CogGenerator(Redirectable):

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

122 """

123 def __init__(self, options=None):

124 Redirectable.__init__(self)

125 self.markers = []

126 self.lines = []

127 self.options = options or CogOptions()

128

129 def parseMarker(self, l):

130 self.markers.append(l)

131

132 def parseLine(self, l):

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

134

135 def getCode(self):

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

137 """

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

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

140 # then remove it from all the lines.

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

142 if prefIn:

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

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

145

146 return reindentBlock(self.lines, '')

147

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

149 # figure out the right whitespace prefix for the output

150 prefOut = whitePrefix(self.markers)

151

152 intext = self.getCode()

153 if not intext:

154 return ''

155

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

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

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

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

160

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

162 cog.cogmodule.msg = self.msg

163 cog.cogmodule.out = self.out

164 cog.cogmodule.outl = self.outl

165 cog.cogmodule.error = self.error

166

167 self.outstring = ''

168 try:

169 eval(code, globals)

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

171 raise

172 except:

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

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

175 frames = find_cog_source(frames, prologue)

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

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

178 raise CogUserException(msg)

179

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

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

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

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

184 self.outstring += '\n'

185

186 return reindentBlock(self.outstring, prefOut)

187

188 def msg(self, s):

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

190

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

192 """ The cog.out function.

193 """

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

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

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

197 del lines[0]

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

199 del lines[-1]

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

201 if dedent:

202 sOut = reindentBlock(sOut)

203 self.outstring += sOut

204

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

206 """ The cog.outl function.

207 """

208 self.out(sOut, **kw)

209 self.out('\n')

210

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

212 """ The cog.error function.

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

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

215 traceback.

216 """

217 raise CogGeneratedError(msg)

218

219

220 class NumberedFileReader:

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

222 """

223 def __init__(self, f):

224 self.f = f

225 self.n = 0

226

227 def readline(self):

228 l = self.f.readline()

229 if l:

230 self.n += 1

231 return l

232

233 def linenumber(self):

234 return self.n

235

236

237 class CogOptions:

238 """ Options for a run of cog.

239 """

240 def __init__(self):

241 # Defaults for argument values.

242 self.args = []

243 self.includePath = []

244 self.defines = {}

245 self.bShowVersion = False

246 self.sMakeWritableCmd = None

247 self.bReplace = False

248 self.bNoGenerate = False

249 self.sOutputName = None

250 self.bWarnEmpty = False

251 self.bHashOutput = False

252 self.bDeleteCode = False

253 self.bEofCanBeEnd = False

254 self.sSuffix = None

255 self.bNewlines = False

256 self.sBeginSpec = '[[[cog'

257 self.sEndSpec = ']]]'

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

259 self.sEncoding = "utf-8"

260 self.verbosity = 2

261 self.sPrologue = ''

262

263 def __eq__(self, other):

264 """ Comparison operator for tests to use.

265 """

266 return self.__dict__ == other.__dict__

267

268 def clone(self):

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

270 """

271 return copy.deepcopy(self)

272

273 def addToIncludePath(self, dirs):

274 """ Add directories to the include path.

275 """

276 dirs = dirs.split(os.pathsep)

277 self.includePath.extend(dirs)

278

279 def parseArgs(self, argv):

280 # Parse the command line arguments.

281 try:

282 opts, self.args = getopt.getopt(

283 argv,

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

285 [

286 'markers=',

287 'verbosity=',

288 ]

289 )

290 except getopt.error as msg:

291 raise CogUsageError(msg)

292

293 # Handle the command line arguments.

294 for o, a in opts:

295 if o == '-c':

296 self.bHashOutput = True

297 elif o == '-d':

298 self.bDeleteCode = True

299 elif o == '-D':

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

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

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

303 self.defines[name] = value

304 elif o == '-e':

305 self.bWarnEmpty = True

306 elif o == '-I':

307 self.addToIncludePath(a)

308 elif o == '-n':

309 self.sEncoding = a

310 elif o == '-o':

311 self.sOutputName = a

312 elif o == '-r':

313 self.bReplace = True

314 elif o == '-s':

315 self.sSuffix = a

316 elif o == '-p':

317 self.sPrologue = a

318 elif o == '-U':

319 self.bNewlines = True

320 elif o == '-v':

321 self.bShowVersion = True

322 elif o == '-w':

323 self.sMakeWritableCmd = a

324 elif o == '-x':

325 self.bNoGenerate = True

326 elif o == '-z':

327 self.bEofCanBeEnd = True

328 elif o == '--markers':

329 self._parse_markers(a)

330 elif o == '--verbosity':

331 self.verbosity = int(a)

332 else:

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

334 # this is an internal error.

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

336

337 def _parse_markers(self, val):

338 try:

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

340 except ValueError:

341 raise CogUsageError(

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

343 )

344

345 def validate(self):

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

347 """

348 if self.bReplace and self.bDeleteCode:

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

350

351 if self.bReplace and self.sOutputName:

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

353

354

355 class Cog(Redirectable):

356 """ The Cog engine.

357 """

358 def __init__(self):

359 Redirectable.__init__(self)

360 self.options = CogOptions()

361 self._fixEndOutputPatterns()

362 self.cogmodulename = "cog"

363 self.installCogModule()

364

365 def _fixEndOutputPatterns(self):

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

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

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

369

370 def showWarning(self, msg):

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

372

373 def isBeginSpecLine(self, s):

374 return self.options.sBeginSpec in s

375

376 def isEndSpecLine(self, s):

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

378

379 def isEndOutputLine(self, s):

380 return self.options.sEndOutput in s

381

382 def installCogModule(self):

383 """ Magic mumbo-jumbo so that imported Python modules

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

385 """

386 self.cogmodule = imp.new_module('cog')

387 self.cogmodule.path = []

388

389 def openOutputFile(self, fname):

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

391 """

392 opts = {}

393 mode = "w"

394 if PY3:

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

396 if self.options.bNewlines:

397 if PY3:

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

399 else:

400 mode = "wb"

401 fdir = os.path.dirname(fname)

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

403 os.makedirs(fdir)

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

405

406 def openInputFile(self, fname):

407 """ Open an input file. """

408 if fname == "-":

409 return sys.stdin

410 else:

411 opts = {}

412 if PY3:

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

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

415

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

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

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

419 """

420

421 sFileIn = fname or ''

422 sFileOut = fname or ''

423 fInToClose = fOutToClose = None

424 # Convert filenames to files.

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

426 # Open the input file.

427 sFileIn = fIn

428 fIn = fInToClose = self.openInputFile(fIn)

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

430 # Open the output file.

431 sFileOut = fOut

432 fOut = fOutToClose = self.openOutputFile(fOut)

433

434 try:

435 fIn = NumberedFileReader(fIn)

436

437 bSawCog = False

438

439 self.cogmodule.inFile = sFileIn

440 self.cogmodule.outFile = sFileOut

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

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

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

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

445

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

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

448 globals = {}

449

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

451 globals.update(self.options.defines)

452

453 # loop over generator chunks

454 l = fIn.readline()

455 while l:

456 # Find the next spec begin

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

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

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

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

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

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

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

464 fOut.write(l)

465 l = fIn.readline()

466 if not l:

467 break

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

469 fOut.write(l)

470

471 # l is the begin spec

472 gen = CogGenerator(options=self.options)

473 gen.setOutput(stdout=self.stdout)

474 gen.parseMarker(l)

475 firstLineNum = fIn.linenumber()

476 self.cogmodule.firstLineNum = firstLineNum

477

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

479 # line of code inside.

480 if self.isEndSpecLine(l):

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

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

483 if beg > end:

484 raise CogError("Cog code markers inverted",

485 file=sFileIn, line=firstLineNum)

486 else:

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

488 gen.parseLine(sCode)

489 else:

490 # Deal with an ordinary code block.

491 l = fIn.readline()

492

493 # Get all the lines in the spec

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

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

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

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

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

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

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

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

502 fOut.write(l)

503 gen.parseLine(l)

504 l = fIn.readline()

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

506 raise CogError(

507 "Cog block begun but never ended.",

508 file=sFileIn, line=firstLineNum)

509

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

511 fOut.write(l)

512 gen.parseMarker(l)

513

514 l = fIn.readline()

515

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

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

518 previous = ""

519 hasher = hashlib.md5()

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

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

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

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

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

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

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

527 previous += l

528 hasher.update(to_bytes(l))

529 l = fIn.readline()

530 curHash = hasher.hexdigest()

531

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

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

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

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

536

537 # Make the previous output available to the current code

538 self.cogmodule.previous = previous

539

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

541 # supposed to generate code.

542 hasher = hashlib.md5()

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

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

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

546 sGen = self.suffixLines(sGen)

547 hasher.update(to_bytes(sGen))

548 fOut.write(sGen)

549 newHash = hasher.hexdigest()

550

551 bSawCog = True

552

553 # Write the ending output line

554 hashMatch = self.reEndOutput.search(l)

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

556 if hashMatch:

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

558 if oldHash != curHash:

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

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

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

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

563 else:

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

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

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

567 else:

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

569 # it.

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

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

572

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

574 fOut.write(l)

575 l = fIn.readline()

576

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

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

579 finally:

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

581 fInToClose.close()

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

583 fOutToClose.close()

584

585

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

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

588

589 def suffixLines(self, text):

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

591 text is many lines, as a single string.

592 """

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

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

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

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

597 return text

598

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

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

601 Return the cogged output as a string.

602 """

603 fOld = StringIO(sInput)

604 fNew = StringIO()

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

606 return fNew.getvalue()

607

608 def replaceFile(self, sOldPath, sNewText):

609 """ Replace file sOldPath with the contents sNewText

610 """

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

612 # Need to ensure we can write.

613 if self.options.sMakeWritableCmd:

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

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

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

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

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

619 else:

620 # Can't write!

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

622 f = self.openOutputFile(sOldPath)

623 f.write(sNewText)

624 f.close()

625

626 def saveIncludePath(self):

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

628 self.savedSysPath = sys.path[:]

629

630 def restoreIncludePath(self):

631 self.options.includePath = self.savedInclude

632 self.cogmodule.path = self.options.includePath

633 sys.path = self.savedSysPath

634

635 def addToIncludePath(self, includePath):

636 self.cogmodule.path.extend(includePath)

637 sys.path.extend(includePath)

638

639 def processOneFile(self, sFile):

640 """ Process one filename through cog.

641 """

642

643 self.saveIncludePath()

644 bNeedNewline = False

645

646 try:

647 self.addToIncludePath(self.options.includePath)

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

649 # push its directory onto the include path.

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

651

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

653 if self.options.sOutputName:

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

655 elif self.options.bReplace:

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

657 # but only if they differ.

658 if self.options.verbosity >= 2:

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

660 bNeedNewline = True

661

662 try:

663 fOldFile = self.openInputFile(sFile)

664 sOldText = fOldFile.read()

665 fOldFile.close()

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

667 if sOldText != sNewText:

668 if self.options.verbosity >= 1:

669 if self.options.verbosity < 2:

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

671 self.prout(" (changed)")

672 bNeedNewline = False

673 self.replaceFile(sFile, sNewText)

674 finally:

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

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

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

678 # any traceback.

679 if bNeedNewline:

680 self.prout("")

681 else:

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

683 finally:

684 self.restoreIncludePath()

685

686 def processWildcards(self, sFile):

687 files = glob.glob(sFile)

688 if files:

689 for sMatchingFile in files:

690 self.processOneFile(sMatchingFile)

691 else:

692 self.processOneFile(sFile)

693

694 def processFileList(self, sFileList):

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

696 """

697 flist = self.openInputFile(sFileList)

698 lines = flist.readlines()

699 flist.close()

700 for l in lines:

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

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

703 lex.whitespace_split = True

704 lex.commenters = '#'

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

706 lex.escape = ''

707 args = list(lex)

708 if args:

709 self.processArguments(args)

710

711 def processArguments(self, args):

712 """ Process one command-line.

713 """

714 saved_options = self.options

715 self.options = self.options.clone()

716

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

718 self.options.validate()

719

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

721 if self.options.sOutputName:

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

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

724 else:

725 self.processWildcards(args[0])

726

727 self.options = saved_options

728

729 def callableMain(self, argv):

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

731 This is used by main.

732 argv is the equivalent of sys.argv.

733 """

734 argv = argv[1:]

735

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

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

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

739 return

740

741 self.options.parseArgs(argv)

742 self.options.validate()

743 self._fixEndOutputPatterns()

744

745 if self.options.bShowVersion:

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

747 return

748

749 if self.options.args:

750 for a in self.options.args:

751 self.processArguments([a])

752 else:

753 raise CogUsageError("No files to process")

754

755 def main(self, argv):

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

757 """

758

759 try:

760 self.callableMain(argv)

761 return 0

762 except CogUsageError as err:

763 self.prerr(err)

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

765 return 2

766 except CogGeneratedError as err:

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

768 return 3

769 except CogUserException as err:

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

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

772 return 4

773 except CogError as err:

774 self.prerr(err)

775 return 1

776

777

778 def find_cog_source(frame_summary, prologue):

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

780

781 Arguments:

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

783 prologue: the text of the code prologue.

784

785 Returns

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

787

788 """

789 prolines = prologue.splitlines()

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

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

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

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

794 if lineno <= len(prolines):

795 filename = '<prologue>'

796 source = prolines[lineno-1]

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

798 else:

799 filename, coglineno = m.groups()

800 coglineno = int(coglineno)

801 lineno += coglineno - len(prolines)

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

803 yield filename, lineno, funcname, source

804

805

806 def main():

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

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