Skip to content

Commit

Permalink
Merge remote-tracking branch 'refs/remotes/origin/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
agoose77 committed Sep 21, 2022
2 parents 9fae298 + 270d9db commit 91bbb3e
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 79 deletions.
22 changes: 11 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
This package provides two Hatch plugins:

- [version source plugin](https://hatch.pypa.io/latest/plugins/version-source/) that reads/writes the package version
from the `version` field of the NodeJS `package.json` file.
from the `version` field of the Node.js `package.json` file.
- [metadata hook plugin](https://hatch.pypa.io/latest/plugins/metadata-hook/) that reads PEP 621 metadata from the
NodeJS `package.json` file.
Node.js `package.json` file.

**Table of Contents**

Expand Down Expand Up @@ -68,7 +68,7 @@ Meanwhile, [PEP 440](https://peps.python.org/pep-0440/#version-scheme) defines:
- `post-release`
- `dev-release`

In order to ensure round-trip support, and ensure semantic consistency between NodeJS and Python, this plugin only
In order to ensure round-trip support, and ensure semantic consistency between Node.js and Python, this plugin only
accepts the common version parts:

- `major`
Expand Down Expand Up @@ -102,14 +102,14 @@ The [metadata hook plugin](https://hatch.pypa.io/dev/plugins/metadata-hook/refer

### Metadata hook options

| Option | Type | Default | Description |
|-------------------------------|-----------------|------------------|-------------------------------------------------------------------------------------------------------------|
| `path` | `str` | `"package.json"` | Relative path to the `package.json` file. |
| `fields` | `list` of `str` | `None` | Optional list of `pyproject.toml` fields to take from their counterparts in `package.json`. |
| `contributors-as-maintainers` | `bool` | `True` | Whether contributors in `package.json` should be considered maintainers (otherwise, treat them as authors). |
| `bugs-label` | `str` | `"Bug Tracker"` | The key in the URLs table of `pyproject.toml` that is populated by the `bugs` field in `package.json` |
| `homepage-label` | `str` | `"Homepage"` | The key in the URLs table of `pyproject.toml` that is populated by the `homepage` field in `package.json` |
| `repository-label` | `str` | `"Repository"` | The key in the URLs table of `pyproject.toml` that is populated by the `repository` field in `package.json` |
| Option | Type | Default | Description |
|-------------------------------|-----------------|------------------|-------------------------------------------------------------------------------------------------------------------------------------------|
| `path` | `str` | `"package.json"` | Relative path to the `package.json` file. |
| `fields` | `list` of `str` | `None` | Optional list of `pyproject.toml` fields to take from their counterparts in `package.json`. If missing, take all of the available fields. |
| `contributors-as-maintainers` | `bool` | `True` | Whether contributors in `package.json` should be considered maintainers (otherwise, treat them as authors). |
| `bugs-label` | `str` | `"Bug Tracker"` | The key in the URLs table of `pyproject.toml` that is populated by the `bugs` field in `package.json` |
| `homepage-label` | `str` | `"Homepage"` | The key in the URLs table of `pyproject.toml` that is populated by the `homepage` field in `package.json` |
| `repository-label` | `str` | `"Repository"` | The key in the URLs table of `pyproject.toml` that is populated by the `repository` field in `package.json` |

## License

Expand Down
15 changes: 7 additions & 8 deletions hatch_nodejs_version/metadata_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def path(self) -> str:
version_file = self.config.get("path", "package.json")
if not isinstance(version_file, str):
raise TypeError(
"Option `path` for build hook `{}` must be a string".format(
"Option `path` for metadata hook `{}` must be a string".format(
self.PLUGIN_NAME
)
)
Expand All @@ -60,7 +60,7 @@ def fields(self) -> None | set[str]:
isinstance(fields, list) and all(isinstance(f, str) for f in fields)
):
raise TypeError(
"Option `fields` for build hook `{}` "
"Option `fields` for metadata hook `{}` "
"must be a list of strings".format(self.PLUGIN_NAME)
)
self.__fields = set(fields)
Expand All @@ -74,7 +74,7 @@ def contributors_as_maintainers(self) -> bool:
)
if not isinstance(contributors_as_maintainers, bool):
raise TypeError(
"Option `contributors-as-maintainers` for build hook `{}` "
"Option `contributors-as-maintainers` for metadata hook `{}` "
"must be a boolean".format(self.PLUGIN_NAME)
)
self.__contributors_as_maintainers = contributors_as_maintainers
Expand All @@ -87,7 +87,7 @@ def homepage_label(self) -> bool:

if not isinstance(homepage_label, str):
raise TypeError(
"Option `homepage-label` for build hook `{}` "
"Option `homepage-label` for metadata hook `{}` "
"must be a string".format(self.PLUGIN_NAME)
)
self.__homepage_label = homepage_label
Expand All @@ -100,9 +100,8 @@ def bugs_label(self) -> bool:

if not isinstance(bug_tracker_label, str):
raise TypeError(
"Option `bugs-label` for build hook `{}` must be a string".format(
self.PLUGIN_NAME
)
"Option `bugs-label` for metadata hook `{}` "
"must be a string".format(self.PLUGIN_NAME)
)
self.__bugs_label = bug_tracker_label
return self.__bugs_label
Expand All @@ -114,7 +113,7 @@ def repository_label(self) -> bool:

if not isinstance(bug_tracker_label, str):
raise TypeError(
"Option `repository-label` for build hook `{}` "
"Option `repository-label` for metadata hook `{}` "
"must be a string".format(self.PLUGIN_NAME)
)
self.__repository_label = bug_tracker_label
Expand Down
83 changes: 56 additions & 27 deletions hatch_nodejs_version/version_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,45 +4,69 @@
import json
import os
import re
from hatchling.version.source.plugin.interface import VersionSourceInterface

from hatchling.version.source.plugin.interface import VersionSourceInterface

# The Python-aware NodeJS version regex
# This is very similar to `packaging.version.VERSION_PATTERN`, with a few changes:
# - Don't accept underscores
# - Only support three-component release and prerelease segments
# - Only support four-component release, prerelease, and build segments
# - Require - to indicate prerelease
NODE_VERSION_PATTERN = r"""
(?P<major>[0-9]+) # major
(?P<major>[0-9]+) # major
\.
(?P<minor>[0-9]+) # minor
(?P<minor>[0-9]+) # minor
\.
(?P<patch>[0-9]+) # patch
(?P<pre> # pre-release
(?P<patch>[0-9]+) # patch
(?P<pre> # pre-release
-
(?P<pre_l>(a|b|c|rc|alpha|beta|pre|preview))
[-\.]?
(?P<pre_n>[0-9]+)?
)?
(?:
\+
(?P<build>
[0-9A-Za-z][0-9A-Za-z-_]* # non-hyphen/dash leading identifier
(?:
(?:\.[0-9A-Za-z-_]+)* # dot-prefixed identifier segments
\. # Final dot-delimited segment
[0-9A-Za-z_-]* # non-hyphen/dash trailing identifier
[0-9A-Za-z]
)?
)
)?
"""

# The NodeJS-aware Python version regex
# This is very similar to `packaging.version.VERSION_PATTERN`, with a few changes:
# - Only support three-component release and prerelease segments
# - Only support four-component release, prerelease, and build segments
PYTHON_VERSION_PATTERN = r"""
v?
(?:
(?P<major>[0-9]+) # major
(?P<major>[0-9]+) # major
\.
(?P<minor>[0-9]+) # minor
(?P<minor>[0-9]+) # minor
\.
(?P<patch>[0-9]+) # patch
(?P<pre> # pre-release
(?P<patch>[0-9]+) # patch
(?P<pre> # pre-release
[-_\.]?
(?P<pre_l>(a|b|c|rc|alpha|beta|pre|preview))
(?P<pre_l>(alpha|beta|preview|a|b|c|rc|pre))
[-_\.]?
(?P<pre_n>[0-9]+)?
)?
(?:
\+
(?P<local>
[0-9A-Za-z][0-9A-Za-z-_]* # non-hyphen/dash leading identifier
(?:
(?:\.[0-9A-Za-z-_]+)* # dot-prefixed identifier
\.
[0-9A-Za-z_-]* # non-hyphen/dash trailing identifier
[0-9A-Za-z]
)?
)
)?
)
"""

Expand All @@ -55,6 +79,21 @@ def __init__(self, *args, **kwargs):

self.__path = None

@property
def path(self):
if self.__path is None:
version_file = self.config.get("path", "package.json")
if not isinstance(version_file, (str, bytes, os.PathLike)):
raise TypeError(
"Option `path` for version source `{}` must be a string".format(
self.PLUGIN_NAME
)
)

self.__path = os.fspath(version_file)

return self.__path

@staticmethod
def node_version_to_python(version: str) -> str:
# NodeJS version strings are a near superset of Python version strings
Expand All @@ -74,6 +113,9 @@ def node_version_to_python(version: str) -> str:
else:
parts.append("{pre_l}{pre_n}".format_map(match))

if match["build"]:
parts.append("+{build}".format_map(match))

return "".join(parts)

@staticmethod
Expand All @@ -95,23 +137,10 @@ def python_version_to_node(version: str) -> str:
else:
parts.append("-{pre_l}{pre_n}".format_map(match))

if match["local"]:
parts.append("+{local}".format_map(match))
return "".join(parts)

@property
def path(self):
if self.__path is None:
version_file = self.config.get("path", "package.json")
if not isinstance(version_file, str):
raise TypeError(
"Option `path` for build hook `{}` must be a string".format(
self.PLUGIN_NAME
)
)

self.__path = version_file

return self.__path

def get_version_data(self):
path = os.path.normpath(os.path.join(self.root, self.path))
if not os.path.isfile(path):
Expand Down
62 changes: 29 additions & 33 deletions tests/test_version_config.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# SPDX-FileCopyrightText: 2022-present Angus Hollands <goosey15@gmail.com>
#
# SPDX-License-Identifier: MIT
import pytest
import json

import pytest

from hatch_nodejs_version.version_source import NodeJSVersionSource

GOOD_NODE_PYTHON_VERSIONS = [
Expand All @@ -17,44 +18,40 @@
("1.4.5-beta0", "1.4.5beta0"),
("1.4.5-pre9", "1.4.5pre9"),
("1.4.5-preview0", "1.4.5preview0"),
]

BAD_NODE_VERSIONS = [
"1.4",
"1.4.5a0",
"1.4.5-c0.post1",
"1.4.5-rc0.post1.dev2",
]
BAD_PYTHON_VERSIONS = [
"1.4",
"1.4.5ab",
"1.4.5-c0.smoke2",
"1.4.5rc.post1@dev2",
("1.4.5-preview0+build1.0.0", "1.4.5preview0+build1.0.0"),
("1.4.5-preview0+build-1.0.0", "1.4.5preview0+build-1.0.0"),
("1.4.5-preview0+good-1_0.0", "1.4.5preview0+good-1_0.0"),
]


class TestVersion:
@pytest.mark.parametrize(
"node_version, python_version",
GOOD_NODE_PYTHON_VERSIONS,
)
def test_parse_correct(self, node_version, python_version):
node_version_parsed = NodeJSVersionSource.python_version_to_node(python_version)
assert node_version_parsed == node_version

@pytest.mark.parametrize(
"python_version",
BAD_PYTHON_VERSIONS,
[
"1.4",
"1.4.5ab",
"1.4.5-c0.smoke2",
"1.4.5rc.post1@dev2",
"1.4.5rc0.post1+-bad",
"1.4.5rc0.post1+bad_",
],
)
def test_parse_python_incorrect(self, python_version):
with pytest.raises(ValueError, match=".* did not match regex"):
NodeJSVersionSource.python_version_to_node(python_version)

@pytest.mark.parametrize(
"node_version",
BAD_NODE_VERSIONS,
[
"1.4",
"1.4.5a0",
"1.4.5-c0.post1",
"1.4.5-rc0.post1.dev2",
"1.4.5-rc0.post1+-bad",
"1.4.5-rc0.post1+bad_",
],
)
def test_parse_python_incorrect(self, node_version):
def test_parse_node_incorrect(self, node_version):
with pytest.raises(ValueError, match=".* did not match regex"):
NodeJSVersionSource.node_version_to_python(node_version)

Expand All @@ -79,7 +76,7 @@ def test_version_from_package(
name = "my-app"
dynamic = ["version"]
[tool.hatch.version]
source = "nodejs"
source = "nodejs"
"""
)
package_json = "package.json" if alt_package_json is None else alt_package_json
Expand All @@ -94,7 +91,7 @@ def test_version_from_package(
config = {} if alt_package_json is None else {"path": alt_package_json}
version_source = NodeJSVersionSource(project, config=config)
data = version_source.get_version_data()
assert data['version'] == python_version
assert data["version"] == python_version

@pytest.mark.parametrize(
"node_version, python_version",
Expand All @@ -117,15 +114,15 @@ def test_version_to_package(
name = "my-app"
dynamic = ["version"]
[tool.hatch.version]
source = "nodejs"
source = "nodejs"
"""
)
(project / package_json).write_text(
f"""
{{
"""
{
"name": "my-app",
"version": "0.0.0"
}}
}
"""
)
config = {} if alt_package_json is None else {"path": alt_package_json}
Expand All @@ -134,5 +131,4 @@ def test_version_to_package(
version_source.set_version(python_version, version_data)

written_package = json.loads((project / package_json).read_text())
assert written_package['version'] == node_version

assert written_package["version"] == node_version

0 comments on commit 91bbb3e

Please sign in to comment.