Coverage for cogapp/cogapp.py: 49.50%

Shortcuts 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

495 statements  

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.2.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 -P Use print() instead of outl for code output. 

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 real_stdout = sys.stdout 

168 if self.options.bPrintOutput: 168 ↛ 169line 168 didn't jump to line 169, because the condition on line 168 was never true

169 sys.stdout = captured_stdout = StringIO() 

170 

171 self.outstring = '' 

172 try: 

173 eval(code, globals) 

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

175 raise 

176 except: 

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

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

179 frames = find_cog_source(frames, prologue) 

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

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

182 raise CogUserException(msg) 

183 finally: 

184 sys.stdout = real_stdout 

185 

186 if self.options.bPrintOutput: 186 ↛ 187line 186 didn't jump to line 187, because the condition on line 186 was never true

187 self.outstring = captured_stdout.getvalue() 

188 

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

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

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

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

193 self.outstring += '\n' 

194 

195 return reindentBlock(self.outstring, prefOut) 

196 

197 def msg(self, s): 

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

199 

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

201 """ The cog.out function. 

202 """ 

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

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

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

206 del lines[0] 

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

208 del lines[-1] 

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

210 if dedent: 

211 sOut = reindentBlock(sOut) 

212 self.outstring += sOut 

213 

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

215 """ The cog.outl function. 

216 """ 

217 self.out(sOut, **kw) 

218 self.out('\n') 

219 

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

221 """ The cog.error function. 

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

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

224 traceback. 

225 """ 

226 raise CogGeneratedError(msg) 

227 

228 

229class NumberedFileReader: 

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

231 """ 

232 def __init__(self, f): 

233 self.f = f 

234 self.n = 0 

235 

236 def readline(self): 

237 l = self.f.readline() 

238 if l: 

239 self.n += 1 

240 return l 

241 

242 def linenumber(self): 

243 return self.n 

244 

245 

246class CogOptions: 

247 """ Options for a run of cog. 

248 """ 

249 def __init__(self): 

250 # Defaults for argument values. 

251 self.args = [] 

252 self.includePath = [] 

253 self.defines = {} 

254 self.bShowVersion = False 

255 self.sMakeWritableCmd = None 

256 self.bReplace = False 

257 self.bNoGenerate = False 

258 self.sOutputName = None 

259 self.bWarnEmpty = False 

260 self.bHashOutput = False 

261 self.bDeleteCode = False 

262 self.bEofCanBeEnd = False 

263 self.sSuffix = None 

264 self.bNewlines = False 

265 self.sBeginSpec = '[[[cog' 

266 self.sEndSpec = ']]]' 

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

268 self.sEncoding = "utf-8" 

269 self.verbosity = 2 

270 self.sPrologue = '' 

271 self.bPrintOutput = False 

272 

273 def __eq__(self, other): 

274 """ Comparison operator for tests to use. 

275 """ 

276 return self.__dict__ == other.__dict__ 

277 

278 def clone(self): 

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

280 """ 

281 return copy.deepcopy(self) 

282 

283 def addToIncludePath(self, dirs): 

284 """ Add directories to the include path. 

285 """ 

286 dirs = dirs.split(os.pathsep) 

287 self.includePath.extend(dirs) 

288 

289 def parseArgs(self, argv): 

290 # Parse the command line arguments. 

291 try: 

292 opts, self.args = getopt.getopt( 

293 argv, 

294 'cdD:eI:n:o:rs:p:PUvw:xz', 

295 [ 

296 'markers=', 

297 'verbosity=', 

298 ] 

299 ) 

300 except getopt.error as msg: 

301 raise CogUsageError(msg) 

302 

303 # Handle the command line arguments. 

304 for o, a in opts: 

305 if o == '-c': 

306 self.bHashOutput = True 

307 elif o == '-d': 

308 self.bDeleteCode = True 

309 elif o == '-D': 

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

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

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

313 self.defines[name] = value 

314 elif o == '-e': 

315 self.bWarnEmpty = True 

316 elif o == '-I': 

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

318 elif o == '-n': 

319 self.sEncoding = a 

320 elif o == '-o': 

321 self.sOutputName = a 

322 elif o == '-r': 

323 self.bReplace = True 

324 elif o == '-s': 

325 self.sSuffix = a 

326 elif o == '-p': 

327 self.sPrologue = a 

328 elif o == '-P': 

329 self.bPrintOutput = True 

330 elif o == '-U': 

331 self.bNewlines = True 

332 elif o == '-v': 

333 self.bShowVersion = True 

334 elif o == '-w': 

335 self.sMakeWritableCmd = a 

336 elif o == '-x': 

337 self.bNoGenerate = True 

338 elif o == '-z': 

339 self.bEofCanBeEnd = True 

340 elif o == '--markers': 

341 self._parse_markers(a) 

342 elif o == '--verbosity': 

343 self.verbosity = int(a) 

344 else: 

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

346 # this is an internal error. 

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

348 

349 def _parse_markers(self, val): 

