Changeset 64
- Timestamp:
- 07/26/07 22:26:55 (1 year ago)
- Files:
-
- projects/Twisted-Goodies/trunk/twisted_goodies/simpleserver/http/resources/external.py (modified) (5 diffs)
- projects/Twisted-Goodies/trunk/twisted_goodies/simpleserver/http/resources/projects.py (modified) (5 diffs)
- projects/Twisted-Goodies/trunk/twisted_goodies/simpleserver/http/resources/test/test_projects.py (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
projects/Twisted-Goodies/trunk/twisted_goodies/simpleserver/http/resources/external.py
r61 r64 33 33 34 34 35 class TracResource( WSGIResource):35 class TracResource(wsgi.WSGIResource): 36 36 """ 37 37 I provide a Trac site for a specified path via a WSGI gateway. … … 50 50 def __init__(self, path, env={}): 51 51 self.path, self.env = path, env 52 WSGIResource.__init__(self, self.tracApplication)52 wsgi.WSGIResource.__init__(self, self.tracApplication) 53 53 54 54 def tracApplication(self, environ, start_response): … … 62 62 63 63 64 class NevowResource( WSGIResource):64 class NevowResource(wsgi.WSGIResource): 65 65 """ 66 66 I provide a Nevow site for a specified path via a WSGI gateway. … … 72 72 def __init__(self, path): 73 73 self.path = path 74 WSGIResource.__init__(self, self.nevowApplication)74 wsgi.WSGIResource.__init__(self, self.nevowApplication) 75 75 76 76 def nevowApplication(self, environ, start_response): … … 81 81 # environ modifications? 82 82 return nevow.wsgi.WSGIRequest(environ, start_response) 83 84 85 class ProxyResource(object): 86 """ 87 I provide proxied resources from another HTTP server. 88 89 When the first instance for a particular upstream server is constructed, 90 the client protocol for proxy connections to its I{upstreamHost} and 91 I{upstreamPort} is created and cached, with an I{SSL} option for securing 92 those connections. 93 """ 94 implements(iweb.IResource) 95 96 upstreamSpecs = {} 97 98 def __init__(self, upstreamHost, upstreamPort, SSL=False): 99 self.cacheKey = (upstreamHost, upstreamPort, SSL) 100 if self.cacheKey not in self.upstreamSpecs: 101 thisProtocol = protocol.ClientCreator( 102 reactor, chttp.HTTPClientProtocol) 103 specs = {'protocol': thisProtocol, 104 'host': upstreamHost, 105 'port': upstreamPort} 106 if SSL: 107 specs['context'] = ssl.ClientContextFactory() 108 self.upstreamSpecs[self.cacheKey] = specs 109 110 def renderHTTP(self, request): 111 """ 112 Returns a deferred to the HTTP rendered by forwarding a client version 113 of the supplied I{request} to another HTTP server. Any I{:<port} 114 substring appearing in the response from that server is removed to 115 ensure that further requests are proxied through me. 116 """ 117 def gotResponse(response): 118 location = response.headers.getHeader('location') 119 if location is not None: 120 newLocation = location.replace(":%d" % specs['port'], "") 121 response.headers.setHeader('location', newLocation) 122 return response 123 124 def connected(protocolObject): 125 self.uri = request.uri 126 newRequest = chttp.ClientRequest( 127 request.method, request.uri, request.headers, request.stream) 128 return protocolObject.submitRequest( 129 newRequest).addCallbacks(gotResponse) 130 131 specs = self.upstreamSpecs[self.cacheKey] 132 if 'context' in specs: 133 d = specs['protocol'].connectSSL( 134 specs['host'], specs['port'], specs['context']) 135 else: 136 d = specs['protocol'].connectTCP(specs['host'], specs['port']) 137 d.addCallback(connected) 138 return d 139 140 def locateChild(self, request, segments): 141 return self, () projects/Twisted-Goodies/trunk/twisted_goodies/simpleserver/http/resources/projects.py
r61 r64 23 23 """ 24 24 25 import tempfile, shutil, os.path 25 import tempfile, shutil, os.path, re 26 26 from twisted.internet import defer, utils 27 27 from twisted.python import procutils 28 28 from twisted.web2.resource import Resource, RedirectResource 29 29 30 from basic import StanResource 31 30 32 31 33 class APIDocResource(Resource): 32 34 """ 35 I provide Web access to generated API documentation for projects hosted 36 within a particular I{vhostPath}. 33 37 """ 38 re_rev = re.compile(r"Revision:\s+(\d+)") 39 34 40 def __init__(self, vhostPath, projectURLProto, projectRepoProto): 35 self.vhostPath = vhostPath41 self.vhostPath = os.path.realpath(vhostPath) 36 42 for name in ('projectURLProto', 'projectRepoProto'): 37 43 string = locals()[name] … … 44 50 if not result: 45 51 raise ImportError("Can't locate %s executable" % name) 52 return result 53 54 def _packageName(self, projectName): 55 return projectName.lower().replace("-", "_") 56 57 def _repo(self, projectName): 58 repo = self.projectRepoProto % projectName 59 return os.path.join(repo, self._packageName(projectName)) 46 60 47 61 def locateChild(self, request, segments): 48 62 """ 63 Returns a (deferred) instance of L{RedirectResource} that points to a 64 particular HTML file containing generated API documentation. If the 65 documentation does not yet exist or is out of date, it will be 66 (re)generated before the deferred fires. 49 67 """ 50 68 def ready(null): … … 54 72 55 73 projectName = segments[0] 56 packageName = projectName.lower().replace("-", "_")57 74 if len(segments) > 1: 58 75 apiFile = segments[1] 59 76 else: 60 apiFile = "%s.html" % packageName77 apiFile = "%s.html" % self._packageName(projectName) 61 78 d = defer.maybeDeferred( 62 self.ensureDocsPresent, projectName, packageName,apiFile)79 self.ensureDocsPresent, projectName, apiFile) 63 80 d.addCallback(ready) 64 81 return d 65 82 66 def ensureDocsPresent(self, projectName, packageName,apiFile):83 def ensureDocsPresent(self, projectName, apiFile): 67 84 """ 85 If the specified I{apiFile} for the project I{projectName} does not 86 exist or is out of date, corrects the situation and returns a deferred 87 that fires when done. Otherwise, returns nothing. 68 88 """ 69 filePath = os.path.join(self.vhostPath, 'api', projectName, apiFile) 70 if False and not os.path.exists(filePath): 71 tmpDir = tempfile.mkdtemp() 72 d = self.svn(tmpDir, projectName) 73 d.addCallback(lambda _: self.pydoctor(tmpDir, projectName)) 74 d.addCallback(lambda _: shutil.rmtree(tmpDir)) 89 def compareRevs(currentRev): 90 if currentRev != docRev: 91 # Tell the world that the docs are at the new rev... 92 fh = open(revFile, 'w') 93 fh.write(currentRev) 94 fh.close() 95 # ...then make good on that (just once) 96 tmpDir = tempfile.mkdtemp() 97 d = self.svnExport(tmpDir, projectName) 98 d.addCallback(lambda _: self.pydoctor(tmpDir, projectName)) 99 d.addCallback(lambda _: shutil.rmtree(tmpDir)) 100 d.addCallback(lambda _: writeNewRev(currentRev)) 101 return d 102 103 revFile = os.path.join(self.vhostPath, 'api', projectName, 'REV') 104 if os.path.exists(revFile): 105 # The rev for which the current docs were generated 106 fh = open(revFile) 107 docRev = fh.readline().strip() 108 fh.close() 109 # Get and compare to the current rev 110 d = self.svnRev(projectName) 111 d.addCallback(compareRevs) 75 112 return d 76 113 77 def svn (self, tmpDir, projectName):114 def svnRev(self, projectName): 78 115 """ 116 Returns a deferred that fires with the current revision of the 117 I{projectName} project in the SVN repo, i.e., the revision of the 118 newest file found from a recursive info command. The revision number is 119 provided as a string. 79 120 """ 80 repo = self.projectRepoProto % projectName 81 args = [ 82 "export", "-q", "--force", 83 "%s/%s/trunk" % (repo, projectName)] 84 return utils.getProcessValue( 85 self._executable('svn'), args=args, path=tmpDir) 121 def gotInfo(info): 122 maxRev = 0 123 for line in [x.strip() for x in info.split("\n")]: 124 match = self.re_rev.match(line) 125 if match: 126 thisRev = int(match.group(1)) 127 maxRev = max([maxRev, thisRev]) 128 return str(maxRev) 129 130 args = ["info", "-R", self._repo(projectName)] 131 d = utils.getProcessOutput(self._executable('svn'), args=args) 132 d.addCallback(gotInfo) 133 return d 86 134 87 def pydoctor(self, tmpDir, vhostPath, projectName):135 def svnExport(self, tmpDir, projectName): 88 136 """ 137 Exports to the specified I{tmpDir} a copy of the SVN repo for the 138 I{projectName} project. Returns a deferred that fires when the export 139 is done. 140 """ 141 repo = self._repo(projectName) 142 packageDir = os.path.realpath( 143 os.path.join(tmpDir, self._packageName(projectName))) 144 args = ["export", "-q", "--force", repo, packageDir] 145 return utils.getProcessValue(self._executable('svn'), args=args) 146 147 def pydoctor(self, tmpDir, projectName): 148 """ 149 Generates API documentation for the package corresponding to the 150 specified I{projectName}, which must be present in a specified 151 I{tmpDir}. The resulting HTML documents will appear in a subdirectory 152 C{api/<projectName>} of my vhost path after the returned deferred fires. 89 153 """ 90 154 url = self.projectURLProto % projectName 91 155 args = [ 92 "-- add-package=%s" % packageName,156 "--make-html", 93 157 "--project-name=%s" % projectName, 94 "-- make-html",158 "--add-package=%s" % self._packageName(projectName), 95 159 "--html-output=%s/api/%s" % (self.vhostPath, projectName), 96 "--project-url=%s %s" % (url, projectName)]160 "--project-url=%s" % url] 97 161 return utils.getProcessValue( 98 162 self._executable('pydoctor'), args=args, path=tmpDir) … … 101 165 class DownloadResource(StanResource): 102 166 """ 167 I provide a page of HTML that shows the different ways to download projects 168 hosted within a particular I{vhostPath}. 103 169 """ 104 170 addSlash = True … … 121 187 def locateChild(self, request, segments): 122 188 self.projectName = segments[0] 123 self.title = "How to Download %s" % projectName189 self.title = "How to Download %s" % self.projectName 124 190 if len(segments) == 1: 125 191 return self, () projects/Twisted-Goodies/trunk/twisted_goodies/simpleserver/http/resources/test/test_projects.py
r63 r64 23 23 """ 24 24 25 import tempfile25 import os, os.path, shutil, tempfile 26 26 from twisted.trial.unittest import TestCase 27 27 … … 29 29 30 30 31 class Test_APIDocResource(TestCase): 31 class BaseTC(TestCase): 32 def _get_vhostPath(self): 33 if not hasattr(self, '_vhostPath'): 34 self._vhostPath = "test.com" 35 self.dirs = [] 36 self.mkdir() 37 return self._vhostPath 38 vhostPath = property(_get_vhostPath) 39 40 def mkdir(self, *pathParts): 41 dirPath = os.path.join(self.vhostPath, *pathParts) 42 if not os.path.exists(dirPath): 43 os.makedirs(dirPath) 44 self.dirs.append(dirPath) 45 return dirPath 46 47 def tearDown(self): 48 dirs = getattr(self, 'dirs', []) 49 while dirs: 50 dirPath = dirs.pop() 51 if os.path.exists(dirPath): 52 shutil.rmtree(dirPath) 53 54 55 class Test_APIDocResource(BaseTC): 32 56 def setUp(self): 33 # TODO34 #self.vhostPath = ...35 57 self.projectURLProto = "/trac/%s/wiki" 36 58 self.projectRepoProto = "svn://tellectual.com/foss/projects/%s/trunk" … … 38 60 self.vhostPath, self.projectURLProto, self.projectRepoProto) 39 61 62 def test_svnRev(self): 63 def gotRev(rev): 64 self.failUnless(rev.isdigit()) 65 self.failUnless(int(rev) >= 63) 66 67 return self.rsrc.svnRev("Twisted-Goodies").addCallback(gotRev) 68 69 def test_svnExport(self): 70 def done(errCode): 71 self.failUnlessEqual(errCode, 0) 72 self.failUnless('twisted_goodies' in os.listdir(tmpDir)) 73 74 tmpDir = self.mkdir("tmp") 75 return self.rsrc.svnExport(tmpDir, "Twisted-Goodies").addCallback(done) 76 77 def test_pydoctor(self): 78 def done(errCode): 79 # pydoctor complains a lot 80 # self.failUnlessEqual(errCode, 0) 81 apiSubDir = os.path.join(apiDir, "Twisted-Goodies") 82 self.failUnless('twisted_goodies.html' in os.listdir(apiSubDir)) 83 84 apiDir = self.mkdir("api") 85 tmpDir = self.mkdir("tmp") 86 d = self.rsrc.svnExport(tmpDir, "Twisted-Goodies") 87 d.addCallback(lambda _: self.rsrc.pydoctor(tmpDir, "Twisted-Goodies")) 88 d.addCallback(done) 89 return d 90
