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