| """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. |
| """ |
| try: |
| from configparser import ConfigParser |
| except ImportError: # pragma: NO COVER |
| 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() |