305 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			305 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			Python
		
	
	
	
| 
 | |
| """ html - generating ad-hoc html out of source browser
 | |
| """
 | |
| 
 | |
| import py
 | |
| from py.xml import html, raw
 | |
| from compiler import ast
 | |
| import time
 | |
| from py.__.apigen.source.color import Tokenizer, PythonSchema
 | |
| from py.__.apigen.source.browser import parse_path
 | |
| 
 | |
| class CompilationException(Exception):
 | |
|     """ raised when something goes wrong while importing a module """
 | |
| 
 | |
| class HtmlEnchanter(object):
 | |
|     def __init__(self, mod):
 | |
|         self.mod = mod
 | |
|         self.create_caches()
 | |
|     
 | |
|     def create_caches(self):
 | |
|         mod = self.mod
 | |
|         linecache = {}
 | |
|         for item in mod.get_children():
 | |
|             linecache[item.firstlineno] = item
 | |
|         self.linecache = linecache
 | |
|     
 | |
|     def enchant_row(self, num, row):
 | |
|         # add some informations to row, like functions defined in that
 | |
|         # line, etc.
 | |
|         try:
 | |
|             item = self.linecache[num]
 | |
|             # XXX: this should not be assertion, rather check, but we want to
 | |
|             #      know if stuff is working
 | |
|             pos = row.find(item.name)
 | |
|             assert pos != -1
 | |
|             end = len(item.name) + pos
 | |
|             chunk = html.a(row[pos:end], href="#" + item.listnames(),
 | |
|                      name=item.listnames())
 | |
|             return [row[:pos], chunk, row[end:]]
 | |
|         except KeyError:
 | |
|             return [row] # no more info
 | |
| 
 | |
| def prepare_line(text, tokenizer, encoding):
 | |
|     """ adds html formatting to text items (list)
 | |
| 
 | |
|         only processes items if they're of a string type (or unicode)
 | |
|     """
 | |
|     ret = []
 | |
|     for item in text:
 | |
|         if type(item) in [str, unicode]:
 | |
|             tokens = tokenizer.tokenize(item)
 | |
|             for t in tokens:
 | |
|                 if not isinstance(t.data, unicode):
 | |
|                     data = unicode(t.data, encoding)
 | |
|                 else:
 | |
|                     data = t.data
 | |
|                 if t.type in ['keyword', 'alt_keyword', 'number',
 | |
|                               'string', 'comment']:
 | |
|                     ret.append(html.span(data, class_=t.type))
 | |
|                 else:
 | |
|                     ret.append(data)
 | |
|         else:
 | |
|             ret.append(item)
 | |
|     return ret
 | |
| 
 | |
| def prepare_module(path, tokenizer, encoding):
 | |
|     path = py.path.local(path)
 | |
|     try:
 | |
|         mod = parse_path(path)
 | |
|     except:
 | |
|         # XXX don't try to catch SystemExit: it's actually raised by one
 | |
|         # of the modules in the py lib on import :(
 | |
|         exc, e, tb = py.std.sys.exc_info()
 | |
|         del tb
 | |
|         raise CompilationException('while compiling %s: %s - %s' % (
 | |
|                                     path, e.__class__.__name__, e))
 | |
|     lines = [unicode(l, encoding) for l in path.readlines()]
 | |
|     
 | |
|     enchanter = HtmlEnchanter(mod)
 | |
|     ret = []
 | |
|     for i, line in enumerate(lines):
 | |
|         text = enchanter.enchant_row(i + 1, line)
 | |
|         if text == ['']:
 | |
|             text = [raw(' ')]
 | |
|         else:
 | |
|             text = prepare_line(text, tokenizer, encoding)
 | |
|         ret.append(text)
 | |
|     return ret
 | |
| 
 | |
| class HTMLDocument(object):
 | |
|     def __init__(self, encoding, tokenizer=None):
 | |
|         self.encoding = encoding
 | |
| 
 | |
|         self.html = root = html.html()
 | |
