| 1 |
# vim: ts=4 expandtab |
|---|
| 2 |
# |
|---|
| 3 |
# Copyright (C) 2005 Jason Parks <jparks@jparks.net>. All rights reserved. |
|---|
| 4 |
# |
|---|
| 5 |
|
|---|
| 6 |
from __future__ import generators |
|---|
| 7 |
|
|---|
| 8 |
import os |
|---|
| 9 |
import time |
|---|
| 10 |
import posixpath |
|---|
| 11 |
import re |
|---|
| 12 |
import mimetypes |
|---|
| 13 |
|
|---|
| 14 |
from trac.config import Option |
|---|
| 15 |
from trac.core import * |
|---|
| 16 |
from trac.web import IRequestHandler |
|---|
| 17 |
from trac.perm import IPermissionRequestor |
|---|
| 18 |
from trac.web.chrome import INavigationContributor, ITemplateProvider, \ |
|---|
| 19 |
add_stylesheet |
|---|
| 20 |
from trac.Search import ISearchSource |
|---|
| 21 |
from trac.wiki import WikiSystem, IWikiSyntaxProvider |
|---|
| 22 |
from trac.wiki.model import WikiPage |
|---|
| 23 |
from trac.wiki.formatter import wiki_to_html |
|---|
| 24 |
from trac.util.html import html |
|---|
| 25 |
|
|---|
| 26 |
def compare_rank(x, y): |
|---|
| 27 |
if x['rank'] == y['rank']: |
|---|
| 28 |
return 0 |
|---|
| 29 |
elif x['rank'] > y['rank']: |
|---|
| 30 |
return -1 |
|---|
| 31 |
return 1 |
|---|
| 32 |
|
|---|
| 33 |
class DoxygenPlugin(Component): |
|---|
| 34 |
implements(IPermissionRequestor, INavigationContributor, IRequestHandler, |
|---|
| 35 |
ITemplateProvider, ISearchSource, IWikiSyntaxProvider) |
|---|
| 36 |
|
|---|
| 37 |
base_path = Option('doxygen', 'path', '/var/lib/trac/doxygen', |
|---|
| 38 |
"""Directory containing doxygen generated files.""") |
|---|
| 39 |
|
|---|
| 40 |
default_doc = Option('doxygen', 'default_documentation', '', |
|---|
| 41 |
"""Default documentation project, relative to `[doxygen] path`. |
|---|
| 42 |
When no explicit path is given in a documentation request, |
|---|
| 43 |
this path will be prepended to the request before looking |
|---|
| 44 |
for documentation files.""") |
|---|
| 45 |
|
|---|
| 46 |
html_output = Option('doxygen', 'html_output', None, |
|---|
| 47 |
"""Default documentation project suffix, as generated by Doxygen |
|---|
| 48 |
using the HTML_OUTPUT Doxygen configuration setting.""") |
|---|
| 49 |
|
|---|
| 50 |
title = Option('doxygen', 'title', 'Doxygen', |
|---|
| 51 |
"""Title to use for the main navigation tab.""") |
|---|
| 52 |
|
|---|
| 53 |
ext = Option('doxygen', 'ext', 'htm html png', |
|---|
| 54 |
"""Space separated list of extensions for doxygen managed files.""") |
|---|
| 55 |
|
|---|
| 56 |
source_ext = Option('doxygen', 'source_ext', |
|---|
| 57 |
'idl odl java cs py php php4 inc phtml m ' |
|---|
| 58 |
'cpp cxx c hpp hxx h', |
|---|
| 59 |
"""Space separated list of source files extensions""") |
|---|
| 60 |
|
|---|
| 61 |
index = Option('doxygen', 'index', 'main.html', |
|---|
| 62 |
"""Default index page to pick in the generated documentation.""") |
|---|
| 63 |
|
|---|
| 64 |
wiki_index = Option('doxygen', 'wiki_index', None, |
|---|
| 65 |
"""Wiki page to use as the default page for the Doxygen main page. |
|---|
| 66 |
If set, supersedes the `[doxygen] index` option.""") |
|---|
| 67 |
|
|---|
| 68 |
encoding = Option('doxygen', 'encoding', 'iso-8859-1', |
|---|
| 69 |
"""Default encoding used by the generated documentation files.""") |
|---|
| 70 |
|
|---|
| 71 |
SUMMARY_PAGES = """ |
|---|
| 72 |
annotated classes dirs files functions globals hierarchy |
|---|
| 73 |
index inherits main namespaces namespacemembers |
|---|
| 74 |
""".split() |
|---|
| 75 |
|
|---|
| 76 |
# IPermissionRequestor methods |
|---|
| 77 |
|
|---|
| 78 |
def get_permission_actions(self): |
|---|
| 79 |
return ['DOXYGEN_VIEW'] |
|---|
| 80 |
|
|---|
| 81 |
# INavigationContributor methods |
|---|
| 82 |
|
|---|
| 83 |
def get_active_navigation_item(self, req): |
|---|
| 84 |
return 'doxygen' |
|---|
| 85 |
|
|---|
| 86 |
def get_navigation_items(self, req): |
|---|
| 87 |
if req.perm.has_permission('DOXYGEN_VIEW'): |
|---|
| 88 |
# Return mainnav buttons. |
|---|
| 89 |
yield 'mainnav', 'doxygen', html.a(self.title, |
|---|
| 90 |
href=req.href.doxygen()) |
|---|
| 91 |
|
|---|
| 92 |
# IRequestHandler methods |
|---|
| 93 |
|
|---|
| 94 |
def match_request(self, req): |
|---|
| 95 |
if re.match(r'^/doxygen(?:$|/)', req.path_info): |
|---|
| 96 |
if 'path' not in req.args: # not coming from a `doxygen:` link |
|---|
| 97 |
segments = filter(None, req.path_info.split('/')) |
|---|
| 98 |
segments = segments[1:] # ditch 'doxygen' |
|---|
| 99 |
if segments: |
|---|
| 100 |
action, path, link = self._doxygen_lookup(segments) |
|---|
| 101 |
if action == 'search' and link: |
|---|
| 102 |
req.args['query'] = link |
|---|
| 103 |
elif action == 'redirect': |
|---|
| 104 |
req.args['link'] = link |
|---|
| 105 |
else: |
|---|
| 106 |
action, path = 'index', '' |
|---|
| 107 |
req.args['action'] = action |
|---|
| 108 |
req.args['path'] = path |
|---|
| 109 |
return True |
|---|
| 110 |
|
|---|
| 111 |
def process_request(self, req): |
|---|
| 112 |
req.perm.assert_permission('DOXYGEN_VIEW') |
|---|
| 113 |
|
|---|
| 114 |
# Get request arguments |
|---|
| 115 |
path = req.args.get('path') |
|---|
| 116 |
action = req.args.get('action') |
|---|
| 117 |
link = req.args.get('link') |
|---|
| 118 |
|
|---|
| 119 |
self.log.debug('Performing %s(%s,%s)"' % (action or 'default', |
|---|
| 120 |
path, link)) |
|---|
| 121 |
|
|---|
| 122 |
# Redirect search requests. |
|---|
| 123 |
if action == 'search': |
|---|
| 124 |
req.redirect(req.href.search(q=req.args.get('query'), |
|---|
| 125 |
doxygen='on')) |
|---|
| 126 |
if action == 'redirect': |
|---|
| 127 |
if link: # we need to really redirect if there is a link |
|---|
| 128 |
if path: |
|---|
| 129 |
req.redirect(req.href.doxygen(path=path)+link) |
|---|
| 130 |
else: |
|---|
| 131 |
req.redirect(req.href.doxygen(link)) |
|---|
| 132 |
else: |
|---|
| 133 |
self.log.warn("redirect without link") |
|---|
| 134 |
|
|---|
| 135 |
if req.path_info == '/doxygen': |
|---|
| 136 |
req.redirect(req.href.doxygen('/')) |
|---|
| 137 |
|
|---|
| 138 |
# Handle /doxygen request |
|---|
| 139 |
if action == 'index': |
|---|
| 140 |
wiki = self.wiki_index |
|---|
| 141 |
if wiki: |
|---|
| 142 |
if WikiSystem(self.env).has_page(wiki): |
|---|
| 143 |
text = WikiPage(self.env, wiki).text |
|---|
| 144 |
else: |
|---|
| 145 |
text = 'Doxygen index page [wiki:%s] does not exist.' % \ |
|---|
| 146 |
wiki |
|---|
| 147 |
text = wiki_to_html(text, self.env, req) |
|---|
| 148 |
req.hdf['doxygen.text'] = text |
|---|
| 149 |
req.hdf['doxygen.wiki_href'] = req.href.wiki(wiki) |
|---|
| 150 |
req.hdf['doxygen.wiki_page'] = wiki |
|---|
| 151 |
return 'doxygen.cs', 'text/html' |
|---|
| 152 |
|
|---|
| 153 |
# use configured Doxygen index |
|---|
| 154 |
path = os.path.join(self.base_path, self.default_doc, |
|---|
| 155 |
self.html_output, self.index) |
|---|
| 156 |
|
|---|
| 157 |
self.log.debug('path: %s' % (path,)) |
|---|
| 158 |
|
|---|
| 159 |
# security check |
|---|
| 160 |
path = os.path.abspath(path) |
|---|
| 161 |
if not path.startswith(self.base_path): |
|---|
| 162 |
raise TracError("Can't access paths outside of " + self.base_path) |
|---|
| 163 |
|
|---|
| 164 |
# view |
|---|
| 165 |
mimetype = mimetypes.guess_type(path)[0] |
|---|
| 166 |
if mimetype == 'text/html': |
|---|
| 167 |
add_stylesheet(req, 'doxygen/css/doxygen.css') |
|---|
| 168 |
req.hdf['doxygen.path'] = path |
|---|
| 169 |
return 'doxygen.cs', 'text/html' |
|---|
| 170 |
else: |
|---|
| 171 |
req.send_file(path, mimetype) |
|---|
| 172 |
|
|---|
| 173 |
# ITemplateProvider methods |
|---|
| 174 |
|
|---|
| 175 |
def get_htdocs_dirs(self): |
|---|
| 176 |
from pkg_resources import resource_filename |
|---|
| 177 |
return [('doxygen', resource_filename(__name__, 'htdocs'))] |
|---|
| 178 |
|
|---|
| 179 |
def get_templates_dirs(self): |
|---|
| 180 |
from pkg_resources import resource_filename |
|---|
| 181 |
return [resource_filename(__name__, 'templates')] |
|---|
| 182 |
|
|---|
| 183 |
# ISearchProvider methods |
|---|
| 184 |
|
|---|
| 185 |
def get_search_filters(self, req): |
|---|
| 186 |
if req.perm.has_permission('DOXYGEN_VIEW'): |
|---|
| 187 |
yield('doxygen', self.title) |
|---|
| 188 |
|
|---|
| 189 |
def get_search_results(self, req, keywords, filters): |
|---|
| 190 |
if not 'doxygen' in filters: |
|---|
| 191 |
return |
|---|
| 192 |
|
|---|
| 193 |
# We have to search for the raw bytes... |
|---|
| 194 |
keywords = [k.encode(self.encoding) for k in keywords] |
|---|
| 195 |
|
|---|
| 196 |
for doc in os.listdir(self.base_path): |
|---|
| 197 |
# Search in documentation directories |
|---|
| 198 |
path = os.path.join(self.base_path, doc) |
|---|
| 199 |
path = os.path.join(path, self.html_output) |
|---|
| 200 |
if os.path.isdir(path): |
|---|
| 201 |
index = os.path.join(path, 'search.idx') |
|---|
| 202 |
if os.path.exists(index): |
|---|
| 203 |
creation = os.path.getctime(index) |
|---|
| 204 |
for result in self._search_in_documentation(doc, keywords): |
|---|
| 205 |
result['url'] = req.href.doxygen(doc) + '/' \ |
|---|
| 206 |
+ result['url'] |
|---|
| 207 |
yield result['url'], result['name'], creation, \ |
|---|
| 208 |
'doxygen', None |
|---|
| 209 |
|
|---|
| 210 |
# Search in common documentation directory |
|---|
| 211 |
index = os.path.join(self.base_path, self.html_output) |
|---|
| 212 |
index = os.path.join(index, 'search.idx') |
|---|
| 213 |
if os.path.exists(index): |
|---|
| 214 |
creation = os.path.getctime(index) |
|---|
| 215 |
for result in self._search_in_documentation('', keywords): |
|---|
| 216 |
result['url'] = req.href.doxygen() + '/' + \ |
|---|
| 217 |
result['url'] |
|---|
| 218 |
yield result['url'], result['name'], creation, 'doxygen', \ |
|---|
| 219 |
None |
|---|
| 220 |
|
|---|
| 221 |
# IWikiSyntaxProvider |
|---|
| 222 |
|
|---|
| 223 |
def get_link_resolvers(self): |
|---|
| 224 |
def doxygen_link(formatter, ns, params, label): |
|---|
| 225 |
if '/' not in params: |
|---|
| 226 |
params = self.default_doc+'/'+params |
|---|
| 227 |
segments = params.split('/') |
|---|
| 228 |
if self.html_output: |
|---|
| 229 |
segments[-1:-1] = [self.html_output] |
|---|
| 230 |
action, path, link = self._doxygen_lookup(segments) |
|---|
| 231 |
if action == 'index': |
|---|
| 232 |
return html.a(label, title=self.title, |
|---|
| 233 |
href=formatter.href.doxygen()) |
|---|
| 234 |
if action == 'redirect' and path: |
|---|
| 235 |
return html.a(label, title="Search result for "+params, |
|---|
| 236 |
href=formatter.href.doxygen(link,path=path)) |
|---|
| 237 |
if action == 'search': |
|---|
| 238 |
return html.a(label, title=params, class_='missing', |
|---|
| 239 |
href=formatter.href.doxygen()) |
|---|
| 240 |
else: |
|---|
| 241 |
return html.a(label, title=params, |
|---|
| 242 |
href=formatter.href.doxygen(link, path=path)) |
|---|
| 243 |
yield ('doxygen', doxygen_link) |
|---|
| 244 |
|
|---|
| 245 |
def get_wiki_syntax(self): |
|---|
| 246 |
return [] |
|---|
| 247 |
|
|---|
| 248 |
# internal methods |
|---|
| 249 |
|
|---|
| 250 |
def _doxygen_lookup(self, segments): |
|---|
| 251 |
"""Try to interpret path components as a request for doxygen targets |
|---|
| 252 |
|
|---|
| 253 |
Return an `(action,path,link)` tuple, where: |
|---|
| 254 |
- `action` describes what should be done (one of 'view', |
|---|
| 255 |
'redirect', or 'search'), |
|---|
| 256 |
- `path` is the location on disk of the resource. |
|---|
| 257 |
- `link` is the link to the resource, relative to the |
|---|
| 258 |
req.href.doxygen base or a target in case of 'redirect' |
|---|
| 259 |
""" |
|---|
| 260 |
doc, file = segments[:-1], segments and segments[-1] |
|---|
| 261 |
|
|---|
| 262 |
if not doc and not file: |
|---|
| 263 |
return ('index', None, None) |
|---|
| 264 |
if doc: |
|---|
| 265 |
doc = os.path.join(*doc) |
|---|
| 266 |
else: |
|---|
| 267 |
if self.default_doc: # we can't stay at the 'doxygen/' level |
|---|
| 268 |
return 'redirect', None, '/'.join([self.default_doc, |
|---|
| 269 |
self.html_output, |
|---|
| 270 |
file or self.index]) |
|---|
| 271 |
else: |
|---|
| 272 |
doc = self.html_output |
|---|
| 273 |
|
|---|
| 274 |
def lookup(file, category='undefined'): |
|---|
| 275 |
"""Build (full path, relative link) and check if path exists.""" |
|---|
| 276 |
path = os.path.join(self.base_path, doc, file) |
|---|
| 277 |
existing_path = os.path.exists(path) and path |
|---|
| 278 |
link = doc+'/'+file |
|---|
| 279 |
self.log.debug(' %s file %s' % (category, existing_path or |
|---|
| 280 |
path+" (not found)")) |
|---|
| 281 |
return existing_path, link |
|---|
| 282 |
|
|---|
| 283 |
self.log.debug('Looking up "%s" in documentation "%s"' % (file, doc)) |
|---|
| 284 |
|
|---|
| 285 |
# Direct request for searching |
|---|
| 286 |
if file == 'search.php': |
|---|
| 287 |
return 'search', None, None # keep existing 'query' arg |
|---|
| 288 |
|
|---|
| 289 |
# Request for a documentation file. |
|---|
| 290 |
doc_ext_re = '|'.join(self.ext.split(' ')) |
|---|
| 291 |
if re.match(r'''^(.*)[.](%s)''' % doc_ext_re, file): |
|---|
| 292 |
path, link = lookup(file, 'documentation') |
|---|
| 293 |
if path: |
|---|
| 294 |
return 'view', path, link |
|---|
| 295 |
else: |
|---|
| 296 |
return 'search', None, file |
|---|
| 297 |
|
|---|
| 298 |
# Request for source file documentation. |
|---|
| 299 |
source_ext_re = '|'.join(self.source_ext.split(' ')) |
|---|
| 300 |
match = re.match(r'''^(.*)[.](%s)''' % source_ext_re, file) |
|---|
| 301 |
if match: |
|---|
| 302 |
basename, suffix = match.groups() |
|---|
| 303 |
basename = basename.replace('_', '__') |
|---|
| 304 |
path, link = lookup('%s_8%s.html' % (basename, suffix), 'source') |
|---|
| 305 |
if path: |
|---|
| 306 |
return 'view', path, link |
|---|
| 307 |
else: |
|---|
| 308 |
return 'search', None, file |
|---|
| 309 |
|
|---|
| 310 |
# Request for summary pages |
|---|
| 311 |
if file in self.SUMMARY_PAGES: |
|---|
| 312 |
path, link = lookup(file + '.html', 'summary') |
|---|
| 313 |
if path: |
|---|
| 314 |
return 'view', path, link |
|---|
| 315 |
|
|---|
| 316 |
# Request for a named object |
|---|
| 317 |
# TODO: |
|---|
| 318 |
# - do something about dirs |
|---|
| 319 |
# - expand with enum, defs, etc. |
|---|
| 320 |
# - this doesn't work well with the CREATE_SUBDIRS Doxygen option |
|---|
| 321 |
path, link = lookup('class%s.html' % file, 'class') |
|---|
| 322 |
if not path: |
|---|
| 323 |
path, link = lookup('struct%s.html' % file, 'struct') |
|---|
| 324 |
if path: |
|---|
| 325 |
return 'view', path, link |
|---|
| 326 |
|
|---|
| 327 |
# Revert to search... |
|---|
| 328 |
results = self._search_in_documentation(doc, [file]) |
|---|
| 329 |
class_ref = file+' Class Reference' |
|---|
| 330 |
for result in results: |
|---|
| 331 |
self.log.debug('Reverted to search, found: ' + repr(result)) |
|---|
| 332 |
name = result['name'] |
|---|
| 333 |
if name == file or name == class_ref: |
|---|
| 334 |
url = result['url'] |
|---|
| 335 |
target = '' |
|---|
| 336 |
if '#' in url: |
|---|
| 337 |
url, target = url.split('#', 2) |
|---|
| 338 |
path, link = lookup(url) |
|---|
| 339 |
if path: |
|---|
| 340 |
return 'redirect', path, link # target # FIXME |
|---|
| 341 |
self.log.debug('%s not found in %s' % (file, doc)) |
|---|
| 342 |
return 'search', None, file |
|---|
| 343 |
|
|---|
| 344 |
def _search_in_documentation(self, doc, keywords): |
|---|
| 345 |
# Open index file for documentation |
|---|
| 346 |
index = os.path.join(self.base_path, doc, self.html_output, 'search.idx') |
|---|
| 347 |
if os.path.exists(index): |
|---|
| 348 |
fd = open(index) |
|---|
| 349 |
|
|---|
| 350 |
# Search for keywords in index |
|---|
| 351 |
results = [] |
|---|
| 352 |
for keyword in keywords: |
|---|
| 353 |
results += self._search(fd, keyword) |
|---|
| 354 |
results.sort(compare_rank) |
|---|
| 355 |
for result in results: |
|---|
| 356 |
yield result |
|---|
| 357 |
|
|---|
| 358 |
def _search(self, fd, word): |
|---|
| 359 |
results = [] |
|---|
| 360 |
index = self._computeIndex(word) |
|---|
| 361 |
if index != -1: |
|---|
| 362 |
fd.seek(index * 4 + 4, 0) |
|---|
| 363 |
index = self._readInt(fd) |
|---|
| 364 |
|
|---|
| 365 |
if index: |
|---|
| 366 |
fd.seek(index) |
|---|
| 367 |
w = self._readString(fd) |
|---|
| 368 |
matches = [] |
|---|
| 369 |
while w != "": |
|---|
| 370 |
statIdx = self._readInt(fd) |
|---|
| 371 |
low = word.lower() |
|---|
| 372 |
if w.find(low) != -1: |
|---|
| 373 |
matches.append({'word': word, 'match': w, |
|---|
| 374 |
'index': statIdx, 'full': len(low) == len(w)}) |
|---|
| 375 |
w = self._readString(fd) |
|---|
| 376 |
|
|---|
| 377 |
count = 0 |
|---|
| 378 |
totalHi = 0 |
|---|
| 379 |
totalFreqHi = 0 |
|---|
| 380 |
totalFreqLo = 0 |
|---|
| 381 |
|
|---|
| 382 |
for match in matches: |
|---|
| 383 |
multiplier = 1 |
|---|
| 384 |
if match['full']: |
|---|
| 385 |
multiplier = 2 |
|---|
| 386 |
|
|---|
| 387 |
fd.seek(match['index']) |
|---|
| 388 |
numDocs = self._readInt(fd) |
|---|
| 389 |
|
|---|
| 390 |
for i in range(numDocs): |
|---|
| 391 |
idx = self._readInt(fd) |
|---|
| 392 |
if idx == -1: |
|---|
| 393 |
freq = 0 |
|---|
| 394 |
else: |
|---|
| 395 |
freq = self._readInt(fd) |
|---|
| 396 |
results.append({'idx': idx, 'freq': freq >> 1, |
|---|
| 397 |
'hi': freq & 1, 'multi': multiplier}) |
|---|
| 398 |
if freq & 1: |
|---|
| 399 |
totalHi += 1 |
|---|
| 400 |
totalFreqHi += freq * multiplier |
|---|
| 401 |
else: |
|---|
| 402 |
totalFreqLo += freq * multiplier |
|---|
| 403 |
|
|---|
| 404 |
for i in range(numDocs): |
|---|
| 405 |
if results[count]['idx'] == -1: |
|---|
| 406 |
results[count]['name'] = '' |
|---|
| 407 |
results[count]['url'] = '' |
|---|
| 408 |
count += 1 |
|---|
| 409 |
continue |
|---|
| 410 |
fd.seek(results[count]['idx']) |
|---|
| 411 |
name = self._readString(fd) |
|---|
| 412 |
url = self._readString(fd) |
|---|
| 413 |
results[count]['name'] = name |
|---|
| 414 |
results[count]['url'] = self.html_output + '/' + url |
|---|
| 415 |
count += 1 |
|---|
| 416 |
|
|---|
| 417 |
totalFreq = (totalHi + 1) * totalFreqLo + totalFreqHi |
|---|
| 418 |
for i in range(count): |
|---|
| 419 |
freq = results[i]['freq'] |
|---|
| 420 |
multi = results[i]['multi'] |
|---|
| 421 |
if results[i]['hi']: |
|---|
| 422 |
results[i]['rank'] = float(freq*multi + totalFreqLo) \ |
|---|
| 423 |
/ float(totalFreq) |
|---|
| 424 |
else: |
|---|
| 425 |
results[i]['rank'] = float(freq*multi) \ |
|---|
| 426 |
/ float(totalFreq) |
|---|
| 427 |
return results |
|---|
| 428 |
|
|---|
| 429 |
def _computeIndex(self, word): |
|---|
| 430 |
if len(word) < 2: |
|---|
| 431 |
return -1 |
|---|
| 432 |
|
|---|
| 433 |
hi = ord(word[0].lower()) |
|---|
| 434 |
if hi == 0: |
|---|
| 435 |
return -1 |
|---|
| 436 |
|
|---|
| 437 |
lo = ord(word[1].lower()) |
|---|
| 438 |
if lo == 0: |
|---|
| 439 |
return -1 |
|---|
| 440 |
|
|---|
| 441 |
return hi * 256 + lo |
|---|
| 442 |
|
|---|
| 443 |
def _readInt(self, fd): |
|---|
| 444 |
b1 = fd.read(1) |
|---|
| 445 |
b2 = fd.read(1) |
|---|
| 446 |
b3 = fd.read(1) |
|---|
| 447 |
b4 = fd.read(1) |
|---|
| 448 |
|
|---|
| 449 |
if not b1 or not b2 or not b3 or not b4: |
|---|
| 450 |
return -1; |
|---|
| 451 |
|
|---|
| 452 |
return (ord(b1) << 24) | (ord(b2) << 16) | (ord(b3) << 8) | ord(b4) |
|---|
| 453 |
|
|---|
| 454 |
def _readString(self, fd): |
|---|
| 455 |
result = '' |
|---|
| 456 |
byte = fd.read(1) |
|---|
| 457 |
while byte != '\0': |
|---|
| 458 |
result = ''.join([result, byte]) |
|---|
| 459 |
byte = fd.read(1) |
|---|
| 460 |
return result |
|---|