diff --git a/.gitignore b/.gitignore index 6e7258d..e5b2136 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +.envrc +.venv .coverage htmlcov *.egg-info diff --git a/pyproject.toml b/pyproject.toml index 78a6398..ead6ae4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,3 +34,39 @@ tbump = "tbump.main:main" [build-system] requires = ["poetry>=0.12"] build-backend = "poetry.masonry.api" + + +[tool.tbump.version] +current = "6.1.1" +regex = ''' + (?P\d+) + \. + (?P\d+) + \. + (?P\d+) + ''' + +[tool.tbump.git] +message_template = "Bump to {new_version}" +tag_template = "v{new_version}" + + +[[tool.tbump.file]] +src = "pyproject.toml" +search = 'version = "{current_version}"' + + +[[tool.tbump.file]] +src = "tbump/main.py" + +[[tool.tbump.before_commit]] +name = "Run CI" +cmd = "./lint.sh && poetry run pytest" + +[[tool.tbump.before_commit]] +name = "Check Changelog" +cmd = "grep -q {new_version} Changelog.rst" + +[[tool.tbump.after_push]] +name = "Publish to pypi" +cmd = "tools/publish.sh" diff --git a/tbump.toml b/tbump.toml deleted file mode 100644 index b15a08c..0000000 --- a/tbump.toml +++ /dev/null @@ -1,38 +0,0 @@ -github_url = "https://github.com/TankerHQ/tbump" - - -[version] -current = "6.1.1" -regex = ''' - (?P\d+) - \. - (?P\d+) - \. - (?P\d+) - ''' - - -[git] -message_template = "Bump to {new_version}" -tag_template = "v{new_version}" - - -[[file]] -src = "pyproject.toml" -search = 'version = "{current_version}"' - - -[[file]] -src = "tbump/main.py" - -[[before_commit]] -name = "Run CI" -cmd = "./lint.sh && poetry run pytest" - -[[before_commit]] -name = "Check Changelog" -cmd = "grep -q {new_version} Changelog.rst" - -[[after_push]] -name = "Publish to pypi" -cmd = "tools/publish.sh" diff --git a/tbump/config.py b/tbump/config.py index b38fb07..ee36808 100644 --- a/tbump/config.py +++ b/tbump/config.py @@ -92,7 +92,8 @@ def validate_re(regex: str) -> str: schema.Optional("github_url"): str, } ) - return cast(Config, tbump_schema.validate(config)) + validated_config = tbump_schema.validate(config) + return cast(Config, validated_config) def validate_config(cfg: Config) -> None: @@ -126,7 +127,19 @@ def validate_config(cfg: Config) -> None: def parse(cfg_path: Path) -> Config: - parsed = tomlkit.loads(cfg_path.text()) + # tbump.toml used to contain "flat" keys while pyproject.toml contains nested keys. + # For example: + # + # [[file]] + # vs + # [[tool.tbump.file]] + # + # In order to minimize the number of changes that are needed for this switch + # we parse `pyproject.toml` and use it to create a new `tomlkit.Document()` with + # a flat structure + parsed_pyproject = tomlkit.loads(cfg_path.text())["tool"]["tbump"] + parsed = tomlkit.document() + parsed.update(parsed_pyproject) parsed = validate_basic_schema(parsed) current_version = parsed["version"]["current"] git_message_template = parsed["git"]["message_template"] diff --git a/tbump/file_bumper.py b/tbump/file_bumper.py index 1aa94aa..2539b97 100644 --- a/tbump/file_bumper.py +++ b/tbump/file_bumper.py @@ -193,7 +193,7 @@ def get_patches(self, new_version: str) -> List[Patch]: self.new_groups = self.parse_version(self.new_version) change_requests = self.compute_change_requests() tbump_toml_change = ChangeRequest( - "tbump.toml", self.current_version, new_version + "pyproject.toml", self.current_version, new_version ) change_requests.append(tbump_toml_change) patches = [] @@ -272,7 +272,7 @@ def compute_change_request_for_file(self, file: tbump.config.File) -> ChangeRequ def bump_files(new_version: str, repo_path: Path = None) -> None: repo_path = repo_path or Path(".") bumper = FileBumper(repo_path) - cfg = tbump.config.parse(repo_path / "tbump.toml") + cfg = tbump.config.parse(repo_path / "pyproject.toml") bumper.set_config(cfg) patches = bumper.get_patches(new_version=new_version) n = len(patches) diff --git a/tbump/init.py b/tbump/init.py index 28ec161..79b432d 100644 --- a/tbump/init.py +++ b/tbump/init.py @@ -20,17 +20,18 @@ def find_files(working_path: Path, current_version: str) -> List[str]: def init(working_path: Path, *, current_version: str) -> None: - """ Interactively creates a new tbump.toml """ + """ Interactively creates a new pyproject.toml """ ui.info_1("Generating tbump config file") - tbump_path = working_path / "tbump.toml" + tbump_path = working_path / "pyproject.toml" if tbump_path.exists(): ui.fatal(tbump_path, "already exists") template = textwrap.dedent( """\ # Uncomment this if your project is hosted on GitHub: + # [tool.tbump] # github_url = https://github.com/// - [version] + [tool.tbump.version] current = "@current_version@" # Example of a semver regexp. @@ -44,7 +45,7 @@ def init(working_path: Path, *, current_version: str) -> None: (?P\\d+) ''' - [git] + [tool.tbump.git] message_template = "Bump to {new_version}" tag_template = "v{new_version}" """ @@ -53,9 +54,9 @@ def init(working_path: Path, *, current_version: str) -> None: file_template = textwrap.dedent( """ # For each file to patch, add a [[file]] config section containing - # the path of the file, relative to the tbump.toml location. - [[file]] - src = "..." + # the path of the file, relative to the pyproject.toml location. + [[tool.tbump.file]] + src = "pyproject.toml" """ ) @@ -81,4 +82,4 @@ def init(working_path: Path, *, current_version: str) -> None: to_write += file_template to_write += hooks_template tbump_path.write_text(to_write) - ui.info_2(ui.check, "Generated tbump.toml") + ui.info_2(ui.check, "Generated pyproject.toml") diff --git a/tbump/main.py b/tbump/main.py index 8563dc6..bd7a91b 100644 --- a/tbump/main.py +++ b/tbump/main.py @@ -105,7 +105,7 @@ def run(cmd: List[str]) -> None: def parse_config(working_path: Path) -> Config: - tbump_path = working_path / "tbump.toml" + tbump_path = working_path / "pyproject.toml" try: config = tbump.config.parse(tbump_path) except IOError as io_error: diff --git a/tbump/test/data/after.py b/tbump/test/data/after.py index 126be39..fe9e591 100644 --- a/tbump/test/data/after.py +++ b/tbump/test/data/after.py @@ -1,4 +1,4 @@ -""" Fake hook used for tetsting. +""" Fake hook used for testing. Just write a file named after-hook.stamp when called, so that test code can check if the hook ran """ diff --git a/tbump/test/data/before.py b/tbump/test/data/before.py index 8c53730..90cdee6 100644 --- a/tbump/test/data/before.py +++ b/tbump/test/data/before.py @@ -1,4 +1,4 @@ -""" Fake hook used for tetsting. +""" Fake hook used for testing. Just write a file named before-hook.stamp when called, so that test code can check if the hook ran """ diff --git a/tbump/test/data/tbump.toml b/tbump/test/data/pyproject.toml similarity index 79% rename from tbump/test/data/tbump.toml rename to tbump/test/data/pyproject.toml index 4c3eb73..2f17664 100644 --- a/tbump/test/data/tbump.toml +++ b/tbump/test/data/pyproject.toml @@ -1,4 +1,4 @@ -[version] +[tool.tbump.version] current = "1.2.41-alpha-1" regex = ''' (?P\d+) @@ -14,21 +14,21 @@ regex = ''' )? ''' -[git] +[tool.tbump.git] message_template = "Bump to {new_version}" tag_template = "v{new_version}" -[[file]] +[[tool.tbump.file]] src = "package.json" search = '"version": "{current_version}"' -[[file]] +[[tool.tbump.file]] src = "VERSION" -[[file]] +[[tool.tbump.file]] src = "pub.js" version_template = "{major}.{minor}.{patch}" -[[file]] +[[tool.tbump.file]] src = "glob*.?" search = 'version_[a-z]+ = "{current_version}"' diff --git a/tbump/test/test_config.py b/tbump/test/test_config.py index 9a425bc..43bff85 100644 --- a/tbump/test/test_config.py +++ b/tbump/test/test_config.py @@ -12,7 +12,7 @@ def test_happy_parse(test_data_path: Path) -> None: - config = tbump.config.parse(test_data_path / "tbump.toml") + config = tbump.config.parse(test_data_path / "pyproject.toml") foo_json = tbump.config.File( src="package.json", search='"version": "{current_version}"' ) @@ -52,7 +52,7 @@ def assert_validation_error(config: Config, expected_message: str) -> None: @pytest.fixture # type: ignore def test_config(test_data_path: Path) -> Config: - return tbump.config.parse(test_data_path / "tbump.toml") + return tbump.config.parse(test_data_path / "pyproject.toml") def test_invalid_commit_message(test_config: Config) -> None: @@ -83,15 +83,15 @@ def test_current_version_does_not_match_expected_regex(test_config: Config) -> N def test_invalid_regex() -> None: contents = textwrap.dedent( r""" - [version] + [tool.tbump.version] current = '1.42a1' regex = '(unbalanced' - [git] + [tool.tbump.git] message_template = "Bump to {new_version}" tag_template = "v{new_version}" - [[file]] + [[tool.tbump.file]] src = "VERSION" """ ) @@ -112,25 +112,25 @@ def test_invalid_custom_template(test_config: Config) -> None: def test_parse_hooks(tmp_path: Path) -> None: - toml_path = tmp_path / "tbump.toml" + toml_path = tmp_path / "pyproject.toml" toml_path.write_text( r""" - [version] + [tool.tbump.version] current = "1.2.3" regex = '(?P\d+)\.(?P\d+)\.(?P\d+)' - [git] + [tool.tbump.git] message_template = "Bump to {new_version}" tag_template = "v{new_version}" - [[file]] + [[tool.tbump.file]] src = "pub.js" - [[before_commit]] + [[tool.tbump.before_commit]] name = "Check changelog" cmd = "grep -q {new_version} Changelog.md" - [[after_push]] + [[tool.tbump.after_push]] name = "After push" cmd = "cargo publish" """ @@ -148,25 +148,25 @@ def test_parse_hooks(tmp_path: Path) -> None: def test_retro_compat_hooks(tmp_path: Path) -> None: - toml_path = tmp_path / "tbump.toml" + toml_path = tmp_path / "pyproject.toml" toml_path.write_text( r""" - [version] + [tool.tbump.version] current = "1.2.3" regex = '(?P\d+)\.(?P\d+)\.(?P\d+)' - [git] + [tool.tbump.git] message_template = "Bump to {new_version}" tag_template = "v{new_version}" - [[file]] + [[tool.tbump.file]] src = "pub.js" - [[hook]] + [[tool.tbump.hook]] name = "very old name" cmd = "old command" - [[before_push]] + [[tool.tbump.before_push]] name = "deprecated name" cmd = "deprecated command" """ diff --git a/tbump/test/test_file_bumper.py b/tbump/test/test_file_bumper.py index 67c0521..b85c911 100644 --- a/tbump/test/test_file_bumper.py +++ b/tbump/test/test_file_bumper.py @@ -7,7 +7,7 @@ def test_file_bumper_simple(test_repo: Path) -> None: bumper = tbump.file_bumper.FileBumper(test_repo) - config = tbump.config.parse(test_repo / "tbump.toml") + config = tbump.config.parse(test_repo / "pyproject.toml") assert bumper.working_path == test_repo bumper.set_config(config) patches = bumper.get_patches(new_version="1.2.41-alpha-2") @@ -34,7 +34,7 @@ def test_patcher_preserve_endings(tmp_path: Path) -> None: def test_file_bumper_preserve_endings(test_repo: Path) -> None: bumper = tbump.file_bumper.FileBumper(test_repo) - config = tbump.config.parse(test_repo / "tbump.toml") + config = tbump.config.parse(test_repo / "pyproject.toml") package_json = test_repo / "package.json" # Make sure package.json contain CRLF line endings @@ -51,10 +51,10 @@ def test_file_bumper_preserve_endings(test_repo: Path) -> None: def test_looking_for_empty_groups(tmp_path: Path) -> None: - tbump_path = tmp_path / "tbump.toml" + tbump_path = tmp_path / "pyproject.toml" tbump_path.write_text( r""" - [version] + [tool.tbump.version] current = "1.2" regex = ''' (?P\d+) @@ -66,11 +66,11 @@ def test_looking_for_empty_groups(tmp_path: Path) -> None: )? ''' - [git] + [tool.tbump.git] message_template = "Bump to {new_version}" tag_template = "v{new_version}" - [[file]] + [[tool.tbump.file]] src = "foo" version_template = "{major}.{minor}.{patch}" @@ -92,18 +92,18 @@ def test_looking_for_empty_groups(tmp_path: Path) -> None: def test_current_version_not_found(tmp_path: Path) -> None: - tbump_path = tmp_path / "tbump.toml" + tbump_path = tmp_path / "pyproject.toml" tbump_path.write_text( r""" - [version] + [tool.tbump.version] current = "1.2.3" regex = ".*" - [git] + [tool.tbump.git] message_template = "Bump to {new_version}" tag_template = "v{new_version}" - [[file]] + [[tool.tbump.file]] src = "version.txt" """ ) @@ -119,10 +119,10 @@ def test_current_version_not_found(tmp_path: Path) -> None: def test_replacing_with_empty_groups(tmp_path: Path) -> None: - tbump_path = tmp_path / "tbump.toml" + tbump_path = tmp_path / "pyproject.toml" tbump_path.write_text( r""" - [version] + [tool.tbump.version] current = "1.2.3" regex = ''' (?P\d+) @@ -134,11 +134,11 @@ def test_replacing_with_empty_groups(tmp_path: Path) -> None: )? ''' - [git] + [tool.tbump.git] message_template = "Bump to {new_version}" tag_template = "v{new_version}" - [[file]] + [[tool.tbump.file]] src = "foo" version_template = "{major}.{minor}.{patch}" @@ -160,10 +160,10 @@ def test_replacing_with_empty_groups(tmp_path: Path) -> None: def test_changing_same_file_twice(tmp_path: Path) -> None: - tbump_path = tmp_path / "tbump.toml" + tbump_path = tmp_path / "pyproject.toml" tbump_path.write_text( r""" - [version] + [tool.tbump.version] current = "1.2.3" regex = ''' (?P\d+) @@ -175,16 +175,16 @@ def test_changing_same_file_twice(tmp_path: Path) -> None: )? ''' - [git] + [tool.tbump.git] message_template = "Bump to {new_version}" tag_template = "v{new_version}" - [[file]] + [[tool.tbump.file]] src = "foo.c" version_template = "{major}.{minor}" search = "PUBLIC_VERSION" - [[file]] + [[tool.tbump.file]] src = "foo.c" search = "FULL_VERSION" diff --git a/tbump/test/test_git_bumper.py b/tbump/test/test_git_bumper.py index ba903bb..8cc19a3 100644 --- a/tbump/test/test_git_bumper.py +++ b/tbump/test/test_git_bumper.py @@ -10,7 +10,7 @@ @pytest.fixture # type: ignore def test_git_bumper(test_repo: Path) -> GitBumper: - config = tbump.config.parse(test_repo / "tbump.toml") + config = tbump.config.parse(test_repo / "pyproject.toml") git_bumper = tbump.git_bumper.GitBumper(test_repo) git_bumper.set_config(config) return git_bumper diff --git a/tbump/test/test_hooks.py b/tbump/test/test_hooks.py index 2b60090..33cd3f9 100644 --- a/tbump/test/test_hooks.py +++ b/tbump/test/test_hooks.py @@ -12,18 +12,20 @@ def add_hook(test_repo: Path, name: str, cmd: str, after_push: bool = False) -> """ Patch the configuration file so that we can also test hooks. """ - cfg_path = test_repo / "tbump.toml" + cfg_path = test_repo / "pyproject.toml" parsed = tomlkit.loads(cfg_path.text()) if after_push: key = "after_push" else: key = "before_commit" - if key not in parsed: - parsed[key] = tomlkit.aot() + if key not in parsed["tool"]["tbump"]: + parsed["tool"]["tbump"][key] = tomlkit.aot() hook_config = tomlkit.table() hook_config.add("cmd", cmd) hook_config.add("name", name) - parsed[key].append(hook_config) + parsed["tool"]["tbump"][key].append(hook_config) + from pprint import pprint + pprint(parsed) cfg_path.write_text(tomlkit.dumps(parsed)) tbump.git.run_git(test_repo, "add", ".") diff --git a/tbump/test/test_init.py b/tbump/test/test_init.py index a67310e..ac7728b 100644 --- a/tbump/test/test_init.py +++ b/tbump/test/test_init.py @@ -9,7 +9,7 @@ def test_creates_config(test_repo: Path) -> None: - tbump_path = test_repo / "tbump.toml" + tbump_path = test_repo / "pyproject.toml" tbump_path.remove() current_version = "1.2.41-alpha1" @@ -17,10 +17,10 @@ def test_creates_config(test_repo: Path) -> None: assert tbump_path.exists() config = tomlkit.loads(tbump_path.text()) - assert config["version"]["current"] == "1.2.41-alpha1" + assert config["tool"]["tbump"]["version"]["current"] == "1.2.41-alpha1" -def test_abort_if_tbump_toml_exists( +def test_abort_if_pyproject_toml_exists( test_repo: Path, message_recorder: MessageRecorder ) -> None: with pytest.raises(SystemExit): diff --git a/tbump/test/test_main.py b/tbump/test/test_main.py index b76014d..2b2ea3d 100644 --- a/tbump/test/test_main.py +++ b/tbump/test/test_main.py @@ -12,9 +12,9 @@ def files_bumped(test_repo: Path) -> bool: - toml_path = test_repo / "tbump.toml" + toml_path = test_repo / "pyproject.toml" new_toml = tomlkit.loads(toml_path.text()) - assert new_toml["version"]["current"] == "1.2.41-alpha-2" + assert new_toml["tool"]["tbump"]["version"]["current"] == "1.2.41-alpha-2" return all( ( @@ -26,9 +26,9 @@ def files_bumped(test_repo: Path) -> bool: def files_not_bumped(test_repo: Path) -> bool: - toml_path = test_repo / "tbump.toml" + toml_path = test_repo / "pyproject.toml" new_toml = tomlkit.loads(toml_path.text()) - assert new_toml["version"]["current"] == "1.2.41-alpha-1" + assert new_toml["tool"]["tbump"]["version"]["current"] == "1.2.41-alpha-1" return all( ( @@ -158,22 +158,22 @@ def test_on_outdated_branch(test_repo: Path) -> None: assert not tag_pushed(test_repo) -def test_tbump_toml_not_found( +def test_pyproject_toml_not_found( test_repo: Path, message_recorder: MessageRecorder ) -> None: - toml_path = test_repo / "tbump.toml" + toml_path = test_repo / "pyproject.toml" toml_path.remove() with pytest.raises(SystemExit): tbump.main.main(["-C", test_repo, "1.2.42", "--non-interactive"]) assert message_recorder.find("No such file") -def test_tbump_toml_bad_syntax( +def test_pyproject_toml_bad_syntax( test_repo: Path, message_recorder: MessageRecorder ) -> None: - toml_path = test_repo / "tbump.toml" + toml_path = test_repo / "pyproject.toml" bad_toml = tomlkit.loads(toml_path.text()) - del bad_toml["git"] + del bad_toml["tool"]["tbump"]["git"] toml_path.write_text(tomlkit.dumps(bad_toml)) with pytest.raises(SystemExit): tbump.main.main(["-C", test_repo, "1.2.42", "--non-interactive"]) @@ -235,10 +235,10 @@ def test_abort_if_file_does_not_match( ) -> None: invalid_src = test_repo / "foo.txt" invalid_src.write_text("this is foo") - tbump_path = test_repo / "tbump.toml" + tbump_path = test_repo / "pyproject.toml" tbump_path.write_text( """\ - [[file]] + [[tool.tbump.file]] src = "foo.txt" """, append=True, @@ -290,9 +290,9 @@ def test_do_not_add_untracked_files(test_repo: Path) -> None: def test_bad_substitution(test_repo: Path, message_recorder: MessageRecorder) -> None: - toml_path = test_repo / "tbump.toml" + toml_path = test_repo / "pyproject.toml" new_toml = tomlkit.loads(toml_path.text()) - new_toml["file"][0]["version_template"] = "{release}" + new_toml["tool"]["tbump"]["file"][0]["version_template"] = "{release}" toml_path.write_text(tomlkit.dumps(new_toml)) tbump.git.run_git(test_repo, "add", ".") tbump.git.run_git(test_repo, "commit", "--message", "update repo")