350 try: 

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

352 except ValueError: 

353 raise CogUsageError( 

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

355 ) 

356 

357 def validate(self): 

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

359 """ 

360 if self.bReplace and self.bDeleteCode: 

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

362 

363 if self.bReplace and self.sOutputName: 

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

365 

366 

367class Cog(Redirectable): 

368 """ The Cog engine. 

369 """ 

370 def __init__(self): 

371 Redirectable.__init__(self) 

372 self.options = CogOptions() 

373 self._fixEndOutputPatterns() 

374 self.cogmodulename = "cog" 

375 self.createCogModule() 

376 

377 def _fixEndOutputPatterns(self): 

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

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

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

381 

382 def showWarning(self, msg): 

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

384 

385 def isBeginSpecLine(self, s): 

386 return self.options.sBeginSpec in s 

387 

388 def isEndSpecLine(self, s): 

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

390 

391 def isEndOutputLine(self, s): 

392 return self.options.sEndOutput in s 

393 

394 def createCogModule(self): 

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

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

397 """ 

398 class DummyModule(object): 

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

400 pass 

401 self.cogmodule = DummyModule() 

402 self.cogmodule.path = [] 

403 

404 def openOutputFile(self, fname): 

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

406 """ 

407 opts = {} 

408 mode = "w" 

409 if PY3: 

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

411 if self.options.bNewlines: 

412 if PY3: 

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

414 else: 

415 mode = "wb" 

416 fdir = os.path.dirname(fname) 

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

418 os.makedirs(fdir) 

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

420 

421 def openInputFile(self, fname): 

422 """ Open an input file. """ 

423 if fname == "-": 

424 return sys.stdin 

425 else: 

426 opts = {} 

427 if PY3: 

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

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

430 

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

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

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

434 """ 

435 

436 sFileIn = fname or '' 

437 sFileOut = fname or '' 

438 fInToClose = fOutToClose = None 

439 # Convert filenames to files. 

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

441 # Open the input file. 

442 sFileIn = fIn 

443 fIn = fInToClose = self.openInputFile(fIn) 

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

445 # Open the output file. 

446 sFileOut = fOut 

447 fOut = fOutToClose = self.openOutputFile(fOut) 

448 

449 try: 

450 fIn = NumberedFileReader(fIn) 

451 

452 bSawCog = False 

453 

454 self.cogmodule.inFile = sFileIn 

455 self.cogmodule.outFile = sFileOut 

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

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

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

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

460 

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

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

463 globals = {} 

464 

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

466 globals.update(self.options.defines) 

467 

468 # loop over generator chunks 

469 l = fIn.readline() 

470 while l: 

471 # Find the next spec begin 

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

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

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

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

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

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

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

479 fOut.write(l) 

480 l = fIn.readline() 

481 if not l: 

482 break 

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

484 fOut.write(l) 

485 

486 # l is the begin spec 

487 gen = CogGenerator(options=self.options) 

488 gen.setOutput(stdout=self.stdout) 

489 gen.parseMarker(l) 

490 firstLineNum = fIn.linenumber() 

491 self.cogmodule.firstLineNum = firstLineNum 

492 

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

494 # line of code inside. 

495 if self.isEndSpecLine(l): 

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

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

498 if beg > end: 

499 raise CogError("Cog code markers inverted", 

500 file=sFileIn, line=firstLineNum) 

501 else: 

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

503 gen.parseLine(sCode) 

504 else: 

505 # Deal with an ordinary code block. 

506 l = fIn.readline() 

507 

508 # Get all the lines in the spec 

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

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

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

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

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

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

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

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

517 fOut.write(l) 

518 gen.parseLine(l) 

519 l = fIn.readline() 

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

521 raise CogError( 

522 "Cog block begun but never ended.", 

523 file=sFileIn, line=firstLineNum) 

524 

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

526 fOut.write(l) 

527 gen.parseMarker(l) 

528 

529 l = fIn.readline() 

530 

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

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

533 previous = "" 

534 hasher = hashlib.md5() 

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

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

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

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

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

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

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

542 previous += l 

543 hasher.update(to_bytes(l)) 

544 l = fIn.readline() 

545 curHash = hasher.hexdigest() 

546 

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

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

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

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

551 

552 # Make the previous output available to the current code 

553 self.cogmodule.previous = previous 

554 

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

556 # supposed to generate code. 

557 hasher = hashlib.md5() 

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

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

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

561 sGen = self.suffixLines(sGen) 

562 hasher.update(to_bytes(sGen)) 

563 fOut.write(sGen) 

564 newHash = hasher.hexdigest() 

565 

566 bSawCog = True 

567 

568 # Write the ending output line 

569 hashMatch = self.reEndOutput.search(l) 

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

571 if hashMatch: 

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

573 if oldHash != curHash: 

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

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

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

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

578 else: 

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

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

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

582 else: 

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

584 # it. 

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

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

587 

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

589 fOut.write(l) 

590 l = fIn.readline() 

591 

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

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

594 finally: 

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

