bin/miniver
eefedd80
 #!/usr/bin/env python3
 # This file is part of 'miniver': https://github.com/jbweston/miniver
 
 import sys
 import os
 import os.path
 import argparse
 import tempfile
 import shutil
 import textwrap
133c3398
 import glob
eefedd80
 from zipfile import ZipFile
133c3398
 from importlib.util import find_spec, spec_from_file_location, module_from_spec
eefedd80
 from urllib.request import urlretrieve
 
1a076952
 if sys.version_info < (3, 5):
6ffb9238
     print("Miniver needs at least Python 3.5")
1a076952
     sys.exit(1)
 
eefedd80
 try:
     import miniver
6ffb9238
 
1a77c48c
     _miniver_version = miniver.__version__
eefedd80
     del miniver
     _miniver_is_installed = True
 except ImportError:
6ffb9238
     _miniver_version = "unknown"
eefedd80
     _miniver_is_installed = False
 
 # When we fetch miniver from local files
6ffb9238
 _miniver_modules = ("_version",)
eefedd80
 
 
 # When we fetch miniver from GitHub
6ffb9238
 _miniver_zip_url = "https://github.com/jbweston/miniver/archive/master.zip"
 _zipfile_root = "miniver-master"  # tied to the fact that we fetch master.zip
eefedd80
 
 # File templates
6ffb9238
 _setup_template = textwrap.dedent(
     '''
eefedd80
     def get_version_and_cmdclass(package_path):
da63b96a
         """Load version.py module without importing the whole package.
 
         Template code from miniver
         """
eefedd80
         import os
         from importlib.util import module_from_spec, spec_from_file_location
9f9cbfb5
 
         spec = spec_from_file_location("version", os.path.join(package_path, "_version.py"))
eefedd80
         module = module_from_spec(spec)
         spec.loader.exec_module(module)
         return module.__version__, module.cmdclass
 
 
9f9cbfb5
     version, cmdclass = get_version_and_cmdclass("{package_dir}")
 
eefedd80
 
     setup(
         ...,
         version=version,
         cmdclass=cmdclass,
     )
6ffb9238
 '''
 )
eefedd80
 
6ffb9238
 _static_version_template = textwrap.dedent(
     """\
7a572c2a
     # -*- coding: utf-8 -*-
     # This file is part of 'miniver': https://github.com/jbweston/miniver
     #
     # This file will be overwritten by setup.py when a source or binary
     # distribution is made.  The magic value "__use_git__" is interpreted by
     # version.py.
 
     version = "__use_git__"
 
     # These values are only set if the distribution was created with 'git archive'
     refnames = "$Format:%D$"
     git_hash = "$Format:%h$"
6ffb9238
 """
 )
7a572c2a
 
6ffb9238
 _init_template = "from ._version import __version__"
 _gitattribute_template = "{package_dir}/_static_version.py export-subst"
eefedd80
 
 
 def _line_in_file(to_find, filename):
     """Return True if the specified line exists in the named file."""
6ffb9238
     assert "\n" not in to_find
eefedd80
     try:
         with open(filename) as f:
             for line in f:
                 if to_find in line:
                     return True
             return False
     except FileNotFoundError:
         return False
 
 
 def _write_line(content, filename):
6ffb9238
     assert "\n" not in content
eefedd80
     if not _line_in_file(content, filename):
6ffb9238
         with open(filename, "a") as f:
eefedd80
             f.write(content)
 
 
7a572c2a
 def _write_content(content, filename):
6ffb9238
     with open(filename, "w") as f:
7a572c2a
         f.write(content)
 
 
eefedd80
 def _fail(msg):
     print(msg, file=sys.stderr)
6ffb9238
     print("Miniver was not installed", file=sys.stderr)
eefedd80
     sys.exit(1)
 
 
 def extract_miniver_from_github():
     filename, _ = urlretrieve(_miniver_zip_url)
     z = ZipFile(filename)
     tmpdir = tempfile.mkdtemp()
