166 lines
5.4 KiB
Python
166 lines
5.4 KiB
Python
import py
|
|
import os
|
|
html = py.xml.html
|
|
|
|
# this here to serve two functions: first it makes the proto part of the temp
|
|
# urls (see TempLinker) customizable easily (for tests and such) and second
|
|
# it makes sure the temp links aren't replaced in generated source code etc.
|
|
# for this file (and its tests) itself.
|
|
TEMPLINK_PROTO = 'apigen.temp'
|
|
|
|
def getrelfspath(dotted_name):
|
|
# XXX need to make sure its imported on non-py lib
|
|
return eval(dotted_name, {"py": py})
|
|
|
|
class LazyHref(object):
|
|
def __init__(self, linker, linkid):
|
|
self._linker = linker
|
|
self._linkid = linkid
|
|
|
|
def __unicode__(self):
|
|
return unicode(self._linker.get_target(self._linkid))
|
|
|
|
class Linker(object):
|
|
fromlocation = None
|
|
|
|
def __init__(self):
|
|
self._linkid2target = {}
|
|
|
|
def get_lazyhref(self, linkid):
|
|
return LazyHref(self, linkid)
|
|
|
|
def set_link(self, linkid, target):
|
|
assert (linkid not in self._linkid2target,
|
|
'linkid %r already used' % (linkid,))
|
|
self._linkid2target[linkid] = target
|
|
|
|
def get_target(self, linkid):
|
|
linktarget = self._linkid2target[linkid]
|
|
if self.fromlocation is not None:
|
|
linktarget = relpath(self.fromlocation, linktarget)
|
|
return linktarget
|
|
|
|
def call_withbase(self, base, func, *args, **kwargs):
|
|
assert self.fromlocation is None
|
|
self.fromlocation = base
|
|
try:
|
|
return func(*args, **kwargs)
|
|
finally:
|
|
del self.fromlocation
|
|
|
|
class TempLinker(object):
|
|
""" performs a similar role to the Linker, but with a different approach
|
|
|
|
instead of returning 'lazy' hrefs, this returns a simple URL-style
|
|
string
|
|
|
|
the 'temporary urls' are replaced on the filesystem after building the
|
|
files, so that means even though a second pass is still required,
|
|
things don't have to be built in-memory (as with the Linker)
|
|
"""
|
|
fromlocation = None
|
|
|
|
def __init__(self):
|
|
self._linkid2target = {}
|
|
|
|
def get_lazyhref(self, linkid, anchor=None):
|
|
href = '%s://%s' % (TEMPLINK_PROTO, linkid)
|
|
if anchor:
|
|
href += '#' + anchor
|
|
return href
|
|
|
|
def set_link(self, linkid, target):
|
|
assert linkid not in self._linkid2target
|
|
self._linkid2target[linkid] = target
|
|
|
|
def get_target(self, tempurl, fromlocation=None):
|
|
assert tempurl.startswith('%s://' % (TEMPLINK_PROTO,))
|
|
anchor = None
|
|
if '#' in tempurl:
|
|
tempurl, anchor = tempurl.split('#', 1)
|
|
linkid = tempurl.split('://', 1)[1]
|
|
linktarget = self._linkid2target[linkid]
|
|
if fromlocation is not None:
|
|
linktarget = relpath(fromlocation, linktarget)
|
|
if anchor is not None:
|
|
linktarget += '#' + anchor
|
|
return linktarget
|
|
|
|
_reg_tempurl = py.std.re.compile('(["\'])(%s:\/\/[^"\'\s#]*)(["\'#])' % (
|
|
TEMPLINK_PROTO,))
|
|
def replace_dirpath(self, dirpath, stoponerrors=True):
|
|
""" replace temporary links in all html files in dirpath and below """
|
|
for fpath in dirpath.visit('*.html'):
|
|
html = fpath.read()
|
|
while 1:
|
|
match = self._reg_tempurl.search(html)
|
|
if not match:
|
|
break
|
|
tempurl = match.group(2)
|
|
pre = match.group(1)
|
|
post = match.group(3)
|
|
try:
|
|
html = html.replace(match.group(0), pre +
|
|
self.get_target(tempurl,
|
|
fpath.relto(dirpath)) + post)
|
|
except KeyError:
|
|
if stoponerrors:
|
|
raise
|
|
html = html.replace(match.group(0), pre +
|
|
'apigen.notfound://%s' % (tempurl,) +
|
|
post)
|
|
fpath.write(html)
|
|
|
|
|
|
def relpath(p1, p2, sep=os.path.sep, back='..', normalize=True):
|
|
""" create a relative path from p1 to p2
|
|
|
|
sep is the seperator used for input and (depending
|
|
on the setting of 'normalize', see below) output
|
|
|
|
back is the string used to indicate the parent directory
|
|
|
|
when 'normalize' is True, any backslashes (\) in the path
|
|
will be replaced with forward slashes, resulting in a consistent
|
|
output on Windows and the rest of the world
|
|
|
|
paths to directories must end on a / (URL style)
|
|
"""
|
|
if normalize:
|
|
p1 = p1.replace(sep, '/')
|
|
p2 = p2.replace(sep, '/')
|
|
sep = '/'
|
|
# XXX would be cool to be able to do long filename expansion and drive
|
|
# letter fixes here, and such... iow: windows sucks :(
|
|
if (p1.startswith(sep) ^ p2.startswith(sep)):
|
|
raise ValueError("mixed absolute relative path: %r -> %r" %(p1, p2))
|
|
fromlist = p1.split(sep)
|
|
tolist = p2.split(sep)
|
|
|
|
# AA
|
|
# AA BB -> AA/BB
|
|
#
|
|
# AA BB
|
|
# AA CC -> CC
|
|
#
|
|
# AA BB
|
|
# AA -> ../AA
|
|
|
|
diffindex = 0
|
|
for x1, x2 in zip(fromlist, tolist):
|
|
if x1 != x2:
|
|
break
|
|
diffindex += 1
|
|
commonindex = diffindex - 1
|
|
|
|
fromlist_diff = fromlist[diffindex:]
|
|
tolist_diff = tolist[diffindex:]
|
|
|
|
if not fromlist_diff:
|
|
return sep.join(tolist[commonindex:])
|
|
backcount = len(fromlist_diff)
|
|
if tolist_diff:
|
|
return sep.join([back,]*(backcount-1) + tolist_diff)
|
|
return sep.join([back,]*(backcount) + tolist[commonindex:])
|
|
|