596 fInToClose.close() 

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

598 fOutToClose.close() 

599 

600 

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

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

603 

604 def suffixLines(self, text): 

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

606 text is many lines, as a single string. 

607 """ 

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

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

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

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

612 return text 

613 

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

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

616 Return the cogged output as a string. 

617 """ 

618 fOld = StringIO(sInput) 

619 fNew = StringIO() 

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

621 return fNew.getvalue() 

622 

623 def replaceFile(self, sOldPath, sNewText): 

624 """ Replace file sOldPath with the contents sNewText 

625 """ 

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

627 # Need to ensure we can write. 

628 if self.options.sMakeWritableCmd: 

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

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

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

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

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

634 else: 

635 # Can't write! 

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

637 f = self.openOutputFile(sOldPath) 

638 f.write(sNewText) 

639 f.close() 

640 

641 def saveIncludePath(self): 

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

643 self.savedSysPath = sys.path[:] 

644 

645 def restoreIncludePath(self): 

646 self.options.includePath = self.savedInclude 

647 self.cogmodule.path = self.options.includePath 

648 sys.path = self.savedSysPath 

649 

650 def addToIncludePath(self, includePath): 

651 self.cogmodule.path.extend(includePath) 

652 sys.path.extend(includePath) 

653 

654 def processOneFile(self, sFile): 

655 """ Process one filename through cog. 

656 """ 

657 

658 self.saveIncludePath() 

659 bNeedNewline = False 

660 

661 try: 

662 self.addToIncludePath(self.options.includePath) 

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

664 # push its directory onto the include path. 

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

666 

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

668 if self.options.sOutputName: 

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

670 elif self.options.bReplace: 

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

672 # but only if they differ. 

673 if self.options.verbosity >= 2: 

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

675 bNeedNewline = True 

676 

677 try: 

678 fOldFile = self.openInputFile(sFile) 

679 sOldText = fOldFile.read() 

680 fOldFile.close() 

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

682 if sOldText != sNewText: 

683 if self.options.verbosity >= 1: 

684 if self.options.verbosity < 2: 

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

686 self.prout(" (changed)") 

687 bNeedNewline = False 

688 self.replaceFile(sFile, sNewText) 

689 finally: 

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

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

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

693 # any traceback. 

694 if bNeedNewline: 

695 self.prout("") 

696 else: 

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

698 finally: 

699 self.restoreIncludePath() 

700 

701 def processWildcards(self, sFile): 

702 files = glob.glob(sFile) 

703 if files: 

704 for sMatchingFile in files: 

705 self.processOneFile(sMatchingFile) 

706 else: 

707 self.processOneFile(sFile) 

708 

709 def processFileList(self, sFileList): 

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

711 """ 

712 flist = self.openInputFile(sFileList) 

713 lines = flist.readlines() 

714 flist.close() 

715 for l in lines: 

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

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

718 lex.whitespace_split = True 

719 lex.commenters = '#' 

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

721 lex.escape = '' 

722 args = list(lex) 

723 if args: 

724 self.processArguments(args) 

725 

726 def processArguments(self, args): 

727 """ Process one command-line. 

728 """ 

729 saved_options = self.options 

730 self.options = self.options.clone() 

731 

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

733 self.options.validate() 

734 

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

736 if self.options.sOutputName: 

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

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

739 else: 

740 self.processWildcards(args[0]) 

741 

742 self.options = saved_options 

743 

744 def callableMain(self, argv): 

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

746 This is used by main. 

747 argv is the equivalent of sys.argv. 

748 """ 

749 argv = argv[1:] 

750 

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

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

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

754 return 

755 

756 self.options.parseArgs(argv) 

757 self.options.validate() 

758 self._fixEndOutputPatterns() 

759 

760 if self.options.bShowVersion: 

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

762 return 

763 

764 if self.options.args: 

765 for a in self.options.args: 

766 self.processArguments([a]) 

767 else: 

768 raise CogUsageError("No files to process") 

769 

770 def main(self, argv): 

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

772 """ 

773 

774 try: 

775 self.callableMain(argv) 

776 return 0 

777 except CogUsageError as err: 

778 self.prerr(err) 

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

780 return 2 

781 except CogGeneratedError as err: 

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

783 return 3 

784 except CogUserException as err: 

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

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

787 return 4 

788 except CogError as err: 

789 self.prerr(err) 

790 return 1 

791 

792 

793def find_cog_source(frame_summary, prologue): 

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

795 

796 Arguments: 

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

798 prologue: the text of the code prologue. 

799 

800 Returns 

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

802 

803 """ 

804 prolines = prologue.splitlines() 

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

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

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

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

809 if lineno <= len(prolines): 

810 filename = '<prologue>' 

811 source = prolines[lineno-1] 

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

813 else: 

814 filename, coglineno = m.groups() 

815 coglineno = int(coglineno) 

816 lineno += coglineno - len(prolines) 

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

818 yield filename, lineno, funcname, source 

819 

820 

821def main(): 

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

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