"""Print the metadata for one or more Python package distributions. Usage: %prog [options] path+ Each 'path' entry can be one of the following: o a source distribution: in this case, 'path' should point to an existing archive file (.tar.gz, .tar.bz2, or .zip) as generated by 'setup.py sdist'. o a binary distribution: in this case, 'path' should point to an existing archive file (.egg) o a "develop" checkout: in this case, 'path' should point to a directory initialized via 'setup.py develop' (under setuptools). o an installed package: in this case, 'path' should be the importable name of the package. """ from configparser import ConfigParser from collections import OrderedDict from csv import writer import json import optparse import os import sys from .utils import get_metadata def _parse_options(args=None): parser = optparse.OptionParser(usage=__doc__) parser.add_option("-m", "--metadata-version", default=None, help="Override metadata version") parser.add_option("-f", "--field", dest="fields", action="append", help="Specify an output field (repeatable)", ) parser.add_option("-d", "--download-url-prefix", dest="download_url_prefix", help="Download URL prefix", ) parser.add_option("--simple", dest="output", action="store_const", const='simple', default='simple', help="Output as simple key-value pairs", ) parser.add_option("-s", "--skip", dest="skip", action="store_true", default=True, help="Skip missing values in simple output", ) parser.add_option("-S", "--no-skip", dest="skip", action="store_false", help="Don't skip missing values in simple output", ) parser.add_option("--single", dest="output", action="store_const", const='single', help="Output delimited values", ) parser.add_option("--item-delim", dest="item_delim", action="store", default=';', help="Delimiter for fields in single-line output", ) parser.add_option("--sequence-delim", dest="sequence_delim", action="store", default=',', help="Delimiter for multi-valued fields", ) parser.add_option("--csv", dest="output", action="store_const", const='csv', help="Output as CSV", ) parser.add_option("--ini", dest="output", action="store_const", const='ini', help="Output as INI", ) parser.add_option("--json", dest="output", action="store_const", const='json', help="Output as JSON", ) options, args = parser.parse_args(args) if len(args)==0: parser.error("Pass one or more files or directories as arguments.") else: return options, args class Base(object): _fields = None def __init__(self, options): if options.fields: self._fields = options.fields def finish(self): # pragma: NO COVER pass class Simple(Base): def __init__(self, options): super(Simple, self).__init__(options) self._skip = options.skip def __call__(self, meta): for field in self._fields or list(meta): value = getattr(meta, field) if (not self._skip) or (value is not None and value!=()): print("%s: %s" % (field, value)) class SingleLine(Base): _fields = None def __init__(self, options): super(SingleLine, self).__init__(options) self._item_delim = options.item_delim self._sequence_delim = options.sequence_delim def __call__(self, meta): if self._fields is None: self._fields = list(meta) values = [] for field in self._fields: value = getattr(meta, field) if isinstance(value, (tuple, list)): value = self._sequence_delim.join(value) else: value = str(value) values.append(value) print(self._item_delim.join(values)) class CSV(Base): _writer = None def __init__(self, options): super(CSV, self).__init__(options) self._sequence_delim = options.sequence_delim def __call__(self, meta): if self._fields is None: self._fields = list(meta) # first dist wins fields = self._fields if self._writer is None: self._writer = writer(sys.stdout) self._writer.writerow(fields) values = [] for field in fields: value = getattr(meta, field) if isinstance(value, (tuple, list)): value = self._sequence_delim.join(value) else: value = str(value) values.append(value) self._writer.writerow(values) class INI(Base): _fields = None def __init__(self, options): super(INI, self).__init__(options) self._parser = ConfigParser() def __call__(self, meta): name = meta.name version = meta.version section = '%s-%s' % (name, version) if self._parser.has_section(section): raise ValueError('Duplicate distribution: %s' % section) self._parser.add_section(section) for field in self._fields or list(meta): value = getattr(meta, field) if isinstance(value, (tuple, list)): value = '\n\t'.join(value) self._parser.set(section, field, value) def finish(self): self._parser.write(sys.stdout) # pragma: NO COVER class JSON(Base): _fields = None def __init__(self, options): super(JSON, self).__init__(options) self._mapping = OrderedDict() def __call__(self, meta): if self._fields is None: self._fields = list(meta) for field in self._fields: value = getattr(meta, field) if value and not isinstance(value, (tuple, list)): value = str(value) if field in self._mapping: raise ValueError('Duplicate field: %(field)r' % locals()) self._mapping[field] = value def finish(self): json.dump(self._mapping, sys.stdout, indent=2) _FORMATTERS = { 'simple': Simple, 'single': SingleLine, 'csv': CSV, 'ini': INI, 'json': JSON, } def main(args=None): """Entry point for pkginfo tool """ options, paths = _parse_options(args) format = getattr(options, 'output', 'simple') formatter = _FORMATTERS[format](options) for path in paths: meta = get_metadata(path, options.metadata_version) if meta is None: continue if options.download_url_prefix: if meta.download_url is None: filename = os.path.basename(path) meta.download_url = '%s/%s' % (options.download_url_prefix, filename) formatter(meta) formatter.finish()