spacepaste

  1.  
  2. #!/usr/bin/env python3
  3. import argparse
  4. import os
  5. import pathlib
  6. import shutil
  7. import subprocess
  8. import sys
  9. import types
  10. class _Registry:
  11. def __init__(self):
  12. self.registered = {}
  13. def __call__(self, function):
  14. self.registered[function.__name__] = function
  15. return function
  16. _register = _Registry()
  17. @_register
  18. def clean(cfg):
  19. """Remove extraneous files."""
  20. paths = [cfg.venv_path, ".coverage"] + list(pathlib.Path().glob(".coverage.*"))
  21. for path in paths:
  22. try:
  23. shutil.rmtree(path)
  24. except FileNotFoundError:
  25. pass
  26. @_register
  27. def init(cfg):
  28. """Set up a virtualenv, install requirements.txt, dev-requirements.txt, and current dir."""
  29. subprocess.run(
  30. ["virtualenv", "--python", sys.executable, cfg.venv_path], check=True
  31. )
  32. lock(cfg)
  33. if pathlib.Path("dev-requirements.txt").exists():
  34. subprocess.run(
  35. [
  36. cfg.venv_path / "bin/pip",
  37. "install",
  38. "--requirement",
  39. "dev-requirements.txt",
  40. ],
  41. check=True,
  42. )
  43. subprocess.run(
  44. [cfg.venv_path / "bin/pip", "install", "--requirement", "requirements.txt"],
  45. check=True,
  46. )
  47. subprocess.run(
  48. [cfg.venv_path / "bin/pip", "install", "--editable", "."], check=True
  49. )
  50. @_register
  51. def lock(cfg):
  52. """Use pip-compile to generate package hashes from setup.py and write them into requirements.txt."""
  53. subprocess.run([cfg.venv_path / "bin/pip", "install", "pip-tools"], check=True)
  54. subprocess.run(
  55. [
  56. cfg.venv_path / "bin/pip-compile",
  57. "--generate-hashes",
  58. "--output-file",
  59. "requirements.txt",
  60. "setup.py",
  61. ],
  62. check=True,
  63. )
  64. subprocess.run(
  65. [
  66. cfg.venv_path / "bin/pip-compile",
  67. "--generate-hashes",
  68. "--output-file",
  69. "dev-requirements.txt",
  70. "setup.py",
  71. "dev-requirements.in",
  72. ],
  73. check=True,
  74. )
  75. @_register
  76. def build(cfg):
  77. """Build source and binary distributions."""
  78. subprocess.run(
  79. [cfg.venv_path / "bin/python", "setup.py", "sdist", "bdist_wheel"], check=True
  80. )
  81. @_register
  82. def upload(cfg):
  83. """Upload the distributions to PyPI."""
  84. subprocess.run([cfg.venv_path / "bin/python", "-m", "pip", "install", "twine"])
  85. dists = [str(path) for path in pathlib.Path("dist").iterdir()]
  86. subprocess.run([cfg.venv_path / "bin/twine", "upload", *dists], check=True)
  87. def _get_default_venv_path():
  88. """Get the default path of the venv."""
  89. venv_path = os.environ.get("VENV_PATH")
  90. if venv_path:
  91. return pathlib.Path(venv_path)
  92. workon_home = os.environ.get("WORKON_HOME")
  93. if workon_home is not None:
  94. project_name = pathlib.Path(os.getcwd()).name
  95. return pathlib.Path(workon_home) / project_name
  96. return pathlib.Path().resolve() / "venv"
  97. def cli():
  98. parser = argparse.ArgumentParser()
  99. parser.add_argument(
  100. "--venv",
  101. default=_get_default_venv_path(),
  102. type=pathlib.Path,
  103. help="Path of the venv directory. Defaults, in order: $VENV_PATH, $WORKON_HOME/[current directory name], venv",
  104. )
  105. subparsers = parser.add_subparsers(dest="command_name")
  106. for name, function in _register.registered.items():
  107. subparsers.add_parser(name, help=function.__doc__)
  108. args = parser.parse_args()
  109. if args.command_name is None:
  110. parser.print_help()
  111. sys.exit(2)
  112. function = _register.registered[args.command_name]
  113. cfg = types.SimpleNamespace()
  114. cfg.venv_path = args.venv
  115. function(cfg)
  116. if __name__ == "__main__":
  117. cli()
  118.