123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269 |
- # -*- test-case-name: twisted.python.test.test_pydoctor -*-
- # Copyright (c) Twisted Matrix Laboratories.
- # See LICENSE for details.
- """
- Support for a few things specific to documenting Twisted using pydoctor.
- FIXME: https://github.com/twisted/pydoctor/issues/106
- This documentation does not link to pydoctor API as there is no public API yet.
- """
- import urllib2
- from compiler import ast
- from pydoctor import model, zopeinterface
- from pydoctor.sphinx import SphinxInventory
- class HeadRequest(urllib2.Request, object):
- """
- A request for the HEAD HTTP method.
- """
- def get_method(self):
- """
- Use the HEAD HTTP method.
- """
- return 'HEAD'
- class TwistedSphinxInventory(SphinxInventory):
- """
- Custom SphinxInventory to work around broken external references to
- Sphinx.
- All exceptions should be reported upstream and a comment should be created
- with a link to the upstream report.
- """
- def getLink(self, name):
- """
- Resolve the full URL for a cross reference.
- @param name: Value of the cross reference.
- @type name: L{str}
- @return: A full URL for the I{name} reference or L{None} if no link was
- found.
- @rtype: L{str} or L{None}
- """
- result = super(TwistedSphinxInventory, self).getLink(name)
- if result is not None:
- # We already got a link. Look no further.
- return result
- if name.startswith('zope.interface.'):
- # This is a link from zope.interface. which is not advertised in
- # the Sphinx inventory.
- # See if the link is a known broken link which should be handled
- # as an exceptional case.
- # We get the base URL from IInterface which is assume that is
- # always and already well defined in the Sphinx index.
- baseURL, _ = self._links.get(
- 'zope.interface.interfaces.IInterface',
- (None, None))
- if baseURL is None:
- # Most probably the zope.interface inventory was
- # not loaded.
- return None
- if name == 'zope.interface.adapter.AdapterRegistry':
- # FIXME:
- # https://github.com/zopefoundation/zope.interface/issues/41
- relativeLink = 'adapter.html'
- else:
- # Not a known exception.
- relativeLink = None
- if relativeLink is None:
- return None
- return '%s/%s' % (baseURL, relativeLink)
- if name.startswith('win32api'):
- # This is a link to pywin32 which does not provide inter-API doc
- # link capabilities
- baseURL = 'http://docs.activestate.com/activepython/2.7/pywin32'
- # For now only links to methods are supported.
- relativeLink = '%s_meth.html' % (name.replace('.', '__'),)
- fullURL = '%s/%s' % (baseURL, relativeLink)
- # Check if URL exists.
- response = self._getURLAsHEAD(fullURL)
- if response:
- if response.code == 200:
- return fullURL
- else:
- # Bad URL resolution.
- print("BAD URL resolution, code: ", response.code)
- return None
- def _getURLAsHEAD(self, url):
- """
- Get are HEAD response for URL.
- Here to help with testing and allow injecting another URL getter.
- @param url: Full URL to the page which is retrieved.
- @type url: L{str}
- @return: The response for the HEAD method.
- @rtype: urllib2 response or L{None}
- """
- try:
- return urllib2.urlopen(HeadRequest(url))
- except Exception as e:
- print("Error opening {}: {}".format(url, e))
- return None
- def getDeprecated(self, decorators):
- """
- With a list of decorators, and the object it is running on, set the
- C{_deprecated_info} flag if any of the decorators are a Twisted deprecation
- decorator.
- """
- for a in decorators:
- if isinstance(a, ast.CallFunc):
- decorator = a.asList()
- # Getattr is used when the decorator is @foo.bar, not @bar
- if isinstance(decorator[0], ast.Getattr):
- getAttr = decorator[0].asList()
- name = getAttr[0].name
- fn = self.expandName(name) + "." + getAttr[1]
- else:
- fn = self.expandName(decorator[0].name)
- if fn == "twisted.python.deprecate.deprecated":
- try:
- self._deprecated_info = deprecatedToUsefulText(
- self.name, decorator)
- except AttributeError:
- # It's a reference or something that we can't figure out
- # from the AST.
- pass
- class TwistedModuleVisitor(zopeinterface.ZopeInterfaceModuleVisitor):
- def visitClass(self, node):
- """
- Called when a class is visited.
- """
- super(TwistedModuleVisitor, self).visitClass(node)
- cls = self.builder.current.contents[node.name]
- getDeprecated(cls, list(cls.raw_decorators))
- def visitFunction(self, node):
- """
- Called when a class is visited.
- """
- super(TwistedModuleVisitor, self).visitFunction(node)
- func = self.builder.current.contents[node.name]
- if func.decorators:
- getDeprecated(func, list(func.decorators))
- def versionToUsefulObject(version):
- """
- Change an AST C{Version()} to a real one.
- """
- from incremental import Version
- return Version(*[x.value for x in version.asList()[1:] if x])
- def deprecatedToUsefulText(name, deprecated):
- """
- Change a C{@deprecated} to a display string.
- """
- from twisted.python.deprecate import _getDeprecationWarningString
- version = versionToUsefulObject(deprecated[1])
- if deprecated[2]:
- if isinstance(deprecated[2], ast.Keyword):
- replacement = deprecated[2].asList()[1].value
- else:
- replacement = deprecated[2].value
- else:
- replacement = None
- return _getDeprecationWarningString(name, version, replacement=replacement) + "."
- class TwistedFunction(zopeinterface.ZopeInterfaceFunction):
- def docsources(self):
- if self.decorators:
- getDeprecated(self, list(self.decorators))
- for x in super(TwistedFunction, self).docsources():
- yield x
- class TwistedASTBuilder(zopeinterface.ZopeInterfaceASTBuilder):
- # Vistor is not a typo...
- ModuleVistor = TwistedModuleVisitor
- class TwistedSystem(zopeinterface.ZopeInterfaceSystem):
- """
- A PyDoctor "system" used to generate the docs.
- """
- defaultBuilder = TwistedASTBuilder
- Function = TwistedFunction
- def __init__(self, options=None):
- super(TwistedSystem, self).__init__(options=options)
- # Use custom SphinxInventory so that we can resolve valid L{} markup
- # for which the Sphinx inventory is not published or broken.
- self.intersphinx = TwistedSphinxInventory(
- logger=self.msg, project_name=self.projectname)
- def privacyClass(self, documentable):
- """
- Report the privacy level for an object.
- Hide all tests with the exception of L{twisted.test.proto_helpers}.
- param obj: Object for which the privacy is reported.
- type obj: C{model.Documentable}
- rtype: C{model.PrivacyClass} member
- """
- if documentable.fullName() == 'twisted.test':
- # Match this package exactly, so that proto_helpers
- # below is visible
- return model.PrivacyClass.VISIBLE
- current = documentable
- while current:
- if current.fullName() == 'twisted.test.proto_helpers':
- return model.PrivacyClass.VISIBLE
- if isinstance(current, model.Package) and current.name == 'test':
- return model.PrivacyClass.HIDDEN
- current = current.parent
- return super(TwistedSystem, self).privacyClass(documentable)
|