# -*- 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 \\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('
# %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')) 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 #\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(''+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 = ' 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]+' '%(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\n'%(MARKUPDICT.get(NAME),self.title, time.ctime())) def _doCSSEnd(self): # End of css/html page self.out.write(self.colors.get(CODEEND,'
\ # %s
\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\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
\ # %s