bump_version.py 4.8 KB

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