|         self.head = head = self.create_head()
 | |
|         root.append(head)
 | |
|         self.body = body = self.create_body()
 | |
|         root.append(body)
 | |
|         self.table, self.tbody = table, tbody = self.create_table()
 | |
|         body.append(table)
 | |
| 
 | |
|         if tokenizer is None:
 | |
|             tokenizer = Tokenizer(PythonSchema)
 | |
|         self.tokenizer = tokenizer
 | |
| 
 | |
|     def create_head(self):
 | |
|         return html.head(
 | |
|             html.title('source view'),
 | |
|             html.style("""
 | |
|                 body, td {
 | |
|                     background-color: #FFF;
 | |
|                     color: black;
 | |
|                     font-family: monospace, Monaco;
 | |
|                 }
 | |
| 
 | |
|                 table, tr {
 | |
|                     margin: 0px;
 | |
|                     padding: 0px;
 | |
|                     border-width: 0px;
 | |
|                 }
 | |
| 
 | |
|                 a {
 | |
|                     color: blue;
 | |
|                     font-weight: bold;
 | |
|                     text-decoration: none;
 | |
|                 }
 | |
|                 
 | |
|                 a:hover {
 | |
|                     color: #005;
 | |
|                 }
 | |
|                 
 | |
|                 .lineno {
 | |
|                     text-align: right;
 | |
|                     color: #555;
 | |
|                     width: 3em;
 | |
|                     padding-right: 1em;
 | |
|                     border: 0px solid black;
 | |
|                     border-right-width: 1px;
 | |
|                 }
 | |
|                 
 | |
|                 .code {
 | |
|                     padding-left: 1em;
 | |
|                     white-space: pre;
 | |
|                 }
 | |
|                 
 | |
|                 .comment {
 | |
|                     color: purple;
 | |
|                 }
 | |
| 
 | |
|                 .string {
 | |
|                     color: #777;
 | |
|                 }
 | |
| 
 | |
|                 .keyword {
 | |
|                     color: blue;
 | |
|                 }
 | |
| 
 | |
|                 .alt_keyword {
 | |
|                     color: green;
 | |
|                 }
 | |
|                 
 | |
|             """, type='text/css'),
 | |
|         )
 | |
| 
 | |
|     def create_body(self):
 | |
|         return html.body()
 | |
| 
 | |
|     def create_table(self):
 | |
|         table = html.table(cellpadding='0', cellspacing='0')
 | |
|         tbody = html.tbody()
 | |
|         table.append(tbody)
 | |
|         return table, tbody
 | |
| 
 | |
|     def add_row(self, lineno, text):
 | |
|         if text == ['']:
 | |
|             text = [raw(' ')]
 | |
|         else:
 | |
|             text = prepare_line(text, self.tokenizer, self.encoding)
 | |
|         self.tbody.append(html.tr(html.td(str(lineno), class_='lineno'),
 | |
|                                   html.td(class_='code', *text)))
 | |
| 
 | |
|     def __unicode__(self):
 | |
|         # XXX don't like to use indent=0 here, but else py.xml's indentation
 | |
|         # messes up the html inside the table cells (which displays formatting)
 | |
|         return self.html.unicode(indent=0)
 | |
| 
 | |
| def create_html(mod):
 | |
|     # out is some kind of stream
 | |
|     #*[html.tr(html.td(i.name)) for i in mod.get_children()]
 | |
|     lines = mod.path.open().readlines()
 | |
|     
 | |
|     enchanter = HtmlEnchanter(mod)
 | |
|     enc = get_module_encoding(mod.path)
 | |
|     doc = HTMLDocument(enc)
 | |
|     for i, row in enumerate(lines):
 | |
|         row = enchanter.enchant_row(i + 1, row)
 | |
|         doc.add_row(i + 1, row)
 | |
|     return unicode(doc)
 | |
| 
 | |
