# -*- coding: Latin-1 -*- """ PySourceColor: color Python source code """ """ PySourceColor.py ---------------------------------------------------------------------------- A python source to colorized html/css/xhtml converter. Hacked by M.E.Farmer Jr. 2004, 2005 Python license ---------------------------------------------------------------------------- - HTML markup does not create w3c valid html, but it works on every browser i've tried so far.(I.E.,Mozilla/Firefox,Opera,Konqueror,wxHTML). - CSS markup is w3c validated html 4.01 strict, but will not render correctly on all browsers. - XHTML markup is w3c validated xhtml 1.0 strict, like html 4.01, will not render correctly on all browsers. ---------------------------------------------------------------------------- Features: -Three types of markup: html (default) css/html 4.01 strict xhtml 1.0 strict -Can tokenize and colorize: 12 types of strings 2 comment types numbers operators brackets math operators class / name def / name decorator / name keywords arguments class/def/decorator linenumbers names text -Eight colorschemes built-in: null mono lite (default) dark dark2 idle viewcvs pythonwin -Header and footer set to '' for builtin header / footer. give path to a file containing the html you want added as header or footer. -Arbitrary text and html html markup converts all to raw (TEXT token) #@# for raw -> send raw text. #$# for span -> inline html and text. #%# for div -> block level html and text. -Linenumbers Supports all styles. New token is called LINENUMBER. Defaults to NAME if not defined. Style options -ALL markups support these text styles: b = bold i = italic u = underline -CSS and XHTML has limited support for borders: HTML markup functions will ignore these. Optional: Border color in RGB hex Defaults to the text forecolor. #rrggbb = border color Border size: l = thick m = medium t = thin Border type: - = dashed . = dotted s = solid d = double g = groove r = ridge n = inset o = outset You can specify multiple sides, they will all use the same style. Optional: Default is full border. v = bottom < = left > = right ^ = top NOTE: Specify the styles you want. The markups will ignore unsupported styles Also note not all browsers can show these options -All tokens default to NAME if not defined so the only absolutely critical ones to define are: NAME, ERRORTOKEN, PAGEBACKGROUND ---------------------------------------------------------------------------- Example usage:: # import import PySourceColor as psc psc.convert('c:/Python22/PySourceColor.py', colors=psc.idle, show=1) # from module import * from PySourceColor import * convert('c:/Python22/Lib', colors=lite, markup="css", header='#$#This is a simpe heading
yourcode" only) colors=null,mono,lite,dark,dark2,idle,or pythonwin """ Parser(sourcestring, colors=colors, title=title, markup=markup, header=header, footer=footer, linenumbers=linenumbers).format(form) def path2stdout(sourcepath, title='', colors=None, markup='html', header=None, footer=None, linenumbers=0, form=None): """Converts code(file) to colorized HTML. Writes to stdout. form='code',or'snip' (for "
yourcode" only) colors=null,mono,lite,dark,dark2,idle,or pythonwin """ sourcestring = open(sourcepath).read() Parser(sourcestring, colors=colors, title=sourcepath, markup=markup, header=header, footer=footer, linenumbers=linenumbers).format(form) def str2html(sourcestring, colors=None, title='', markup='html', header=None, footer=None, linenumbers=0, form=None): """Converts a code(string) to colorized HTML. Returns an HTML string. form='code',or'snip' (for "
yourcode" only) colors=null,mono,lite,dark,dark2,idle,or pythonwin """ stringIO = StringIO.StringIO() Parser(sourcestring, colors=colors, title=title, out=stringIO, markup=markup, header=header, footer=footer, linenumbers=linenumbers).format(form) stringIO.seek(0) return stringIO.read() def str2css(sourcestring, colors=None, title='', markup='css', header=None, footer=None, linenumbers=0, form=None): """Converts a code string to colorized CSS/HTML. Returns CSS/HTML string If form != None then this will return (stylesheet_str, code_str) colors=null,mono,lite,dark,dark2,idle,or pythonwin """ if markup.lower() not in ['css' ,'xhtml']: markup = 'css' stringIO = StringIO.StringIO() parse = Parser(sourcestring, colors=colors, title=title, out=stringIO, markup=markup, header=header, footer=footer, linenumbers=linenumbers) parse.format(form) stringIO.seek(0) if form != None: return parse._sendCSSStyle(external=1), stringIO.read() else: return None, stringIO.read() def str2markup(sourcestring, colors=None, title = '', markup='xhtml', header=None, footer=None, linenumbers=0, form=None): """ Convert code strings into ([stylesheet or None], colorized string) """ if markup.lower() == 'html': return None, str2html(sourcestring, colors=colors, title=title, header=header, footer=footer, markup=markup, linenumbers=linenumbers, form=form) else: return str2css(sourcestring, colors=colors, title=title, header=header, footer=footer, markup=markup, linenumbers=linenumbers, form=form) def str2file(sourcestring, outfile, colors=None, title='', markup='html', header=None, footer=None, linenumbers=0, show=0, dosheet=1, form=None): """Converts a code string to a file. makes no attempt at correcting bad pathnames """ css , html = str2markup(sourcestring, colors=colors, title='', markup=markup, header=header, footer=footer, linenumbers=linenumbers, form=form) # write html f = open(outfile,'wt') f.writelines(html) f.close() #write css if css != None and dosheet: dir = os.path.dirname(outfile) outcss = os.path.join(dir,'pystyle.css') f = open(outcss,'wt') f.writelines(css) f.close() if show: showpage(outfile) def path2html(sourcepath, colors=None, markup='html', header=None, footer=None, linenumbers=0, form=None): """Converts code(file) to colorized HTML. Returns an HTML string. form='code',or'snip' (for "
yourcode" only) colors=null,mono,lite,dark,dark2,idle,or pythonwin """ stringIO = StringIO.StringIO() sourcestring = open(sourcepath).read() Parser(sourcestring, colors, title=sourcepath, out=stringIO, markup=markup, header=header, footer=footer, linenumbers=linenumbers).format(form) stringIO.seek(0) return stringIO.read() def convert(source, outdir=None, colors=None, show=0, markup='html', quiet=0, header=None, footer=None, linenumbers=0, form=None): """Takes a file or dir as input and places the html in the outdir. If outdir is none it defaults to the input dir """ count=0 # If it is a filename then path2file if not os.path.isdir(source): if os.path.isfile(source): count+=1 path2file(source, outdir, colors, show, markup, quiet, form, header, footer, linenumbers, count) else: raise PathError, 'File does not exist!' # If we pass in a dir we need to walkdir for files. # Then we need to colorize them with path2file else: fileList = walkdir(source) if fileList != None: # make sure outdir is a dir if outdir != None: if os.path.splitext(outdir)[1] != '': outdir = os.path.split(outdir)[0] for item in fileList: count+=1 path2file(item, outdir, colors, show, markup, quiet, form, header, footer, linenumbers, count) _printinfo('Completed colorizing %s files.'%str(count), quiet) else: _printinfo("No files to convert in dir.", quiet) def path2file(sourcePath, out=None, colors=None, show=0, markup='html', quiet=0, form=None, header=None, footer=None, linenumbers=0, count=1): """ Converts python source to html file""" # If no outdir is given we use the sourcePath if out == None:#this is a guess htmlPath = sourcePath + '.html' else: # If we do give an out_dir, and it does # not exist , it will be created. if os.path.splitext(out)[1] == '': if not os.path.isdir(out): os.makedirs(out) sourceName = os.path.basename(sourcePath) htmlPath = os.path.join(out,sourceName)+'.html' # If we do give an out_name, and its dir does # not exist , it will be created. else: outdir = os.path.split(out)[0] if not os.path.isdir(outdir): os.makedirs(outdir) htmlPath = out htmlPath = os.path.abspath(htmlPath) # Open the text and do the parsing. source = open(sourcePath).read() parse = Parser(source, colors, sourcePath, open(htmlPath, 'wt'), markup, header, footer, linenumbers) parse.format(form) _printinfo(" wrote %s" % htmlPath, quiet) # html markup will ignore the external flag, but # we need to stop the blank file from being written. if form == 'external' and count == 1 and markup != 'html': cssSheet = parse._sendCSSStyle(external=1) cssPath = os.path.join(os.path.dirname(htmlPath),'pystyle.css') css = open(cssPath, 'wt') css.write(cssSheet) css.close() _printinfo(" wrote %s" % cssPath, quiet) if show: # load HTML page into the default web browser. showpage(htmlPath) return htmlPath def tagreplace(sourcestr, colors=lite, markup='xhtml', linenumbers=0, dosheet=1, tagstart='
\n'))
def _doSnippetEnd(self):
# End of html snippet
self.out.write(self.colors.get(CODEEND,'\n'))
######################################################## markup selectors
def _getFile(self, filepath):
try:
_file = open(filepath,'r')
content = _file.read()
_file.close()
except:
traceback.print_exc()
content = ''
return content
def _doPageStart(self):
getattr(self, '_do%sStart'%(self.markup))()
def _doPageHeader(self):
if self.header != None:
if self.header.find('#$#') != -1 or \
self.header.find('#$#') != -1 or \
self.header.find('#%#') != -1:
self.out.write(self.header[3:])
else:
if self.header != '':
self.header = self._getFile(self.header)
getattr(self, '_do%sHeader'%(self.markup))()
def _doPageFooter(self):
if self.footer != None:
if self.footer.find('#$#') != -1 or \
self.footer.find('#@#') != -1 or \
self.footer.find('#%#') != -1:
self.out.write(self.footer[3:])
else:
if self.footer != '':
self.footer = self._getFile(self.footer)
getattr(self, '_do%sFooter'%(self.markup))()
def _doPageEnd(self):
getattr(self, '_do%sEnd'%(self.markup))()
################################################### color/style retrieval
## Some of these are not used anymore but are kept for documentation
def _getLineNumber(self):
num = self.linenum
self.linenum+=1
return str(num).rjust(5)+" "
def _getTags(self, key):
# style tags
return self.colors.get(key, self.colors[NAME])[0]
def _getForeColor(self, key):
# get text foreground color, if not set to black
color = self.colors.get(key, self.colors[NAME])[1]
if color[:1] != '#':
color = '#000000'
return color
def _getBackColor(self, key):
# get text background color
return self.colors.get(key, self.colors[NAME])[2]
def _getPageColor(self):
# get page background color
return self.colors.get(PAGEBACKGROUND, '#FFFFFF')
def _getStyle(self, key):
# get the token style from the color dictionary
return self.colors.get(key, self.colors[NAME])
def _getMarkupClass(self, key):
# get the markup class name from the markup dictionary
return MARKUPDICT.get(key, MARKUPDICT[NAME])
def _getDocumentCreatedBy(self):
return '\n'%(
__title__,__version__,time.ctime())
################################################### HTML markup functions
def _doHTMLStart(self):
# Start of html page
self.out.write('\n')
self.out.write('')
def _getHTMLStyles(self, toktype, toktext):
# Get styles
tags, color = self.colors.get(toktype, self.colors[NAME])[:2]#
tagstart=[]
tagend=[]
# check for styles and set them if needed.
if 'b' in tags:#Bold
tagstart.append('')
tagend.append('')
if 'i' in tags:#Italics
tagstart.append('')
tagend.append('')
if 'u' in tags:#Underline
tagstart.append('')
tagend.append('')
# HTML tags should be paired like so : Doh!
tagend.reverse()
starttags="".join(tagstart)
endtags="".join(tagend)
return starttags,endtags,color
def _sendHTMLText(self, toktype, toktext):
numberlinks = self.numberlinks
# If it is an error, set a red box around the bad tokens
# older browsers should ignore it
if toktype == ERRORTOKEN:
style = ' style="border: solid 1.5pt #FF0000;"'
else:
style = ''
# Get styles
starttag, endtag, color = self._getHTMLStyles(toktype, toktext)
# This is a hack to 'fix' multi-line strings.
# Multi-line strings are treated as only one token
# even though they can be several physical lines.
# That makes it hard to spot the start of a line,
# because at this level all we know about are tokens.
if toktext.count(self.LINENUMHOLDER):
# rip apart the string and separate it by line.
# count lines and change all linenum token to line numbers.
# embedded all the new font tags inside the current one.
# Do this by ending the tag first then writing our new tags,
# then starting another font tag exactly like the first one.
if toktype == LINENUMBER:
splittext = toktext.split(self.LINENUMHOLDER)
else:
splittext = toktext.split(self.LINENUMHOLDER+' ')
store = []
store.append(splittext.pop(0))
lstarttag, lendtag, lcolor = self._getHTMLStyles(LINENUMBER, toktext)
count = len(splittext)
for item in splittext:
num = self._getLineNumber()
if numberlinks:
numstrip = num.strip()
content = '%s' \
%(numstrip,numstrip,num)
else:
content = num
if count <= 1:
endtag,starttag = '',''
linenumber = ''.join([endtag,'',
lstarttag, content, lendtag, '' ,starttag])
store.append(linenumber+item)
toktext = ''.join(store)
# send text
## Output optimization
# skip font tag if black text, but styles will still be sent. (b,u,i)
if color !='#000000':
startfont = ''%(color, style)
endfont = ''
else:
startfont, endfont = ('','')
if toktype != LINENUMBER:
self.out.write(''.join([startfont,starttag,
toktext,endtag,endfont]))
else:
self.out.write(toktext)
return
def _doHTMLHeader(self):
# Optional
if self.header != '':
self.out.write('%s\n'%self.header)
else:
color = self._getForeColor(NAME)
self.out.write('# %s \
# %s
\n'%
(color, self.title, time.ctime()))
def _doHTMLFooter(self):
# Optional
if self.footer != '':
self.out.write('%s\n'%self.footer)
else:
color = self._getForeColor(NAME)
self.out.write(' \
# %s
# %s\n'%
(color, self.title, time.ctime()))
def _doHTMLEnd(self):
# End of html page
self.out.write('\n')
# Write a little info at the bottom
self._doPageFooter()
self.out.write('\n')
#################################################### CSS markup functions
def _getCSSStyle(self, key):
# Get the tags and colors from the dictionary
tags, forecolor, backcolor = self._getStyle(key)
style=[]
border = None
bordercolor = None
tags = tags.lower()
if tags:
# get the border color if specified
# the border color will be appended to
# the list after we define a border
if '#' in tags:# border color
start = tags.find('#')
end = start + 7
bordercolor = tags[start:end]
tags.replace(bordercolor,'',1)
# text styles
if 'b' in tags:# Bold
style.append('font-weight:bold;')
else:
style.append('font-weight:normal;')
if 'i' in tags:# Italic
style.append('font-style:italic;')
if 'u' in tags:# Underline
style.append('text-decoration:underline;')
# border size
if 'l' in tags:# thick border
size='thick'
elif 'm' in tags:# medium border
size='medium'
elif 't' in tags:# thin border
size='thin'
else:# default
size='medium'
# border styles
if 'n' in tags:# inset border
border='inset'
elif 'o' in tags:# outset border
border='outset'
elif 'r' in tags:# ridge border
border='ridge'
elif 'g' in tags:# groove border
border='groove'
elif '=' in tags:# double border
border='double'
elif '.' in tags:# dotted border
border='dotted'
elif '-' in tags:# dashed border
border='dashed'
elif 's' in tags:# solid border
border='solid'
# border type check
seperate_sides=0
for side in ['<','>','^','v']:
if side in tags:
seperate_sides+=1
# border box or seperate sides
if seperate_sides==0 and border:
style.append('border: %s %s;'%(border,size))
else:
if border == None:
border = 'solid'
if 'v' in tags:# bottom border
style.append('border-bottom:%s %s;'%(border,size))
if '<' in tags:# left border
style.append('border-left:%s %s;'%(border,size))
if '>' in tags:# right border
style.append('border-right:%s %s;'%(border,size))
if '^' in tags:# top border
style.append('border-top:%s %s;'%(border,size))
else:
style.append('font-weight:normal;')# css inherited style fix
# we have to define our borders before we set colors
if bordercolor:
style.append('border-color:%s;'%bordercolor)
# text forecolor
style.append('color:%s;'% forecolor)
# text backcolor
if backcolor:
style.append('background-color:%s;'%backcolor)
return (self._getMarkupClass(key),' '.join(style))
def _sendCSSStyle(self, external=0):
""" create external and internal style sheets"""
styles = []
external += self.external
if not external:
styles.append('\n')
return ''.join(styles)
def _doCSSStart(self):
# Start of css/html 4.01 page
self.out.write('\n')
self.out.write('\n'))
return
def _doCSSStyleSheet(self):
if not self.external:
# write an embedded style sheet
self.out.write(self._sendCSSStyle())
else:
# write a link to an external style sheet
self.out.write('')
return
def _sendCSSText(self, toktype, toktext):
# This is a hack to 'fix' multi-line strings.
# Multi-line strings are treated as only one token
# even though they can be several physical lines.
# That makes it hard to spot the start of a line,
# because at this level all we know about are tokens.
markupclass = MARKUPDICT.get(toktype, MARKUPDICT[NAME])
# if it is a LINENUMBER type then we can skip the rest
if toktext == self.LINESTART and toktype == LINENUMBER:
self.out.write('')
return
if toktext.count(self.LINENUMHOLDER):
# rip apart the string and separate it by line
# count lines and change all linenum token to line numbers
# also convert linestart and lineend tokens
# lnum text
#################################################
newmarkup = MARKUPDICT.get(LINENUMBER, MARKUPDICT[NAME])
lstartspan = ''%(newmarkup)
if toktype == LINENUMBER:
splittext = toktext.split(self.LINENUMHOLDER)
else:
splittext = toktext.split(self.LINENUMHOLDER+' ')
store = []
# we have already seen the first linenumber token
# so we can skip the first one
store.append(splittext.pop(0))
for item in splittext:
num = self._getLineNumber()
if self.numberlinks:
numstrip = num.strip()
content= '%s' \
%(numstrip,numstrip,num)
else:
content = num
linenumber= ''.join([lstartspan,content,''])
store.append(linenumber+item)
toktext = ''.join(store)
if toktext.count(self.LINESTART):
# wraps the textline in a line span
# this adds a lot of kludges, is it really worth it?
store = []
parts = toktext.split(self.LINESTART+' ')
# handle the first part differently
# the whole token gets wraqpped in a span later on
first = parts.pop(0)
# place spans before the newline
pos = first.rfind('\n')
if pos != -1:
first=first[:pos]+' '+first[pos:]
store.append(first)
#process the rest of the string
for item in parts:
#handle line numbers if present
if self.dolinenums:
item = item.replace('',
''%(markupclass))
else:
item = '%s'%(markupclass,item)
# add endings for line and string tokens
pos = item.rfind('\n')
if pos != -1:
item=item[:pos]+'\n'
store.append(item)
# add start tags for lines
toktext = ''.join(store)
# Send text
if toktype != LINENUMBER:
if toktype == TEXT and self.textFlag == 'DIV':
startspan = ''%(markupclass)
endspan = ''
elif toktype == TEXT and self.textFlag == 'RAW':
startspan,endspan = ('','')
else:
startspan = ''%(markupclass)
endspan = ''
self.out.write(''.join([startspan, toktext, endspan]))
else:
self.out.write(toktext)
return
def _doCSSHeader(self):
if self.header != '':
self.out.write('%s\n'%self.header)
else:
name = MARKUPDICT.get(NAME)
self.out.write('# %s
\
# %s
\n'%(name, self.title, time.ctime()))
def _doCSSFooter(self):
# Optional
if self.footer != '':
self.out.write('%s\n'%self.footer)
else:
self.out.write('
# %s
\
# %s\n'%(MARKUPDICT.get(NAME),self.title, time.ctime()))
def _doCSSEnd(self):
# End of css/html page
self.out.write(self.colors.get(CODEEND,'\n'))
# Write a little info at the bottom
self._doPageFooter()
self.out.write('\n')
return
################################################## XHTML markup functions
def _doXHTMLStart(self):
# XHTML is really just XML + HTML 4.01.
# We only need to change the page headers,
# and a few tags to get valid XHTML.
# Start of xhtml page
self.out.write('\n \
\n \
\n')
self.out.write('\n'))
return
def _doXHTMLStyleSheet(self):
if not self.external:
# write an embedded style sheet
self.out.write(self._sendCSSStyle())
else:
# write a link to an external style sheet
self.out.write('\n')
return
def _sendXHTMLText(self, toktype, toktext):
self._sendCSSText(toktype, toktext)
def _doXHTMLHeader(self):
# Optional
if self.header:
self.out.write('%s\n'%self.header)
else:
name = MARKUPDICT.get(NAME)
self.out.write('# %s
\
# %s
\n '%(
name, self.title, time.ctime()))
def _doXHTMLFooter(self):
# Optional
if self.footer:
self.out.write('%s\n'%self.footer)
else:
self.out.write('
# %s
\
# %s\n'%(MARKUPDICT.get(NAME), self.title, time.ctime()))
def _doXHTMLEnd(self):
self._doCSSEnd()
#############################################################################
if __name__ == '__main__':
cli()
#############################################################################
# PySourceColor.py
# 2004, 2005 M.E.Farmer Jr.
# Python license