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 linecache 

15import os 

16import re 

17import shlex 

18import sys 

19import traceback 

20 

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

22 

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

24 

25__version__ = '3.0.0' 

26 

27usage = """\ 

28cog - generate content with inlined Python code. 

29 

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

31 

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

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

34 other @FILELISTs. 

35 

36OPTIONS: 

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 

65from .whiteutils import * 

66 

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

76class CogUsageError(CogError): 

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

78 """ 

79 pass 

80 

81class CogInternalError(CogError): 

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

83 """ 

84 pass 

85 

86class CogGeneratedError(CogError): 

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

88 """ 

89 pass 

90 

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

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

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

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

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

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

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

808def main(): 

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

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