| style = html.style("""
 | |
|   
 | |
|   body, p, td {
 | |
|     background-color: #FFF;
 | |
|     color: black;
 | |
|     font-family: monospace, Monaco;
 | |
|   }
 | |
| 
 | |
|   td.type {
 | |
|     width: 2em;
 | |
|   }
 | |
| 
 | |
|   td.name {
 | |
|     width: 30em;
 | |
|   }
 | |
| 
 | |
|   td.mtime {
 | |
|     width: 13em;
 | |
|   }
 | |
| 
 | |
|   td.size {
 | |
|     text-alignment: right;
 | |
|   }
 | |
| 
 | |
| """)
 | |
| 
 | |
| def create_dir_html(path, href_prefix=''):
 | |
|     h = html.html(
 | |
|         html.head(
 | |
|             html.title('directory listing of %s' % (path,)),
 | |
|             style,
 | |
|         ),
 | |
|     )
 | |
|     body = html.body(
 | |
|         html.h1('directory listing of %s' % (path,)),
 | |
|     )
 | |
|     h.append(body)
 | |
|     table = html.table()
 | |
|     body.append(table)
 | |
|     tbody = html.tbody()
 | |
|     table.append(tbody)
 | |
|     items = list(path.listdir())
 | |
|     items.sort(key=lambda p: p.basename)
 | |
|     items.sort(key=lambda p: not p.check(dir=True))
 | |
|     for fpath in items:
 | |
|         tr = html.tr()
 | |
|         tbody.append(tr)
 | |
|         td1 = html.td(fpath.check(dir=True) and 'D' or 'F', class_='type')
 | |
|         tr.append(td1)
 | |
|         href = fpath.basename
 | |
|         if href_prefix:
 | |
|             href = '%s%s' % (href_prefix, href)
 | |
|         if fpath.check(dir=True):
 | |
|             href += '/'
 | |
|         td2 = html.td(html.a(fpath.basename, href=href), class_='name')
 | |
|         tr.append(td2)
 | |
|         td3 = html.td(time.strftime('%Y-%m-%d %H:%M:%S',
 | |
|                       time.gmtime(fpath.mtime())), class_='mtime')
 | |
|         tr.append(td3)
 | |
|         if fpath.check(dir=True):
 | |
|             size = ''
 | |
|             unit = ''
 | |
|         else:
 | |
|             size = fpath.size()
 | |
|             unit = 'B'
 | |
|             for u in ['kB', 'MB', 'GB', 'TB']:
 | |
|                 if size > 1024:
 | |
|                     size = round(size / 1024.0, 2)
 | |
|                     unit = u
 | |
|         td4 = html.td('%s %s' % (size, unit), class_='size')
 | |
|         tr.append(td4)
 | |
|     return unicode(h)
 | |
| 
 | |
| def create_unknown_html(path):
 | |
|     h = html.html(
 | |
|         html.head(
 | |
|             html.title('Can not display page'),
 | |
|             style,
 | |
|         ),
 | |
|         html.body(
 | |
|             html.p('The data URL (%s) does not contain Python code.' % (path,))
 | |
|         ),
 | |
|     )
 | |
|     return h.unicode()
 | |
| 
 | |
| _reg_enc = py.std.re.compile(r'coding[:=]\s*([-\w.]+)')
 | |
| def get_module_encoding(path):
 | |
|     if hasattr(path, 'strpath'):
 | |
|         path = path.strpath
 | |
|     if path[-1] in ['c', 'o']:
 | |
|         path = path[:-1]
 | |
|     fpath = py.path.local(path)
 | |
|     fp = fpath.open()
 | |
|     lines = []
 | |
|     try:
 | |
|         # encoding is only allowed in the first two lines
 | |
|         for i in range(2):
 | |
|             lines.append(fp.readline())
 | |
|     finally:
 | |
|         fp.close()
 | |
|     match = _reg_enc.search('\n'.join(lines))
 | |
|     if match:
 | |
|         return match.group(1)
 | |
|     return 'ISO-8859-1'
 | |
| 
 |