233 lines
7.3 KiB
Python
233 lines
7.3 KiB
Python
|
"""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()
|