Changeset 107:5b3eea549cfb

Show
Ignore:
Timestamp:
2007-08-01 17:40:08 (1 year ago)
Author:
Stefan Schwarzer <sschwarzer@sschwarzer.net>
branch:
default
Message:
Extracted conversions to HTML to module `converter.py`.
Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • websourcebrowser.py

    r105 r107  
    2525import BaseHTTPServer 
    2626import cgi 
     27import email.Utils 
     28import httplib 
     29import mimetypes 
     30import os 
     31import sys 
     32import urllib 
     33 
    2734import coding 
    2835import config 
    29 import email.Utils 
    30 import httplib 
    31 import itertools 
    32 import fnmatch 
    33 import mimetypes 
    34 import optparse 
    35 import os 
    36 import random 
    37 import stat 
    38 import sys 
    39 import urllib 
     36import converter 
     37import pygments_finder 
    4038import urlpath 
    41  
    42 try: 
    43     import pygments 
    44     from pygments import lexers 
    45     from pygments import formatters 
    46     IMPORTED_PYGMENTS = True 
    47 except ImportError: 
    48     IMPORTED_PYGMENTS = False 
    49  
    50 CSS_FILE_NAME = "websourcebrowser.css" 
    51 # window target for files to display 
    52 SOURCE_WINDOW_TARGET = "websourcebrowser_source" 
    53 # used to distinguish actual files from directories or files for 
    54 #  internal use, e. g. style sheets 
    55 #SPECIAL_DIR = str(random.randrange(1000000, 10000000)) 
    56 SPECIAL_DIR = "269b89b5c7930683" 
    5739 
    5840 
     
    9274    return absolute_path.startswith(config.root + os.sep) 
    9375 
    94 def dir_level(path): 
    95     """ 
    96     Return the "depth" of a directory, here defined as the number of 
    97     directory separators in the string `path`. 
    98     """ 
    99     return path.count(os.sep) 
    100  
    101 def walk(path, depth=sys.maxint, _max_level=None): 
    102     """ 
    103     Return a generator function which recursively yields the items 
    104     (directories and files) with their full paths, starting at the 
    105     root `path`. On each level, the items are sorted with directories 
    106     first, then regular files. 
    107  
    108     If `depth` is given, it's taken for the maximum recursion depth of 
    109     the algorithm, i. e. if `depth` is 1, no recursion is done, only 
    110     the directories and files in `path` are listed. By default, the 
    111     directory `path` with all its nested subdirectories is visited. 
    112  
    113     Like with `os.walk`, if a directory is really a symbolic link, it 
    114     will be listed but not visited to avoid infinite link cycles. 
    115     """ 
    116     path = os.path.abspath(path) 
    117     if _max_level is None: 
    118         _max_level = dir_level(path) + depth + 1 
    119     # `listdir` doesn't return items with path separators 
    120     dirs_and_files = os.listdir(path) 
    121     # filter out other items than directories and files and add a 
    122     #  priority value for sorting 
    123     for index, item in enumerate(dirs_and_files): 
    124         joined_path = os.path.join(path, item) 
    125         try: 
    126             item_mode = os.stat(joined_path)[0] 
    127         except OSError: 
    128             priority = 0 
    129             dirs_and_files[index] = (priority, item) 
    130             continue 
    131         if stat.S_ISDIR(item_mode): 
    132             priority = 1 
    133         elif stat.S_ISREG(item_mode): 
    134             priority = 2 
    135         else: 
    136             # ignore sockets, device files etc. 
    137             priority = 0 
    138         # prepend priority value (1 for directories, 2 for files, 0 else) 
    139         dirs_and_files[index] = (priority, item) 
    140     # remove items with priority 0 
    141     dirs_and_files = [item for item in dirs_and_files if item[0]] 
    142     # sort by priority, then basename 
    143     dirs_and_files.sort() 
    144     # yield the sorted items, if necessary, recursively 
    145     for priority, item in dirs_and_files: 
    146         item = os.path.join(path, item) 
    147         yield item 
    148         is_directory = (priority == 1) 
    149         follow_non_link = is_directory and not os.path.islink(item) 
    150         levels_left = (dir_level(item) + 1 < _max_level) 
    151         if is_directory and follow_non_link and levels_left: 
    152             for inner_item in walk(item, _max_level=_max_level): 
    153                 yield inner_item 
    15476 
    15577 
     
    187109              refresh_html=refresh_html) 
    188110 
    189     # 
    190     # content to HTML converters 
    191     # 
    192     def _ignore_item(self, item): 
    193         """ 
    194         Return `True` if the path `item` should be omitted from the 
    195         list of directories and files, else return `False`. 
    196         """ 
    197         for pattern in config.ignore_patterns: 
    198             if fnmatch.fnmatch(item, pattern): 
    199                 return True 
    200         return False 
    201  
    202     def _dir_to_html(self, path): 
    203         """ 
    204         Return HTML code unicode string for the directory `path`. 
    205         """ 
    206         path = os.path.normpath(path) 
    207         start_level = dir_level(path) 
    208         html_parts = [] 
    209         # show horizontal menu of directory levels 
    210         html_parts.append('<p class="Options">') 
    211         html_parts.append('Directory levels: ') 
    212         for arg in ("1", "2", "3", "all"): 
    213             if arg == "all": 
    214                 numeric_arg = sys.maxint 
    215             else: 
    216                 numeric_arg = int(arg) 
    217             if numeric_arg == config.dir_levels: 
    218                 html_parts.append(u'&nbsp;%s&nbsp;&nbsp;' % arg) 
    219             else: 
    220                 old_url = path.replace(config.root, "") or "/" 
    221                 old_url = urllib.quote(old_url) 
    222                 html_parts.append( 
    223                   (u'<a href="/%s/set?dir_levels=%s&old_url=%s">' 
    224                    u'&nbsp;%s&nbsp;</a>&nbsp;') % 
    225                   (SPECIAL_DIR, arg, old_url, arg)) 
    226         html_parts.append('</p>') 
    227         # show directory listing 
    228         html_parts.append(u'<table>') 
    229         if path != config.root: 
    230             html_parts.append(u'<tr><td><a href="..">[up]</a></td></tr>') 
    231         for item in itertools.ifilterfalse(self._ignore_item, 
    232                     walk(path, config.dir_levels)): 
    233             # if the item is a directory, append a slash 
    234             if os.path.isdir(item): 
    235                 mode_char = u"&nbsp;/" 
    236                 target_attribute = u"" 
    237             else: 
    238                 mode_char = "" 
    239                 target_attribute = u' target="%s"' % SOURCE_WINDOW_TARGET 
    240             spacing = config.dir_indent * u'&nbsp;' * \ 
    241                       (dir_level(item) - start_level - 1) 
    242             link_text = cgi.escape(coding.decode(os.path.basename(item))) 
    243             link = u'<a href="%s"%s>%s</a>' % ( 
    244                    urllib.quote(item.replace(config.root, "")), 
    245                    target_attribute, link_text) 
    246             html_parts.append(u'<tr><td>%s%s%s</td></tr>' % 
    247                               (spacing, link, mode_char)) 
    248         html_parts.append(u'</table>') 
    249         return u"\n".join(html_parts) 
    250  
    251     def _dumb_text_to_html(self, text): 
    252         """ 
    253         Return an HTML representation of the `text`. The part "dumb" 
    254         in the name of the method means that the method does the 
    255         minimally necessary work. 
    256         """ 
    257         return u"\n".join(('<pre>', cgi.escape(text), '</pre>')) 
    258  
    259     def _text_to_html(self, text, path): 
    260         """ 
    261         Convert the unicode string `text` to HTML and return the HTML 
    262         code, also as unicode string. Use the `path` of a text file to 
    263         guess the MIME type, which is used for coloring source code. 
    264         """ 
    265         if not IMPORTED_PYGMENTS: 
    266             return self._dumb_text_to_html(text) 
    267         try: 
    268             # assume the link doesn't point to a link or at least has 
    269             #  a sensible name 
    270             if os.path.islink(path): 
    271                 path = os.path.abspath(os.readlink(path)) 
    272             lexer = lexers.guess_lexer_for_filename(path, text) 
    273         except lexers.ClassNotFound: 
    274             # files with many extensions are actually XML files, so 
    275             #  test explicitly for them 
    276             if text.lstrip().startswith(u'<?xml '): 
    277                 lexer = lexers.XmlLexer() 
    278             else: 
    279                 lexer = lexers.TextLexer() 
    280         formatter = formatters.HtmlFormatter(linenos=config.line_numbers) 
    281         return pygments.highlight(text, lexer, formatter) 
    282  
    283     def _image_to_html(self, url): 
    284         """ 
    285         Return HTML code (as unicode) for an image link for an image 
    286         which can be found under the absolute `url`. 
    287         """ 
    288         image_source = "/%s/raw_data?url=%s" % (SPECIAL_DIR, 
    289                                                 urllib.quote(url)) 
    290         return u'<img src="%s" />' % image_source 
    291  
    292     def _binary_to_html(self, data): 
    293         """ 
    294         Return HTML code (as unicode) for supposed binary `data` in 
    295         form of a hex dump. The `data` must have been read in binary 
    296         mode. 
    297         """ 
    298         # based on 
    299         #  http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/142812 
    300         FILTER = ''.join( 
    301           [(len(repr(chr(x)))==3) and chr(x) or '.' for x in range(256)]) 
    302         length = 16 
    303         result = [] 
    304         for i in xrange(0, len(data), length): 
    305             s = data[i:i+length] 
    306             hexa = ' '.join(["%02X" % ord(x) for x in s]) 
    307             if len(s) > length // 2: 
    308                 hexa = "%s %s" % (hexa[:23], hexa[23:]) 
    309             printable = s.translate(FILTER) 
    310             result.append("%08X    %-*s    %s\n" % (i, length*3, hexa, 
    311                                                     printable)) 
    312         return self._dumb_text_to_html(''.join(result)) 
    313111 
    314112    # 
     
    352150              'project_title': cgi.escape(config.project_title), 
    353151              'h1_class': h1_class, 
    354               'special_dir': SPECIAL_DIR, 
     152              'special_dir': config.SPECIAL_DIR, 
    355153              'refresh_html': refresh_html})) 
    356154        if content_type.startswith("text/"): 
     
    365163        Handle HTTP GET request. 
    366164        """ 
    367         if self.path.startswith("/%s/" % SPECIAL_DIR): 
     165        if self.path.startswith("/%s/" % config.SPECIAL_DIR): 
    368166            #TODO move the builtins handling into a new method 
    369167            #  `handle_builtin` 
    370168            # determine builtin command/file 
    371             builtin = self.path[2+len(SPECIAL_DIR):] 
    372             if builtin == CSS_FILE_NAME: 
     169            builtin = self.path[2+len(config.SPECIAL_DIR):] 
     170            if builtin == config.CSS_FILE_NAME: 
    373171                self._emit_data(self._get_file(actual_css_path()), 
    374172                                content_type="text/css", 
     
    398196        else: 
    399197            if os.path.isdir(path): 
    400                 html = self._dir_to_html(path) 
     198                html = converter.dir_to_html(path) 
    401199            elif self._is_image_path(path): 
    402                 html = self._image_to_html(self.path) 
     200                html = converter.image_to_html(self.path) 
    403201            else: 
    404202                data = self._get_file(path, raw=True, 
     
    406204                if self._is_binary(data): 
    407205                    data = self._get_file(path, raw=True) 
    408                     html = self._binary_to_html(data) 
     206                    html = converter.binary_to_html(data) 
    409207                else: 
    410208                    text = self._get_file(path) 
    411                     html = self._text_to_html(text, path) 
     209                    html = converter.text_to_html(text, path) 
    412210            # assume title is UTF-8-encoded though we don't know for sure 
    413211            title = coding.decode(self.path[1:]) or u"." 
     
    468266    print ("Type http://localhost:%d/ into the address field of your " \ 
    469267           "browser.") % config.http_port 
    470     if IMPORTED_PYGMENTS
     268    if pygments_finder.found_pygments
    471269        print "Using Pygments library for syntax highlighting." 
    472270    else: