1 #!/usr/bin/env python
2 # -*- coding: Latin-1 -*-
3 """
4 #############################################################################
5 # PySourceColor.py
6 #############################################################################
7 # A python source to colorized html/css/xhtml converter.
8 # Hacked by M.E.Farmer Jr. 2004
9 # Python license
10 #############################################################################
11 # Now supports:
12 # - HTML markup does not create w3c valid html, but it works on every
13 # browser i've tried so far.(I.E.,Mozilla/Firefox,Opera,Konqueror,wxHTML).
14 # - CSS markup is w3c validated html 4.01 strict,
15 # but will not render correctly on all browsers.
16 # - XHTML markup is w3c validated xhtml 1.0 strict,
17 # like html 4.01, will not render correctly on all browsers.
18 #############################################################################
19 # Features:
20 # -Three types of markup:
21 # html (default)
22 # css/html 4.01 strict
23 # xhtml 1.0 strict
24 #
25 # -Can tokenize and colorize:
26 # 12 types of strings
27 # 2 comment types
28 # numbers
29 # operators
30 # brackets
31 # math operators
32 # class / name
33 # def / name
34 # decorator / name
35 # keywords
36 # arguments class/def/decorator
37 # text
38 # linenumbers
39 #
40 # -Eight colorschemes built-in:
41 # null
42 # mono
43 # dark (default)
44 # dark2
45 # lite
46 # idle
47 # viewcvs
48 # pythonwin
49 #
50 # -Header and footer
51 # set to '' for builtin header / footer
52 # or give path to a file containing the html
53 # you want added as header and footer
54 #
55 # -Linenumbers
56 # Supports all styles. New token is called LINE.
57 # Defaults to NAME if not defined
58 #
59 # Style options
60 # -ALL markups support these text styles:
61 # b = bold
62 # i = italic
63 # u = underline
64 # -CSS and XHTML has limited support for borders:
65 # HTML markup functions will ignore these.
66 # Optional: Border color in RGB hex
67 # Defaults to the text forecolor.
68 # #rrggbb = border color
69 # Optional: specify one type only
70 # - = dashed
71 # . = dotted
72 # s = solid
73 # d = double
74 # g = groove
75 # r = ridge
76 # n = inset
77 # o = outset
78 # You can specify multiple sides,
79 # they will all use the same style.
80 # Optional: Default is full border.
81 # v = bottom
82 # < = left
83 # > = right
84 # ^ = top
85 # NOTE: Specify the styles you want.
86 # The markups will ignore unsupported styles
87 # Also note not all browsers can show these options
88 #
89 # -All tokens default to NAME if not defined
90 # so the only absolutely critical ones to define are:
91 # NAME, ERRORTOKEN, PAGEBACKGROUND
92 #
93 #############################################################################
94 # Example usage:
95 #############################################################################
96 # # import
97 # import PySourceColor as psc
98 # psc.convert('c:/Python22/PySourceColor.py', colors=psc.idle, show=1)
99 #----------------------------------------------------------------------------
100 # # from module import *
101 # from PySourceColor import *
102 # convert('c:/Python22/Lib', colors=lite, markup="css", header='')
103 #----------------------------------------------------------------------------
104 # # How to use a custom colorscheme, and most of the 'features'
105 # from PySourceColor import *
106 # new = {
107 # ERRORTOKEN: ('bui','#FF8080',''),
108 # DECORATOR_NAME: ('s','#AACBBC',''),
109 # DECORATOR: ('n','#333333',''),
110 # NAME: ('t.<v','#1133AA','#DDFF22'),
111 # NUMBER: ('','#236676','#FF5555'),
112 # OPERATOR: ('b','#454567','#BBBB11'),
113 # MATH_OPERATOR: ('','#935623','#423afb'),
114 # BRACKETS: ('b','#ac34bf','#6457a5'),
115 # COMMENT: ('t-#0022FF','#545366','#AABBFF'),
116 # DOUBLECOMMENT: ('<l#553455','#553455','#FF00FF'),
117 # CLASS_NAME: ('m^v-','#000000','#FFFFFF'),
118 # DEF_NAME: ('l=<v','#897845','#000022'),
119 # KEYWORD: ('.b','#345345','#FFFF22'),
120 # SINGLEQUOTE: ('mn','#223344','#AADDCC'),
121 # SINGLEQUOTE_R: ('','#344522',''),
122 # SINGLEQUOTE_U: ('','#234234',''),
123 # DOUBLEQUOTE: ('m#0022FF','#334421',''),
124 # DOUBLEQUOTE_R: ('','#345345',''),
125 # DOUBLEQUOTE_U: ('','#678673',''),
126 # TRIPLESINGLEQUOTE: ('tv','#FFFFFF','#000000'),
127 # TRIPLESINGLEQUOTE_R: ('tbu','#443256','#DDFFDA'),
128 # TRIPLESINGLEQUOTE_U: ('','#423454','#DDFFDA'),
129 # TRIPLEDOUBLEQUOTE: ('li#236fd3b<>','#000000','#FFFFFF'),
130 # TRIPLEDOUBLEQUOTE_R: ('tub','#000000','#FFFFFF'),
131 # TRIPLEDOUBLEQUOTE_U: ('-', '#CCAABB','#FFFAFF'),
132 # LINE: ('ib-','#ff66aa','#7733FF'),
133 # PAGEBACKGROUND: '#FFFAAA',
134 # }
135 # if __name__ == '__main__':
136 # import sys
137 # convert(sys.argv[1], './css.html',colors=new, markup='xhtml', show=1,
138 # linenumbers=1)
139 # convert(sys.argv[1], './html.html',colors=new, markup='html', show=1,
140 # linenumbers=1)
141 #############################################################################
142 """
143
144 __all__ = ['ERRORTOKEN','DECORATOR_NAME', 'DECORATOR', 'ARGS',
145 'NAME', 'NUMBER', 'OPERATOR', 'COMMENT', 'MATH_OPERATOR',
146 'DOUBLECOMMENT', 'CLASS_NAME', 'DEF_NAME', 'KEYWORD', 'BRACKETS',
147 'SINGLEQUOTE','SINGLEQUOTE_R','SINGLEQUOTE_U','DOUBLEQUOTE',
148 'DOUBLEQUOTE_R', 'DOUBLEQUOTE_U', 'TRIPLESINGLEQUOTE',
149 'TRIPLESINGLEQUOTE_R', 'TRIPLESINGLEQUOTE_U', 'TRIPLEDOUBLEQUOTE',
150 'TRIPLEDOUBLEQUOTE_R', 'TRIPLEDOUBLEQUOTE_U', 'PAGEBACKGROUND', 'LINE',
151 'null', 'mono', 'lite', 'dark','dark2', 'pythonwin','idle',
152 'viewcvs', 'Usage', 'cli', 'str2stdout', 'path2stdout', 'Parser',
153 'str2file', 'str2html', 'str2css', 'path2file', 'path2html',
154 'convert', 'walkdir', 'defaultColors', 'showpage']
155 __title__ = 'PySourceColor'
156 __version__ = "1.9.97"
157 __date__ = '17 December 2004'
158 __author__ = "M.E.Farmer Jr."
159 __credits__ = '''This was originally based on a python recipe
160 submitted by Jürgen Hermann to ASPN. Now based on the voices in my head.
161 M.E.Farmer 2004
162 Python license
163 '''
164 import os
165 import cgi
166 import sys
167 import time
168 import glob
169 import getopt
170 import keyword
171 import token
172 import tokenize
173 import cStringIO
174 import traceback
175 import webbrowser
176
177 # Do not edit
178 NAME = token.NAME
179 NUMBER = token.NUMBER
180 COMMENT = tokenize.COMMENT
181 OPERATOR = token.OP
182 ERRORTOKEN = token.ERRORTOKEN
183 ARGS = token.NT_OFFSET + 1
184 DOUBLECOMMENT = token.NT_OFFSET + 2
185 CLASS_NAME = token.NT_OFFSET + 3
186 DEF_NAME = token.NT_OFFSET + 4
187 KEYWORD = token.NT_OFFSET + 5
188 SINGLEQUOTE = token.NT_OFFSET + 6
189 SINGLEQUOTE_R = token.NT_OFFSET + 7
190 SINGLEQUOTE_U = token.NT_OFFSET + 8
191 DOUBLEQUOTE = token.NT_OFFSET + 9
192 DOUBLEQUOTE_R = token.NT_OFFSET + 10
193 DOUBLEQUOTE_U = token.NT_OFFSET + 11
194 TRIPLESINGLEQUOTE = token.NT_OFFSET + 12
195 TRIPLESINGLEQUOTE_R = token.NT_OFFSET + 13
196 TRIPLESINGLEQUOTE_U = token.NT_OFFSET + 14
197 TRIPLEDOUBLEQUOTE = token.NT_OFFSET + 15
198 TRIPLEDOUBLEQUOTE_R = token.NT_OFFSET + 16
199 TRIPLEDOUBLEQUOTE_U = token.NT_OFFSET + 17
200 PAGEBACKGROUND = token.NT_OFFSET + 18
201 DECORATOR = token.NT_OFFSET + 19
202 DECORATOR_NAME = token.NT_OFFSET + 20
203 BRACKETS = token.NT_OFFSET + 21
204 MATH_OPERATOR = token.NT_OFFSET + 22
205 LINE = token.NT_OFFSET + 23
206
207 # Do not edit (markup classname lookup)
208 MARKUPDICT = {
209 ERRORTOKEN: 'err',
210 DECORATOR_NAME: 'decn',
211 DECORATOR: 'dec',
212 ARGS: 'args',
213 NAME: 'name',
214 NUMBER: 'num',
215 OPERATOR: 'op',
216 COMMENT: 'com',
217 DOUBLECOMMENT: 'dcom',
218 CLASS_NAME: 'clsn',
219 DEF_NAME: 'defn',
220 KEYWORD: 'key',
221 SINGLEQUOTE: 'sq',
222 SINGLEQUOTE_R: 'sqr',
223 SINGLEQUOTE_U: 'squ',
224 DOUBLEQUOTE: 'dq',
225 DOUBLEQUOTE_R: 'dqr',
226 DOUBLEQUOTE_U: 'dqu',
227 TRIPLESINGLEQUOTE: 'tsq',
228 TRIPLESINGLEQUOTE_R: 'tsqr',
229 TRIPLESINGLEQUOTE_U: 'tsqu',
230 TRIPLEDOUBLEQUOTE: 'tdq',
231 TRIPLEDOUBLEQUOTE_R: 'tdqr',
232 TRIPLEDOUBLEQUOTE_U: 'tdqu',
233 BRACKETS: 'bra',
234 MATH_OPERATOR: 'mop',
235 LINE: 'line'
236 }
237
238 ######################################################################
239 # Edit colors and styles to taste
240 # Create your own scheme, just copy one below , rename and edit.
241 # Custom styles must at least define NAME, ERRORTOKEN, PAGEBACKGROUND,
242 # all missing elements will default to NAME.
243 # See module docstring for details on style attributes.
244 ######################################################################
245 # Copy null and use it as a starter colorscheme.
246 null = {# tokentype: ('tags border_color', 'textforecolor', 'textbackcolor')
247 ERRORTOKEN: ('','#FF8080',''),# Error token
248 DECORATOR_NAME: ('','#000000',''),# Decorator name
249 DECORATOR: ('','#000000',''),# @ symbol
250 ARGS: ('','#000000',''),# class,def,deco arguments
251 NAME: ('','#000000',''),# All other text
252 NUMBER: ('','#000000',''),# 0->10
253 OPERATOR: ('','#000000',''),# ':','`',';',',','.','='
254 MATH_OPERATOR: ('','#000000',''),# '+','-','==','!=','*',etc
255 BRACKETS: ('','#000000',''),# '[',']','(',')','{','}'
256 COMMENT: ('','#000000',''),# Single comment
257 DOUBLECOMMENT: ('','#000000',''),## Double comment
258 CLASS_NAME: ('','#000000',''),# Class name
259 DEF_NAME: ('','#000000',''),# Def name
260 KEYWORD: ('','#000000',''),# Python keywords
261 SINGLEQUOTE: ('','#000000',''),# 'SINGLEQUOTE'
262 SINGLEQUOTE_R: ('','#000000',''),# r'SINGLEQUOTE'
263 SINGLEQUOTE_U: ('','#000000',''),# u'SINGLEQUOTE'
264 DOUBLEQUOTE: ('','#000000',''),# "DOUBLEQUOTE"
265 DOUBLEQUOTE_R: ('','#000000',''),# r"DOUBLEQUOTE"
266 DOUBLEQUOTE_U: ('','#000000',''),# u"DOUBLEQUOTE"
267 TRIPLESINGLEQUOTE: ('','#000000',''),# '''TRIPLESINGLEQUOTE'''
268 TRIPLESINGLEQUOTE_R: ('','#000000',''),# r'''TRIPLESINGLEQUOTE'''
269 TRIPLESINGLEQUOTE_U: ('','#000000',''),# u'''TRIPLESINGLEQUOTE'''
270 TRIPLEDOUBLEQUOTE: ('','#000000',''),# """TRIPLEDOUBLEQUOTE"""
271 TRIPLEDOUBLEQUOTE_R: ('','#000000',''),# r"""TRIPLEDOUBLEQUOTE"""
272 TRIPLEDOUBLEQUOTE_U: ('','#000000',''),# u"""TRIPLEDOUBLEQUOTE"""
273 PAGEBACKGROUND: '#FFFFFF'# set the page background
274 }
275
276 mono = {
277 ERRORTOKEN: ('','#FF8080',''),
278 DECORATOR_NAME: ('bu','#000000',''),
279 DECORATOR: ('b','#000000',''),
280 ARGS: ('b','#000000',''),
281 NAME: ('','#000000',''),
282 NUMBER: ('b','#000000',''),
283 OPERATOR: ('b','#000000',''),
284 MATH_OPERATOR: ('b','#000000',''),
285 BRACKETS: ('b','#000000',''),
286 COMMENT: ('i','#000000',''),
287 DOUBLECOMMENT: ('b','#000000',''),
288 CLASS_NAME: ('bu','#000000',''),
289 DEF_NAME: ('b','#000000',''),
290 KEYWORD: ('b','#000000',''),
291 SINGLEQUOTE: ('','#000000',''),
292 SINGLEQUOTE_R: ('','#000000',''),
293 SINGLEQUOTE_U: ('','#000000',''),
294 DOUBLEQUOTE: ('','#000000',''),
295 DOUBLEQUOTE_R: ('','#000000',''),
296 DOUBLEQUOTE_U: ('','#000000',''),
297 TRIPLESINGLEQUOTE: ('','#000000',''),
298 TRIPLESINGLEQUOTE_R: ('','#000000',''),
299 TRIPLESINGLEQUOTE_U: ('','#000000',''),
300 TRIPLEDOUBLEQUOTE: ('i','#000000',''),
301 TRIPLEDOUBLEQUOTE_R: ('i','#000000',''),
302 TRIPLEDOUBLEQUOTE_U: ('i','#000000',''),
303 PAGEBACKGROUND: '#FFFFFF'
304 }
305
306 dark = {
307 ERRORTOKEN: ('','#FF0000',''),
308 DECORATOR_NAME: ('b','#FFBBAA',''),
309 DECORATOR: ('b','#CC5511',''),
310 ARGS: ('b','#CCCCEE',''),
311 NAME: ('','#FFFFFF',''),
312 NUMBER: ('','#FF0000',''),
313 OPERATOR: ('b','#FAF785',''),
314 MATH_OPERATOR: ('b','#FAF785',''),
315 BRACKETS: ('b','#FAF785',''),
316 COMMENT: ('','#45FCA0',''),
317 DOUBLECOMMENT: ('i','#A7C7A9',''),
318 CLASS_NAME: ('b','#B599FD',''),
319 DEF_NAME: ('b','#EBAE5C',''),
320 KEYWORD: ('b','#8680FF',''),
321 SINGLEQUOTE: ('','#F8BAFE',''),
322 SINGLEQUOTE_R: ('','#F8BAFE',''),
323 SINGLEQUOTE_U: ('','#F8BAFE',''),
324 DOUBLEQUOTE: ('','#FF80C0',''),
325 DOUBLEQUOTE_R: ('','#FF80C0',''),
326 DOUBLEQUOTE_U: ('','#FF80C0',''),
327 TRIPLESINGLEQUOTE: ('','#FF9595',''),
328 TRIPLESINGLEQUOTE_R: ('','#FF9595',''),
329 TRIPLESINGLEQUOTE_U: ('','#FF9595',''),
330 TRIPLEDOUBLEQUOTE: ('','#B3FFFF',''),
331 TRIPLEDOUBLEQUOTE_R: ('','#B3FFFF',''),
332 TRIPLEDOUBLEQUOTE_U: ('','#B3FFFF',''),
333 LINE: ('>mi#555555','#bbccbb','#333333'),
334 PAGEBACKGROUND: '#000000'
335 }
336
337 dark2 = {
338 ERRORTOKEN: ('','#FF0000',''),
339 DECORATOR_NAME: ('b','#FFBBAA',''),
340 DECORATOR: ('b','#CC5511',''),
341 ARGS: ('b','#EEEEEE',''),
342 NAME: ('','#C0C0C0',''),
343 NUMBER: ('b','#00FF00',''),
344 OPERATOR: ('b','#FF090F',''),
345 MATH_OPERATOR: ('b','#F07040',''),
346 BRACKETS: ('b','#FFB90F',''),
347 COMMENT: ('i','#D0D709','#622000'),
348 DOUBLECOMMENT: ('i','#D0D709','#622000'),
349 CLASS_NAME: ('','#7E58C7',''),
350 DEF_NAME: ('b','#FF8040',''),
351 KEYWORD: ('b','#4726E1',''),
352 SINGLEQUOTE: ('','#8080C0',''),
353 SINGLEQUOTE_R: ('','#8080C0',''),
354 SINGLEQUOTE_U: ('','#8080C0',''),
355 DOUBLEQUOTE: ('','#ADB9F1',''),
356 DOUBLEQUOTE_R: ('','#ADB9F1',''),
357 DOUBLEQUOTE_U: ('','#ADB9F1',''),
358 TRIPLESINGLEQUOTE: ('','#00C1C1',''),
359 TRIPLESINGLEQUOTE_R: ('','#00C1C1',''),
360 TRIPLESINGLEQUOTE_U: ('','#00C1C1',''),
361 TRIPLEDOUBLEQUOTE: ('','#33e3e3',''),
362 TRIPLEDOUBLEQUOTE_R: ('','#33e3e3',''),
363 TRIPLEDOUBLEQUOTE_U: ('','#33e3e3',''),
364 LINE: ('>mi#555555','#bbccbb','#333333'),
365 PAGEBACKGROUND: '#000000'
366 }
367
368 lite = {
369 ERRORTOKEN: ('','#FF8080',''),
370 DECORATOR_NAME: ('b','#BB4422',''),
371 DECORATOR: ('b','#3333af',''),
372 ARGS: ('b','#000000',''),
373 NAME: ('','#000000',''),
374 NUMBER: ('','#FF2200',''),
375 OPERATOR: ('b','#303000',''),
376 MATH_OPERATOR: ('b','#303000',''),
377 BRACKETS: ('b','#303000',''),
378 COMMENT: ('','#007F00',''),
379 DOUBLECOMMENT: ('','#606060',''),
380 CLASS_NAME: ('','#0000FF',''),
381 DEF_NAME: ('b','#9C7A00',''),
382 KEYWORD: ('b','#0000AF',''),
383 SINGLEQUOTE: ('','#600080',''),
384 SINGLEQUOTE_R: ('','#600080',''),
385 SINGLEQUOTE_U: ('','#600080',''),
386 DOUBLEQUOTE: ('','#A0008A',''),
387 DOUBLEQUOTE_R: ('','#A0008A',''),
388 DOUBLEQUOTE_U: ('','#A0008A',''),
389 TRIPLESINGLEQUOTE: ('','#337799',''),
390 TRIPLESINGLEQUOTE_R: ('','#337799',''),
391 TRIPLESINGLEQUOTE_U: ('','#337799',''),
392 TRIPLEDOUBLEQUOTE: ('','#1166AA',''),
393 TRIPLEDOUBLEQUOTE_R: ('','#1166AA',''),
394 TRIPLEDOUBLEQUOTE_U: ('','#1166AA',''),
395 PAGEBACKGROUND: '#FFFFFF'
396 }
397
398 idle = {
399 ERRORTOKEN: ('','#FF8080',''),
400 DECORATOR_NAME: ('','#900090',''),
401 DECORATOR: ('','#000000',''),
402 NAME: ('','#000000',''),
403 NUMBER: ('','#000000',''),
404 OPERATOR: ('','#000000',''),
405 MATH_OPERATOR: ('','#000000',''),
406 BRACKETS: ('','#000000',''),
407 COMMENT: ('','#DD0000',''),
408 DOUBLECOMMENT: ('','#DD0000',''),
409 CLASS_NAME: ('','#0000FF',''),
410 DEF_NAME: ('','#0000FF',''),
411 KEYWORD: ('','#FF7700',''),
412 SINGLEQUOTE: ('','#00AA00',''),
413 SINGLEQUOTE_R: ('','#00AA00',''),
414 SINGLEQUOTE_U: ('','#00AA00',''),
415 DOUBLEQUOTE: ('','#00AA00',''),
416 DOUBLEQUOTE_R: ('','#00AA00',''),
417 DOUBLEQUOTE_U: ('','#00AA00',''),
418 TRIPLESINGLEQUOTE: ('','#00AA00',''),
419 TRIPLESINGLEQUOTE_R: ('','#00AA00',''),
420 TRIPLESINGLEQUOTE_U: ('','#00AA00',''),
421 TRIPLEDOUBLEQUOTE: ('','#00AA00',''),
422 TRIPLEDOUBLEQUOTE_R: ('','#00AA00',''),
423 TRIPLEDOUBLEQUOTE_U: ('','#00AA00',''),
424 PAGEBACKGROUND: '#FFFFFF'
425 }
426
427 pythonwin = {
428 ERRORTOKEN: ('','#FF8080',''),
429 DECORATOR_NAME: ('b','#303030',''),
430 DECORATOR: ('b','#DD0080',''),
431 ARGS: ('','#000000',''),
432 NAME: ('','#303030',''),
433 NUMBER: ('','#008080',''),
434 OPERATOR: ('','#000000',''),
435 MATH_OPERATOR: ('','#000000',''),
436 BRACKETS: ('','#000000',''),
437 COMMENT: ('','#007F00',''),
438 DOUBLECOMMENT: ('','#7F7F7F',''),
439 CLASS_NAME: ('b','#0000FF',''),
440 DEF_NAME: ('b','#007F7F',''),
441 KEYWORD: ('b','#000080',''),
442 SINGLEQUOTE: ('','#808000',''),
443 SINGLEQUOTE_R: ('','#808000',''),
444 SINGLEQUOTE_U: ('','#808000',''),
445 DOUBLEQUOTE: ('','#808000',''),
446 DOUBLEQUOTE_R: ('','#808000',''),
447 DOUBLEQUOTE_U: ('','#808000',''),
448 TRIPLESINGLEQUOTE: ('','#808000',''),
449 TRIPLESINGLEQUOTE_R: ('','#808000',''),
450 TRIPLESINGLEQUOTE_U: ('','#808000',''),
451 TRIPLEDOUBLEQUOTE: ('','#808000',''),
452 TRIPLEDOUBLEQUOTE_R: ('','#808000',''),
453 TRIPLEDOUBLEQUOTE_U: ('','#808000',''),
454 PAGEBACKGROUND: '#FFFFFF'
455 }
456
457 viewcvs = {
458 ERRORTOKEN: ('b','#FF8080',''),
459 DECORATOR_NAME: ('','#000000',''),
460 DECORATOR: ('','#000000',''),
461 ARGS: ('','#000000',''),
462 NAME: ('','#000000',''),
463 NUMBER: ('','#000000',''),
464 OPERATOR: ('','#000000',''),
465 MATH_OPERATOR: ('','#000000',''),
466 BRACKETS: ('','#000000',''),
467 COMMENT: ('i','#b22222',''),
468 DOUBLECOMMENT: ('i','#b22222',''),
469 CLASS_NAME: ('','#000000',''),
470 DEF_NAME: ('b','#0000ff',''),
471 KEYWORD: ('b','#a020f0',''),
472 SINGLEQUOTE: ('b','#bc8f8f',''),
473 SINGLEQUOTE_R: ('b','#bc8f8f',''),
474 SINGLEQUOTE_U: ('b','#bc8f8f',''),
475 DOUBLEQUOTE: ('b','#bc8f8f',''),
476 DOUBLEQUOTE_R: ('b','#bc8f8f',''),
477 DOUBLEQUOTE_U: ('b','#bc8f8f',''),
478 TRIPLESINGLEQUOTE: ('b','#bc8f8f',''),
479 TRIPLESINGLEQUOTE_R: ('b','#bc8f8f',''),
480 TRIPLESINGLEQUOTE_U: ('b','#bc8f8f',''),
481 TRIPLEDOUBLEQUOTE: ('b','#bc8f8f',''),
482 TRIPLEDOUBLEQUOTE_R: ('b','#bc8f8f',''),
483 TRIPLEDOUBLEQUOTE_U: ('b','#bc8f8f',''),
484 PAGEBACKGROUND: '#FFFFFF'
485 }
486
487 defaultColors = dark
488
489 def Usage():
490 """
491 -----------------------------------------------------------------------------
492 PySourceColor.py ver: %s
493 -----------------------------------------------------------------------------
494 Module summary:
495 This module is designed to colorize python source code.
496 Input python source
497 Output colorized html, css, xhtml
498 Standalone:
499 This module will work from the command line with options.
500 This module will work with redirected stdio.
501 Imported:
502 This module can be imported and used directly in your code.
503 -----------------------------------------------------------------------------
504 Command line options:
505 -h, --help
506 Optional-> Display this help message.
507 -t, --test
508 Optional-> Will ignore all others flags but --profile
509 test all schemes and markup combinations
510 -p, --profile
511 Optional-> Works only with --test or -t
512 runs profile.py and makes the test work in quiet mode.
513 -i, --in, --input
514 Use any of these for the current dir (.,cwd)
515 Input can be file or dir.
516 Input from stdin use one of the following (-,stdin)
517 If stdin is used as input stdout is output unless specified.
518 -o, --out, --output
519 Optional-> output dir for the colorized source.
520 default: output dir is the input dir.
521 To output html to stdout use one of the following (-,stdout)
522 Stdout can be used without stdin if you give a file as input.
523 -c, --color
524 Optional-> null, mono, dark, dark2, lite, idle, pythonwin, viewcvs
525 default: dark
526 -s, --show
527 Optional-> Show webpage after creation.
528 default: no show
529 -m, --markup
530 Optional-> html, css, xhtml
531 css, xhtml also support external stylesheets (-e,--external)
532 default: HTML
533 -e, --external
534 Optional-> use with css, xhtml
535 Writes an style sheet instead of embedding it in the page
536 saves it as style.css in the same directory.
537 html markup will silently ignore this flag.
538 -H, --header
539 Opional-> add a page header to the top of the output
540 -H
541 Builtin header (name,date,hrule)
542 --header
543 You must specify a filename.
544 The header file must be valid html
545 and must handle its own font colors.
546 ex. --header c:/tmp/header.txt
547 -F, --footer
548 Opional-> add a page footer to the bottom of the output
549 -F
550 Builtin footer (hrule,name,date)
551 --footer
552 You must specify a filename.
553 The footer file must be valid html
554 and must handle its own font colors.
555 ex. --footer c:/tmp/footer.txt
556 -l, --linenumbers
557 Optional-> default is no linenumbers
558 Adds line numbers to the start of each line in the code.
559
560 -----------------------------------------------------------------------------
561 Option usage:
562 # Test and show pages
563 python PySourceColor.py -t -s
564 # Test and only show profile results
565 python PySAourceColor.py -t -p
566 # Colorize all .py,.pyw files in cwdir you can also use: (.,cwd)
567 python PySourceColor.py -i .
568 # Using long options w/ =
569 python PySourceColor.py --in=c:/myDir/my.py --color=lite --show
570 # Using short options w/out =
571 python PySourceColor.py -i c:/myDir/ -c idle -m css -e
572 # Using any mix
573 python PySourceColor.py --in . -o=c:/myDir --show
574 # Place a custom header on your files
575 python PySourceColor.py -i . -o c:/tmp -m xhtml --header c:/header.txt
576 -----------------------------------------------------------------------------
577 Stdio usage:
578 # Stdio using no options
579 python PySourceColor.py < c:/MyFile.py >> c:/tmp/MyFile.html
580 # Using stdin alone automatically uses stdout for output: (stdin,-)
581 python PySourceColor.py -i- < c:/MyFile.py >> c:/tmp/myfile.html
582 # Stdout can also be written to directly from a file instead of stdin
583 python PySourceColor.py -i c:/MyFile.py -m css -o- >> c:/tmp/myfile.html
584 # Stdin can be used as input , but output can still be specified
585 python PySourceColor.py -i- -o c:/pydoc.py.html -s < c:/Python22/my.py
586 _____________________________________________________________________________
587 """
588 print Usage.__doc__% (__version__)
589 sys.exit(1)
590
591 ###################################################### Command line interface
592
593 def cli():
594 """Handle command line args and redirections"""
595 try:
596 # try to get command line args
597 opts, args = getopt.getopt(sys.argv[1:],
598 "hseqtplHFi:o:c:m:h:f:",["help", "show", "quiet", "profile",
599 "test", "external", "linenumbers", "input=", "output=",
600 "color=", "markup=","header=", "footer="])
601 except getopt.GetoptError:
602 # on error print help information and exit:
603 Usage()
604 # init some names
605 input = None
606 output = None
607 colorscheme = None
608 markup = 'html'
609 header = None
610 footer = None
611 linenumbers = 0
612 show = 0
613 quiet = 0
614 test = 0
615 profile = 0
616 form = None
617 # if we have args then process them
618 for o, a in opts:
619 if o in ["-h", "--help"]:
620 Usage()
621 sys.exit()
622 if o in ["-o", "--output", "--out"]:
623 output = a
624 if o in ["-i", "--input", "--in"]:
625 input = a
626 if input in [".", "cwd"]:
627 input = os.getcwd()
628 if o in ["-s", "--show"]:
629 show = 1
630 if o in ["-q", "--quiet"]:
631 quiet = 1
632 if o in ["-t", "--test"]:
633 test = 1
634 if o in ["-p", "--profile"]:
635 profile = 1
636 if o in ["-e", "--external"]:
637 form = 'external'
638 if o in ["-m", "--markup"]:
639 markup = str(a)
640 if o in ["-l", "--linenumbers"]:
641 linenumbers = 1
642 if o in ["--header"]:
643 header = str(a)
644 elif o == "-H":
645 header = ''
646 if o in ["--footer"]:
647 footer = str(a)
648 elif o == "-F":
649 footer = ''
650 if o in ["-c", "--color"]:
651 try:
652 colorscheme = globals().get(a.lower())
653 except:
654 traceback.print_exc()
655 Usage()
656 if test:
657 if profile:
658 import profile
659 profile.run('_test(show=%s, quiet=%s)'%(show,quiet))
660 else:
661 # Parse this script in every possible colorscheme and markup
662 _test(show,quiet)
663 elif input in [None, "-", "stdin"] or output in ["-", "stdout"]:
664 # determine if we are going to use stdio
665 if input not in [None, "-", "stdin"]:
666 if os.path.isfile(input) :
667 path2stdout(input, colors=colorscheme, markup=markup,
668 linenumbers=linenumbers, header=header,
669 footer=footer, form=form)
670 else:
671 raise PathError, 'File does not exists!'
672 else:
673 try:
674 if sys.stdin.isatty():
675 raise InputError, 'Please check input!'
676 else:
677 if output in [None,"-","stdout"]:
678 str2stdout(sys.stdin.read(), colors=colorscheme,
679 markup=markup, header=header,
680 footer=footer, linenumbers=linenumbers,
681 form=form)
682 else:
683 str2file(sys.stdin.read(), outfile=output, show=show,
684 markup=markup, header=header, footer=footer,
685 linenumbers=linenumbers, form=form)
686 except:
687 traceback.print_exc()
688 Usage()
689 else:
690 if os.path.exists(input):
691 # if there was at least an input given we can proceed
692 convert(source=input, outdir=output, colors=colorscheme,
693 show=show, markup=markup, quiet=quiet, header=header,
694 footer=footer, linenumbers=linenumbers, form=form)
695 else:
696 raise PathError, 'File does not exists!'
697 Usage()
698
699 ######################################################### Simple markup tests
700
701 def _test(show=0, quiet=0):
702 """Test the parser and most of the functions.
703
704 There are 15 _test total(seven colorschemes in two diffrent markups,
705 and a str2file test. Most functions are tested by this.
706 """
707 fi = sys.argv[0]
708 if not fi.endswith('.exe'):# Do not test if frozen as an archive
709 # this is a collection of test, most things are covered.
710 path2file(fi, '/tmp/null.html', null, show=show, quiet=quiet)
711 path2file(fi, '/tmp/null_css.html', null, show=show,
712 markup='css', quiet=quiet)
713 path2file(fi, '/tmp/mono.html', mono, show=show, quiet=quiet)
714 path2file(fi, '/tmp/mono_css.html', mono, show=show,
715 markup='css', quiet=quiet)
716 path2file(fi, '/tmp/lite.html', lite, show=show, quiet=quiet)
717 path2file(fi, '/tmp/lite_css.html', lite, show=show,
718 markup='css', quiet=quiet, header='', footer='',
719 linenumbers=1)
720 path2file(fi, '/tmp/lite_xhtml.html', lite, show=show,
721 markup='xhtml', quiet=quiet)
722 path2file(fi, '/tmp/dark.html', dark, show=show, quiet=quiet)
723 path2file(fi, '/tmp/dark_css.html', dark, show=show,
724 markup='css', quiet=quiet, linenumbers=1)
725 path2file(fi, '/tmp/dark2.html', dark2, show=show, quiet=quiet)
726 path2file(fi, '/tmp/dark2_css.html', dark2, show=show,
727 markup='css', quiet=quiet)
728 path2file(fi, '/tmp/dark2_xhtml.html', dark2, show=show,
729 markup='xhtml', quiet=quiet, header='', footer='',
730 linenumbers=1, form='external')
731 path2file(fi, '/tmp/idle.html', idle, show=show, quiet=quiet)
732 path2file(fi, '/tmp/idle_css.html', idle, show=show,
733 markup='css', quiet=quiet)
734 path2file(fi, '/tmp/viewcvs.html', viewcvs, show=show,
735 quiet=quiet, linenumbers=1)
736 path2file(fi, '/tmp/viewcvs_css.html', viewcvs, show=show,
737 markup='css', quiet=quiet)
738 path2file(fi, '/tmp/pythonwin.html', pythonwin, show=show,
739 quiet=quiet)
740 path2file(fi, '/tmp/pythonwin_css.html', pythonwin, show=show,
741 markup='css', quiet=quiet)
742 teststr=r'''"""This is a test of decorators and other things"""
743 # This should be line 421...
744 @whatever(arg,arg2)
745 @A @B(arghh) @C
746 def LlamaSaysNi(arg='Ni!',arg2="RALPH"):
747 """This docstring is deeply disturbed by all the llama references"""
748 print '%s The Wonder Llama says %s'% (arg2,arg)
749 # So I was like duh!, and he was like ya know?!,
750 # and so we were both like huh...wtf!? RTFM!! LOL!!;)
751 @staticmethod## Double comments are KewL.
752 def LlamasRLumpy():
753 """This docstring is too sexy to be here.
754 """
755 u"""
756 =============================
757 A Møøse once bit my sister...
758 =============================
759 """
760 ## Relax, this won't hurt a bit, just a simple, painless procedure,
761 ## hold still while I get the anesthetizing hammer.
762 m = {'three':'1','won':'2','too':'3'}
763 o = r'fishy\fishy\fishy/fish\oh/where/is\my/little\..'
764 python = uR"""
765 No realli! She was Karving her initials øn the møøse with the sharpened end
766 of an interspace tøøthbrush given her by Svenge - her brother-in-law -an Oslo
767 dentist and star of many Norwegian møvies: "The Høt Hands of an Oslo
768 Dentist", "Fillings of Passion", "The Huge Mølars of Horst Nordfink"..."""
769 RU"""142 MEXICAN WHOOPING LLAMAS"""#<-Can you fit 142 llamas in a red box?
770 n = u' HERMSGERVØRDENBRØTBØRDA ' + """ YUTTE """
771 t = """SAMALLNIATNUOMNAIRODAUCE"""+"DENIARTYLLAICEPS04"
772 ## We apologise for the fault in the
773 ## comments. Those responsible have been
774 ## sacked.
775 y = '14 NORTH CHILEAN GUANACOS \
776 (CLOSELY RELATED TO THE LLAMA)'
777 rules = [0,1,2,3,4,5]
778 print y'''
779 htmlPath = os.path.abspath('/tmp/strtest.html')
780 str2file(teststr, htmlPath, colors=dark, markup='xhtml',
781 linenumbers=420, show=show)
782 _printinfo(" wrote %s" % htmlPath, quiet)
783 else:
784 Usage()
785 return
786 ####################################################### User level funtctions
787
788 def str2stdout(sourcestring, colors=None, markup='html',
789 header=None, footer=None,
790 linenumbers=0, form=None):
791 """Converts a code(string) to colorized HTML. Writes to stdout.
792
793 form='code',or'snip' (for "<pre>yourcode</pre>" only)
794 colors=null,mono,lite,dark,dark2,idle,or pythonwin
795 """
796 Parser(sourcestring, colors, markup=markup,
797 header=header, footer=footer,
798 linenumbers=linenumbers).format(form)
799
800 def path2stdout(sourcepath, colors=None, markup='html',
801 header=None, footer=None,
802 linenumbers=0, form=None):
803 """Converts code(file) to colorized HTML. Writes to stdout.
804
805 form='code',or'snip' (for "<pre>yourcode</pre>" only)
806 colors=null,mono,lite,dark,dark2,idle,or pythonwin
807 """
808 sourcestring = open(sourcepath).read()
809 Parser(sourcestring, colors=colors, title=sourcepath, markup=markup,
810 header=header, footer=footer,
811 linenumbers=linenumbers).format(form)
812
813 def str2html(sourcestring, colors=None, markup='html',
814 header=None, footer=None,
815 linenumbers=0, form=None):
816 """Converts a code(string) to colorized HTML. Returns an HTML string.
817
818 form='code',or'snip' (for "<pre>yourcode</pre>" only)
819 colors=null,mono,lite,dark,dark2,idle,or pythonwin
820 """
821 stringIO = cStringIO.StringIO()
822 Parser(sourcestring, colors=colors, out=stringIO, markup=markup,
823 header=header, footer=footer,
824 linenumbers=linenumbers).format(form)
825 stringIO.seek(0)
826 return stringIO.read()
827
828 def str2css(sourcestring, colors=None, header=None, footer=None,
829 linenumbers=0, form=None):
830 """Converts a code string to colorized CSS/HTML. Returns CSS/HTML string
831
832 If form is specified then this will return (stylesheet, code)
833 colors=null,mono,lite,dark,dark2,idle,or pythonwin
834 """
835 stringIO = cStringIO.StringIO()
836 parse = Parser(sourcestring, colors, out=stringIO, markup='css',
837 header=header, footer=footer,
838 linenumbers=linenumbers)
839 parse.format(form)
840 stringIO.seek(0)
841 if form != None:
842 return parse._sendCSSStyle(external=1), stringIO.read()
843 else:
844 return stringIO.read()
845
846 def str2file(sourcestring, outfile, colors=None, markup='html',
847 header=None, footer=None,
848 linenumbers=0, show=0):
849 """Converts a code string to a file.
850
851 makes no attempt at correcting bad pathnames
852 """
853 html = str2html(sourcestring, colors, markup=markup,
854 header=header, footer=footer,
855 linenumbers=linenumbers)
856 f = open(outfile,'wt')
857 f.writelines(html)
858 f.close()
859 if show:
860 showpage(outfile)
861
862 def path2html(sourcepath, colors=None, markup='html',
863 header=None, footer=None,
864 linenumbers=0, form=None):
865 """Converts code(file) to colorized HTML. Returns an HTML string.
866
867 form='code',or'snip' (for "<pre>yourcode</pre>" only)
868 colors=null,mono,lite,dark,dark2,idle,or pythonwin
869 """
870 stringIO = cStringIO.StringIO()
871 sourcestring = open(sourcepath).read()
872 Parser(sourcestring, colors, title=sourcepath, out=stringIO,
873 markup=markup, header=header, footer=footer,
874 linenumbers=linenumbers).format(form)
875 stringIO.seek(0)
876 return stringIO.read()
877
878 def convert(source, outdir=None, colors=None,
879 show=0, markup='html', quiet=0,
880 header=None, footer=None, linenumbers=0, form=None):
881 """Takes a file or dir as input and places the html in the outdir.
882
883 If outdir is none it defaults to the input dir
884 """
885 c=0
886 # If it is a filename then path2file
887 if not os.path.isdir(source):
888 if os.path.isfile(source):
889 c+=1
890 path2file(source, outdir, colors, show, markup,
891 quiet, form, header, footer, linenumbers, c)
892 else:
893 raise PathError, 'File does not exist!'
894 # If we pass in a dir we need to walkdir for files.
895 # Then we need to colorize them with path2file
896 else:
897 fileList = walkdir(source)
898 if fileList != None:
899 # make sure outdir is a dir
900 if outdir != None:
901 if os.path.splitext(outdir)[1] != '':
902 outdir = os.path.split(outdir)[0]
903 for item in fileList:
904 c+=1
905 path2file(item, outdir, colors, show, markup,
906 quiet, form, header, footer, linenumbers,c)
907 _printinfo('Completed colorizing %s files.'%str(c), quiet)
908 else:
909 _printinfo("No files to convert in dir.", quiet)
910
911 def path2file(sourcePath, out=None, colors=None, show=0,
912 markup='html', quiet=0, form=None,
913 header=None, footer=None, linenumbers=0, count=1):
914 """ Converts python source to html file"""
915 # If no outdir is given we use the sourcePath
916 if out == None:#this is a guess
917 htmlPath = sourcePath + '.html'
918 else:
919 # If we do give an out_dir, and it does
920 # not exist , it will be created.
921 if os.path.splitext(out)[1] == '':
922 if not os.path.isdir(out):
923 os.makedirs(out)
924 sourceName = os.path.basename(sourcePath)
925 htmlPath = os.path.join(out,sourceName)+'.html'
926 # If we do give an out_name, and its dir does
927 # not exist , it will be created.
928 else:
929 outdir = os.path.split(out)[0]
930 if not os.path.isdir(outdir):
931 os.makedirs(outdir)
932 htmlPath = out
933 htmlPath = os.path.abspath(htmlPath)
934 # Open the text and do the parsing.
935 source = open(sourcePath).read()
936 parse = Parser(source, colors, sourcePath, open(htmlPath, 'wt'),
937 markup, header, footer, linenumbers)
938 parse.format(form)
939 _printinfo(" wrote %s" % htmlPath, quiet)
940 # html markup will ignore the external flag, but
941 # we need to stop the blank file from being written.
942 if form == 'external' and count == 1 and markup != 'html':
943 cssSheet = parse._sendCSSStyle(external=1)
944 cssPath = os.path.join(os.path.dirname(htmlPath),'style.css')
945 css = open(cssPath, 'wt')
946 css.write(cssSheet)
947 css.close()
948 _printinfo(" wrote %s" % cssPath, quiet)
949 if show:
950 # load HTML page into the default web browser.
951 showpage(htmlPath)
952 return htmlPath
953
954 def walkdir(dir):
955 """Return a list of .py and .pyw files from a given directory.
956
957 This function can be written as a generator Python 2.3, or a genexp
958 in Python 2.4. But 2.2 and 2.1 would be left out....
959 """
960 # Get a list of files that match *.py*
961 GLOB_PATTERN = os.path.join(dir, "*.[p][y]*")
962 pathlist = glob.glob(GLOB_PATTERN)
963 # Now filter out all but py and pyw
964 filterlist = [x for x in pathlist
965 if x.endswith('.py')
966 or x.endswith('.pyw')]
967 if filterlist != []:
968 # if we have a list send it
969 return filterlist
970 else:
971 return None
972
973 def showpage(path):
974 """Helper function to open webpages"""
975 try:
976 webbrowser.open_new(os.path.abspath(path))
977 except:
978 traceback.print_exc()
979
980 def _printinfo(message, quiet):
981 """Helper to print messages"""
982 if not quiet:
983 print message
984
985 ########################################################### Custom Exceptions
986
987 class PySourceColorError(Exception):
988 # Base for custom errors
989 def __init__(self, msg=''):
990 self._msg = msg
991 Exception.__init__(self, msg)
992 def __repr__(self):
993 return self._msg
994 __str__ = __repr__
995
996 class PathError(PySourceColorError):
997 def __init__(self, msg):
998 PySourceColorError.__init__(self,
999 'Path error! : %s'% msg)
1000
1001 class InputError(PySourceColorError):
1002 def __init__(self, msg):
1003 PySourceColorError.__init__(self,
1004 'Input error! : %s'% msg)
1005
1006 ########################################################## Python code parser
1007
1008 class Parser(object):
1009
1010 """MoinMoin python parser heavily chopped :)"""
1011
1012 def __init__(self, raw, colors=None, title='', out=sys.stdout,
1013 markup='html', header=None, footer=None, linenumbers=0):
1014 """Store the source text & set some flags"""
1015 if colors == None:
1016 colors = defaultColors
1017 self.raw = raw.expandtabs().strip()
1018 self.title = os.path.basename(title)
1019 self.out = out
1020 self.lasttext = ''
1021 self.argFlag = 0
1022 self.classFlag = 0
1023 self.defFlag = 0
1024 self.decoratorFlag = 0
1025 self.external = 0
1026 self.markup = markup.upper()
1027 self.colors = colors
1028 self.header = header
1029 self.footer = footer
1030 self.dolinenums = self.linenum = linenumbers
1031
1032
1033 def format(self, form=None):
1034 """Parse and send the colorized source"""
1035 if form in ('snip','code'):
1036 self.addEnds = 0
1037 else:
1038 if form == 'external':
1039 self.external = 1
1040 self.addEnds = 1
1041
1042 # Store line offsets in self.lines
1043 self.lines = [0, 0]
1044 pos = 0
1045
1046 # Add linenumbers
1047 if self.dolinenums:
1048 newlines = []
1049 lines = self.raw.splitlines()
1050 for l in lines:
1051 newlines.append("___LINE___ "+l)
1052 self.raw = "\n".join(newlines)
1053
1054 # Gather lines
1055 while 1:
1056 pos = self.raw.find('\n', pos) + 1
1057 if not pos: break
1058 self.lines.append(pos)
1059 self.lines.append(len(self.raw))
1060
1061 # Wrap text in a filelike object
1062 self.pos = 0
1063 text = cStringIO.StringIO(self.raw)
1064
1065 # Markup start
1066 if self.addEnds:
1067 self._doPageStart()
1068 else:
1069 self._doSnippetStart()
1070
1071 ## Tokenize calls the __call__
1072 ## function for each token till done.
1073 # Parse the source and write out the results.
1074 try:
1075 tokenize.tokenize(text.readline, self)
1076 except tokenize.TokenError, ex:
1077 msg = ex[0]
1078 line = ex[1][0]
1079 self.out.write("<h3>ERROR: %s</h3>%s\n"%
1080 (msg, self.raw[self.lines[line]:]))
1081 traceback.print_exc()
1082
1083 # Markup end
1084 if self.addEnds:
1085 self._doPageEnd()
1086 else:
1087 self._doSnippetEnd()
1088
1089 def __call__(self, toktype, toktext, (srow,scol), (erow,ecol), line):
1090 """Token handler. Order is important do not rearrange."""
1091 # Calculate new positions
1092 oldpos = self.pos
1093 newpos = self.lines[srow] + scol
1094 self.pos = newpos + len(toktext)
1095
1096 # Handle newlines
1097 if toktype in (token.NEWLINE, tokenize.NL):
1098 self.linenum+=1
1099 self.out.write('\n')
1100 return
1101
1102 # Send the original whitespace, if needed
1103 if newpos > oldpos:
1104 self.out.write(self.raw[oldpos:newpos])
1105
1106 # Skip indenting tokens
1107 if toktype in (token.INDENT, token.DEDENT):
1108 self.pos = newpos
1109 return
1110
1111 # Look for operators
1112 if token.LPAR <= toktype and toktype <= token.OP:
1113 # Trap decorators py2.4 >
1114 if toktext == '@':
1115 toktype = DECORATOR
1116 # Set a flag if this was the decorator start so
1117 # the decorator name and arguments can be identified
1118 self.decoratorFlag = self.argFlag = 1
1119 else:
1120 # Find the start for arguments
1121 if toktext == '(' and self.argFlag:
1122 self.argFlag = 2
1123 # Find the end for arguments
1124 elif toktext == ':':
1125 self.argFlag = 0
1126 # Seperate the diffrent operator types
1127 if toktext in ['[',']','(',')','{','}']:
1128 toktype = BRACKETS
1129 elif toktext in [':','`',';',',','.','=']:
1130 toktype = OPERATOR
1131 # example how flags should work.
1132 # def fun(arg=argvalue,arg2=argvalue2):
1133 # 0 1 2 A 1 N 2 A 1 N 0
1134 if toktext == "=" and self.argFlag == 2:
1135 self.argFlag = 1
1136 elif toktext == "," and self.argFlag == 1:
1137 self.argFlag = 2
1138 else:# !=, ==, +, -, **, etc.
1139 toktype = MATH_OPERATOR
1140
1141 # Look for keywords
1142 elif toktype == NAME and keyword.iskeyword(toktext):
1143 toktype = KEYWORD
1144 # Set a flag if this was the class / def start so
1145 # the class / def name and arguments can be identified
1146 if toktext =='class':
1147 self.classFlag = self.argFlag = 1
1148 elif toktext == 'def':
1149 self.defFlag = self.argFlag = 1
1150
1151 # Look for class, def, decorator name
1152 elif self.classFlag or self.defFlag or self.decoratorFlag:
1153 if self.classFlag:
1154 self.classFlag = 0
1155 toktype = CLASS_NAME
1156 elif self.defFlag:
1157 self.defFlag = 0
1158 toktype = DEF_NAME
1159 elif self.decoratorFlag:
1160 self.decoratorFlag = 0
1161 toktype = DECORATOR_NAME
1162
1163 # Look for strings
1164 # Order of evaluation is important do not change.
1165 elif toktype == token.STRING:
1166 text = toktext.lower()
1167 # TRIPLE DOUBLE QUOTE's
1168 if (text[:3].lower() == '"""'):
1169 toktype = TRIPLEDOUBLEQUOTE
1170 elif (text[:4] == 'r"""'):
1171 toktype = TRIPLEDOUBLEQUOTE_R
1172 elif (text[:4] == 'u"""' or
1173 text[:5] == 'ur"""'):
1174 toktype = TRIPLEDOUBLEQUOTE_U
1175 # DOUBLE QUOTE's
1176 elif (text[:1] == '"'):
1177 toktype = DOUBLEQUOTE
1178 elif (text[:2] == 'r"'):
1179 toktype = DOUBLEQUOTE_R
1180 elif (text[:2] == 'u"' or
1181 text[:3] == 'ur"'):
1182 toktype = DOUBLEQUOTE_U
1183 # TRIPLE SINGLE QUOTE's
1184 elif (text[:3] == "'''"):
1185 toktype = TRIPLESINGLEQUOTE
1186 elif (text[:4] == "r'''"):
1187 toktype = TRIPLESINGLEQUOTE_R
1188 elif (text[:4] == "u'''" or
1189 text[:5] == "ur'''"):
1190 toktype = TRIPLESINGLEQUOTE_U
1191 # SINGLE QUOTE's
1192 elif (text[:1] == "'"):
1193 toktype = SINGLEQUOTE
1194 elif (text[:2] == "r'"):
1195 toktype = SINGLEQUOTE_R
1196 elif (text[:2] == "u'" or
1197 text[:3] == "ur'"):
1198 toktype = SINGLEQUOTE_U
1199
1200 # test for invalid string declaration
1201 if self.lasttext.lower() == 'ru':
1202 toktype = ERRORTOKEN
1203
1204 # Look for comments
1205 elif toktype == COMMENT:
1206 if toktext[:2] == "##":
1207 toktype = DOUBLECOMMENT
1208
1209 # Seperate errors from decorators
1210 elif toktype == ERRORTOKEN:
1211 # Bug fix for < py2.4
1212 # space between decorators
1213 if self.argFlag and toktext.isspace():
1214 toktype = NAME
1215 # trap decorators < py2.4
1216 if toktext == '@':
1217 toktype = DECORATOR
1218 # Set a flag if this was the decorator start so
1219 # the decorator name and arguments can be identified
1220 self.decoratorFlag = self.argFlag = 1
1221 # Bug fix for py2.2 linenumbers with decorators
1222 if toktext.isspace():
1223 toktype = NAME
1224 # Seperate args from names
1225 elif (self.argFlag == 2 and
1226 toktype == NAME and
1227 toktext != 'None'):
1228 toktype = ARGS
1229
1230 # Look for line numbers, this
1231 # won't catch multiline strings.
1232 # The detection code for them is in the
1233 # send text functions.
1234 if toktext == "___LINE___":
1235 toktext = toktext.replace("___LINE___", self._getLineNumber())
1236 toktype = LINE
1237
1238 # Skip blank token that made it thru
1239 ## bugfix for the last empty tags.
1240 if toktext == '':
1241 return
1242
1243 # Last token text history
1244 # Used to detect invalid string declaration 'ru'
1245 self.lasttext = toktext
1246
1247 # Send text for any markup
1248 getattr(self, '_send%sText'%(self.markup))(toktype, toktext)
1249 return
1250
1251 ################################################################# Helpers
1252
1253 def _doSnippetStart(self):
1254 # Start of html snippet
1255 self.out.write('<pre>\n')
1256
1257 def _doSnippetEnd(self):
1258 # End of html snippet
1259 self.out.write('</pre>\n')
1260
1261 ######################################################## markup selectors
1262
1263 def _getFile(self, filepath):
1264 try:
1265 _file = open(filepath,'r')
1266 content = _file.read()
1267 _file.close()
1268 # do string subs here
1269 # title time filepath
1270 except:
1271 traceback.print_exc()
1272 content = ''
1273 return content
1274
1275 def _doPageStart(self):
1276 getattr(self, '_do%sStart'%(self.markup))()
1277
1278 def _doPageHeader(self):
1279 if self.header != None:
1280 if self.header != '':
1281 self.header = self._getFile(self.header)
1282 getattr(self, '_do%sHeader'%(self.markup))()
1283
1284 def _doPageFooter(self):
1285 if self.footer != None:
1286 if self.footer != '':
1287 self.footer = self._getFile(self.footer)
1288 getattr(self, '_do%sFooter'%(self.markup))()
1289
1290 def _doPageEnd(self):
1291 getattr(self, '_do%sEnd'%(self.markup))()
1292
1293 ################################################### color/style retrieval
1294
1295 def _getLineNumber(self):
1296 return str(self.linenum).rjust(5)+" "
1297
1298 def _getTags(self, key):
1299 # style tags
1300 return self.colors.get(key, self.colors[NAME])[0]
1301
1302 def _getForeColor(self, key):
1303 # get text foreground color, if not set to black
1304 color = self.colors.get(key, self.colors[NAME])[1]
1305 if color[:1] != '#':
1306 color = '#000000'
1307 return color
1308
1309 def _getBackColor(self, key):
1310 # get text background color
1311 return self.colors.get(key, self.colors[NAME])[2]
1312
1313 def _getPageColor(self):
1314 # get page background color
1315 return self.colors.get(PAGEBACKGROUND, '#FFFFFF')
1316
1317 def _getStyle(self, key):
1318 # get the token style from the color dictionary
1319 return self.colors.get(key, self.colors[NAME])
1320
1321 def _getMarkupClass(self, key):
1322 # get the markup class name from the markup dictionary
1323 return MARKUPDICT.get(key, MARKUPDICT[NAME])
1324
1325 def _getDocumentCreatedBy(self):
1326 return '<!--This document created by %s ver.%s on: %s-->\n'%(
1327 __title__,__version__,time.ctime())
1328
1329 ################################################### HTML markup functions
1330
1331 def _doHTMLStart(self):
1332 # Start of html page
1333 self.out.write('<!DOCTYPE html PUBLIC \
1334 "-//W3C//DTD HTML 4.01//EN">\n')
1335 self.out.write('<html><head><title>%s</title>\n'%(self.title))
1336 self.out.write(self._getDocumentCreatedBy())
1337 self.out.write('<meta http-equiv="Content-Type" \
1338 content="text/html;charset=iso-8859-1">\n')
1339 # Get background
1340 self.out.write('</head><body bgcolor="%s">\n'%self._getPageColor())
1341 self._doPageHeader()
1342 self.out.write('<pre>')
1343
1344 def _getHTMLStyles(self, toktype, toktext):
1345 # Get styles
1346 tags, color = self.colors.get(toktype, self.colors[NAME])[:2]#
1347 tagstart=[]
1348 tagend=[]
1349 # check for styles and set them if needed.
1350 if 'b' in tags:#Bold
1351 tagstart.append('<b>')
1352 tagend.append('</b>')
1353 if 'i' in tags:#Italics
1354 tagstart.append('<i>')
1355 tagend.append('</i>')
1356 if 'u' in tags:#Underline
1357 tagstart.append('<u>')
1358 tagend.append('</u>')
1359 # HTML tags should be paired like so : <b><i><u>Doh!</u></i></b>
1360 tagend.reverse()
1361 starttags="".join(tagstart)
1362 endtags="".join(tagend)
1363 return starttags,endtags,color
1364
1365 def _sendHTMLText(self, toktype, toktext):
1366 sendtext = cgi.escape(toktext)
1367 # If it is an error set a red box around the bad tokens
1368 # older browsers will ignore it
1369 if toktype == ERRORTOKEN:
1370 style = ' style="border: solid 1.5pt #FF0000;"'
1371 else:
1372 style = ''
1373 # Get styles
1374 starttag, endtag, color = self._getHTMLStyles(toktype, toktext)
1375 # This is a hack to 'fix' multi-line strings.
1376 # Multi-line strings are treated as only one token
1377 # even though they can be several physical lines.
1378 # That makes it hard to spot the start of a line,
1379 # because at this level all we know about are tokens.
1380 if toktext.count("___LINE___"):
1381 if __title__ == 'PySourceColor' and toktype == DOUBLEQUOTE:
1382 pass#yea we cheat just to pass our own test ;)
1383 else:
1384 # rip apart the string and separate it by line
1385 # count lines and change all linenum token to line numbers
1386 # embedded all the new font tags inside the current one.
1387 # we do this by ending the tag first doing our new tags
1388 # then starting another font tag exactly like the first one.
1389 splittext = sendtext.split("___LINE___")
1390 store = []
1391 store.append(splittext.pop(0))
1392 lstarttag, lendtag, lcolor = self._getHTMLStyles(LINE, toktext)
1393 for item in splittext:
1394 self.linenum += 1
1395 linenumber= ''.join([endtag,'<font color=',lcolor,'>',
1396 lstarttag,self._getLineNumber(),
1397 lendtag,'</font>',starttag])
1398 store.append(linenumber+item)
1399 sendtext = ''.join(store)
1400
1401 # send text
1402 ## Output optimization
1403 # skip font tag if black text, but styles will still be sent. (b,u,i)
1404 if color !='#000000':
1405 startfont = '<font color="%s"%s>'%(color, style)
1406 endfont = '</font>'
1407 else:
1408 startfont, endfont = ('','')
1409 self.out.write(''.join([startfont,starttag,sendtext,endtag,endfont]))
1410 return
1411
1412 def _doHTMLHeader(self):
1413 # Optional
1414 if self.header != '':
1415 self.out.write('%s\n'%self.header)
1416 else:
1417 color = self._getForeColor(token.NAME)
1418 self.out.write('<b><font color="%s"># %s \
1419 <br># %s</font></b><hr>\n'%
1420 (color, self.title, time.ctime()))
1421
1422 def _doHTMLFooter(self):
1423 # Optional
1424 if self.footer != '':
1425 self.out.write('%s\n'%self.footer)
1426 else:
1427 color = self._getForeColor(token.NAME)
1428 self.out.write('<b><font color="%s">\
1429 <hr># %s<br># %s</font></b>\n'%
1430 (color, self.title, time.ctime()))
1431
1432 def _doHTMLEnd(self):
1433 # End of html page
1434 self.out.write('</pre>\n')
1435 # Write a little info at the bottom
1436 self._doPageFooter()
1437 self.out.write('</body></html>\n')
1438
1439 #################################################### CSS markup functions
1440
1441 def _getCSSStyle(self, key):
1442 # Get the tags and colors from the dictionary
1443 tags, forecolor, backcolor = self._getStyle(key)
1444 style=[]
1445 border = None
1446 bordercolor = None
1447 tags = tags.lower()
1448 if tags:
1449 # get the border color if specified
1450 # the border color will be appended to
1451 # the list after we define a border
1452 if '#' in tags:# border color
1453 start = tags.find('#')
1454 end = start + 7
1455 bordercolor = tags[start:end]
1456 tags.replace(bordercolor,'',1)
1457 # border size
1458 if 'l' in tags:# thick border
1459 size='thick'
1460 elif 'm' in tags:# medium border
1461 size='medium'
1462 elif 't' in tags:# thin border
1463 size='thin'
1464 else:# default
1465 size='medium'
1466 # text styles
1467 if 'b' in tags:# Bold
1468 style.append('font-weight:bold;')
1469 if 'i' in tags:# Italic
1470 style.append('font-style:italic;')
1471 if 'u' in tags:# Underline
1472 style.append('text-decoration:underline;')
1473 # border styles
1474 if 'n' in tags:# inset border
1475 border='inset'
1476 elif 'o' in tags:# outset border
1477 border='outset'
1478 elif 'r' in tags:# ridge border
1479 border='ridge'
1480 elif 'g' in tags:# groove border
1481 border='groove'
1482 elif '=' in tags:# double border
1483 border='double'
1484 elif '.' in tags:# dotted border
1485 border='dotted'
1486 elif '-' in tags:# dashed border
1487 border='dashed'
1488 elif 's' in tags:# solid border
1489 border='solid'
1490 # border type check
1491 seperate_sides=0
1492 for side in ['<','>','^','v']:
1493 if side in tags:
1494 seperate_sides+=1
1495 # border box or seperate sides
1496 if seperate_sides==0 and border:
1497 style.append('border: %s %s;'%(border,size))
1498 else:
1499 if border == None:
1500 border = 'solid'
1501 if 'v' in tags:# bottom border
1502 style.append('border-bottom:%s %s;'%(border,size))
1503 if '<' in tags:# left border
1504 style.append('border-left:%s %s;'%(border,size))
1505 if '>' in tags:# right border
1506 style.append('border-right:%s %s;'%(border,size))
1507 if '^' in tags:# top border
1508 style.append('border-top:%s %s;'%(border,size))
1509 # we have to define our borders before we set colors
1510 if bordercolor:
1511 style.append('border-color:%s;'%bordercolor)
1512 # text forecolor
1513 style.append('color:%s;'% forecolor)
1514 # text backcolor
1515 if backcolor:
1516 style.append('background-color:%s;'%backcolor)
1517 return (self._getMarkupClass(key),''.join(style))
1518
1519 def _sendCSSStyle(self, external=0):
1520 """ create external and internal style sheets"""
1521 styles = []
1522 if not self.external:
1523 styles.append('<style type="text/css">\n<!--\n')
1524 # Get page background color and write styles ignore all but b,i,u
1525 styles.append('body {background:%s;}\n'%self._getPageColor())
1526 # write out the various css styles
1527 for key in MARKUPDICT:
1528 if key == ERRORTOKEN:
1529 # If it is an errortoken set a red box around it
1530 styles.append('.%s {border: solid 1.5pt #FF0000;%s}\n'
1531 %self._getCSSStyle(ERRORTOKEN))
1532 else:
1533 # set the styles for all but errortokens
1534 styles.append('.%s {%s}\n'%self._getCSSStyle(key))
1535 if not self.external:
1536 styles.append('--></style>\n')
1537 return ''.join(styles)
1538
1539 def _doCSSStart(self):
1540 # Start of css/html page
1541 self.out.write('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">\n')
1542 self.out.write('<html><head><title>%s</title>\n'%(self.title))
1543 self.out.write(self._getDocumentCreatedBy())
1544 self.out.write('<meta http-equiv="Content-Type" \
1545 content="text/html;charset=iso-8859-1">\n')
1546 self._doCSSStyleSheet()
1547 self.out.write('</head>\n<body>\n')
1548 # Write a little info at the top.
1549 self._doPageHeader()
1550 self.out.write('<pre>')
1551 return
1552
1553 def _doCSSStyleSheet(self):
1554 if not self.external:
1555 # write an embedded style sheet
1556 self.out.write(self._sendCSSStyle())
1557 else:
1558 # write a link to an external style sheet
1559 self.out.write('<link rel="stylesheet" \
1560 href="style.css" type="text/css">')
1561 return
1562
1563 def _sendCSSText(self, toktype, toktext):
1564 toktext = cgi.escape(toktext)
1565 # This is a hack to 'fix' multi-line strings.
1566 # Multi-line strings are treated as only one token
1567 # even though they can be several physical lines.
1568 # That makes it hard to spot the start of a line,
1569 # because at this level all we know about are tokens.
1570 if toktext.count("___LINE___"):
1571 if __title__ == 'PySourceColor' and toktype == DOUBLEQUOTE:
1572 pass#yea we cheat just to pass our own test ;)
1573 else:
1574 # rip apart the string and separate it by line
1575 # count lines and change all linenum token to line numbers
1576 # embed linenum span inside current span class
1577 newmarkup = MARKUPDICT.get(LINE, MARKUPDICT[NAME])
1578 lstartspan = '<span class="%s">'%(newmarkup)
1579 splittext = toktext.split("___LINE___")
1580 store = []
1581 store.append(splittext.pop(0))
1582 for item in splittext:
1583 self.linenum += 1
1584 linenumber= ''.join(
1585 [lstartspan, self._getLineNumber(), '</span>'])
1586 store.append(linenumber+item)
1587 toktext = ''.join(store)
1588 # Send text
1589 markupclass = MARKUPDICT.get(toktype, MARKUPDICT[NAME])
1590 startspan = '<span class="%s">'%(markupclass)
1591 self.out.write(''.join([startspan,toktext,'</span>']))
1592 return
1593
1594 def _doCSSHeader(self):
1595 # Optional
1596 if self.header:
1597 self.out.write('%s\n'%self.header)
1598 else:
1599 self.out.write('<div><span class="name"># %s </span><br>\
1600 <span class="name"># %s</span></div><hr>\
1601 '%(self.title, time.ctime()))
1602
1603 def _doCSSFooter(self):
1604 # Optional
1605 if self.footer:
1606 self.out.write('%s\n'%self.footer)
1607 else:
1608 self.out.write('<hr><div><span class="name"># %s <br>\
1609 # %s</span></div>'%(self.title, time.ctime()))
1610
1611 def _doCSSEnd(self):
1612 # End of css/html page
1613 self.out.write('</pre>\n')
1614 # Write a little info at the bottom
1615 self._doPageFooter()
1616 self.out.write('</body></html>\n')
1617 return
1618
1619 ################################################## XHTML markup functions
1620
1621 def _doXHTMLStart(self):
1622 # XHTML is really just XML + HTML 4.01.
1623 # We only need to change the page headers to get valid XHTML.
1624 # Start of xhtml page
1625 self.out.write('<?xml version="1.0"?>\n\
1626 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"\n\
1627 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n\
1628 <html xmlns="http://www.w3.org/1999/xhtml">\n')
1629 self.out.write('<head><title>%s</title>\n'%(self.title))
1630 self.out.write(self._getDocumentCreatedBy())
1631 self.out.write('<meta http-equiv="Content-Type" \
1632 content="text/html;charset=iso-8859-1"/>\n')
1633 self._doXHTMLStyleSheet()
1634 self.out.write('</head>\n<body>\n')
1635 # Write a little info at the top.
1636 self._doPageHeader()
1637 self.out.write('<pre>')
1638 return
1639
1640 def _doXHTMLStyleSheet(self):
1641 if not self.external:
1642 # write an embedded style sheet
1643 self.out.write(self._sendCSSStyle())
1644 else:
1645 # write a link to an external style sheet
1646 self.out.write('<link rel="stylesheet" \
1647 href="style.css" type="text/css"/>')
1648 return
1649
1650 def _sendXHTMLText(self, toktype, toktext):
1651 self._sendCSSText(toktype, toktext)
1652
1653 def _doXHTMLHeader(self):
1654 # Optional
1655 if self.header:
1656 self.out.write('%s\n'%self.header)
1657 else:
1658 self.out.write('<div><span class="name"># %s </span><br/>\
1659 <span class="name"># %s</span></div><hr/>'%(self.title, time.ctime()))
1660
1661 def _doXHTMLFooter(self):
1662 # Optional
1663 if self.footer:
1664 self.out.write('%s\n'%self.footer)
1665 else:
1666 self.out.write('<hr/><div><span class="name"># %s <br/>\
1667 # %s</span></div>'%(self.title, time.ctime()))
1668
1669 def _doXHTMLEnd(self):
1670 self._doCSSEnd()
1671
1672 #############################################################################
1673
1674 if __name__ == '__main__':
1675 cli()
1676
1677 #############################################################################
1678 # 2004 M.E.Farmer Jr.
1679 # Python license