| 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 |
|---|
| 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' %s ' % 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' %s </a> ') % |
|---|
| 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" /" |
|---|
| 236 | | target_attribute = u"" |
|---|
| 237 | | else: |
|---|
| 238 | | mode_char = "" |
|---|
| 239 | | target_attribute = u' target="%s"' % SOURCE_WINDOW_TARGET |
|---|
| 240 | | spacing = config.dir_indent * u' ' * \ |
|---|
| 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)) |
|---|