bump_version.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. #!/usr/bin/env python
  2. from __future__ import absolute_import
  3. import errno
  4. import os
  5. import re
  6. import shlex
  7. import subprocess
  8. import sys
  9. from contextlib import contextmanager
  10. from tempfile import NamedTemporaryFile
  11. str_t = str if sys.version_info[0] >= 3 else basestring
  12. def rq(s):
  13. return s.strip("\"'")
  14. def cmd(*args):
  15. return subprocess.Popen(args, stdout=subprocess.PIPE).communicate()[0]
  16. @contextmanager
  17. def no_enoent():
  18. try:
  19. yield
  20. except OSError as exc:
  21. if exc.errno != errno.ENOENT:
  22. raise
  23. class StringVersion(object):
  24. def decode(self, s):
  25. s = rq(s)
  26. text = ""
  27. major, minor, release = s.split(".")
  28. if not release.isdigit():
  29. pos = release.index(re.split("\d+", release)[1][0])
  30. release, text = release[:pos], release[pos:]
  31. return int(major), int(minor), int(release), text
  32. def encode(self, v):
  33. return ".".join(map(str, v[:3])) + v[3]
  34. to_str = StringVersion().encode
  35. from_str = StringVersion().decode
  36. class TupleVersion(object):
  37. def decode(self, s):
  38. v = list(map(rq, s.split(", ")))
  39. return (tuple(map(int, v[0:3])) +
  40. tuple(["".join(v[3:])]))
  41. def encode(self, v):
  42. v = list(v)
  43. def quote(lit):
  44. if isinstance(lit, str_t):
  45. return '"{0}"'.format(lit)
  46. return str(lit)
  47. if not v[-1]:
  48. v.pop()
  49. return ", ".join(map(quote, v))
  50. class VersionFile(object):
  51. def __init__(self, filename):
  52. self.filename = filename
  53. self._kept = None
  54. def _as_orig(self, version):
  55. return self.wb.format(version=self.type.encode(version),
  56. kept=self._kept)
  57. def write(self, version):
  58. pattern = self.regex
  59. with no_enoent():
  60. with NamedTemporaryFile() as dest:
  61. with open(self.filename) as orig:
  62. for line in orig:
  63. if pattern.match(line):
  64. dest.write(self._as_orig(version))
  65. else:
  66. dest.write(line)
  67. os.rename(dest.name, self.filename)
  68. def parse(self):
  69. pattern = self.regex
  70. gpos = 0
  71. with open(self.filename) as fh:
  72. for line in fh:
  73. m = pattern.match(line)
  74. if m:
  75. if "?P<keep>" in pattern.pattern:
  76. self._kept, gpos = m.groupdict()["keep"], 1
  77. return self.type.decode(m.groups()[gpos])
  78. class PyVersion(VersionFile):
  79. regex = re.compile(r'^VERSION\s*=\s*\((.+?)\)')
  80. wb = "VERSION = ({version})\n"
  81. type = TupleVersion()
  82. class SphinxVersion(VersionFile):
  83. regex = re.compile(r'^:[Vv]ersion:\s*(.+?)$')
  84. wb = ':Version: {version}\n'
  85. type = StringVersion()
  86. class CPPVersion(VersionFile):
  87. regex = re.compile(r'^\#\s*define\s*(?P<keep>\w*)VERSION\s+(.+)')
  88. wb = '#define {kept}VERSION "{version}"\n'
  89. type = StringVersion()
  90. _filetype_to_type = {"py": PyVersion,
  91. "rst": SphinxVersion,
  92. "txt": SphinxVersion,
  93. "c": CPPVersion,
  94. "h": CPPVersion}
  95. def filetype_to_type(filename):
  96. _, _, suffix = filename.rpartition(".")
  97. return _filetype_to_type[suffix](filename)
  98. def bump(*files, **kwargs):
  99. version = kwargs.get("version")
  100. before_commit = kwargs.get("before_commit")
  101. files = [filetype_to_type(f) for f in files]
  102. versions = [v.parse() for v in files]
  103. current = list(reversed(sorted(versions)))[0] # find highest
  104. current = current.split()[0] # only first sentence
  105. if version:
  106. next = from_str(version)
  107. else:
  108. major, minor, release, text = current
  109. if text:
  110. raise Exception("Can't bump alpha releases")
  111. next = (major, minor, release + 1, text)
  112. print("Bump version from {0} -> {1}".format(to_str(current), to_str(next)))
  113. for v in files:
  114. print(" writing {0.filename!r}...".format(v))
  115. v.write(next)
  116. if before_commit:
  117. cmd(*shlex.split(before_commit))
  118. print(cmd("git", "commit", "-m", "Bumps version to {0}".format(
  119. to_str(next)), *[f.filename for f in files]))
  120. print(cmd("git", "tag", "v{0}".format(to_str(next))))
  121. def main(argv=sys.argv, version=None, before_commit=None):
  122. if not len(argv) > 1:
  123. print("Usage: distdir [docfile] -- <custom version>")
  124. sys.exit(0)
  125. args = []
  126. for arg in argv:
  127. if arg.startswith("--before-commit="):
  128. _, before_commit = arg.split('=')
  129. else:
  130. args.append(arg)
  131. if "--" in args:
  132. c = args.index('--')
  133. version = args[c + 1]
  134. argv = args[:c]
  135. bump(*args[1:], version=version, before_commit=before_commit)
  136. if __name__ == "__main__":
  137. main()