155 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			155 lines
		
	
	
		
			5.1 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):
 | |
|         return '%s://%s' % (TEMPLINK_PROTO, linkid)
 | |
| 
 | |
|     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,))
 | |
|         linkid = '://'.join(tempurl.split('://')[1:])
 | |
|         linktarget = self._linkid2target[linkid]
 | |
|         if fromlocation is not None:
 | |
|             linktarget = relpath(fromlocation, linktarget)
 | |
|         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(1)
 | |
|                 try:
 | |
|                     html = html.replace('"' + tempurl + '"',
 | |
|                                         '"' + self.get_target(tempurl,
 | |
|                                                 fpath.relto(dirpath)) + '"')
 | |
|                 except KeyError:
 | |
|                     if stoponerrors:
 | |
|                         raise
 | |
|                     html = html.replace('"' + tempurl + '"',
 | |
|                                         '"apigen.notfound://%s"' % (tempurl,))
 | |
|             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:])
 | |
| 
 |