Mercurial > hg > PaInt
view paint/info.py @ 83:7f442f585580
paint/package.py
author | Jeff Hammel <jhammel@mozilla.com> |
---|---|
date | Tue, 17 Sep 2013 16:04:05 -0700 |
parents | 00c0f668f332 |
children |
line wrap: on
line source
""" interfaces to get information from a package """ import imp import pkginfo import os import subprocess import sys from distutils.dist import Distribution from subprocess import check_call as call from StringIO import StringIO # TODO: # Reconcile the difference between the keys (and values) between the different # implementations. Pick a canon and stick with it. # SetupOverridePackageInfo: # {'entry_points': '\n', 'description': 'a dummy package', 'license': '', 'author': 'Jeff Hammel', 'install_requires': [], 'include_package_data': True, 'classifiers': [], 'url': 'http://example.com/', 'author_email': 'jhammel@mozilla.com', 'version': '0.1', 'zip_safe': False, 'packages': ['dummy'], 'long_description': 'dummy\n===========\n\na dummy package\n\n----\n\nJeff Hammel\n\nhttp://example.com/\n\n', 'name': 'dummy'} # EggInfo: # {'Name': 'dummy', 'License': 'UNKNOWN', 'Author': 'Jeff Hammel', 'Metadata-Version': '1.0', 'Home-page': 'http://example.com/', 'Summary': 'a dummy package', 'Platform': 'UNKNOWN', 'Version': '0.1', 'Author-email': 'jhammel@mozilla.com', 'Description': 'dummy'} # see http://www.python.org/dev/peps/pep-0314/ : # Metadata for Python Software Packages # TODO: consider using pkginfo def setup2metadata(**attrs): """ convert setup arguments to standard python metadata: http://www.python.org/dev/peps/pep-0314/ """ distribution = Distribution(attrs) buffer = StringIO() distribution.metadata.write_pkg_file(buffer) pkginfo_dist = pkginfo.Distribution() pkinfo_dist.parse(buffer.getvalue()) newattrs = dict((i, getattr(pkginfo_dist, i)) for i in pkginfo_dist) header_dict = dict((attr_name, header_name) for header_name, attr_name, multiple in pkginfo_dist._getHeaderAttrs()) info = dict((header_dict[key], value) for key, value in newattrs.items()) # XXX pkginfo says 'Home-Page' though the spec says Home-page info.setdefault('Home-page', info['Home-Page']) return info class PackageInfo(object): """abstract base class of package info""" def __init__(self, path): """ - path : path to setup.py or its directory """ if os.path.isdir(path): path = os.path.join(path, 'setup.py') assert os.path.exists(path), "'%s' not found" % path self.setup_py = os.path.abspath(path) def __call__(self): """returns dictionary of package info""" raise NotImplementedError("abstract base class") def dependencies(self): raise NotImplementedError("abstract base class") class SetupOverridePackageInfo(PackageInfo): """ gather setup.py information by overriding the function """ # TODO: override distutils.core.setup as well # http://docs.python.org/2/distutils/index.html#distutils-index def __call__(self): setuptools = sys.modules.get('setuptools') sys.modules['setuptools'] = sys.modules[__name__] globals()['setup'] = self._setup try: module = imp.load_source('setup', self.setup_py) finally: sys.modules.pop('setuptools') if setuptools: sys.modules['setuptools'] = setuptools globals().pop('setup') return self.__dict__.pop('_info') def _setup(self, **kwargs): self._info = kwargs class EggInfo(PackageInfo): """ use `python setup.py egg_info` to gather package information """ def __call__(self): info = self.read_pkg_info(self._pkg_info()) # TODO: install_requires return info @classmethod def read_pkg_info(cls, path): """reads PKG-INFO and returns a dict""" # read the package information info_dict = {} for line in file(path).readlines(): if not line or line[0].isspace(): continue # XXX neglects description assert ':' in line key, value = [i.strip() for i in line.split(':', 1)] info_dict[key] = value # return the information return info_dict def dependencies(self): """return the dependencies of the package""" # TODO: should probably have a more detailed dict: # {'mozinfo': {'version': '>= 0.2', # 'url': 'http://something.com/'}} # get the egg_info directory egg_info = self._egg_info() # read the dependencies requires = os.path.join(egg_info, 'requires.txt') if os.path.exists(requires): dependencies = [i.strip() for i in file(requires).readlines() if i.strip()] else: dependencies = [] dependencies = dict([(i, None) for i in dependencies]) # read the dependency links dependency_links = os.path.join(egg_info, 'dependency_links.txt') if os.path.exists(dependency_links): links = [i.strip() for i in file(dependency_links).readlines() if i.strip()] for link in links: # XXX pretty ghetto assert '#egg=' in link url, dep = link.split('#egg=', 1) if dep in dependencies: dependencies[dep] = link return dependencies def _egg_info(self): """build the egg_info directory""" # cached result if getattr(self, '_egg_info_path', None): return self._egg_info_path directory = os.path.dirname(self.setup_py) # setup the egg info try: call([sys.executable, 'setup.py', 'egg_info'], cwd=directory, stdout=subprocess.PIPE) except Exception: print "Failure to generate egg_info: %s" % self.setup_py raise # get the .egg-info directory egg_info = [i for i in os.listdir(directory) if i.endswith('.egg-info')] assert len(egg_info) == 1, 'Expected one .egg-info directory in %s, got: %s' % (directory, egg_info) egg_info = os.path.join(directory, egg_info[0]) assert os.path.isdir(egg_info), "%s is not a directory" % egg_info # cache it self._egg_info_path = egg_info return self._egg_info_path def _pkg_info(self): """returns path to PKG-INFO file""" if getattr(self, '_pkg_info_path', None): # return cached value return self._pkg_info_path try: egg_info = self._egg_info() except Exception, exception: # try to get the package info from a file path = os.path.dirname(self.setup_py) pkg_info = os.path.join(path, 'PKG-INFO') if os.path.exists(pkg_info): self._pkg_info_path = pkg_info return self._pkg_info_path raise Exception("Cannot find or generate PKG-INFO") pkg_info = os.path.join(egg_info, 'PKG-INFO') assert os.path.exists(pkg_info) self._pkg_info_path = pkg_info return self._pkg_info_path