diff --git a/.github/workflows/check_metadata.yaml b/.github/workflows/check_metadata.yaml new file mode 100644 index 000000000..4d0446bcf --- /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: LGPL-3.0-or-later + +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 || echo "Metadata files require updates. Please commit the changes." diff --git a/.github/workflows/cleanup_cache.yml b/.github/workflows/cleanup_cache.yml index 7b542fcbc..0c6e4b93d 100644 --- a/.github/workflows/cleanup_cache.yml +++ b/.github/workflows/cleanup_cache.yml @@ -1,3 +1,6 @@ +# +# SPDX-License-Identifier: LGPL-3.0-or-later + name: Cleanup old caches on: diff --git a/.github/workflows/codemeta_validate.yaml b/.github/workflows/codemeta_validate.yaml index 503f664ce..ffa8cc38a 100644 --- a/.github/workflows/codemeta_validate.yaml +++ b/.github/workflows/codemeta_validate.yaml @@ -1,3 +1,6 @@ +# +# SPDX-License-Identifier: LGPL-3.0-or-later + name: validate codemeta on: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0bb06921d..9530d6afd 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -41,6 +41,8 @@ # the pull request and merge respectively. For the pull request (Experimental), # the project is built with the cache files from ccache while for the merge # (Continuous) it's built from scratch without using any cache files. +# +# SPDX-License-Identifier: LGPL-3.0-or-later name: CI-CD diff --git a/.github/workflows/static_analysis.yml b/.github/workflows/static_analysis.yml index 965fac36c..d6d846c5f 100644 --- a/.github/workflows/static_analysis.yml +++ b/.github/workflows/static_analysis.yml @@ -1,3 +1,6 @@ +# +# SPDX-License-Identifier: LGPL-3.0-or-later + name: static analysis on: push: diff --git a/.zenodo.json b/.zenodo.json new file mode 100644 index 000000000..40a3fbe8d --- /dev/null +++ b/.zenodo.json @@ -0,0 +1,189 @@ +{ + "creators": [ + { + "name": "Al-Turany, Mohammad", + "orcid": "0000-0002-8071-4497" + }, + { + "name": "Alvarez Pol, Hector", + "orcid": "0000-0001-9643-6252" + }, + { + "name": "Heil, Michael" + }, + { + "name": "Kresan, Dmytro", + "orcid": "0000-0002-7537-2875" + }, + { + "name": "Mayer, Jan" + }, + { + "name": "Rodriguez Sanchez, Jose Luis", + "orcid": "0000-0002-4702-5294" + }, + { + "name": "Wagner, Vadim" + } + ], + "contributors": [ + { + "type": "Other", + "name": "Boretzky, Konstanze" + }, + { + "type": "Other", + "name": "Cabanelas, Pablo" + }, + { + "type": "Other", + "name": "Chatillon, Audrey" + }, + { + "type": "Other", + "name": "Douma, Christiaan" + }, + { + "type": "Other", + "name": "Falduto, Ashton" + }, + { + "type": "Other", + "name": "Feijoo, Martina" + }, + { + "type": "Other", + "name": "Galiana, Elisabet" + }, + { + "type": "Other", + "name": "Garcia, Gabriel" + }, + { + "type": "Other", + "name": "Gasparic, Igor" + }, + { + "type": "Other", + "name": "Gra\u00f1a, Antia" + }, + { + "type": "Other", + "name": "Johansson, H\u00e5kan" + }, + { + "type": "Other", + "name": "Heiss, Benjamin" + }, + { + "type": "Other", + "name": "Horvat, Andrea" + }, + { + "type": "Other", + "name": "Inglessi, Alexander" + }, + { + "type": "Other", + "name": "Kelic-Heil, Aleksandra" + }, + { + "type": "Other", + "name": "Klenze, Philipp" + }, + { + "type": "Other", + "name": "Kripko, Aron" + }, + { + "type": "Other", + "name": "Kudaibergenova, Eleonora" + }, + { + "type": "Other", + "name": "Labiche, Marc" + }, + { + "type": "Other", + "name": "Lagni, Andrea" + }, + { + "type": "Other", + "name": "Lihtar, Ivana" + }, + { + "type": "Other", + "name": "Loeher, Bastian" + }, + { + "type": "Other", + "name": "Milhomens da Fonseca, Leandro" + }, + { + "type": "Other", + "name": "Mozumdar, Nikhil" + }, + { + "type": "Other", + "name": "Panin, Valerii" + }, + { + "type": "Other", + "name": "Plag, Ralf" + }, + { + "type": "Other", + "name": "Ponnath, Lukas" + }, + { + "type": "Other", + "name": "Rybalchenko, Alexey" + }, + { + "type": "Other", + "name": "Storck, Sonja" + }, + { + "type": "Other", + "name": "Syndikus, Ina" + }, + { + "type": "Other", + "name": "Taniuchi, Ryo" + }, + { + "type": "Other", + "name": "Tscheuschner, Joachim" + }, + { + "type": "Other", + "name": "Wang, Yanzhao" + }, + { + "type": "Other", + "name": "Whitehead, Matthew" + }, + { + "type": "Other", + "name": "Wongel, Alicia" + }, + { + "type": "Other", + "name": "Xarepe, Manuel" + }, + { + "type": "Other", + "name": "Zanetti, Lorenzo" + } + ], + "description": "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). The user can create simulated data and/or perform analysis with the same framework. Geant3 and Geant4 transport engines are supported, however, the implemented tasks that create simulated data do not depend on a particular Monte Carlo engine. The framework delivers base classes which enable the users to construct their detectors and/or analysis tasks in a simple way, it also delivers some general functionality like track visualization. Moreover, an interface for reading experimental and/or simulated magnetic field maps is also implemented.", + "related_identifiers": [ + { + "identifier": "https://github.com/R3BRootGroup/R3BRoot/", + "relation": "isSupplementTo", + "resource_type": "software", + "scheme": "url" + } + ], + "title": "R3BRoot" +} diff --git a/AUTHORS b/AUTHORS index ee7a26c3a..e074a2a82 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,7 +1,7 @@ -Al-Turany, Mohammad -Alvarez Pol, Hector +Al-Turany, Mohammad [https://orcid.org/0000-0002-8071-4497] +Alvarez Pol, Hector [https://orcid.org/0000-0001-9643-6252] Heil, Michael -Kresan, Dmytro +Kresan, Dmytro [https://orcid.org/0000-0002-7537-2875] Mayer, Jan -Rodriguez Sanchez, Jose Luis +Rodriguez Sanchez, Jose Luis [https://orcid.org/0000-0002-4702-5294] Wagner, Vadim diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 62948dbd1..2e5069324 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -35,4 +35,3 @@ Whitehead, Matthew Wongel, Alicia Xarepe, Manuel Zanetti, Lorenzo - diff --git a/README.md b/README.md index a106d4366..e66d60096 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# R3BRoot Software [![license](https://alfa-ci.gsi.de/shields/badge/license-GPL--3.0-orange.svg)](COPYRIGHT) +# R3BRoot Software [![license](https://alfa-ci.gsi.de/shields/badge/license-GPL--3.0-orange.svg)](COPYRIGHT) [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.3896282.svg)](https://doi.org/10.5281/zenodo.12193515) [![CI-CD](https://github.com/R3BRootGroup/R3BRoot/actions/workflows/main.yml/badge.svg)](https://github.com/R3BRootGroup/R3BRoot/actions/workflows/main.yml) [![Static Analysis](https://github.com/R3BRootGroup/R3BRoot/actions/workflows/static_analysis.yml/badge.svg)](https://github.com/R3BRootGroup/R3BRoot/actions/workflows/static_analysis.yml) [![Validate Codemeta](https://github.com/R3BRootGroup/R3BRoot/actions/workflows/codemeta_validate.yaml/badge.svg)](https://github.com/R3BRootGroup/R3BRoot/actions/workflows/codemeta_validate.yaml) [![Cleanup Caches on PR Close](https://github.com/R3BRootGroup/R3BRoot/actions/workflows/cleanup_cache.yml/badge.svg)](https://github.com/R3BRootGroup/R3BRoot/actions/workflows/cleanup_cache.yml) diff --git a/codemeta.json b/codemeta.json index 9d7346594..f0978251d 100644 --- a/codemeta.json +++ b/codemeta.json @@ -6,11 +6,11 @@ "contIntegration": "https://github.com/R3BRootGroup/R3BRoot/actions", "dateCreated": "2009-04-14", "datePublished": "2009-08-01", - "dateModified": "2023-11-04", - "downloadUrl": "https://github.com/R3BRootGroup/R3BRoot/archive/refs/tags/nov23.tar.gz", + "dateModified": "2024-12-06", + "downloadUrl": "https://github.com/R3BRootGroup/R3BRoot/archive/refs/tags/dec24.tar.gz", "issueTracker": "https://github.com/R3BRootGroup/R3BRoot/issues", "name": "R3BRoot", - "softwareVersion": "nov23", + "softwareVersion": "dec24", "description": "Software for simulations and data analysis of Reactions with Relativistic Radioactive Beams experiment at FAIR", "readme": "https://github.com/R3BRootGroup/R3BRoot/blob/dev/README.md", "releaseNotes": "", @@ -18,7 +18,14 @@ "developmentStatus": "active", "referencePublication": "https://doi.org/10.1088/1742-6596/523/1/012034", "keywords": [ - "MC simulation", + "Geant4", + "c-plus-plus", + "cmake", + "vmc", + "modular", + "Event reconstruction", + "Simulation", + "Data analysis", "Structure and dynamics of nuclei", "Nuclear reactions", "Nuclear fission", @@ -43,13 +50,15 @@ "maintainer": { "@type": "Person", "givenName": "Jose Luis", - "familyName": "Rodriguez Sanchez" + "familyName": "Rodriguez Sanchez", + "@id": "https://orcid.org/0000-0002-4702-5294" }, "author": [ { "@type": "Person", "givenName": "Hector", - "familyName": "Alvarez Pol" + "familyName": "Alvarez Pol", + "@id": "https://orcid.org/0000-0001-9643-6252" }, { "@type": "Person", @@ -59,7 +68,8 @@ { "@type": "Person", "givenName": "Dmytro", - "familyName": "Kresan" + "familyName": "Kresan", + "@id": "https://orcid.org/0000-0002-7537-2875" }, { "@type": "Person", @@ -69,7 +79,8 @@ { "@type": "Person", "givenName": "Jose Luis", - "familyName": "Rodriguez Sanchez" + "familyName": "Rodriguez Sanchez", + "@id": "https://orcid.org/0000-0002-4702-5294" }, { "@type": "Person", @@ -79,7 +90,8 @@ { "@type": "Person", "givenName": "Mohammad", - "familyName": "Al-Turany" + "familyName": "Al-Turany", + "@id": "https://orcid.org/0000-0002-8071-4497" } ], "contributor": [ @@ -248,6 +260,11 @@ "givenName": "Lorenzo", "familyName": "Zanetti" }, + { + "@type": "Person", + "givenName": "Matthew", + "familyName": "Whitehead" + }, { "@type": "Person", "givenName": "Konstanze", diff --git a/codemeta_update.py b/codemeta_update.py deleted file mode 100755 index d7c98eb7d..000000000 --- a/codemeta_update.py +++ /dev/null @@ -1,81 +0,0 @@ -#! /usr/bin/env python3 -# Copyright (C) 2019 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH -# -# SPDX-License-Identifier: LGPL-3.0-or-later - -import argparse -import json -import re -from collections import OrderedDict - -class CodeMetaManipulator(object): - def load(self, filename='codemeta.json'): - with open(filename, 'rb') as fp: - self.data = json.load(fp, object_pairs_hook=OrderedDict) - - @staticmethod - def _dict_entry_cmp(dict1, dict2, field): - if (field in dict1) and (field in dict2): - return dict1[field] == dict2[field] - else: - return False - - @classmethod - def find_person_entry(cls, person_list, matchdict): - 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 ('givenName', 'familyName', 'email'): - val = matchdict.get(field, None) - if val is not None: - entry[field] = val - return entry - - def handle_person_list_file(self, filename, cm_field_name): - fp = open(filename, 'r', encoding='utf8') - findregex = re.compile(r'^(?P[-\w\s]*[-\w]),\s*' - r'(?P[-\w\s]*[-\w])\s*' - r'(?:<(?P\S+@\S+)>)?$') - person_list = self.data.setdefault(cm_field_name, []) - for line in fp: - line = line.strip() - m = 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()) - if found_entry is None: - person_list.append(entry) - - def save(self, filename='codemeta.json'): - with open('codemeta.json', 'w', encoding='utf8') as fp: - json.dump(self.data, fp, indent=2) - fp.write('\n') - - -def main(): - parser = argparse.ArgumentParser(description='Update codemeta.json') - parser.add_argument('--set-version', dest='newversion') - args = parser.parse_args() - - cm = CodeMetaManipulator() - cm.load() - if args.newversion is not None: - cm.data['softwareVersion'] = args.newversion - cm.handle_person_list_file('AUTHORS', 'author') - cm.handle_person_list_file('CONTRIBUTORS', 'contributor') - cm.save() - - -if __name__ == '__main__': - main() diff --git a/meta_update.py b/meta_update.py new file mode 100755 index 000000000..b4a76d96e --- /dev/null +++ b/meta_update.py @@ -0,0 +1,156 @@ +#! /usr/bin/env python3 +# Copyright (C) 2019-2024 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH +# +# SPDX-License-Identifier: LGPL-3.0-or-later + +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()