diff --git a/docs/source/reference/available_package_parsers.rst b/docs/source/reference/available_package_parsers.rst index 86954ef68a0..213838ac9e8 100644 --- a/docs/source/reference/available_package_parsers.rst +++ b/docs/source/reference/available_package_parsers.rst @@ -141,6 +141,12 @@ parsers in scancode-toolkit during documentation builds. - ``chrome_crx`` - JavaScript - https://chrome.google.com/extensions + * - Citation File Format + - ``*.cff`` + - ``citation`` + - ``citation_cff`` + - None + - https://citation-file-format.github.io/ * - Cocoapods Podfile - ``*Podfile`` - ``cocoapods`` diff --git a/src/packagedcode/__init__.py b/src/packagedcode/__init__.py index 0887a1859da..d44865c2765 100644 --- a/src/packagedcode/__init__.py +++ b/src/packagedcode/__init__.py @@ -20,6 +20,7 @@ from packagedcode import debian from packagedcode import debian_copyright from packagedcode import distro +from packagedcode import citation from packagedcode import conda from packagedcode import cocoapods from packagedcode import cran @@ -74,6 +75,8 @@ cocoapods.PodfileLockHandler, cocoapods.PodfileHandler, + citation.CitationHandler, + conda.CondaYamlHandler, conda.CondaMetaYamlHandler, diff --git a/src/packagedcode/citation.py b/src/packagedcode/citation.py new file mode 100644 index 00000000000..f27a30903a7 --- /dev/null +++ b/src/packagedcode/citation.py @@ -0,0 +1,85 @@ +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# ScanCode is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/nexB/scancode-toolkit for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. +# + +import io +import saneyaml +from packagedcode import models + +""" +Handle CITATION.cff files, see https://citation-file-format.github.io/ +https://github.com/citation-file-format/citation-file-format/blob/main/schema-guide.md + +See https://github.com/citation-file-format/citation-file-format/tree/main/examples/1.2.0/pass for examples. +""" + +class CitationHandler(models.DatafileHandler): + datasource_id = 'citation_cff' + default_package_type = 'citation' + path_patterns = ('*/CITATION.cff',) + description = 'Citation file format' + documentation_url = 'https://citation-file-format.github.io/' + + @classmethod + def parse(cls, location): + metayaml = get_cff_data(location) + title = metayaml.get('title') + abstract = metayaml.get('abstract') + keywords = metayaml.get('keywords') + release_date = metayaml.get('date-released') + # This is the version of the software/item and not cff-version + version = metayaml.get('version') + parties = [] + authors = metayaml.get('authors') + if authors: + for author in authors: + name = author.get('family-names') or author.get('name') + email = author.get('email') + url = author.get('orcid') + parties.append(models.Party( + name=name, + email=email, + url=url + )) + repository_homepage_url = metayaml.get('repository') + repository_download_url = metayaml.get('repository-code') + extracted_license_statement = metayaml.get('license') + homepage_url = metayaml.get('url') + + yield models.PackageData( + datasource_id=cls.datasource_id, + type=cls.default_package_type, + name=title, + description=abstract, + keywords=keywords, + release_date=release_date, + parties=parties, + version=version, + repository_homepage_url=repository_homepage_url, + repository_download_url=repository_download_url, + extracted_license_statement=extracted_license_statement, + homepage_url=homepage_url + ) + + +def get_cff_data(location): + """ + Return a mapping of yaml/cff data loaded from a CITATION.cff files. + """ + + # TODO: Should this be Jinja-based? + yaml_lines = [] + with io.open(location, encoding='utf-8') as citationcff: + for line in citationcff: + if not line: + continue + yaml_lines.append(line) + + return saneyaml.load('\n'.join(yaml_lines)) + + diff --git a/tests/packagedcode/data/citation/CITATION.cff b/tests/packagedcode/data/citation/CITATION.cff new file mode 100644 index 00000000000..51ba977cb78 --- /dev/null +++ b/tests/packagedcode/data/citation/CITATION.cff @@ -0,0 +1,89 @@ +# This CITATION.cff file was created by ruby-cff (v 1.1.0). +# Added few changes to help with testing. +# Gem: https://rubygems.org/gems/cff +# CFF: https://citation-file-format.github.io/ +# Link: https://github.com/citation-file-format/ruby-cff/blob/main/CITATION.cff + +cff-version: 1.2.0 +message: If you use ruby-cff in your work, please cite it using the following metadata +title: Ruby CFF Library +type: software +abstract: This library provides a Ruby interface to manipulate Citation File Format files +authors: +- family-names: Haines + given-names: Robert + orcid: https://orcid.org/0000-0002-9538-7919 + affiliation: The University of Manchester, UK +- name: The Ruby Citation File Format Developers + email: abc@developer.com +keywords: +- ruby +- credit +- software citation +- research software +- software sustainability +- metadata +- citation file format +- CFF +version: 1.1.0 +doi: 10.5281/zenodo.1184077 +date-released: 2023-04-10 +license: Apache-2.0 +url: https://rubygems.org/ +repository: https://github.com/citation-file-format/ +repository-artifact: https://rubygems.org/gems/cff +repository-code: https://github.com/citation-file-format/ruby-cff +references: +- type: software + title: Citation File Format + authors: + - family-names: Druskat + given-names: Stephan + orcid: https://orcid.org/0000-0003-4925-7248 + - family-names: Spaaks + given-names: Jurriaan H. + orcid: https://orcid.org/0000-0002-7064-4069 + - family-names: Chue Hong + given-names: Neil + orcid: https://orcid.org/0000-0002-8876-7606 + - family-names: Haines + given-names: Robert + orcid: https://orcid.org/0000-0002-9538-7919 + - family-names: Baker + given-names: James + orcid: https://orcid.org/0000-0002-2682-6922 + - family-names: Bliven + given-names: Spencer + orcid: https://orcid.org/0000-0002-1200-1698 + email: spencer.bliven@gmail.com + - family-names: Willighagen + given-names: Egon + orcid: https://orcid.org/0000-0001-7542-0286 + - family-names: Pérez-Suárez + given-names: David + orcid: https://orcid.org/0000-0003-0784-6909 + website: https://dpshelio.github.io + - family-names: Konovalov + given-names: Alexander + orcid: https://orcid.org/0000-0001-5299-3292 + identifiers: + - type: doi + value: 10.5281/zenodo.1003149 + description: The concept DOI for the collection containing all versions of the Citation File Format. + - type: doi + value: 10.5281/zenodo.5171937 + description: The versioned DOI for the version 1.2.0 of the Citation File Format. + keywords: + - citation file format + - CFF + - citation files + - software citation + - file format + - YAML + - software sustainability + - research software + - credit + abstract: CITATION.cff files are plain text files with human- and machine-readable citation information for software. Code developers can include them in their repositories to let others know how to correctly cite their software. This is the specification for the Citation File Format. + date-released: 2021-08-09 + license: CC-BY-4.0 + version: 1.2.0 \ No newline at end of file diff --git a/tests/packagedcode/data/citation/citation.cff.expected.json b/tests/packagedcode/data/citation/citation.cff.expected.json new file mode 100644 index 00000000000..fa59b7a525f --- /dev/null +++ b/tests/packagedcode/data/citation/citation.cff.expected.json @@ -0,0 +1,88 @@ +[ + { + "type": "citation", + "namespace": null, + "name": "Ruby CFF Library", + "version": "1.1.0", + "qualifiers": {}, + "subpath": null, + "primary_language": null, + "description": "This library provides a Ruby interface to manipulate Citation File Format files", + "release_date": "2023-04-10", + "parties": [ + { + "type": null, + "role": null, + "name": "Haines", + "email": null, + "url": "https://orcid.org/0000-0002-9538-7919" + }, + { + "type": null, + "role": null, + "name": "The Ruby Citation File Format Developers", + "email": "abc@developer.com", + "url": null + } + ], + "keywords": [ + "ruby", + "credit", + "software citation", + "research software", + "software sustainability", + "metadata", + "citation file format", + "CFF" + ], + "homepage_url": "https://rubygems.org/", + "download_url": null, + "size": null, + "sha1": null, + "md5": null, + "sha256": null, + "sha512": null, + "bug_tracking_url": null, + "code_view_url": null, + "vcs_url": null, + "copyright": null, + "holder": null, + "declared_license_expression": "apache-2.0", + "declared_license_expression_spdx": "Apache-2.0", + "license_detections": [ + { + "license_expression": "apache-2.0", + "matches": [ + { + "score": 100.0, + "start_line": 1, + "end_line": 1, + "matched_length": 3, + "match_coverage": 100.0, + "matcher": "1-hash", + "license_expression": "apache-2.0", + "rule_identifier": "spdx_license_id_apache-2.0_for_apache-2.0.RULE", + "rule_relevance": 100, + "rule_url": "https://github.com/nexB/scancode-toolkit/tree/develop/src/licensedcode/data/rules/spdx_license_id_apache-2.0_for_apache-2.0.RULE", + "matched_text": "Apache-2.0" + } + ], + "identifier": "apache_2_0-d66ab77d-a5cc-7104-e702-dc7df61fe9e8" + } + ], + "other_license_expression": null, + "other_license_expression_spdx": null, + "other_license_detections": [], + "extracted_license_statement": "Apache-2.0", + "notice_text": null, + "source_packages": [], + "file_references": [], + "extra_data": {}, + "dependencies": [], + "repository_homepage_url": "https://github.com/citation-file-format/", + "repository_download_url": "https://github.com/citation-file-format/ruby-cff", + "api_data_url": null, + "datasource_id": "citation_cff", + "purl": "pkg:citation/Ruby%20CFF%20Library@1.1.0" + } +] diff --git a/tests/packagedcode/test_citation.py b/tests/packagedcode/test_citation.py new file mode 100644 index 00000000000..c7d370c67d2 --- /dev/null +++ b/tests/packagedcode/test_citation.py @@ -0,0 +1,35 @@ +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# ScanCode is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/nexB/scancode-toolkit for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. +# + +import os + +from packages_test_utils import PackageTester +from packagedcode import citation +from scancode_config import REGEN_TEST_FIXTURES + + +class TestCitation(PackageTester): + #test_data_dir = os.path.join(os.path.dirname(__file__), 'data') + + def test_get_cff_data(self): + test_file = self.get_test_loc('citation/CITATION.cff') + results = citation.get_cff_data(test_file) + assert list(results.items())[0] == ((u'cff-version', u'1.2.0')) \ + and (list(results.items())[8]) == (u'doi', + u'10.5281/zenodo.1184077') + + def test_citationcff_is_package_data_file(self): + test_file = self.get_test_loc('citation/CITATION.cff') + assert citation.CitationHandler.is_datafile(test_file) + + def test_parse(self): + test_file = self.get_test_loc('citation/CITATION.cff') + package = citation.CitationHandler.parse(test_file) + expected_loc = self.get_test_loc('citation/citation.cff.expected.json') + self.check_packages_data(package, expected_loc, regen=REGEN_TEST_FIXTURES)