6ffb9238
     input_paths = [
         "/".join((_zipfile_root, "miniver", module + ".py"))
         for module in _miniver_modules
     ]
eefedd80
     for p in input_paths:
         z.extract(p, path=tmpdir)
     return [os.path.join(tmpdir, *p.split()) for p in input_paths]
 
 
 def extract_miniver_from_local():
6ffb9238
     return [
         find_spec("." + module, package="miniver").origin for module in _miniver_modules
     ]
eefedd80
 
 
10679ce5
 def get_parser():
     parser = argparse.ArgumentParser(description="Interact with miniver")
6ffb9238
     parser.add_argument("-v", "--version", action="version", version=_miniver_version)
10679ce5
     # TODO: when we can depend on Python 3.7 make this "add_subparsers(required=True)"
     subparsers = parser.add_subparsers()
     # 'install' command
     install_parser = subparsers.add_parser(
         "install", help="Install miniver into the current Python package"
     )
     install_parser.add_argument(
6ffb9238
         "package_directory", help="Directory to install 'miniver' into."
     )
10679ce5
     install_parser.set_defaults(dispatch=install)
133c3398
     # 'ver' command
     ver_parser = subparsers.add_parser(
         "ver", help="Get generated version"
     )
     ver_parser.add_argument(
         "package_directory", help="Directory 'miniver' was installed."
     )
     ver_parser.set_defaults(dispatch=ver)
10679ce5
     return parser
eefedd80
 
 
10679ce5
 def install(args):
     package_dir = args.package_directory
eefedd80
     if not os.path.isdir(package_dir):
         _fail("Directory '{}' does not exist".format(package_dir))
     if package_dir != os.path.relpath(package_dir):
         _fail("'{}' is not a relative directory".format(package_dir))
 
     # Get miniver files
     if _miniver_is_installed:
         miniver_paths = extract_miniver_from_local()
     else:
         miniver_paths = extract_miniver_from_github()
6ffb9238
     output_paths = [
         os.path.join(package_dir, os.path.basename(path)) for path in miniver_paths
     ]
eefedd80
 
     for path in output_paths:
         if os.path.exists(path):
             _fail("'{}' already exists".format(path))
 
     # Write content to local package directory
     for path, output_path in zip(miniver_paths, output_paths):
         shutil.copy(path, output_path)
6ffb9238
     _write_content(
         _static_version_template, os.path.join(package_dir, "_static_version.py")
     )
     _write_line(
         _gitattribute_template.format(package_dir=package_dir), ".gitattributes"
     )
     _write_line(
         _init_template.format(package_dir=package_dir),
         os.path.join(package_dir, "__init__.py"),
     )
eefedd80
 
6ffb9238
     msg = "\n".join(
         textwrap.wrap(
             "Miniver is installed into '{package_dir}/'. "
             "You still have to copy the following snippet into your 'setup.py':"
         )
     )
     print("\n".join((msg, _setup_template)).format(package_dir=package_dir))
eefedd80
 
 
133c3398
 def ver(args):
     package_dir = args.package_directory
5008088a
     try:
         version_location, = glob.glob(f"{package_dir}/**/_version.py", recursive=True)
     except ValueError as err:
         if "not enough" in str(err):
             print(f"'_version.py' not found in '{package_dir}'", file=sys.stderr)
             sys.exit(1)
         elif "too many" in str(err):
             print(f"More than 1 '_version.py' found in '{package_dir}'",  file=sys.stderr)
             sys.exit(1)
         else:
             raise
133c3398
     version_spec = spec_from_file_location("version", version_location)
     version = module_from_spec(version_spec)
     version_spec.loader.exec_module(version)
     print(version.get_version())
 
 
10679ce5
 def main():
     parser = get_parser()
     args = parser.parse_args()
     # TODO: remove this check when we can rely on Python 3.7 and
     #       can make subparsers required.
     if "dispatch" in args:
         args.dispatch(args)
     else:
9cce6560
         parser.parse_args(["-h"])
10679ce5
 
 
6ffb9238
 if __name__ == "__main__":
eefedd80
     main()