Commit 992e379c authored by PJ Eby's avatar PJ Eby

Partial support for autolocation of packages via link harvesting. Still

needs a little work to get PyPI support working, and automatic dependency
resolution.  But for now you can do:

    easy_install -s http://example.com/download.html SomePackage

And easy_install will find the best-looking download link on the page for
instaling SomePackage.

--HG--
branch : setuptools
extra : convert_revision : svn%3A6015fed2-1504-0410-9fe1-9d1591cc4771/sandbox/trunk/setuptools%4041039
parent 6b7bb085
...@@ -31,6 +31,129 @@ from pkg_resources import * ...@@ -31,6 +31,129 @@ from pkg_resources import *
_os = sys.modules[os.name] _os = sys.modules[os.name]
_open = open _open = open
EXTENSIONS = (
(EGG_DIST, ".egg"),
(SOURCE_DIST, ".tar.gz"),
(SOURCE_DIST, ".tar.bz2"),
(SOURCE_DIST, ".tar"),
(SOURCE_DIST, ".zip"),
(SOURCE_DIST, ".tgz"),
)
class URLDistribution(Distribution):
"""A distribution that has not been installed"""
def __init__(self, url, metadata=None):
path = urlparse.urlparse(url)[2]
base = path.split('/')[-1]
for typecode, ext in EXTENSIONS:
if base.endswith(ext):
base = base[:-len(ext)]
break
else:
raise DistributionNotFound(url)
self.typecode = typecode
name, version, py_version, platform = [None]*4
match = pkg_resources.EGG_NAME(base)
if match:
name,version,py_version,platform = match.group(
'name','ver','pyver','plat'
)
else:
name = base
Distribution.__init__(self,
url, metadata=metadata, name=name, version=version or "0",
py_version=py_version or pkg_resources.PY_MAJOR, platform=platform
)
class PackageIndex(AvailableDistributions):
"""A distribution index that scans web pages for download URLs"""
def __init__(self,index_url="http://www.python.org/pypi",*args,**kw):
AvailableDistributions.__init__(self,*args,**kw)
self.index_url = index_url
self.scanned_urls = {}
def scan_url(self, url):
self.process_url(url, True)
def process_url(self, url, retrieve=False):
if url in self.scanned_urls:
return
try:
dist = URLDistribution(url)
except DistributionNotFound: # not a distro, so scan the page
if not retrieve:
return # unless we're skipping retrieval
else:
# It's a distro, just process it
self.scanned_urls[url] = True
self.add(dist) # XXX should check py_ver/platform!
return
f = urllib.urlopen(url)
self.scanned_urls[url] = True
if 'html' not in f.headers['content-type'].lower():
f.close() # not html, we can't process it
return
url = f.url # handle redirects
href = re.compile(r"""href\s*=\s*['"]?([^'"> ]+)""", re.I)
page = f.read()
f.close()
for match in href.finditer(page):
link = urlparse.urljoin(url, match.group(1))
self.process_url(link)
def obtain(self,requirement):
self.find_packages(requirement)
for dist in self.get(requirement.key, ()):
if dist in requirement:
return dist
def find_packages(self,requirement):
pass # XXX process PyPI entries for package
...@@ -45,7 +168,11 @@ class Installer: ...@@ -45,7 +168,11 @@ class Installer:
pth_file = None pth_file = None
cleanup = False cleanup = False
def __init__(self, instdir=None, zip_ok=False, multi=None, tmpdir=None): def __init__(self,
instdir=None, zip_ok=False, multi=None, tmpdir=None, index=None
):
if index is None:
index = AvailableDistributions()
if tmpdir is None: if tmpdir is None:
tmpdir = tempfile.mkdtemp(prefix="easy_install-") tmpdir = tempfile.mkdtemp(prefix="easy_install-")
self.cleanup = True self.cleanup = True
...@@ -67,7 +194,7 @@ class Installer: ...@@ -67,7 +194,7 @@ class Installer:
raise RuntimeError( raise RuntimeError(
"Can't do single-version installs outside site-packages" "Can't do single-version installs outside site-packages"
) )
self.index = index
self.instdir = instdir self.instdir = instdir
self.zip_ok = zip_ok self.zip_ok = zip_ok
self.multi = multi self.multi = multi
...@@ -79,7 +206,6 @@ class Installer: ...@@ -79,7 +206,6 @@ class Installer:
def __del__(self): def __del__(self):
self.close() self.close()
def samefile(self,p1,p2): def samefile(self,p1,p2):
if hasattr(os.path,'samefile') and ( if hasattr(os.path,'samefile') and (
os.path.exists(p1) and os.path.exists(p2) os.path.exists(p1) and os.path.exists(p2)
...@@ -90,7 +216,6 @@ class Installer: ...@@ -90,7 +216,6 @@ class Installer:
os.path.normpath(os.path.normcase(p2)) os.path.normpath(os.path.normcase(p2))
) )
def download(self, spec): def download(self, spec):
"""Locate and/or download or `spec`, returning a local filename""" """Locate and/or download or `spec`, returning a local filename"""
if isinstance(spec,Requirement): if isinstance(spec,Requirement):
...@@ -114,12 +239,10 @@ class Installer: ...@@ -114,12 +239,10 @@ class Installer:
) )
# process a Requirement # process a Requirement
dist = AvailableDistributions().best_match(spec,[]) dist = self.index.best_match(spec,[])
if dist is not None and dist.path.endswith('.egg'): if dist is not None:
return dist.path return self.download(dist.path)
return None
return self.download(self._find_package(spec))
def install_eggs(self, dist_filename): def install_eggs(self, dist_filename):
# .egg dirs or files are already built, so just return them # .egg dirs or files are already built, so just return them
...@@ -231,10 +354,10 @@ class Installer: ...@@ -231,10 +354,10 @@ class Installer:
sys.argv[:] = save_argv sys.argv[:] = save_argv
def _find_package(self, req):
# TODO: search here for a distro to download, matching Requirement
# 'req' and return the package URL or filename
raise DistributionNotFound(spec)
...@@ -277,7 +400,7 @@ class Installer: ...@@ -277,7 +400,7 @@ class Installer:
else: else:
metadata = EggMetadata(zipimport.zipimporter(destination)) metadata = EggMetadata(zipimport.zipimporter(destination))
dist = Distribution.from_filename(destination,metadata=metadata) dist = Distribution.from_filename(destination,metadata=metadata)
self.index.add(dist)
if self.pth_file is not None: if self.pth_file is not None:
map(self.pth_file.remove, self.pth_file.get(dist.key,())) # drop old map(self.pth_file.remove, self.pth_file.get(dist.key,())) # drop old
if not self.multi: if not self.multi:
...@@ -314,7 +437,7 @@ class Installer: ...@@ -314,7 +437,7 @@ class Installer:
else: else:
raise raise
if headers['content-type'].lower().startswith('text/html'): if 'html' in headers['content-type'].lower():
return self._download_html(url, headers, filename) return self._download_html(url, headers, filename)
# and return its filename # and return its filename
...@@ -657,7 +780,9 @@ class PthDistributions(AvailableDistributions): ...@@ -657,7 +780,9 @@ class PthDistributions(AvailableDistributions):
URL_SCHEME = re.compile('([-+.a-z0-9]{2,}):',re.I).match URL_SCHEME = re.compile('([-+.a-z0-9]{2,}):',re.I).match
def main(argv, factory=Installer): def main(argv, factory=Installer):
from optparse import OptionParser from optparse import OptionParser
parser = OptionParser(usage = "usage: %prog [options] url [url...]") parser = OptionParser(usage = "usage: %prog [options] url [url...]")
parser.add_option("-d", "--install-dir", dest="instdir", default=None, parser.add_option("-d", "--install-dir", dest="instdir", default=None,
help="install package to DIR", metavar="DIR") help="install package to DIR", metavar="DIR")
...@@ -673,28 +798,55 @@ def main(argv, factory=Installer): ...@@ -673,28 +798,55 @@ def main(argv, factory=Installer):
parser.add_option("-b", "--build-directory", dest="tmpdir", metavar="DIR", parser.add_option("-b", "--build-directory", dest="tmpdir", metavar="DIR",
default=None, default=None,
help="download/extract/build in DIR; keep the results") help="download/extract/build in DIR; keep the results")
parser.add_option("-u", "--index-url", dest="index_url", metavar="URL",
default="http://www.python.org/pypi",
help="Base URL of Python Package Index")
parser.add_option("-s", "--scan-url", dest="scan_urls", metavar="URL",
action="append",
help="Additional URL(s) to search for packages")
(options, args) = parser.parse_args() (options, args) = parser.parse_args()
if not args:
parser.error("No urls, filenames, or requirements specified")
elif len(args)>1 and options.tmpdir is not None:
parser.error("Build directory can only be set when using one URL")
try: try:
if not args: index = PackageIndex(options.index_url)
parser.error("No urls, filenames, or requirements specified") if options.scan_urls:
elif len(args)>1 and options.tmpdir is not None: for url in options.scan_urls:
parser.error("Build directory can only be set when using one URL") index.scan_url(url)
for spec in args: for spec in args:
inst = factory( inst = factory(
options.instdir, options.zip_ok, options.multi, options.tmpdir options.instdir, options.zip_ok, options.multi, options.tmpdir,
index
) )
try: try:
print "Downloading", spec print "Downloading", spec
downloaded = inst.download(spec) downloaded = inst.download(spec)
if downloaded is None:
raise RuntimeError(
"Could not find distribution for %r" % spec
)
print "Installing", os.path.basename(downloaded) print "Installing", os.path.basename(downloaded)
for dist in inst.install_eggs(downloaded): for dist in inst.install_eggs(downloaded):
print inst.installation_report(dist) print inst.installation_report(dist)
finally: finally:
inst.close() inst.close()
except RuntimeError, v: except RuntimeError, v:
print >>sys.stderr,"error:",v print >>sys.stderr,"error:",v
sys.exit(1) sys.exit(1)
if __name__ == '__main__': if __name__ == '__main__':
main(sys.argv[1:]) main(sys.argv[1:])
...@@ -707,32 +859,3 @@ if __name__ == '__main__': ...@@ -707,32 +859,3 @@ if __name__ == '__main__':
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment