Hide keyboard shortcuts

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 

8from __future__ import absolute_import, print_function 

9 

10import copy 

11import getopt 

12import glob 

13import hashlib 

14import imp 

15import linecache 

16import os 

17import re 

18import shlex 

19import sys 

20import traceback 

21 

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

23 

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

25 

26__version__ = '3.0.0' 

27 

28usage = """\ 

29cog - generate content with inlined Python code. 

30 

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

32 

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

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

35 other @FILELISTs. 

36 

37OPTIONS: 

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 

66from .whiteutils import * 

67 

68class 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 

77class CogUsageError(CogError): 

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

79 """ 

80 pass 

81 

82class CogInternalError(CogError): 

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

84 """ 

85 pass 

86 

87class CogGeneratedError(CogError): 

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

89 """ 

90 pass 

91 

92class 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 

98class 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 

120class 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 

220class 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 

237class 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 

355class 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 

778def 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 

806def main(): 

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

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