Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

update.py 8.7KB

1 year ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. # Copyright (c) Twisted Matrix Laboratories.
  2. # See LICENSE for details.
  3. from __future__ import absolute_import, division, print_function
  4. import click
  5. import os
  6. import datetime
  7. from typing import TYPE_CHECKING, Dict, Optional, Callable, Iterable
  8. from incremental import Version
  9. if TYPE_CHECKING:
  10. from typing_extensions import Protocol
  11. class _ReadableWritable(Protocol):
  12. def read(self): # type: () -> bytes
  13. pass
  14. def write(self, v): # type: (bytes) -> object
  15. pass
  16. def __enter__(self): # type: () -> _ReadableWritable
  17. pass
  18. def __exit__(self, *args, **kwargs): # type: (object, object) -> Optional[bool]
  19. pass
  20. # FilePath is missing type annotations
  21. # https://twistedmatrix.com/trac/ticket/10148
  22. class FilePath(object):
  23. def __init__(self, path): # type: (str) -> None
  24. self.path = path
  25. def child(self, v): # type: (str) -> FilePath
  26. pass
  27. def isdir(self): # type: () -> bool
  28. pass
  29. def isfile(self): # type: () -> bool
  30. pass
  31. def getContent(self): # type: () -> bytes
  32. pass
  33. def open(self, mode): # type: (str) -> _ReadableWritable
  34. pass
  35. def walk(self): # type: () -> Iterable[FilePath]
  36. pass
  37. else:
  38. from twisted.python.filepath import FilePath
  39. _VERSIONPY_TEMPLATE = '''"""
  40. Provides {package} version information.
  41. """
  42. # This file is auto-generated! Do not edit!
  43. # Use `python -m incremental.update {package}` to change this file.
  44. from incremental import Version
  45. __version__ = {version_repr}
  46. __all__ = ["__version__"]
  47. '''
  48. _YEAR_START = 2000
  49. def _findPath(path, package): # type: (str, str) -> FilePath
  50. cwd = FilePath(path)
  51. src_dir = cwd.child("src").child(package.lower())
  52. current_dir = cwd.child(package.lower())
  53. if src_dir.isdir():
  54. return src_dir
  55. elif current_dir.isdir():
  56. return current_dir
  57. else:
  58. raise ValueError(
  59. "Can't find under `./src` or `./`. Check the "
  60. "package name is right (note that we expect your "
  61. "package name to be lower cased), or pass it using "
  62. "'--path'."
  63. )
  64. def _existing_version(path): # type: (FilePath) -> Version
  65. version_info = {} # type: Dict[str, Version]
  66. with path.child("_version.py").open("r") as f:
  67. exec(f.read(), version_info)
  68. return version_info["__version__"]
  69. def _run(
  70. package, # type: str
  71. path, # type: Optional[str]
  72. newversion, # type: Optional[str]
  73. patch, # type: bool
  74. rc, # type: bool
  75. post, # type: bool
  76. dev, # type: bool
  77. create, # type: bool
  78. _date=None, # type: Optional[datetime.date]
  79. _getcwd=None, # type: Optional[Callable[[], str]]
  80. _print=print, # type: Callable[[object], object]
  81. ): # type: (...) -> None
  82. if not _getcwd:
  83. _getcwd = os.getcwd
  84. if not _date:
  85. _date = datetime.date.today()
  86. if type(package) != str:
  87. package = package.encode("utf8") # type: ignore[assignment]
  88. _path = FilePath(path) if path else _findPath(_getcwd(), package)
  89. if (
  90. newversion
  91. and patch
  92. or newversion
  93. and dev
  94. or newversion
  95. and rc
  96. or newversion
  97. and post
  98. ):
  99. raise ValueError("Only give --newversion")
  100. if dev and patch or dev and rc or dev and post:
  101. raise ValueError("Only give --dev")
  102. if (
  103. create
  104. and dev
  105. or create
  106. and patch
  107. or create
  108. and rc
  109. or create
  110. and post
  111. or create
  112. and newversion
  113. ):
  114. raise ValueError("Only give --create")
  115. if newversion:
  116. from pkg_resources import parse_version
  117. existing = _existing_version(_path)
  118. st_version = parse_version(newversion)._version # type: ignore[attr-defined]
  119. release = list(st_version.release)
  120. minor = 0
  121. micro = 0
  122. if len(release) == 1:
  123. (major,) = release
  124. elif len(release) == 2:
  125. major, minor = release
  126. else:
  127. major, minor, micro = release
  128. v = Version(
  129. package,
  130. major,
  131. minor,
  132. micro,
  133. release_candidate=st_version.pre[1] if st_version.pre else None,
  134. post=st_version.post[1] if st_version.post else None,
  135. dev=st_version.dev[1] if st_version.dev else None,
  136. )
  137. elif create:
  138. v = Version(package, _date.year - _YEAR_START, _date.month, 0)
  139. existing = v
  140. elif rc and not patch:
  141. existing = _existing_version(_path)
  142. if existing.release_candidate:
  143. v = Version(
  144. package,
  145. existing.major,
  146. existing.minor,
  147. existing.micro,
  148. existing.release_candidate + 1,
  149. )
  150. else:
  151. v = Version(package, _date.year - _YEAR_START, _date.month, 0, 1)
  152. elif patch:
  153. existing = _existing_version(_path)
  154. v = Version(
  155. package,
  156. existing.major,
  157. existing.minor,
  158. existing.micro + 1,
  159. 1 if rc else None,
  160. )
  161. elif post:
  162. existing = _existing_version(_path)
  163. if existing.post is None:
  164. _post = 0
  165. else:
  166. _post = existing.post + 1
  167. v = Version(package, existing.major, existing.minor, existing.micro, post=_post)
  168. elif dev:
  169. existing = _existing_version(_path)
  170. if existing.dev is None:
  171. _dev = 0
  172. else:
  173. _dev = existing.dev + 1
  174. v = Version(
  175. package,
  176. existing.major,
  177. existing.minor,
  178. existing.micro,
  179. existing.release_candidate,
  180. dev=_dev,
  181. )
  182. else:
  183. existing = _existing_version(_path)
  184. if existing.release_candidate:
  185. v = Version(package, existing.major, existing.minor, existing.micro)
  186. else:
  187. raise ValueError("You need to issue a rc before updating the major/minor")
  188. NEXT_repr = repr(Version(package, "NEXT", 0, 0)).split("#")[0].replace("'", '"')
  189. NEXT_repr_bytes = NEXT_repr.encode("utf8")
  190. version_repr = repr(v).split("#")[0].replace("'", '"')
  191. version_repr_bytes = version_repr.encode("utf8")
  192. existing_version_repr = repr(existing).split("#")[0].replace("'", '"')
  193. existing_version_repr_bytes = existing_version_repr.encode("utf8")
  194. _print("Updating codebase to %s" % (v.public()))
  195. for x in _path.walk():
  196. if not x.isfile():
  197. continue
  198. original_content = x.getContent()
  199. content = original_content
  200. # Replace previous release_candidate calls to the new one
  201. if existing.release_candidate:
  202. content = content.replace(existing_version_repr_bytes, version_repr_bytes)
  203. content = content.replace(
  204. (package.encode("utf8") + b" " + existing.public().encode("utf8")),
  205. (package.encode("utf8") + b" " + v.public().encode("utf8")),
  206. )
  207. # Replace NEXT Version calls with the new one
  208. content = content.replace(NEXT_repr_bytes, version_repr_bytes)
  209. content = content.replace(
  210. NEXT_repr_bytes.replace(b"'", b'"'), version_repr_bytes
  211. )
  212. # Replace <package> NEXT with <package> <public>
  213. content = content.replace(
  214. package.encode("utf8") + b" NEXT",
  215. (package.encode("utf8") + b" " + v.public().encode("utf8")),
  216. )
  217. if content != original_content:
  218. _print("Updating %s" % (x.path,))
  219. with x.open("w") as f:
  220. f.write(content)
  221. _print("Updating %s/_version.py" % (_path.path))
  222. with _path.child("_version.py").open("w") as f:
  223. f.write(
  224. (
  225. _VERSIONPY_TEMPLATE.format(package=package, version_repr=version_repr)
  226. ).encode("utf8")
  227. )
  228. @click.command()
  229. @click.argument("package")
  230. @click.option("--path", default=None)
  231. @click.option("--newversion", default=None)
  232. @click.option("--patch", is_flag=True)
  233. @click.option("--rc", is_flag=True)
  234. @click.option("--post", is_flag=True)
  235. @click.option("--dev", is_flag=True)
  236. @click.option("--create", is_flag=True)
  237. def run(
  238. package, # type: str
  239. path, # type: Optional[str]
  240. newversion, # type: Optional[str]
  241. patch, # type: bool
  242. rc, # type: bool
  243. post, # type: bool
  244. dev, # type: bool
  245. create, # type: bool
  246. ): # type: (...) -> None
  247. return _run(
  248. package=package,
  249. path=path,
  250. newversion=newversion,
  251. patch=patch,
  252. rc=rc,
  253. post=post,
  254. dev=dev,
  255. create=create,
  256. )
  257. if __name__ == "__main__": # pragma: no cover
  258. run()