diff --git a/.github/workflows/check_metadata.yaml b/.github/workflows/check_metadata.yaml new file mode 100644 index 00000000..024d4cc8 --- /dev/null +++ b/.github/workflows/check_metadata.yaml @@ -0,0 +1,29 @@ +# SPDX-FileCopyrightText: 2024 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH, Darmstadt, Germany +# +# SPDX-License-Identifier: CC0-1.0 + +name: Check AUTHORS and CONTRIBUTORS in metadata + +on: + push: + paths: + - AUTHORS + - CONTRIBUTORS + - codemeta.json + - .zenodo.json + pull_request: + paths: + - AUTHORS + - CONTRIBUTORS + - codemeta.json + - .zenodo.json + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Try updating metadata + run: python meta_update.py + - name: Check for Updates + run: git diff --exit-code diff --git a/.zenodo.json b/.zenodo.json new file mode 100644 index 00000000..a350c019 --- /dev/null +++ b/.zenodo.json @@ -0,0 +1,51 @@ +{ + "creators": [ + { + "name": "Alvarez Pol, Hector", + "orcid": "0000-0001-9643-6252" + }, + { + "name": "Chatillon, Audrey" + }, + { + "name": "Rodriguez Sanchez, Jose Luis", + "orcid": "0000-0002-4702-5294" + } + ], + "contributors": [ + { + "type": "Other", + "name": "Garcia Jimenez, Gabriel" + }, + { + "type": "Other", + "name": "Gra\u00f1a Gonzalez, Antia" + }, + { + "type": "Other", + "name": "Mayer, Jan" + }, + { + "type": "Other", + "name": "Morfouace, Pierre" + }, + { + "type": "Other", + "name": "Taniuchi, Ryo" + }, + { + "type": "Other", + "name": "Wang, Yanzhao" + } + ], + "description": "SOFIA folder inside R3BRoot describes the SOFIA detectors which are going to be employed for the fission setup within the R3B/GLAD magnet. The R3BRoot software is based on the FairRoot framework and can be used to perform Monte Carlo simulations and experimental data analysis of the R3B (Reactions with Relativistic Radioactive Beams) nuclear physics experiments at the GSI-FAIR research center (Facility for Antiproton and Ion Research).", + "related_identifiers": [ + { + "identifier": "https://github.com/R3BRootGroup/sofia/", + "relation": "isSupplementTo", + "resource_type": "software", + "scheme": "url" + } + ], + "title": "sofia" +} diff --git a/AUTHORS b/AUTHORS index d38cb4cb..ddfb0494 100755 --- a/AUTHORS +++ b/AUTHORS @@ -1,3 +1,3 @@ -Alvarez Pol, Hector +Alvarez Pol, Hector [https://orcid.org/0000-0001-9643-6252] Chatillon, Audrey -Rodriguez Sanchez, Jose Luis +Rodriguez Sanchez, Jose Luis [https://orcid.org/0000-0002-4702-5294] diff --git a/codemeta.json b/codemeta.json new file mode 100644 index 00000000..1fbc4748 --- /dev/null +++ b/codemeta.json @@ -0,0 +1,96 @@ +{ + "@context": "https://doi.org/10.5063/schema/codemeta-2.0", + "@type": "SoftwareSourceCode", + "codeRepository": "https://github.com/R3BRootGroup/sofia", + "contIntegration": "https://github.com/R3BRootGroup/sofia/actions", + "dateCreated": "2017-05-15", + "datePublished": "2017-05-15", + "dateModified": "2024-12-05", + "downloadUrl": "https://github.com/R3BRootGroup/sofia/archive/refs/tags/nov23.tar.gz", + "issueTracker": "https://github.com/R3BRootGroup/sofia/issues", + "name": "sofia", + "softwareVersion": "jun24", + "description": "Software for the SOFIA/R3B fission experiments at FAIR", + "readme": "https://github.com/R3BRootGroup/sofia/blob/dev/README.md", + "releaseNotes": "", + "applicationCategory": "Nuclear physics", + "developmentStatus": "active", + "referencePublication": "https://doi.org/10.1088/1742-6596/523/1/012034", + "keywords": [ + "MC simulation", + "Structure and dynamics of nuclei", + "Nuclear reactions", + "Nuclear fission" + ], + "programmingLanguage": [ + "C", + "C++" + ], + "runtimePlatform": [ + "ROOT" + ], + "operatingSystem": [ + "Linux", + "macOS" + ], + "softwareRequirements": [ + "FairRoot, FairSoft, R3BRoot" + ], + "maintainer": { + "@type": "Person", + "givenName": "Jose Luis", + "familyName": "Rodriguez Sanchez", + "@id": "https://orcid.org/0000-0002-4702-5294" + }, + "author": [ + { + "@type": "Person", + "givenName": "Hector", + "familyName": "Alvarez Pol", + "@id": "https://orcid.org/0000-0001-9643-6252" + }, + { + "@type": "Person", + "givenName": "Audrey", + "familyName": "Chatillon" + }, + { + "@type": "Person", + "givenName": "Jose Luis", + "familyName": "Rodriguez Sanchez", + "@id": "https://orcid.org/0000-0002-4702-5294" + } + ], + "contributor": [ + { + "@type": "Person", + "givenName": "Gabriel", + "familyName": "Garcia Jimenez" + }, + { + "@type": "Person", + "givenName": "Antia", + "familyName": "Gra\u00f1a Gonzalez" + }, + { + "@type": "Person", + "givenName": "Jan", + "familyName": "Mayer" + }, + { + "@type": "Person", + "givenName": "Pierre", + "familyName": "Morfouace" + }, + { + "@type": "Person", + "givenName": "Ryo", + "familyName": "Taniuchi" + }, + { + "@type": "Person", + "givenName": "Yanzhao", + "familyName": "Wang" + } + ] +} diff --git a/meta_update.py b/meta_update.py new file mode 100755 index 00000000..0f93aae7 --- /dev/null +++ b/meta_update.py @@ -0,0 +1,153 @@ +#! /usr/bin/env python3 + +from argparse import ArgumentParser +import json +import re +from collections import OrderedDict + + +class Manipulator(object): + def __str__(self): + return self.__class__.__name__ + + def load(self, filename=None): + if filename is None: + filename = self.default_filename + with open(filename, 'rb') as fp: + self.data = json.load(fp, object_pairs_hook=OrderedDict) + + def save(self, filename=None, indent=2): + if filename is None: + filename = self.default_filename + with open(filename, 'w', encoding='utf8') as fp: + json.dump(self.data, fp, indent=indent) + fp.write('\n') + + @staticmethod + def _dict_entry_cmp(dict1, dict2, field1, field2=None): + if field2 is None: + field2 = field1 + if (field1 in dict1) and (field2 in dict2): + return dict1[field1] == dict2[field2] + else: + return False + + def _handle_person_list_file(self, filename, field_name, **kwargs): + fp = open(filename, 'r', encoding='utf8') + person_list = self.data.setdefault(field_name, []) + for i, line in enumerate(fp, start=0): + line = line.strip() + m = self.findregex.match(line) + if m is None: + raise RuntimeError("Could not analyze line %r" % line) + found_entry = self._find_person_entry(person_list, m.groupdict()) + entry = self.update_person_entry(found_entry, m.groupdict(), + **kwargs) + if found_entry is None: + person_list.insert(i, entry) + + +class CodeMetaManipulator(Manipulator): + default_filename = 'codemeta.json' + findregex = re.compile(r'^(?P[-\w\s]*[-\w]),\s*' + r'(?P[-\w\s]*[-\w])\s*' + r'(?:<(?P\S+@\S+)>)?\s*' + r'(\[(?P\S+)\])?$') + + @classmethod + def _find_person_entry(cls, person_list, matchdict): + # orcid is unique + for entry in person_list: + if cls._dict_entry_cmp(entry, matchdict, '@id', 'orcid'): + return entry + for entry in person_list: + if cls._dict_entry_cmp(entry, matchdict, 'email'): + return entry + if cls._dict_entry_cmp(entry, matchdict, 'familyName') \ + and cls._dict_entry_cmp(entry, matchdict, 'givenName'): + return entry + return None + + @staticmethod + def update_person_entry(entry, matchdict): + if entry is None: + entry = OrderedDict() + entry['@type'] = 'Person' + for field in ('orcid', 'givenName', 'familyName', 'email'): + val = matchdict.get(field, None) + if val is not None: + if field == 'orcid': + entry['@id'] = val + else: + entry[field] = val + return entry + + def update_authors(self): + self._handle_person_list_file('AUTHORS', 'author') + self._handle_person_list_file('CONTRIBUTORS', 'contributor') + + def version(self, new_version): + self.data['softwareVersion'] = new_version + + +class ZenodoManipulator(Manipulator): + default_filename = '.zenodo.json' + findregex = re.compile(r'^(?P[-\w\s,]*[-\w])\s*' + r'(?:<(?P\S+@\S+)>)?\s*' + r'(\[https://orcid\.org/(?P\S+)\])?$') + + @classmethod + def _find_person_entry(cls, person_list, matchdict): + # Match on orcid first + for entry in person_list: + if cls._dict_entry_cmp(entry, matchdict, 'orcid'): + return entry + for entry in person_list: + if cls._dict_entry_cmp(entry, matchdict, 'name'): + return entry + return None + + @staticmethod + def update_person_entry(entry, matchdict, contributor_type=None): + if entry is None: + entry = OrderedDict() + if contributor_type: + entry['type'] = contributor_type + for field in ('name', 'orcid'): + val = matchdict.get(field, None) + if val is not None: + entry[field] = val + return entry + + def update_authors(self): + self._handle_person_list_file('AUTHORS', 'creators') + self._handle_person_list_file('CONTRIBUTORS', 'contributors', + contributor_type='Other') + + def save(self, filename=None): + super().save(filename, 4) + + def version(self, new_version): + self.data['version'] = new_version + + +def main(): + parser = ArgumentParser(description='Update codemeta.json and ' + '.zenodo.json') + parser.add_argument('--set-version', dest='newversion') + args = parser.parse_args() + + for manipulator in (CodeMetaManipulator(), ZenodoManipulator()): + try: + manipulator.load() + except FileNotFoundError as e: + print('*** Skipping {}: {}'.format(manipulator, e)) + continue + if args.newversion is not None: + manipulator.version(args.newversion) + manipulator.update_authors() + manipulator.save() + + +if __name__ == '__main__': + main()