diff --git a/swh/indexer/data/Gitea.csv b/swh/indexer/data/Gitea.csv new file mode 100644 --- /dev/null +++ b/swh/indexer/data/Gitea.csv @@ -0,0 +1,76 @@ +Property,Gitea +codeRepository,html_url +programmingLanguage,languages +runtimePlatform, +targetProduct, +applicationCategory, +applicationSubCategory, +downloadUrl, +fileSize, +installUrl, +memoryRequirements, +operatingSystem, +permissions, +processorRequirements, +releaseNotes, +softwareHelp, +softwareRequirements, +softwareVersion, +storageRequirements, +supportingData, +author,owner +citation, +contributor, +copyrightHolder, +copyrightYear, +dateCreated,created_at +dateModified,updated_at +datePublished, +editor, +encoding, +fileFormat, +funder, +keywords, +license, +producer, +provider, +publisher, +sponsor, +version, +isAccessibleForFree, +isPartOf, +hasPart, +position, +description,description +identifier, +name,name +sameAs, +url,website +relatedLink, +givenName, +familyName, +email, +affiliation, +identifier, +name,name +address, +type, +id, +softwareSuggestions, +maintainer, +contIntegration, +buildInstructions, +developmentStatus, +embargoDate, +funding, +issueTracker, +referencePublication, +readme, +, +, +, +, +, +, +, +, diff --git a/swh/indexer/metadata_dictionary/__init__.py b/swh/indexer/metadata_dictionary/__init__.py --- a/swh/indexer/metadata_dictionary/__init__.py +++ b/swh/indexer/metadata_dictionary/__init__.py @@ -8,7 +8,19 @@ import click -from . import cff, codemeta, composer, dart, github, maven, npm, nuget, python, ruby +from . import ( + cff, + codemeta, + composer, + dart, + gitea, + github, + maven, + npm, + nuget, + python, + ruby, +) from .base import BaseExtrinsicMapping, BaseIntrinsicMapping, BaseMapping INTRINSIC_MAPPINGS: Dict[str, Type[BaseIntrinsicMapping]] = { @@ -24,6 +36,7 @@ } EXTRINSIC_MAPPINGS: Dict[str, Type[BaseExtrinsicMapping]] = { + "GiteaMapping": gitea.GiteaMapping, "GitHubMapping": github.GitHubMapping, "JsonSwordCodemetaMapping": codemeta.JsonSwordCodemetaMapping, "SwordCodemetaMapping": codemeta.SwordCodemetaMapping, diff --git a/swh/indexer/metadata_dictionary/gitea.py b/swh/indexer/metadata_dictionary/gitea.py new file mode 100644 --- /dev/null +++ b/swh/indexer/metadata_dictionary/gitea.py @@ -0,0 +1,116 @@ +# Copyright (C) 2022 The Software Heritage developers +# See the AUTHORS file at the top-level directory of this distribution +# License: GNU General Public License version 3, or any later version +# See top-level LICENSE file for more information + +import os +from typing import Any, Tuple + +from rdflib import RDF, BNode, Graph, Literal, URIRef + +from swh.indexer.codemeta import _DATA_DIR, _read_crosstable +from swh.indexer.namespaces import ACTIVITYSTREAMS, FORGEFED, SCHEMA + +from .base import BaseExtrinsicMapping, JsonMapping, produce_terms +from .utils import prettyprint_graph # noqa + +SPDX = URIRef("https://spdx.org/licenses/") + + +GITEA_TABLE_PATH = os.path.join(_DATA_DIR, "Gitea.csv") + +with open(GITEA_TABLE_PATH) as fd: + (CODEMETA_TERMS, GITEA_TABLE) = _read_crosstable(fd) + + +class GiteaMapping(BaseExtrinsicMapping, JsonMapping): + name = "gitea" + mapping = GITEA_TABLE["Gitea"] + uri_fields = [ + "html_url", + "website", + ] + date_fields = [ + "created_at", + "updated_at", + ] + string_fields = [ + "name", + "full_name", + "languages", + "description", + ] + + @classmethod + def extrinsic_metadata_formats(cls) -> Tuple[str, ...]: + return ("gitea-project-json", "gogs-project-json") + + def extra_translation(self, graph, root, content_dict): + graph.remove((root, RDF.type, SCHEMA.SoftwareSourceCode)) + graph.add((root, RDF.type, FORGEFED.Repository)) + + @produce_terms(FORGEFED.forks, ACTIVITYSTREAMS.totalItems) + def translate_forks_count(self, graph: Graph, root: BNode, v: Any) -> None: + """ + + >>> graph = Graph() + >>> root = URIRef("http://example.org/test-software") + >>> GiteaMapping().translate_forks_count(graph, root, 42) + >>> prettyprint_graph(graph, root) + { + "@id": ..., + "https://forgefed.org/ns#forks": { + "@type": "https://www.w3.org/ns/activitystreams#OrderedCollection", + "https://www.w3.org/ns/activitystreams#totalItems": 42 + } + } + """ + if isinstance(v, int): + collection = BNode() + graph.add((root, FORGEFED.forks, collection)) + graph.add((collection, RDF.type, ACTIVITYSTREAMS.OrderedCollection)) + graph.add((collection, ACTIVITYSTREAMS.totalItems, Literal(v))) + + @produce_terms(ACTIVITYSTREAMS.likes, ACTIVITYSTREAMS.totalItems) + def translate_stars_count(self, graph: Graph, root: BNode, v: Any) -> None: + """ + + >>> graph = Graph() + >>> root = URIRef("http://example.org/test-software") + >>> GiteaMapping().translate_stars_count(graph, root, 42) + >>> prettyprint_graph(graph, root) + { + "@id": ..., + "https://www.w3.org/ns/activitystreams#likes": { + "@type": "https://www.w3.org/ns/activitystreams#Collection", + "https://www.w3.org/ns/activitystreams#totalItems": 42 + } + } + """ + if isinstance(v, int): + collection = BNode() + graph.add((root, ACTIVITYSTREAMS.likes, collection)) + graph.add((collection, RDF.type, ACTIVITYSTREAMS.Collection)) + graph.add((collection, ACTIVITYSTREAMS.totalItems, Literal(v))) + + @produce_terms(ACTIVITYSTREAMS.followers, ACTIVITYSTREAMS.totalItems) + def translate_watchers_count(self, graph: Graph, root: BNode, v: Any) -> None: + """ + + >>> graph = Graph() + >>> root = URIRef("http://example.org/test-software") + >>> GiteaMapping().translate_watchers_count(graph, root, 42) + >>> prettyprint_graph(graph, root) + { + "@id": ..., + "https://www.w3.org/ns/activitystreams#followers": { + "@type": "https://www.w3.org/ns/activitystreams#Collection", + "https://www.w3.org/ns/activitystreams#totalItems": 42 + } + } + """ + if isinstance(v, int): + collection = BNode() + graph.add((root, ACTIVITYSTREAMS.followers, collection)) + graph.add((collection, RDF.type, ACTIVITYSTREAMS.Collection)) + graph.add((collection, ACTIVITYSTREAMS.totalItems, Literal(v))) diff --git a/swh/indexer/tests/metadata_dictionary/test_gitea.py b/swh/indexer/tests/metadata_dictionary/test_gitea.py new file mode 100644 --- /dev/null +++ b/swh/indexer/tests/metadata_dictionary/test_gitea.py @@ -0,0 +1,142 @@ +# Copyright (C) 2022 The Software Heritage developers +# See the AUTHORS file at the top-level directory of this distribution +# License: GNU General Public License version 3, or any later version +# See top-level LICENSE file for more information + +from swh.indexer.metadata_dictionary import MAPPINGS + +CONTEXT = [ + "https://doi.org/10.5063/schema/codemeta-2.0", + { + "as": "https://www.w3.org/ns/activitystreams#", + "forge": "https://forgefed.org/ns#", + }, +] + + +def test_compute_metadata_none(): + """ + testing content empty content is empty + should return None + """ + content = b"" + + # None if no metadata was found or an error occurred + declared_metadata = None + result = MAPPINGS["GiteaMapping"]().translate(content) + assert declared_metadata == result + + +def test_supported_terms(): + terms = MAPPINGS["GiteaMapping"].supported_terms() + assert { + "http://schema.org/name", + "http://schema.org/dateCreated", + "https://forgefed.org/ns#forks", + "https://www.w3.org/ns/activitystreams#totalItems", + } <= terms + + +def test_compute_metadata_gitea(): + content = b""" +{ + "id": 48043, + "owner": { + "id": 48018, + "login": "ForgeFed", + "full_name": "", + "email": "", + "avatar_url": "https://codeberg.org/avatars/c20f7a6733a6156304137566ee35ef33", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2022-04-30T20:13:17+02:00", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "website": "https://forgefed.org/", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "ForgeFed" + }, + "name": "ForgeFed", + "full_name": "ForgeFed/ForgeFed", + "description": "ActivityPub-based forge federation protocol specification", + "empty": false, + "private": false, + "fork": false, + "template": false, + "parent": null, + "mirror": false, + "size": 3780, + "language": "CSS", + "languages_url": "https://codeberg.org/api/v1/repos/ForgeFed/ForgeFed/languages", + "html_url": "https://codeberg.org/ForgeFed/ForgeFed", + "ssh_url": "git@codeberg.org:ForgeFed/ForgeFed.git", + "clone_url": "https://codeberg.org/ForgeFed/ForgeFed.git", + "original_url": "https://notabug.org/peers/forgefed", + "website": "https://forgefed.org", + "stars_count": 30, + "forks_count": 6, + "watchers_count": 11, + "open_issues_count": 61, + "open_pr_counter": 10, + "release_counter": 0, + "default_branch": "main", + "archived": false, + "created_at": "2022-06-13T18:54:26+02:00", + "updated_at": "2022-09-02T03:57:22+02:00", + "permissions": { + "admin": false, + "push": false, + "pull": true + }, + "has_issues": true, + "internal_tracker": { + "enable_time_tracker": true, + "allow_only_contributors_to_track_time": true, + "enable_issue_dependencies": true + }, + "has_wiki": false, + "has_pull_requests": true, + "has_projects": true, + "ignore_whitespace_conflicts": false, + "allow_merge_commits": false, + "allow_rebase": false, + "allow_rebase_explicit": false, + "allow_squash_merge": true, + "default_merge_style": "squash", + "avatar_url": "", + "internal": false, + "mirror_interval": "", + "mirror_updated": "0001-01-01T00:00:00Z", + "repo_transfer": null +} + """ + result = MAPPINGS["GiteaMapping"]().translate(content) + assert result == { + "@context": CONTEXT, + "type": "forge:Repository", + "forge:forks": { + "as:totalItems": 6, + "type": "as:OrderedCollection", + }, + "as:likes": { + "as:totalItems": 30, + "type": "as:Collection", + }, + "as:followers": { + "as:totalItems": 11, + "type": "as:Collection", + }, + "name": "ForgeFed", + "description": "ActivityPub-based forge federation protocol specification", + "codeRepository": "https://codeberg.org/ForgeFed/ForgeFed", + "dateCreated": "2022-06-13T18:54:26+02:00", + "dateModified": "2022-09-02T03:57:22+02:00", + "url": "https://forgefed.org", + } diff --git a/swh/indexer/tests/test_cli.py b/swh/indexer/tests/test_cli.py --- a/swh/indexer/tests/test_cli.py +++ b/swh/indexer/tests/test_cli.py @@ -110,6 +110,7 @@ "codemeta", "composer", "gemspec", + "gitea", "github", "json-sword-codemeta", "maven",