text: #!/usr/bin/env python
  # -*- coding: utf-8 -*-
  """
  Utility script to create a pypi-like directory structure (localpi)
  from a number of Python packages in a directory of the local filesystem.
  
    DIRECTORY STRUCTURE (before):
        +-- downloads/
             +-- alice-1.0.zip
             +-- alice-1.0.tar.gz
             +-- bob-1.3.0.tar.gz
             +-- bob-1.4.2.tar.gz
             +-- charly-1.0.tar.bz2
  
    DIRECTORY STRUCTURE (afterwards):
        +-- downloads/
             +-- simple/
             |      +-- alice/index.html   --> ../../alice-*.*
             |      +-- bob/index.html     --> ../../bob-*.*
             |      +-- charly/index.html  --> ../../charly-*.*
             |      +-- index.html  --> alice/, bob/, ...
             +-- alice-1.0.zip
             +-- alice-1.0.tar.gz
             +-- bob-1.3.0.tar.gz
             +-- bob-1.4.2.tar.gz
             +-- charly-1.0.tar.bz2
  
  USAGE EXAMPLE:
  
      mkdir -p /tmp/downloads
      pip install --download=/tmp/downloads argparse Jinja2
      make_localpi.py /tmp/downloads
      pip install --index-url=file:///tmp/downloads/simple argparse Jinja2
  
  ALTERNATIVE:
  
      pip install --download=/tmp/downloads argparse Jinja2
      pip install --find-links=/tmp/downloads --no-index argparse Jinja2
  """
  
  from __future__ import with_statement, print_function
  from fnmatch import fnmatch
  import os.path
  import shutil
  import sys
  
  
  __author__  = "Jens Engel"
  __version__ = "0.2"
  __license__ = "BSD"
  __copyright__ = "(c) 2013 by Jens Engel"
  
  
  class Package(object):
      """
      Package entity that keeps track of:
        * one or more versions of this package
        * one or more archive types
      """
      PATTERNS = [
          "*.egg", "*.exe", "*.whl", "*.zip", "*.tar.gz", "*.tar.bz2", "*.7z"
      ]
  
      def __init__(self, filename, name=None):
          if not name and filename:
              name = self.get_pkgname(filename)
          self.name  = name
          self.files = []
          if filename:
              self.files.append(filename)
  
      @property
      def versions(self):
          versions_info = [ self.get_pkgversion(p) for p in self.files ]
          return versions_info
  
      @classmethod
      def split_pkgname_parts(cls, filename):
          basename = cls.splitext(os.path.basename(filename))
          if basename.startswith("http") and r"%2F" in basename:
              # -- PIP DOWNLOAD-CACHE PACKAGE FILE NAME SCHEMA:
              pos = basename.rfind("%2F")
              basename = basename[pos+3:]
  
          version_part_index = 0
          parts = basename.split("-")
          for index, part in enumerate(parts):
              if index == 0:
                  continue
              elif part and part[0].isdigit() and len(part) >= 3:
                  version_part_index = index
                  break
          name = "-".join(parts[:version_part_index])
          version = "0.0"
          remainder = None
          if version_part_index > 0:
              version = parts[version_part_index]
              if version_part_index+1 < len(parts):
                  remainder = "-".join(parts[version_part_index+1:])
          assert name, "OOPS: basename=%s, name='%s'" % (basename, name)
          return (name, version, remainder)
  
  
      @classmethod
      def get_pkgname(cls, filename):
          return cls.split_pkgname_parts(filename)[0]
  
      @classmethod
      def get_pkgversion(cls, filename):
          return cls.split_pkgname_parts(filename)[1]
  
      @classmethod
      def make_pkgname_with_version(cls, filename):
          pkg_name = cls.get_pkgname(filename)
          pkg_version = cls.get_pkgversion(filename)
          return "%s-%s" % (pkg_name, pkg_version)
  
      @staticmethod
      def splitext(filename):
          fname = os.path.splitext(filename)[0]
          if fname.endswith(".tar"):
              fname = os.path.splitext(fname)[0]
          return fname
  
      @classmethod
      def isa(cls, filename):
          basename = os.path.basename(filename)
          if basename.startswith("."):
              return False
          for pattern in cls.PATTERNS:
              if fnmatch(filename, pattern):
                  return True
          return False
  
  def collect_packages(package_dir, package_map=None):
      if package_map is None:
          package_map = {}
      packages = []
      for filename in sorted(os.listdir(package_dir)):
          if not Package.isa(filename):
              continue
          pkg_filepath = os.path.join(package_dir, filename)
          package_name = Package.get_pkgname(pkg_filepath)
          package = package_map.get(package_name, None)
          if not package:
              # -- NEW PACKAGE DETECTED: Store/register package.
              package = Package(pkg_filepath)
              package_map[package.name] = package
              packages.append(package)
          else:
              # -- SAME PACKAGE: Collect other variant/version.
              package.files.append(pkg_filepath)
      return packages
  
  def make_index_for(package, index_dir, verbose=True):
      """
      Create an 'index.html' for one package.
  
      :param package:   Package object to use.
      :param index_dir: Where 'index.html' should be created.
      """
      index_template = """\
  <html>
  <head><title>{title}</title></head>
  <body>
  <h1>{title}</h1>
  <ul>
  {packages}
  </ul>
  </body>
  </html>
  """
      item_template = '<li><a href="{1}">{0}</a></li>'
      index_filename = os.path.join(index_dir, "index.html")
      if not os.path.isdir(index_dir):
          os.makedirs(index_dir)
  
      parts = []
      for pkg_filename in package.files:
          pkg_name = os.path.basename(pkg_filename)
          if pkg_name == "index.html":
              # -- ROOT-INDEX:
              pkg_name = os.path.basename(os.path.dirname(pkg_filename))
          else:
              # pkg_name = package.splitext(pkg_name)
              pkg_name = package.make_pkgname_with_version(pkg_filename)
          pkg_relpath_to = os.path.relpath(pkg_filename, index_dir)
          parts.append(item_template.format(pkg_name, pkg_relpath_to))
  
      if not parts:
          print("OOPS: Package %s has no files" % package.name)
          return
  
      if verbose:
          root_index = not Package.isa(package.files[0])
          if root_index:
              info = "with %d package(s)" % len(package.files)
          else:
              package_versions = sorted(set(package.versions))
              info = ", ".join(reversed(package_versions))
          message = "%-30s  %s" % (package.name, info)
          print(message)
  
      with open(index_filename, "w") as f:
          packages = "\n".join(parts)
          text = index_template.format(title=package.name, packages=packages)
          f.write(text.strip())
          f.close()
  
  
  def make_package_index(download_dir):
      """
      Create a pypi server like file structure below download directory.
  
      :param download_dir:    Download directory with packages.
  
      EXAMPLE BEFORE:
        +-- downloads/
             +-- wheelhouse/bob-1.4.2-*.whl
             +-- alice-1.0.zip
             +-- alice-1.0.tar.gz
             +-- bob-1.3.0.tar.gz
             +-- bob-1.4.2.tar.gz
             +-- charly-1.0.tar.bz2
  
      EXAMPLE AFTERWARDS:
        +-- downloads/
             +-- simple/
             |      +-- alice/index.html   --> ../../alice-*.*
             |      +-- bob/index.html     --> ../../bob-*.*
             |      +-- charly/index.html  --> ../../charly-*.*
             |      +-- index.html  --> alice/index.html, bob/index.html, ...
             +-- wheelhouse/bob-1.4.2-*.whl
             +-- alice-1.0.zip
             +-- alice-1.0.tar.gz
             +-- bob-1.3.0.tar.gz
             +-- bob-1.4.2.tar.gz
             +-- charly-1.0.tar.bz2
      """
      if not os.path.isdir(download_dir):
          raise ValueError("No such directory: %r" % download_dir)
  
      pkg_rootdir = os.path.join(download_dir, "simple")
      if os.path.isdir(pkg_rootdir):
          shutil.rmtree(pkg_rootdir, ignore_errors=True)
      os.mkdir(pkg_rootdir)
  
      package_dirs = [download_dir]
      wheelhouse_dir = os.path.join(download_dir, "wheelhouse")
      if os.path.isdir(wheelhouse_dir):
          print("Using wheelhouse: %s" % wheelhouse_dir)
          package_dirs.append(wheelhouse_dir)
  
      # -- STEP: Collect all packages.
      package_map = {}
      packages = []
      for package_dir in package_dirs:
          new_packages = collect_packages(package_dir, package_map)
          packages.extend(new_packages)
  
      # -- STEP: Make local PYTHON PACKAGE INDEX.
      root_package = Package(None, "Python Package Index")
      root_package.files = [ os.path.join(pkg_rootdir, pkg.name, "index.html")
                             for pkg in packages ]
      make_index_for(root_package, pkg_rootdir)
      for package in packages:
          index_dir = os.path.join(pkg_rootdir, package.name)
          make_index_for(package, index_dir)
  
  
  # -----------------------------------------------------------------------------
  # MAIN:
  # -----------------------------------------------------------------------------
  if __name__ == "__main__":
      if (len(sys.argv) != 2) or "-h" in sys.argv[1:] or "--help" in sys.argv[1:]:
          print("USAGE: %s DOWNLOAD_DIR" % os.path.basename(sys.argv[0]))
          print(__doc__)
          sys.exit(1)
      make_package_index(sys.argv[1])
  