diff --git a/README.md b/README.md index 5eac728..424182d 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ Supported generators * Perl ([CPAN::Meta::Spec](http://search.cpan.org/perldoc?CPAN::Meta::Spec)) * PHP ([Composer](https://getcomposer.org/doc/04-schema.md), [PEAR/PECL](https://pear.php.net/manual/en/guide.developers.package2.php)) * Python ([PEP 621](https://peps.python.org/pep-0621/), [PEP 643](https://peps.python.org/pep-0643/), [Setuptools](https://setuptools.pypa.io/en/latest/userguide/declarative_config.html)) +* Ruby ([Gem metadata](https://guides.rubygems.org/specification-reference/)) * Rust ([Cargo](https://doc.rust-lang.org/cargo/reference/manifest.html)) Language-independent: diff --git a/docs/release-notes.rst b/docs/release-notes.rst index e39d97c..1958712 100644 --- a/docs/release-notes.rst +++ b/docs/release-notes.rst @@ -9,6 +9,7 @@ Release Notes ----- * New generator: Perl CPAN::Meta::Spec +* New generator: Ruby Gem * Add command-line flags to control tests selection 0.4.0 diff --git a/gentle/__main__.py b/gentle/__main__.py index 131ed76..ecbeafa 100644 --- a/gentle/__main__.py +++ b/gentle/__main__.py @@ -22,6 +22,7 @@ import gentle.generators.cargo import gentle.generators.composer import gentle.generators.cpan import gentle.generators.doap +import gentle.generators.gemspec import gentle.generators.hpack import gentle.generators.npm import gentle.generators.nuspec diff --git a/gentle/generators/gemspec.py b/gentle/generators/gemspec.py new file mode 100644 index 0000000..e4e78f0 --- /dev/null +++ b/gentle/generators/gemspec.py @@ -0,0 +1,80 @@ +# SPDX-License-Identifier: WTFPL +# SPDX-FileCopyrightText: 2022 Anna +# No warranty + +""" +Metadata XML generator for Crystal Shards. + +The following attributes are supported: + +* Upstream maintainer(s) +* Upstream documentation +* Remote ID +""" + +import logging +from pathlib import Path + +from gentle.generators import AbstractGenerator +from gentle.metadata import MetadataXML +from gentle.metadata.utils import extract_remote_id + +try: + import yaml + from yaml import Loader + _HAS_PYYAML = True + + class VersionTag(yaml.YAMLObject): + """ Dummy version tag """ + yaml_tag = "!ruby/object:Gem::Version" + + class RequirementTag(yaml.YAMLObject): + """ Dummy requirement tag """ + yaml_tag = "!ruby/object:Gem::Requirement" + + class DependencyTag(yaml.YAMLObject): + """ Dummy dependency tag """ + yaml_tag = "!ruby/object:Gem::Dependency" + + class SpecificationTag(yaml.YAMLObject): + """ Dummy specification tag """ + yaml_tag = "!ruby/object:Gem::Specification" +except ModuleNotFoundError: + _HAS_PYYAML = False + +logger = logging.getLogger("gemspec") + + +class GemspecGenerator(AbstractGenerator): + def __init__(self, srcdir: Path): + self.metadata_yml = srcdir / "all" / "metadata" + + def update_metadata_xml(self, mxml: MetadataXML) -> None: + with open(self.metadata_yml) as file: + if (metadata := yaml.load(file, Loader)) is None: + return + + if metadata.homepage: + logger.info("Found homepage: %s", metadata.homepage) + if (remote_id := extract_remote_id(metadata.homepage)) is not None: + mxml.add_upstream_remote_id(remote_id) + + for name, value in metadata.metadata.items(): + if not isinstance(value, str): + continue + + logger.info("Found %s: %s", name, value) + match name: + case "bug_tracker_uri": + mxml.set_upstream_bugs_to(value) + case "changelog_uri": + mxml.set_upstream_changelog(value) + case "documentation_uri": + mxml.set_upstream_doc(value) + case "homepage_uri" | "source_code_uri": + if (remote_id := extract_remote_id(value)) is not None: + mxml.add_upstream_remote_id(remote_id) + + @property + def active(self) -> bool: + return _HAS_PYYAML and self.metadata_yml.is_file() diff --git a/tests/gemspec/__init__.py b/tests/gemspec/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/gemspec/pkg_empty/all/metadata b/tests/gemspec/pkg_empty/all/metadata new file mode 100644 index 0000000..e69de29 diff --git a/tests/gemspec/pkg_none/all/.gitkeep b/tests/gemspec/pkg_none/all/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/gemspec/rails/all/metadata b/tests/gemspec/rails/all/metadata new file mode 100644 index 0000000..ae6b81d --- /dev/null +++ b/tests/gemspec/rails/all/metadata @@ -0,0 +1,234 @@ +--- !ruby/object:Gem::Specification +name: rails +version: !ruby/object:Gem::Version + version: 7.0.6 +platform: ruby +authors: +- David Heinemeier Hansson +autorequire: +bindir: bin +cert_chain: [] +date: 2023-06-29 00:00:00.000000000 Z +dependencies: +- !ruby/object:Gem::Dependency + name: activesupport + requirement: !ruby/object:Gem::Requirement + requirements: + - - '=' + - !ruby/object:Gem::Version + version: 7.0.6 + type: :runtime + prerelease: false + version_requirements: !ruby/object:Gem::Requirement + requirements: + - - '=' + - !ruby/object:Gem::Version + version: 7.0.6 +- !ruby/object:Gem::Dependency + name: actionpack + requirement: !ruby/object:Gem::Requirement + requirements: + - - '=' + - !ruby/object:Gem::Version + version: 7.0.6 + type: :runtime + prerelease: false + version_requirements: !ruby/object:Gem::Requirement + requirements: + - - '=' + - !ruby/object:Gem::Version + version: 7.0.6 +- !ruby/object:Gem::Dependency + name: actionview + requirement: !ruby/object:Gem::Requirement + requirements: + - - '=' + - !ruby/object:Gem::Version + version: 7.0.6 + type: :runtime + prerelease: false + version_requirements: !ruby/object:Gem::Requirement + requirements: + - - '=' + - !ruby/object:Gem::Version + version: 7.0.6 +- !ruby/object:Gem::Dependency + name: activemodel + requirement: !ruby/object:Gem::Requirement + requirements: + - - '=' + - !ruby/object:Gem::Version + version: 7.0.6 + type: :runtime + prerelease: false + version_requirements: !ruby/object:Gem::Requirement + requirements: + - - '=' + - !ruby/object:Gem::Version + version: 7.0.6 +- !ruby/object:Gem::Dependency + name: activerecord + requirement: !ruby/object:Gem::Requirement + requirements: + - - '=' + - !ruby/object:Gem::Version + version: 7.0.6 + type: :runtime + prerelease: false + version_requirements: !ruby/object:Gem::Requirement + requirements: + - - '=' + - !ruby/object:Gem::Version + version: 7.0.6 +- !ruby/object:Gem::Dependency + name: actionmailer + requirement: !ruby/object:Gem::Requirement + requirements: + - - '=' + - !ruby/object:Gem::Version + version: 7.0.6 + type: :runtime + prerelease: false + version_requirements: !ruby/object:Gem::Requirement + requirements: + - - '=' + - !ruby/object:Gem::Version + version: 7.0.6 +- !ruby/object:Gem::Dependency + name: activejob + requirement: !ruby/object:Gem::Requirement + requirements: + - - '=' + - !ruby/object:Gem::Version + version: 7.0.6 + type: :runtime + prerelease: false + version_requirements: !ruby/object:Gem::Requirement + requirements: + - - '=' + - !ruby/object:Gem::Version + version: 7.0.6 +- !ruby/object:Gem::Dependency + name: actioncable + requirement: !ruby/object:Gem::Requirement + requirements: + - - '=' + - !ruby/object:Gem::Version + version: 7.0.6 + type: :runtime + prerelease: false + version_requirements: !ruby/object:Gem::Requirement + requirements: + - - '=' + - !ruby/object:Gem::Version + version: 7.0.6 +- !ruby/object:Gem::Dependency + name: activestorage + requirement: !ruby/object:Gem::Requirement + requirements: + - - '=' + - !ruby/object:Gem::Version + version: 7.0.6 + type: :runtime + prerelease: false + version_requirements: !ruby/object:Gem::Requirement + requirements: + - - '=' + - !ruby/object:Gem::Version + version: 7.0.6 +- !ruby/object:Gem::Dependency + name: actionmailbox + requirement: !ruby/object:Gem::Requirement + requirements: + - - '=' + - !ruby/object:Gem::Version + version: 7.0.6 + type: :runtime + prerelease: false + version_requirements: !ruby/object:Gem::Requirement + requirements: + - - '=' + - !ruby/object:Gem::Version + version: 7.0.6 +- !ruby/object:Gem::Dependency + name: actiontext + requirement: !ruby/object:Gem::Requirement + requirements: + - - '=' + - !ruby/object:Gem::Version + version: 7.0.6 + type: :runtime + prerelease: false + version_requirements: !ruby/object:Gem::Requirement + requirements: + - - '=' + - !ruby/object:Gem::Version + version: 7.0.6 +- !ruby/object:Gem::Dependency + name: railties + requirement: !ruby/object:Gem::Requirement + requirements: + - - '=' + - !ruby/object:Gem::Version + version: 7.0.6 + type: :runtime + prerelease: false + version_requirements: !ruby/object:Gem::Requirement + requirements: + - - '=' + - !ruby/object:Gem::Version + version: 7.0.6 +- !ruby/object:Gem::Dependency + name: bundler + requirement: !ruby/object:Gem::Requirement + requirements: + - - ">=" + - !ruby/object:Gem::Version + version: 1.15.0 + type: :runtime + prerelease: false + version_requirements: !ruby/object:Gem::Requirement + requirements: + - - ">=" + - !ruby/object:Gem::Version + version: 1.15.0 +description: Ruby on Rails is a full-stack web framework optimized for programmer + happiness and sustainable productivity. It encourages beautiful code by favoring + convention over configuration. +email: david@loudthinking.com +executables: [] +extensions: [] +extra_rdoc_files: [] +files: +- MIT-LICENSE +- README.md +homepage: https://rubyonrails.org +licenses: +- MIT +metadata: + bug_tracker_uri: https://github.com/rails/rails/issues + changelog_uri: https://github.com/rails/rails/releases/tag/v7.0.6 + documentation_uri: https://api.rubyonrails.org/v7.0.6/ + mailing_list_uri: https://discuss.rubyonrails.org/c/rubyonrails-talk + source_code_uri: https://github.com/rails/rails/tree/v7.0.6 + rubygems_mfa_required: 'true' +post_install_message: +rdoc_options: [] +require_paths: +- lib +required_ruby_version: !ruby/object:Gem::Requirement + requirements: + - - ">=" + - !ruby/object:Gem::Version + version: 2.7.0 +required_rubygems_version: !ruby/object:Gem::Requirement + requirements: + - - ">=" + - !ruby/object:Gem::Version + version: 1.8.11 +requirements: [] +rubygems_version: 3.4.13 +signing_key: +specification_version: 4 +summary: Full-stack web application framework. +test_files: [] diff --git a/tests/gemspec/rails/all/metadata.license b/tests/gemspec/rails/all/metadata.license new file mode 100644 index 0000000..dad4a66 --- /dev/null +++ b/tests/gemspec/rails/all/metadata.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2023 David Heinemeier Hansson + +SPDX-License-Identifier: MIT diff --git a/tests/gemspec/rails/metadata.xml b/tests/gemspec/rails/metadata.xml new file mode 100644 index 0000000..261d506 --- /dev/null +++ b/tests/gemspec/rails/metadata.xml @@ -0,0 +1,9 @@ + + + + https://github.com/rails/rails/issues + https://github.com/rails/rails/releases/tag/v7.0.6 + https://api.rubyonrails.org/v7.0.6/ + rails/rails + + diff --git a/tests/gemspec/rails/metadata.xml.license b/tests/gemspec/rails/metadata.xml.license new file mode 100644 index 0000000..f0352aa --- /dev/null +++ b/tests/gemspec/rails/metadata.xml.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2023 Anna + +SPDX-License-Identifier: CC0-1.0 diff --git a/tests/gemspec/test_generator.py b/tests/gemspec/test_generator.py new file mode 100644 index 0000000..d4bf4dc --- /dev/null +++ b/tests/gemspec/test_generator.py @@ -0,0 +1,40 @@ +# SPDX-License-Identifier: WTFPL +# SPDX-FileCopyrightText: 2023 Anna +# No warranty + +from copy import deepcopy +from pathlib import Path + +import pytest + +from gentle.generators.gemspec import GemspecGenerator +from gentle.metadata import MetadataXML +from tests.utils import compare_mxml + + +def test_pkg_none(mxml: MetadataXML): + gen = GemspecGenerator(Path(__file__).parent / "pkg_none") + assert not gen.active + + +def test_pkg_empty(mxml: MetadataXML): + gen = GemspecGenerator(Path(__file__).parent / "pkg_empty") + assert gen.active + + mxml_old = deepcopy(mxml) + gen.update_metadata_xml(mxml) + assert compare_mxml(mxml_old, mxml) == "" + + +@pytest.mark.parametrize("dirname", ["rails"]) +def test_pkg(mxml: MetadataXML, dirname: str): + gen = GemspecGenerator(Path(__file__).parent / dirname) + assert gen.active + + gen.update_metadata_xml(mxml) + with open(Path(__file__).parent / dirname / "metadata.xml") as file: + assert mxml.dumps() == file.read().rstrip() + + mxml_prev = deepcopy(mxml) + gen.update_metadata_xml(mxml) + assert compare_mxml(mxml_prev, mxml) == ""