diff --git a/CITATION.cff b/CITATION.cff index 5ed00fe..e185cdc 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -34,4 +34,4 @@ keywords: - indicators - fairness license: MIT -version: 0.1.5 +version: 0.1.6 diff --git a/README.md b/README.md index 985aebd..432d24e 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,8 @@ Given a repository URL, RSFC will perform a series of checks based on a list of - versioning_standards_use - support_issue_tracking - has_contribution_guidelines +- project_is_active +- software_is_containerized For more information about these RSQIs, you can check https://github.com/EVERSE-ResearchSoftware/indicators. We have plans to implement all of the RSQIs available in that repository. diff --git a/RSFC_REPORT.md b/RSFC_REPORT.md index 7392912..d71eebc 100644 --- a/RSFC_REPORT.md +++ b/RSFC_REPORT.md @@ -1,66 +1,65 @@ -# Quality Assessment for rsfc v0.1.4 +# Quality Assessment for rsfc v0.1.5 -An automated assessment of the rsfc tool based on the EVERSE software quality indicators, run on 2026-04-13. +An automated assessment of the rsfc tool based on the EVERSE software quality indicators, run on 2026-06-29. ## General Information - **Software:** rsfc - **Repository:** https://github.com/oeg-upm/rsfc -- **Assessment date:** 2026-04-13T11:10:53Z -- **Total checks:** 42 +- **Assessment date:** 2026-06-29T07:12:44Z +- **Total checks:** 41 ## Summary -- **Passed (`true`)**: 32 -- **Failed (`false`)**: 10 +- **Passed (`true`)**: 34 +- **Failed (`false`)**: 7 - **Errors (`error`)**: 0 ## Results Table | TEST ID | Short Description | Output | | --- | --- | --- | -| RSFC-01-1 | There is an identifier and it resolves | true | -| RSFC-01-2 | There is an identifier in the metadata files | true | -| RSFC-01-3 | There is an identifier and it follows a common schema | true | -| RSFC-03-1 | The software has releases | true | -| RSFC-03-2 | Releases have version and identifier | true | -| RSFC-03-3 | Release versions follow SemVer or CalVer | true | -| RSFC-03-4 | Release identifiers follow the same scheme | true | -| RSFC-03-5 | Last release version corresponds to version in package file | false | -| RSFC-03-6 | There is a version number stated in metadata files | true | -| RSFC-04-1 | Metadata files exist | true | -| RSFC-04-2 | There is a README file | true | -| RSFC-04-3 | Title and description are declared | true | -| RSFC-04-4 | There is descriptive metadata | true | -| RSFC-04-5 | There is a codemeta file | true | -| RSFC-05-1 | There is a repostatus badge in the README file | true | -| RSFC-05-2 | Contact and support metadata exists | false | -| RSFC-05-3 | Software documentation exists | true | -| RSFC-06-1 | Authors are declared | true | -| RSFC-06-2 | Contributors are declared | false | -| RSFC-06-3 | Authors have an ORCID assigned | false | -| RSFC-06-4 | Authors have their roles stated | false | -| RSFC-07-1 | There is an identifier in README or CITATION | true | -| RSFC-07-2 | Software identifier resolves and links back to software | true | -| RSFC-08-1 | Metadata record is found in SWHeritage or Zenodo | true | -| RSFC-09-1 | Repository is from Github or Gitlab | true | -| RSFC-12-1 | There is an article citation or reference publication | false | -| RSFC-13-1 | Dependencies are declared | true | -| RSFC-13-2 | There are installation instructions | true | -| RSFC-13-3 | Dependencies have version numbers | false | -| RSFC-13-4 | Dependencies are in a machine-readable format | true | -| RSFC-14-1 | Tests are provided | true | -| RSFC-14-2 | There are actions to automate tests | false | -| RSFC-15-1 | There is a license | true | -| RSFC-15-2 | License is in SPDX format | false | -| RSFC-16-1 | License is referenced in metadata files | true | -| RSFC-17-1 | The repository has an 'active' status | true | -| RSFC-17-2 | Repository has a commit history | true | -| RSFC-17-3 | Commits are linked to issues | true | -| RSFC-18-1 | There are citations | true | -| RSFC-19-1 | Repository has continuous integration workflows | true | -| RSFC-20-1 | Repository has an issue tracker | true | -| RSFC-21-1 | Repository has contribution guidelines | false | +| [RSFC-01-1](https://w3id.org/rsfc/test/RSFC-01-1) | There is an identifier and it resolves | true | +| [RSFC-01-2](https://w3id.org/rsfc/test/RSFC-01-2) | There is an identifier in the metadata files | true | +| [RSFC-01-3](https://w3id.org/rsfc/test/RSFC-01-3) | There is an identifier and it follows a common schema | true | +| [RSFC-03-1](https://w3id.org/rsfc/test/RSFC-03-1) | The software has releases | true | +| [RSFC-03-2](https://w3id.org/rsfc/test/RSFC-03-2) | Releases have version and identifier | true | +| [RSFC-03-3](https://w3id.org/rsfc/test/RSFC-03-3) | Release versions follow SemVer or CalVer | true | +| [RSFC-03-4](https://w3id.org/rsfc/test/RSFC-03-4) | Release identifiers follow the same scheme | true | +| [RSFC-03-5](https://w3id.org/rsfc/test/RSFC-03-5) | Last release version corresponds to version in package file | true | +| [RSFC-03-6](https://w3id.org/rsfc/test/RSFC-03-6) | There is a version number stated in metadata files | true | +| [RSFC-04-1](https://w3id.org/rsfc/test/RSFC-04-1) | Metadata files exist | true | +| [RSFC-04-2](https://w3id.org/rsfc/test/RSFC-04-2) | There is a README file | true | +| [RSFC-04-3](https://w3id.org/rsfc/test/RSFC-04-3) | Title and description are declared | true | +| [RSFC-04-4](https://w3id.org/rsfc/test/RSFC-04-4) | There is descriptive metadata | true | +| [RSFC-04-5](https://w3id.org/rsfc/test/RSFC-04-5) | There is a codemeta file | true | +| [RSFC-05-1](https://w3id.org/rsfc/test/RSFC-05-1) | There is a repostatus badge in the README file | true | +| [RSFC-05-2](https://w3id.org/rsfc/test/RSFC-05-2) | Contact and support metadata exists | true | +| [RSFC-05-3](https://w3id.org/rsfc/test/RSFC-05-3) | Software documentation exists | true | +| [RSFC-06-1](https://w3id.org/rsfc/test/RSFC-06-1) | Authors are declared | true | +| [RSFC-06-2](https://w3id.org/rsfc/test/RSFC-06-2) | Contributors are declared | false | +| [RSFC-06-3](https://w3id.org/rsfc/test/RSFC-06-3) | Authors have an ORCID assigned | false | +| [RSFC-07-1](https://w3id.org/rsfc/test/RSFC-07-1) | There is an identifier in README or CITATION | true | +| [RSFC-07-2](https://w3id.org/rsfc/test/RSFC-07-2) | Software identifier resolves and links back to software | true | +| [RSFC-08-1](https://w3id.org/rsfc/test/RSFC-08-1) | Metadata record is found in SWHeritage or Zenodo | true | +| [RSFC-09-1](https://w3id.org/rsfc/test/RSFC-09-1) | Repository is from Github or Gitlab | true | +| [RSFC-12-1](https://w3id.org/rsfc/test/RSFC-12-1) | There is an article citation or reference publication | false | +| [RSFC-13-1](https://w3id.org/rsfc/test/RSFC-13-1) | Dependencies are declared | true | +| [RSFC-13-2](https://w3id.org/rsfc/test/RSFC-13-2) | There are installation instructions | true | +| [RSFC-13-3](https://w3id.org/rsfc/test/RSFC-13-3) | Dependencies have version numbers | false | +| [RSFC-13-4](https://w3id.org/rsfc/test/RSFC-13-4) | Dependencies are in a machine-readable format | true | +| [RSFC-14-1](https://w3id.org/rsfc/test/RSFC-14-1) | Tests are provided | true | +| [RSFC-14-2](https://w3id.org/rsfc/test/RSFC-14-2) | There are actions to automate tests | false | +| [RSFC-15-1](https://w3id.org/rsfc/test/RSFC-15-1) | There is a license | true | +| [RSFC-15-2](https://w3id.org/rsfc/test/RSFC-15-2) | License is in SPDX format | true | +| [RSFC-16-1](https://w3id.org/rsfc/test/RSFC-16-1) | License is referenced in metadata files | true | +| [RSFC-17-2](https://w3id.org/rsfc/test/RSFC-17-2) | Repository has a commit history | true | +| [RSFC-17-3](https://w3id.org/rsfc/test/RSFC-17-3) | Commits are linked to issues | false | +| [RSFC-18-1](https://w3id.org/rsfc/test/RSFC-18-1) | There are citations | true | +| [RSFC-19-1](https://w3id.org/rsfc/test/RSFC-19-1) | Repository has continuous integration workflows | true | +| [RSFC-20-1](https://w3id.org/rsfc/test/RSFC-20-1) | Repository has an issue tracker | true | +| [RSFC-21-1](https://w3id.org/rsfc/test/RSFC-21-1) | Repository has contribution guidelines | false | +| [RSFC-22-1](https://w3id.org/rsfc/test/RSFC-22-1) | Software offers a container file to run it | true | ## Detailed Results by Indicator @@ -72,8 +71,9 @@ An automated assessment of the rsfc tool based on the EVERSE software quality in - **Test ID:** https://w3id.org/rsfc/test/RSFC-08-1 - **Result:** true - **Process:** Searches for Zenodo and Software Heritage badges in the README file of the repository -- **Evidence:** A Zenodo DOI identifier was found in the repository -- **Suggestions:** No suggestions +- **Evidence:** A Zenodo DOI identifier was found in: + - https://doi.org/10.5281/zenodo.16531481 +- **Suggestions:** N/A ### descriptive_metadata @@ -83,8 +83,11 @@ An automated assessment of the rsfc tool based on the EVERSE software quality in - **Test ID:** https://w3id.org/rsfc/test/RSFC-03-6 - **Result:** true - **Process:** Checks if a version number for the software is indicated in the CITATION.cff, codemeta.json or package files(i.e. pyproject.toml, pom.xml, etc.) -- **Evidence:** Found the software version in one of the specified files -- **Suggestions:** No suggestions +- **Evidence:** Found the software version in: + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/CITATION.cff + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/codemeta.json + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml +- **Suggestions:** N/A #### Metadata exists @@ -92,8 +95,8 @@ An automated assessment of the rsfc tool based on the EVERSE software quality in - **Test ID:** https://w3id.org/rsfc/test/RSFC-04-1 - **Result:** true - **Process:** Searches for codemeta, citation and package files in the repository -- **Evidence:** Found codemeta, citation and package files in the repository -- **Suggestions:** No suggestions +- **Evidence:** Found CITATION.cff, codemeta.json, package_file in the repository +- **Suggestions:** N/A #### There are title and description @@ -101,8 +104,8 @@ An automated assessment of the rsfc tool based on the EVERSE software quality in - **Test ID:** https://w3id.org/rsfc/test/RSFC-04-3 - **Result:** true - **Process:** Checks if there is a title and a description for the software in the metadata -- **Evidence:** Title and description were found in the repository -- **Suggestions:** No suggestions +- **Evidence:** Found title in https://raw.githubusercontent.com/oeg-upm/rsfc/main/README.md and description (no source found, obtained via GitHub_API). +- **Suggestions:** N/A #### Software has descriptive metadata @@ -110,8 +113,8 @@ An automated assessment of the rsfc tool based on the EVERSE software quality in - **Test ID:** https://w3id.org/rsfc/test/RSFC-04-4 - **Result:** true - **Process:** Searches for description, programming languages, date of creation and keywords in the repository -- **Evidence:** Descriptive metadata was found in the repository -- **Suggestions:** No suggestions +- **Evidence:** Descriptive metadata found in: Description [https://raw.githubusercontent.com/oeg-upm/rsfc/main/codemeta.json, https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml], Languages [https://raw.githubusercontent.com/oeg-upm/rsfc/main/codemeta.json], Date Created [https://raw.githubusercontent.com/oeg-upm/rsfc/main/codemeta.json], Keywords [https://raw.githubusercontent.com/oeg-upm/rsfc/main/codemeta.json] +- **Suggestions:** N/A #### There is a codemeta file @@ -120,7 +123,7 @@ An automated assessment of the rsfc tool based on the EVERSE software quality in - **Result:** true - **Process:** Searches for a codemeta.json file in the repository - **Evidence:** A codemeta.json file was found in the root of the repository -- **Suggestions:** No suggestions +- **Suggestions:** N/A #### Authors are declared @@ -128,8 +131,11 @@ An automated assessment of the rsfc tool based on the EVERSE software quality in - **Test ID:** https://w3id.org/rsfc/test/RSFC-06-1 - **Result:** true - **Process:** Searches for authors in various files of the repository (i.e. CITATION.cff, AUTHORS.md, codemeta.json) -- **Evidence:** Authors were found in the repository -- **Suggestions:** No suggestions +- **Evidence:** Authors were found in: + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/CITATION.cff + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/codemeta.json + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml +- **Suggestions:** N/A #### Contributors are declared @@ -137,7 +143,7 @@ An automated assessment of the rsfc tool based on the EVERSE software quality in - **Test ID:** https://w3id.org/rsfc/test/RSFC-06-2 - **Result:** false - **Process:** Searches for contributors in various files of the repository (i.e. codemeta.json, pyproject.toml, pom.xml)' -- **Evidence:** Found authors but could not find any contributors in the repository +- **Evidence:** Could not find any contributors in the repository - **Suggestions:** Your software should also document its contributors if there are any. More information at https://everse.software/RSQKit/documenting_software_project @@ -146,18 +152,11 @@ An automated assessment of the rsfc tool based on the EVERSE software quality in - **Test ID:** https://w3id.org/rsfc/test/RSFC-06-3 - **Result:** false - **Process:** Checks if all authors stated in the CITATION.cff file have an ORCID assigned -- **Evidence:** One or more authors do not have an ORCID assigned +- **Evidence:** Authors that do not have an orcid were found in: + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/CITATION.cff + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/codemeta.json - **Suggestions:** When documenting your software's authors, you should include their ORCIDs if possible. - -#### Authors have roles - -- **Test ID:** https://w3id.org/rsfc/test/RSFC-06-4 -- **Result:** false -- **Process:** Checks if all authors stated in a codemeta.json file have a role assigned -- **Evidence:** There are one or more authors in the codemeta file that do not have roles assigned -- **Suggestions:** When documenting your software's authors, you should include their roles if possible. - ### has_contribution_guidelines @@ -178,21 +177,22 @@ An automated assessment of the rsfc tool based on the EVERSE software quality in - **Result:** true - **Process:** Searches for release tags in the repository - **Evidence:** These releases were found: - - RSFC-0.1.4 - - RSFC-0.1.3 - - RSFC-0.1.2 - - RSFC-0.1.1 - - RSFC-0.1.0 - - RSFC-0.0.9 - - RSFC-0.0.8 - - RSFC-0.0.7 - - RSFC-0.0.6 - - RSFC-0.0.5 - - RSFC-0.0.4 - - RSFC-0.0.3 - - RSFC-0.0.2 - - RSFC-0.0.1 -- **Suggestions:** No suggestions + - https://github.com/oeg-upm/rsfc/releases/tag/v0.1.5 + - https://github.com/oeg-upm/rsfc/releases/tag/v0.1.4 + - https://github.com/oeg-upm/rsfc/releases/tag/v0.1.3 + - https://github.com/oeg-upm/rsfc/releases/tag/v0.1.2 + - https://github.com/oeg-upm/rsfc/releases/tag/v0.1.1 + - https://github.com/oeg-upm/rsfc/releases/tag/v0.1.0 + - https://github.com/oeg-upm/rsfc/releases/tag/v0.0.9 + - https://github.com/oeg-upm/rsfc/releases/tag/v0.0.8 + - https://github.com/oeg-upm/rsfc/releases/tag/v0.0.7 + - https://github.com/oeg-upm/rsfc/releases/tag/v0.0.6 + - https://github.com/oeg-upm/rsfc/releases/tag/v0.0.5 + - https://github.com/oeg-upm/rsfc/releases/tag/v0.0.4 + - https://github.com/oeg-upm/rsfc/releases/tag/v0.0.3 + - https://github.com/oeg-upm/rsfc/releases/tag/v0.0.2 + - https://github.com/oeg-upm/rsfc/releases/tag/v0.0.1 +- **Suggestions:** N/A #### Releases have an id and version number @@ -201,7 +201,7 @@ An automated assessment of the rsfc tool based on the EVERSE software quality in - **Result:** true - **Process:** Checks if all of the releases have an identifier and a version - **Evidence:** All of the releases have an id and a version -- **Suggestions:** No suggestions +- **Suggestions:** N/A #### Release identifiers follow the same scheme @@ -210,16 +210,16 @@ An automated assessment of the rsfc tool based on the EVERSE software quality in - **Result:** true - **Process:** Checks if all of the version identifiers follow the same scheme - **Evidence:** All of the releases URLs follow the same scheme -- **Suggestions:** No suggestions +- **Suggestions:** N/A #### Last release consistency - **Test ID:** https://w3id.org/rsfc/test/RSFC-03-5 -- **Result:** false -- **Process:** Checks if the latest release tag matches the version stated in the package file of the repository -- **Evidence:** Latest release does not match the latest version stated -- **Suggestions:** It is good practice to keep consistency between the version of your latest release and the version in your metadata files +- **Result:** true +- **Process:** Checks if the latest release tag matches the version stated in the codemeta or package files of the repository +- **Evidence:** Latest release matches the latest version stated in the metadata files +- **Suggestions:** N/A ### persistent_and_unique_identifier @@ -230,7 +230,7 @@ An automated assessment of the rsfc tool based on the EVERSE software quality in - **Result:** true - **Process:** Searches for an identifier (i.e. DOI or SWHID) in the README file of the repository - **Evidence:** Found the identifier https://doi.org/10.5281/zenodo.16531481 in the README and it resolves -- **Suggestions:** No suggestions +- **Suggestions:** N/A #### There is an identifier associated with the software @@ -238,8 +238,8 @@ An automated assessment of the rsfc tool based on the EVERSE software quality in - **Test ID:** https://w3id.org/rsfc/test/RSFC-01-2 - **Result:** true - **Process:** Searches for an identifier in the CITATION.cff, codemeta.json and README files -- **Evidence:** An identifier was found but could not find it in the following locations: -- **Suggestions:** No suggestions +- **Evidence:** An identifier was found in CITATION.cff, README.md, codemeta.json. +- **Suggestions:** N/A #### Software identifier follows a proper schema @@ -248,7 +248,7 @@ An automated assessment of the rsfc tool based on the EVERSE software quality in - **Result:** true - **Process:** Checks if the identifiers associated with the software follow any of these schemas: DOI, URN, GITHUB and SWHID - **Evidence:** All of the identifiers detected follow a common schema -- **Suggestions:** No suggestions +- **Suggestions:** N/A #### There is an identifier in README or CITATION.cff @@ -257,7 +257,9 @@ An automated assessment of the rsfc tool based on the EVERSE software quality in - **Result:** true - **Process:** Searches for an identifier in the README or CITATION.cff files of the repository - **Evidence:** An identifier was found in both the README and CITATION.cff files of the repository -- **Suggestions:** No suggestions + - https://doi.org/10.5281/zenodo.16531481 + - 10.5281/zenodo.16531481 +- **Suggestions:** N/A #### Software identifier resolves to software @@ -265,8 +267,8 @@ An automated assessment of the rsfc tool based on the EVERSE software quality in - **Test ID:** https://w3id.org/rsfc/test/RSFC-07-2 - **Result:** true - **Process:** Checks if the identifier found in the README file or metadata files (i.e. codemeta.json, CITATION.cff) resolves to a page that links back to the software repository -- **Evidence:** The landing page of the software's identifier links back to the software repository -- **Suggestions:** No suggestions +- **Evidence:** The landing page of the software's identifier https://doi.org/10.5281/zenodo.16531481 links back to the software repository +- **Suggestions:** N/A ### repository_workflows @@ -287,9 +289,9 @@ An automated assessment of the rsfc tool based on the EVERSE software quality in - **Process:** Searches for workflows in the repository - **Evidence:** Workflows were found in: - https://raw.githubusercontent.com/oeg-upm/rsfc/main/.github/workflows/pypi-publish.yml - - https://raw.githubusercontent.com/oeg-upm/rsfc/main/.github/workflows/use-rsfc.yml - https://raw.githubusercontent.com/oeg-upm/rsfc/main/.github/workflows/run-rsfc.yml -- **Suggestions:** No suggestions + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/.github/workflows/use-rsfc.yml +- **Suggestions:** N/A ### requirements_specified @@ -300,10 +302,10 @@ An automated assessment of the rsfc tool based on the EVERSE software quality in - **Result:** true - **Process:** Searches for dependencies in project configuration files, README and dependencies files such as requirements.txt - **Evidence:** Requirements were found in: - - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt - https://raw.githubusercontent.com/oeg-upm/rsfc/main/README.md -- **Suggestions:** No suggestions +- **Suggestions:** N/A #### Dependencies have version numbers @@ -311,7 +313,8 @@ An automated assessment of the rsfc tool based on the EVERSE software quality in - **Test ID:** https://w3id.org/rsfc/test/RSFC-13-3 - **Result:** false - **Process:** Checks if all of the dependencies stated in the machine-readable file (e.g. requirements.txt, pyproject.toml, etc.) of the repository have a version indicated -- **Evidence:** One or more dependencies do not have a version stated +- **Evidence:** The following dependencies do not have a version stated: + - poetry-core - **Suggestions:** All of your dependencies should have their versions stated to ensure its reproducibility. More information at https://everse.software/RSQKit/reproducible_software_environments @@ -320,8 +323,179 @@ An automated assessment of the rsfc tool based on the EVERSE software quality in - **Test ID:** https://w3id.org/rsfc/test/RSFC-13-4 - **Result:** true - **Process:** Checks if dependencies are indicated in a machine-readable file -- **Evidence:** There is a machine-readable file for dependencies -- **Suggestions:** No suggestions +- **Evidence:** There is a machine-readable file for dependencies at: + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/requirements.txt +- **Suggestions:** N/A ### software_has_citation @@ -342,7 +516,7 @@ An automated assessment of the rsfc tool based on the EVERSE software quality in - **Process:** Searches for a CITATION.cff file and README file in the repository - **Evidence:** A citation was found in: - https://raw.githubusercontent.com/oeg-upm/rsfc/main/CITATION.cff -- **Suggestions:** No suggestions +- **Suggestions:** N/A ### software_has_documentation @@ -353,16 +527,17 @@ An automated assessment of the rsfc tool based on the EVERSE software quality in - **Result:** true - **Process:** Searches for a README file in the repository - **Evidence:** There is a README file in the repository -- **Suggestions:** No suggestions +- **Suggestions:** N/A #### There is contact and/or support metadata - **Test ID:** https://w3id.org/rsfc/test/RSFC-05-2 -- **Result:** false +- **Result:** true - **Process:** Searches for contact and support information in the repository -- **Evidence:** Could not find any of the following information: contact, support_channels -- **Suggestions:** You should include contact information in your software's metadata in case someone wants to ask for information. +- **Evidence:** Contact and support information was found in: + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/README.md +- **Suggestions:** N/A #### Software documentation @@ -370,9 +545,9 @@ An automated assessment of the rsfc tool based on the EVERSE software quality in - **Test ID:** https://w3id.org/rsfc/test/RSFC-05-3 - **Result:** true - **Process:** Searches for a README file in the root repository and other forms of documentation such as a Read The Docs badge or url -- **Evidence:** Documentation was found in: -- https://raw.githubusercontent.com/oeg-upm/rsfc/main/README.md -- **Suggestions:** No suggest +- **Evidence:** Documentation was found in: + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/README.md +- **Suggestions:** N/A #### There are installation instructions @@ -380,8 +555,9 @@ An automated assessment of the rsfc tool based on the EVERSE software quality in - **Test ID:** https://w3id.org/rsfc/test/RSFC-13-2 - **Result:** true - **Process:** Searches for installation instructions in the README file of the repository -- **Evidence:** Installation instructions were found in the repository -- **Suggestions:** No suggestions +- **Evidence:** Installation instructions were found in: + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/README.md +- **Suggestions:** N/A ### software_has_license @@ -392,28 +568,29 @@ An automated assessment of the rsfc tool based on the EVERSE software quality in - **Result:** true - **Process:** Searches for a file named 'LICENSE' or 'LICENSE.md' in the root of the repository. - **Evidence:** A license was found in: - - https://raw.githubusercontent.com/oeg-upm/rsfc/main/codemeta.json - https://raw.githubusercontent.com/oeg-upm/rsfc/main/pyproject.toml + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/CITATION.cff - https://raw.githubusercontent.com/oeg-upm/rsfc/main/LICENSE -- **Suggestions:** No suggestions + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/codemeta.json +- **Suggestions:** N/A #### License is SPDX compliant - **Test ID:** https://w3id.org/rsfc/test/RSFC-15-2 -- **Result:** false +- **Result:** true - **Process:** Checks if the licenses detected are SPDX compliant -- **Evidence:** There is one or more licenses that are not SPDX compliant -- **Suggestions:** You should include SPDX tags to ensure that your licenses are machine-readable. More information at https://everse.software/RSQKit/licensing_software +- **Evidence:** All licenses are SPDX compliant +- **Suggestions:** N/A #### License referenced in metadata files - **Test ID:** https://w3id.org/rsfc/test/RSFC-16-1 - **Result:** true -- **Process:** Searches for licensing information in the codemeta, citation and package files if they exist -- **Evidence:** License information was found in metadata files -- **Suggestions:** No suggestions +- **Process:** Searches for licensing information in the codemeta, CITATION.cff and package files if they exist +- **Evidence:** License information was found in codemeta (MIT), CITATION.cff (MIT), package (MIT) +- **Suggestions:** N/A ### software_has_tests @@ -422,179 +599,24 @@ An automated assessment of the rsfc tool based on the EVERSE software quality in - **Test ID:** https://w3id.org/rsfc/test/RSFC-14-1 - **Result:** true -- **Process:** Searches for files and/or directories that mention test in their names -- **Evidence:** Files and/or directories that mention test were found at: -- doc/test -- doc/test/RSFC-01-1 -- doc/test/RSFC-01-1/RSFC-01-1.html -- doc/test/RSFC-01-1/RSFC-01-1.jsonld -- doc/test/RSFC-01-1/RSFC-01-1.ttl -- doc/test/RSFC-01-2 -- doc/test/RSFC-01-2/RSFC-01-2.html -- doc/test/RSFC-01-2/RSFC-01-2.jsonld -- doc/test/RSFC-01-2/RSFC-01-2.ttl -- doc/test/RSFC-01-3 -- doc/test/RSFC-01-3/RSFC-01-3.html -- doc/test/RSFC-01-3/RSFC-01-3.jsonld -- doc/test/RSFC-01-3/RSFC-01-3.ttl -- doc/test/RSFC-03-1 -- doc/test/RSFC-03-1/RSFC-03-1.html -- doc/test/RSFC-03-1/RSFC-03-1.jsonld -- doc/test/RSFC-03-1/RSFC-03-1.ttl -- doc/test/RSFC-03-2 -- doc/test/RSFC-03-2/RSFC-03-2.html -- doc/test/RSFC-03-2/RSFC-03-2.jsonld -- doc/test/RSFC-03-2/RSFC-03-2.ttl -- doc/test/RSFC-03-3 -- doc/test/RSFC-03-3/RSFC-03-3.html -- doc/test/RSFC-03-3/RSFC-03-3.jsonld -- doc/test/RSFC-03-3/RSFC-03-3.ttl -- doc/test/RSFC-03-4 -- doc/test/RSFC-03-4/RSFC-03-4.html -- doc/test/RSFC-03-4/RSFC-03-4.jsonld -- doc/test/RSFC-03-4/RSFC-03-4.ttl -- doc/test/RSFC-03-5 -- doc/test/RSFC-03-5/RSFC-03-5.html -- doc/test/RSFC-03-5/RSFC-03-5.jsonld -- doc/test/RSFC-03-5/RSFC-03-5.ttl -- doc/test/RSFC-03-6 -- doc/test/RSFC-03-6/RSFC-03-6.html -- doc/test/RSFC-03-6/RSFC-03-6.jsonld -- doc/test/RSFC-03-6/RSFC-03-6.ttl -- doc/test/RSFC-04-1 -- doc/test/RSFC-04-1/RSFC-04-1.html -- doc/test/RSFC-04-1/RSFC-04-1.jsonld -- doc/test/RSFC-04-1/RSFC-04-1.ttl -- doc/test/RSFC-04-2 -- doc/test/RSFC-04-2/RSFC-04-2.html -- doc/test/RSFC-04-2/RSFC-04-2.jsonld -- doc/test/RSFC-04-2/RSFC-04-2.ttl -- doc/test/RSFC-04-3 -- doc/test/RSFC-04-3/RSFC-04-3.html -- doc/test/RSFC-04-3/RSFC-04-3.jsonld -- doc/test/RSFC-04-3/RSFC-04-3.ttl -- doc/test/RSFC-04-4 -- doc/test/RSFC-04-4/RSFC-04-4.html -- doc/test/RSFC-04-4/RSFC-04-4.jsonld -- doc/test/RSFC-04-4/RSFC-04-4.ttl -- doc/test/RSFC-04-5 -- doc/test/RSFC-04-5/RSFC-04-5.html -- doc/test/RSFC-04-5/RSFC-04-5.jsonld -- doc/test/RSFC-04-5/RSFC-04-5.ttl -- doc/test/RSFC-05-1 -- doc/test/RSFC-05-1/RSFC-05-1.html -- doc/test/RSFC-05-1/RSFC-05-1.jsonld -- doc/test/RSFC-05-1/RSFC-05-1.ttl -- doc/test/RSFC-05-2 -- doc/test/RSFC-05-2/RSFC-05-2.html -- doc/test/RSFC-05-2/RSFC-05-2.jsonld -- doc/test/RSFC-05-2/RSFC-05-2.ttl -- doc/test/RSFC-05-3 -- doc/test/RSFC-05-3/RSFC-05-3.html -- doc/test/RSFC-05-3/RSFC-05-3.jsonld -- doc/test/RSFC-05-3/RSFC-05-3.ttl -- doc/test/RSFC-06-1 -- doc/test/RSFC-06-1/RSFC-06-1.html -- doc/test/RSFC-06-1/RSFC-06-1.jsonld -- doc/test/RSFC-06-1/RSFC-06-1.ttl -- doc/test/RSFC-06-2 -- doc/test/RSFC-06-2/RSFC-06-2.html -- doc/test/RSFC-06-2/RSFC-06-2.jsonld -- doc/test/RSFC-06-2/RSFC-06-2.ttl -- doc/test/RSFC-06-3 -- doc/test/RSFC-06-3/RSFC-06-3.html -- doc/test/RSFC-06-3/RSFC-06-3.jsonld -- doc/test/RSFC-06-3/RSFC-06-3.ttl -- doc/test/RSFC-06-4 -- doc/test/RSFC-06-4/RSFC-06-4.html -- doc/test/RSFC-06-4/RSFC-06-4.jsonld -- doc/test/RSFC-06-4/RSFC-06-4.ttl -- doc/test/RSFC-07-1 -- doc/test/RSFC-07-1/RSFC-07-1.html -- doc/test/RSFC-07-1/RSFC-07-1.jsonld -- doc/test/RSFC-07-1/RSFC-07-1.ttl -- doc/test/RSFC-07-2 -- doc/test/RSFC-07-2/RSFC-07-2.html -- doc/test/RSFC-07-2/RSFC-07-2.jsonld -- doc/test/RSFC-07-2/RSFC-07-2.ttl -- doc/test/RSFC-08-1 -- doc/test/RSFC-08-1/RSFC-08-1.html -- doc/test/RSFC-08-1/RSFC-08-1.jsonld -- doc/test/RSFC-08-1/RSFC-08-1.ttl -- doc/test/RSFC-09-1 -- doc/test/RSFC-09-1/RSFC-09-1.html -- doc/test/RSFC-09-1/RSFC-09-1.jsonld -- doc/test/RSFC-09-1/RSFC-09-1.ttl -- doc/test/RSFC-12-1 -- doc/test/RSFC-12-1/RSFC-12-1.html -- doc/test/RSFC-12-1/RSFC-12-1.jsonld -- doc/test/RSFC-12-1/RSFC-12-1.ttl -- doc/test/RSFC-13-1 -- doc/test/RSFC-13-1/RSFC-13-1.html -- doc/test/RSFC-13-1/RSFC-13-1.jsonld -- doc/test/RSFC-13-1/RSFC-13-1.ttl -- doc/test/RSFC-13-2 -- doc/test/RSFC-13-2/RSFC-13-2.html -- doc/test/RSFC-13-2/RSFC-13-2.jsonld -- doc/test/RSFC-13-2/RSFC-13-2.ttl -- doc/test/RSFC-13-3 -- doc/test/RSFC-13-3/RSFC-13-3.html -- doc/test/RSFC-13-3/RSFC-13-3.jsonld -- doc/test/RSFC-13-3/RSFC-13-3.ttl -- doc/test/RSFC-13-4 -- doc/test/RSFC-13-4/RSFC-13-4.html -- doc/test/RSFC-13-4/RSFC-13-4.jsonld -- doc/test/RSFC-13-4/RSFC-13-4.ttl -- doc/test/RSFC-14-1 -- doc/test/RSFC-14-1/RSFC-14-1.html -- doc/test/RSFC-14-1/RSFC-14-1.jsonld -- doc/test/RSFC-14-1/RSFC-14-1.ttl -- doc/test/RSFC-14-2 -- doc/test/RSFC-14-2/RSFC-14-2.html -- doc/test/RSFC-14-2/RSFC-14-2.jsonld -- doc/test/RSFC-14-2/RSFC-14-2.ttl -- doc/test/RSFC-15-1 -- doc/test/RSFC-15-1/RSFC-15-1.html -- doc/test/RSFC-15-1/RSFC-15-1.jsonld -- doc/test/RSFC-15-1/RSFC-15-1.ttl -- doc/test/RSFC-15-2 -- doc/test/RSFC-15-2/RSFC-15-2.html -- doc/test/RSFC-15-2/RSFC-15-2.jsonld -- doc/test/RSFC-15-2/RSFC-15-2.ttl -- doc/test/RSFC-16-1 -- doc/test/RSFC-16-1/RSFC-16-1.html -- doc/test/RSFC-16-1/RSFC-16-1.jsonld -- doc/test/RSFC-16-1/RSFC-16-1.ttl -- doc/test/RSFC-17-1 -- doc/test/RSFC-17-1/RSFC-17-1.html -- doc/test/RSFC-17-1/RSFC-17-1.jsonld -- doc/test/RSFC-17-1/RSFC-17-1.ttl -- doc/test/RSFC-17-2 -- doc/test/RSFC-17-2/RSFC-17-2.html -- doc/test/RSFC-17-2/RSFC-17-2.jsonld -- doc/test/RSFC-17-2/RSFC-17-2.ttl -- doc/test/RSFC-17-3 -- doc/test/RSFC-17-3/RSFC-17-3.html -- doc/test/RSFC-17-3/RSFC-17-3.jsonld -- doc/test/RSFC-17-3/RSFC-17-3.ttl -- doc/test/RSFC-18-1 -- doc/test/RSFC-18-1/RSFC-18-1.html -- doc/test/RSFC-18-1/RSFC-18-1.jsonld -- doc/test/RSFC-18-1/RSFC-18-1.ttl -- doc/test/RSFC-19-1 -- doc/test/RSFC-19-1/RSFC-19-1.html -- doc/test/RSFC-19-1/RSFC-19-1.jsonld -- doc/test/RSFC-19-1/RSFC-19-1.ttl -- doc/test/RSFC-20-1 -- doc/test/RSFC-20-1/RSFC-20-1.html -- doc/test/RSFC-20-1/RSFC-20-1.jsonld -- doc/test/RSFC-20-1/RSFC-20-1.ttl -- doc/web_generation_scripts/templates/template_test.html -- doc/web_generation_scripts/test_register.py -- src/rsfc/rsfc_tests -- src/rsfc/rsfc_tests/__init__.py -- src/rsfc/rsfc_tests/rsfc_tests.py -- **Suggestions:** No suggestions +- **Process:** Searches for files and/or directories that mention test in their names. Also, ignores doc and docs directories +- **Evidence:** Files and/or directories that mention test were found at: + - src/rsfc/rsfc_tests + - src/rsfc/rsfc_tests/__init__.py + - src/rsfc/rsfc_tests/rsfc_tests.py +- **Suggestions:** N/A + +### software_is_containerized + + +#### Software is containerized + +- **Test ID:** https://w3id.org/rsfc/test/RSFC-22-1 +- **Result:** true +- **Process:** Searches in the root of the repository for container files such as dockerfile, apptainer, podman, etc. +- **Evidence:** Found container files at: + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/Dockerfile +- **Suggestions:** N/A ### support_issue_tracking @@ -604,8 +626,8 @@ An automated assessment of the rsfc tool based on the EVERSE software quality in - **Test ID:** https://w3id.org/rsfc/test/RSFC-20-1 - **Result:** true - **Process:** Checks if there is an issue tracker in the repository. -- **Evidence:** Found an issue tracker in the repository -- **Suggestions:** No suggestions +- **Evidence:** There is an issue tracking system in the repository but it isn't stated in any of the files +- **Suggestions:** N/A ### version_control_use @@ -615,8 +637,9 @@ An automated assessment of the rsfc tool based on the EVERSE software quality in - **Test ID:** https://w3id.org/rsfc/test/RSFC-05-1 - **Result:** true - **Process:** Searches for a repo status badge in the README file of the repository -- **Evidence:** A repo status badge was found in the repository -- **Suggestions:** No suggestions +- **Evidence:** A repo status badge was found in: + - https://raw.githubusercontent.com/oeg-upm/rsfc/main/README.md +- **Suggestions:** N/A #### Repository is from Github/Gitlab @@ -625,16 +648,7 @@ An automated assessment of the rsfc tool based on the EVERSE software quality in - **Result:** true - **Process:** Checks if the URL provided is indeed a Github or Gitlab repository - **Evidence:** URL provided is a Github or Gitlab repository -- **Suggestions:** No suggestions - - -#### Repository active - -- **Test ID:** https://w3id.org/rsfc/test/RSFC-17-1 -- **Result:** true -- **Process:** Checks if there is a repo_status badge with value Active and if there are commits in the repository -- **Evidence:** Repository is enabled and has commits -- **Suggestions:** No suggestions +- **Suggestions:** N/A #### Commit history @@ -642,17 +656,18 @@ An automated assessment of the rsfc tool based on the EVERSE software quality in - **Test ID:** https://w3id.org/rsfc/test/RSFC-17-2 - **Result:** true - **Process:** Checks if the software repository has a commits history -- **Evidence:** Commits were found in the repository -- **Suggestions:** No suggestions +- **Evidence:** A commit history was found in: + - https://api.github.com/repos/oeg-upm/rsfc/commits?sha=main&since=2026-03-31T07:12:29.604180+00:00&per_page=100 +- **Suggestions:** N/A #### Commits are linked to issues - **Test ID:** https://w3id.org/rsfc/test/RSFC-17-3 -- **Result:** true +- **Result:** false - **Process:** Checks if there is at least one of the existing issues (opened or closed) referenced in any of the commits made in the default branch of the repository -- **Evidence:** There is at least one commit linked to an issue -- **Suggestions:** No suggestions +- **Evidence:** There is not any commits linked to any issues in the repository +- **Suggestions:** It is good practice to indicate in your commits which issues you are targeting or solving ### versioning_standards_use @@ -663,4 +678,4 @@ An automated assessment of the rsfc tool based on the EVERSE software quality in - **Result:** true - **Process:** Checks if all of the releases versions follow the SemVer or CalVer versioning standards - **Evidence:** All of the releases follow a versioning standard -- **Suggestions:** No suggestions +- **Suggestions:** N/A diff --git a/codemeta.json b/codemeta.json index d7c10cd..49b1207 100644 --- a/codemeta.json +++ b/codemeta.json @@ -48,6 +48,6 @@ "operatingSystem": "Linux", "programmingLanguage": "Python", "relatedLink": "https://github.com/EVERSE-ResearchSoftware/indicators", - "version": "0.1.5", + "version": "0.1.6", "developmentStatus": "wip" } \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index 9dbf2d8..5688157 100644 --- a/poetry.lock +++ b/poetry.lock @@ -591,28 +591,32 @@ all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2 [[package]] name = "imbalanced-learn" -version = "0.12.4" -description = "Toolbox for imbalanced dataset in machine learning." +version = "0.14.1" +description = "Toolbox for imbalanced dataset in machine learning" optional = false -python-versions = "*" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "imbalanced-learn-0.12.4.tar.gz", hash = "sha256:8153ba385d296b07d97e0901a2624a86c06b48c94c2f92da3a5354827697b7a3"}, - {file = "imbalanced_learn-0.12.4-py3-none-any.whl", hash = "sha256:d47fc599160d3ea882e712a3a6b02bdd353c1a6436d8d68d41b1922e6ee4a703"}, + {file = "imbalanced_learn-0.14.1-py3-none-any.whl", hash = "sha256:fcdff8d27870d6992ea3496230788b97ff98e24302e7f6c598701da525ae440f"}, + {file = "imbalanced_learn-0.14.1.tar.gz", hash = "sha256:46eeb5773a96b6fa92426356da66f3e4390a7ed8e715a633c6fb68ee4a3ccdcb"}, ] [package.dependencies] -joblib = ">=1.1.1" -numpy = ">=1.17.3" -scikit-learn = ">=1.0.2" -scipy = ">=1.5.0" -threadpoolctl = ">=2.0.0" +joblib = ">=1.2.0,<2" +numpy = ">=1.25.2,<3" +scikit-learn = ">=1.4.2,<2" +scipy = ">=1.11.4,<2" +sklearn-compat = ">=0.1.5,<0.2" +threadpoolctl = ">=2.0.0,<4" [package.extras] -docs = ["keras (>=2.4.3)", "matplotlib (>=3.1.2)", "memory-profiler (>=0.57.0)", "numpydoc (>=1.5.0)", "pandas (>=1.0.5)", "pydata-sphinx-theme (>=0.13.3)", "seaborn (>=0.9.0)", "sphinx (>=6.0.0)", "sphinx-copybutton (>=0.5.2)", "sphinx-design (>=0.5.0)", "sphinx-gallery (>=0.13.0)", "sphinxcontrib-bibtex (>=2.4.1)", "tensorflow (>=2.4.3)"] -examples = ["keras (>=2.4.3)", "matplotlib (>=3.1.2)", "pandas (>=1.0.5)", "seaborn (>=0.9.0)", "tensorflow (>=2.4.3)"] -optional = ["keras (>=2.4.3)", "pandas (>=1.0.5)", "tensorflow (>=2.4.3)"] -tests = ["black (>=23.3.0)", "flake8 (>=3.8.2)", "keras (>=2.4.3)", "mypy (>=1.3.0)", "pandas (>=1.0.5)", "pytest (>=5.0.1)", "pytest-cov (>=2.9.0)", "tensorflow (>=2.4.3)"] +dev = ["ipykernel", "ipython", "jupyterlab"] +docs = ["matplotlib (>=3.7.3,<4)", "memory_profiler (>=0.61.0,<1)", "numpydoc (>=1.5.0,<2)", "pandas (>=2.0.3,<3)", "pydata-sphinx-theme (>=0.15.4,<1)", "seaborn (>=0.12.2,<1)", "sphinx (>=8.0.2,<9)", "sphinx-copybutton (>=0.5.2,<1)", "sphinx-design (>=0.6.1,<1)", "sphinx-gallery (>=0.13.0,<1)", "sphinxcontrib-bibtex (>=2.6.3,<3)", "tensorflow (>=2.16.1,<3)"] +keras = ["keras (>=3.3.3,<4)"] +linters = ["black (==23.3.0)", "pre-commit", "ruff (==0.14.2)"] +optional = ["pandas (>=2.0.3,<3)"] +tensorflow = ["tensorflow (>=2.16.1,<3)"] +tests = ["packaging (>=23.2,<25)", "pytest (>=7.2.2,<9)", "pytest-cov (>=4.1.0,<6)", "pytest-xdist (>=3.5.0,<4)"] [[package]] name = "inflect" @@ -842,97 +846,153 @@ files = [ [[package]] name = "lxml" -version = "5.1.0" +version = "6.1.0" description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" groups = ["main"] files = [ - {file = "lxml-5.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:704f5572ff473a5f897745abebc6df40f22d4133c1e0a1f124e4f2bd3330ff7e"}, - {file = "lxml-5.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9d3c0f8567ffe7502d969c2c1b809892dc793b5d0665f602aad19895f8d508da"}, - {file = "lxml-5.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5fcfbebdb0c5d8d18b84118842f31965d59ee3e66996ac842e21f957eb76138c"}, - {file = "lxml-5.1.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f37c6d7106a9d6f0708d4e164b707037b7380fcd0b04c5bd9cae1fb46a856fb"}, - {file = "lxml-5.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2befa20a13f1a75c751f47e00929fb3433d67eb9923c2c0b364de449121f447c"}, - {file = "lxml-5.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22b7ee4c35f374e2c20337a95502057964d7e35b996b1c667b5c65c567d2252a"}, - {file = "lxml-5.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:bf8443781533b8d37b295016a4b53c1494fa9a03573c09ca5104550c138d5c05"}, - {file = "lxml-5.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:82bddf0e72cb2af3cbba7cec1d2fd11fda0de6be8f4492223d4a268713ef2147"}, - {file = "lxml-5.1.0-cp310-cp310-win32.whl", hash = "sha256:b66aa6357b265670bb574f050ffceefb98549c721cf28351b748be1ef9577d93"}, - {file = "lxml-5.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:4946e7f59b7b6a9e27bef34422f645e9a368cb2be11bf1ef3cafc39a1f6ba68d"}, - {file = "lxml-5.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:14deca1460b4b0f6b01f1ddc9557704e8b365f55c63070463f6c18619ebf964f"}, - {file = "lxml-5.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ed8c3d2cd329bf779b7ed38db176738f3f8be637bb395ce9629fc76f78afe3d4"}, - {file = "lxml-5.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:436a943c2900bb98123b06437cdd30580a61340fbdb7b28aaf345a459c19046a"}, - {file = "lxml-5.1.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:acb6b2f96f60f70e7f34efe0c3ea34ca63f19ca63ce90019c6cbca6b676e81fa"}, - {file = "lxml-5.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:af8920ce4a55ff41167ddbc20077f5698c2e710ad3353d32a07d3264f3a2021e"}, - {file = "lxml-5.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cfced4a069003d8913408e10ca8ed092c49a7f6cefee9bb74b6b3e860683b45"}, - {file = "lxml-5.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9e5ac3437746189a9b4121db2a7b86056ac8786b12e88838696899328fc44bb2"}, - {file = "lxml-5.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f4c9bda132ad108b387c33fabfea47866af87f4ea6ffb79418004f0521e63204"}, - {file = "lxml-5.1.0-cp311-cp311-win32.whl", hash = "sha256:bc64d1b1dab08f679fb89c368f4c05693f58a9faf744c4d390d7ed1d8223869b"}, - {file = "lxml-5.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:a5ab722ae5a873d8dcee1f5f45ddd93c34210aed44ff2dc643b5025981908cda"}, - {file = "lxml-5.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9aa543980ab1fbf1720969af1d99095a548ea42e00361e727c58a40832439114"}, - {file = "lxml-5.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6f11b77ec0979f7e4dc5ae081325a2946f1fe424148d3945f943ceaede98adb8"}, - {file = "lxml-5.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a36c506e5f8aeb40680491d39ed94670487ce6614b9d27cabe45d94cd5d63e1e"}, - {file = "lxml-5.1.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f643ffd2669ffd4b5a3e9b41c909b72b2a1d5e4915da90a77e119b8d48ce867a"}, - {file = "lxml-5.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16dd953fb719f0ffc5bc067428fc9e88f599e15723a85618c45847c96f11f431"}, - {file = "lxml-5.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16018f7099245157564d7148165132c70adb272fb5a17c048ba70d9cc542a1a1"}, - {file = "lxml-5.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:82cd34f1081ae4ea2ede3d52f71b7be313756e99b4b5f829f89b12da552d3aa3"}, - {file = "lxml-5.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:19a1bc898ae9f06bccb7c3e1dfd73897ecbbd2c96afe9095a6026016e5ca97b8"}, - {file = "lxml-5.1.0-cp312-cp312-win32.whl", hash = "sha256:13521a321a25c641b9ea127ef478b580b5ec82aa2e9fc076c86169d161798b01"}, - {file = "lxml-5.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:1ad17c20e3666c035db502c78b86e58ff6b5991906e55bdbef94977700c72623"}, - {file = "lxml-5.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:24ef5a4631c0b6cceaf2dbca21687e29725b7c4e171f33a8f8ce23c12558ded1"}, - {file = "lxml-5.1.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8d2900b7f5318bc7ad8631d3d40190b95ef2aa8cc59473b73b294e4a55e9f30f"}, - {file = "lxml-5.1.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:601f4a75797d7a770daed8b42b97cd1bb1ba18bd51a9382077a6a247a12aa38d"}, - {file = "lxml-5.1.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4b68c961b5cc402cbd99cca5eb2547e46ce77260eb705f4d117fd9c3f932b95"}, - {file = "lxml-5.1.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:afd825e30f8d1f521713a5669b63657bcfe5980a916c95855060048b88e1adb7"}, - {file = "lxml-5.1.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:262bc5f512a66b527d026518507e78c2f9c2bd9eb5c8aeeb9f0eb43fcb69dc67"}, - {file = "lxml-5.1.0-cp36-cp36m-win32.whl", hash = "sha256:e856c1c7255c739434489ec9c8aa9cdf5179785d10ff20add308b5d673bed5cd"}, - {file = "lxml-5.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:c7257171bb8d4432fe9d6fdde4d55fdbe663a63636a17f7f9aaba9bcb3153ad7"}, - {file = "lxml-5.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b9e240ae0ba96477682aa87899d94ddec1cc7926f9df29b1dd57b39e797d5ab5"}, - {file = "lxml-5.1.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a96f02ba1bcd330807fc060ed91d1f7a20853da6dd449e5da4b09bfcc08fdcf5"}, - {file = "lxml-5.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e3898ae2b58eeafedfe99e542a17859017d72d7f6a63de0f04f99c2cb125936"}, - {file = "lxml-5.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61c5a7edbd7c695e54fca029ceb351fc45cd8860119a0f83e48be44e1c464862"}, - {file = "lxml-5.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3aeca824b38ca78d9ee2ab82bd9883083d0492d9d17df065ba3b94e88e4d7ee6"}, - {file = "lxml-5.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8f52fe6859b9db71ee609b0c0a70fea5f1e71c3462ecf144ca800d3f434f0764"}, - {file = "lxml-5.1.0-cp37-cp37m-win32.whl", hash = "sha256:d42e3a3fc18acc88b838efded0e6ec3edf3e328a58c68fbd36a7263a874906c8"}, - {file = "lxml-5.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:eac68f96539b32fce2c9b47eb7c25bb2582bdaf1bbb360d25f564ee9e04c542b"}, - {file = "lxml-5.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ae15347a88cf8af0949a9872b57a320d2605ae069bcdf047677318bc0bba45b1"}, - {file = "lxml-5.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c26aab6ea9c54d3bed716b8851c8bfc40cb249b8e9880e250d1eddde9f709bf5"}, - {file = "lxml-5.1.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:342e95bddec3a698ac24378d61996b3ee5ba9acfeb253986002ac53c9a5f6f84"}, - {file = "lxml-5.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:725e171e0b99a66ec8605ac77fa12239dbe061482ac854d25720e2294652eeaa"}, - {file = "lxml-5.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d184e0d5c918cff04cdde9dbdf9600e960161d773666958c9d7b565ccc60c45"}, - {file = "lxml-5.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:98f3f020a2b736566c707c8e034945c02aa94e124c24f77ca097c446f81b01f1"}, - {file = "lxml-5.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d48fc57e7c1e3df57be5ae8614bab6d4e7b60f65c5457915c26892c41afc59e"}, - {file = "lxml-5.1.0-cp38-cp38-win32.whl", hash = "sha256:7ec465e6549ed97e9f1e5ed51c657c9ede767bc1c11552f7f4d022c4df4a977a"}, - {file = "lxml-5.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:b21b4031b53d25b0858d4e124f2f9131ffc1530431c6d1321805c90da78388d1"}, - {file = "lxml-5.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:52427a7eadc98f9e62cb1368a5079ae826f94f05755d2d567d93ee1bc3ceb354"}, - {file = "lxml-5.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6a2a2c724d97c1eb8cf966b16ca2915566a4904b9aad2ed9a09c748ffe14f969"}, - {file = "lxml-5.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:843b9c835580d52828d8f69ea4302537337a21e6b4f1ec711a52241ba4a824f3"}, - {file = "lxml-5.1.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9b99f564659cfa704a2dd82d0684207b1aadf7d02d33e54845f9fc78e06b7581"}, - {file = "lxml-5.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f8b0c78e7aac24979ef09b7f50da871c2de2def043d468c4b41f512d831e912"}, - {file = "lxml-5.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9bcf86dfc8ff3e992fed847c077bd875d9e0ba2fa25d859c3a0f0f76f07f0c8d"}, - {file = "lxml-5.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:49a9b4af45e8b925e1cd6f3b15bbba2c81e7dba6dce170c677c9cda547411e14"}, - {file = "lxml-5.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:280f3edf15c2a967d923bcfb1f8f15337ad36f93525828b40a0f9d6c2ad24890"}, - {file = "lxml-5.1.0-cp39-cp39-win32.whl", hash = "sha256:ed7326563024b6e91fef6b6c7a1a2ff0a71b97793ac33dbbcf38f6005e51ff6e"}, - {file = "lxml-5.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:8d7b4beebb178e9183138f552238f7e6613162a42164233e2bda00cb3afac58f"}, - {file = "lxml-5.1.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9bd0ae7cc2b85320abd5e0abad5ccee5564ed5f0cc90245d2f9a8ef330a8deae"}, - {file = "lxml-5.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8c1d679df4361408b628f42b26a5d62bd3e9ba7f0c0e7969f925021554755aa"}, - {file = "lxml-5.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2ad3a8ce9e8a767131061a22cd28fdffa3cd2dc193f399ff7b81777f3520e372"}, - {file = "lxml-5.1.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:304128394c9c22b6569eba2a6d98392b56fbdfbad58f83ea702530be80d0f9df"}, - {file = "lxml-5.1.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d74fcaf87132ffc0447b3c685a9f862ffb5b43e70ea6beec2fb8057d5d2a1fea"}, - {file = "lxml-5.1.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:8cf5877f7ed384dabfdcc37922c3191bf27e55b498fecece9fd5c2c7aaa34c33"}, - {file = "lxml-5.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:877efb968c3d7eb2dad540b6cabf2f1d3c0fbf4b2d309a3c141f79c7e0061324"}, - {file = "lxml-5.1.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f14a4fb1c1c402a22e6a341a24c1341b4a3def81b41cd354386dcb795f83897"}, - {file = "lxml-5.1.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:25663d6e99659544ee8fe1b89b1a8c0aaa5e34b103fab124b17fa958c4a324a6"}, - {file = "lxml-5.1.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8b9f19df998761babaa7f09e6bc169294eefafd6149aaa272081cbddc7ba4ca3"}, - {file = "lxml-5.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e53d7e6a98b64fe54775d23a7c669763451340c3d44ad5e3a3b48a1efbdc96f"}, - {file = "lxml-5.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c3cd1fc1dc7c376c54440aeaaa0dcc803d2126732ff5c6b68ccd619f2e64be4f"}, - {file = "lxml-5.1.0.tar.gz", hash = "sha256:3eea6ed6e6c918e468e693c41ef07f3c3acc310b70ddd9cc72d9ef84bc9564ca"}, + {file = "lxml-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:41dcc4c7b10484257cbd6c37b83ddb26df2b0e5aff5ac00d095689015af868ec"}, + {file = "lxml-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a31286dbb5e74c8e9a5344465b77ab4c5bd511a253b355b5ca2fae7e579fafec"}, + {file = "lxml-6.1.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1bc4cc83fb7f66ffb16f74d6dd0162e144333fc36ebcce32246f80c8735b2551"}, + {file = "lxml-6.1.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:20cf4d0651987c906a2f5cba4e3a8d6ba4bfdf973cfe2a96c0d6053888ea2ecd"}, + {file = "lxml-6.1.0-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ffb34ea45a82dd637c2c97ae1bbb920850c1e59bcae79ce1c15af531d83e7215"}, + {file = "lxml-6.1.0-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a1d9b99e5b2597e4f5aed2484fef835256fa1b68a19e4265c97628ef4bf8bcf4"}, + {file = "lxml-6.1.0-cp310-cp310-manylinux_2_28_i686.whl", hash = "sha256:d43aa26dcda363f21e79afa0668f5029ed7394b3bb8c92a6927a3d34e8b610ea"}, + {file = "lxml-6.1.0-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:6262b87f9e5c1e5fe501d6c153247289af42eb44ad7660b9b3de17baaf92d6f6"}, + {file = "lxml-6.1.0-cp310-cp310-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d1392c569c032f78a11a25d1de1c43fff13294c793b39e19d84fade3045cbbc3"}, + {file = "lxml-6.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:045e387d1f4f42a418380930fa3f45c73c9b392faf67e495e58902e68e8f44a7"}, + {file = "lxml-6.1.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:9f93d5b8b07f73e8c77e3c6556a3db269918390c804b5e5fcdd4858232cc8f16"}, + {file = "lxml-6.1.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:de550d129f18d8ab819651ffe4f38b1b713c7e116707de3c0c6400d0ef34fbc1"}, + {file = "lxml-6.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c08da09dc003c9e8c70e06b53a11db6fb3b250c21c4236b03c7d7b443c318e7a"}, + {file = "lxml-6.1.0-cp310-cp310-win32.whl", hash = "sha256:37448bf9c7d7adfc5254763901e2bbd6bb876228dfc1fc7f66e58c06368a7544"}, + {file = "lxml-6.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:2593a0a6621545b9095b71ad74ed4226eba438a7d9fc3712a99bdb15508cf93a"}, + {file = "lxml-6.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:e80807d72f96b96ad5588cb85c75616e4f2795a7737d4630784c51497beb7776"}, + {file = "lxml-6.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cec05be8c876f92a5aa07b01d60bbb4d11cfbdd654cad0561c0d7b5c043a61b9"}, + {file = "lxml-6.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9c03e048b6ce8e77b09c734e931584894ecd58d08296804ca2d0b184c933ce50"}, + {file = "lxml-6.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:942454ff253da14218f972b23dc72fa4edf6c943f37edd19cd697618b626fac5"}, + {file = "lxml-6.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d036ee7b99d5148072ac7c9b847193decdfeac633db350363f7bce4fff108f0e"}, + {file = "lxml-6.1.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ae5d8d5427f3cc317e7950f2da7ad276df0cfa37b8de2f5658959e618ea8512"}, + {file = "lxml-6.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:363e47283bde87051b821826e71dde47f107e08614e1aa312ba0c5711e77738c"}, + {file = "lxml-6.1.0-cp311-cp311-manylinux_2_28_i686.whl", hash = "sha256:f504d861d9f2a8f94020130adac88d66de93841707a23a86244263d1e54682f5"}, + {file = "lxml-6.1.0-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:23a5dc68e08ed13331d61815c08f260f46b4a60fdd1640bbeb82cf89a9d90289"}, + {file = "lxml-6.1.0-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f15401d8d3dbf239e23c818afc10c7207f7b95f9a307e092122b6f86dd43209a"}, + {file = "lxml-6.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fcf3da95e93349e0647d48d4b36a12783105bcc74cb0c416952f9988410846a3"}, + {file = "lxml-6.1.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:0d082495c5fcf426e425a6e28daaba1fcb6d8f854a4ff01effb1f1f381203eb9"}, + {file = "lxml-6.1.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:e3c4f84b24a1fcba435157d111c4b755099c6ff00a3daee1ad281817de75ed11"}, + {file = "lxml-6.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:976a6b39b1b13e8c354ad8d3f261f3a4ac6609518af91bdb5094760a08f132c4"}, + {file = "lxml-6.1.0-cp311-cp311-win32.whl", hash = "sha256:857efde87d365706590847b916baff69c0bc9252dc5af030e378c9800c0b10e3"}, + {file = "lxml-6.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:183bfb45a493081943be7ea2b5adfc2b611e1cf377cefa8b8a8be404f45ef9a7"}, + {file = "lxml-6.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:19f4164243fc206d12ed3d866e80e74f5bc3627966520da1a5f97e42c32a3f39"}, + {file = "lxml-6.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d2f17a16cd8751e8eb233a7e41aecdf8e511712e00088bf9be455f604cd0d28d"}, + {file = "lxml-6.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f0cea5b1d3e6e77d71bd2b9972eb2446221a69dc52bb0b9c3c6f6e5700592d93"}, + {file = "lxml-6.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fc46da94826188ed45cb53bd8e3fc076ae22675aea2087843d4735627f867c6d"}, + {file = "lxml-6.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9147d8e386ec3b82c3b15d88927f734f565b0aaadef7def562b853adca45784a"}, + {file = "lxml-6.1.0-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5715e0e28736a070f3f34a7ccc09e2fdcba0e3060abbcf61a1a5718ff6d6b105"}, + {file = "lxml-6.1.0-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4937460dc5df0cdd2f06a86c285c28afda06aefa3af949f9477d3e8df430c485"}, + {file = "lxml-6.1.0-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bc783ee3147e60a25aa0445ea82b3e8aabb83b240f2b95d32cb75587ff781814"}, + {file = "lxml-6.1.0-cp312-cp312-manylinux_2_28_i686.whl", hash = "sha256:40d9189f80075f2e1f88db21ef815a2b17b28adf8e50aaf5c789bfe737027f32"}, + {file = "lxml-6.1.0-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:05b9b8787e35bec69e68daf4952b2e6dfcfb0db7ecf1a06f8cdfbbac4eb71aad"}, + {file = "lxml-6.1.0-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0f0f08beb0182e3e9a86fae124b3c47a7b41b7b69b225e1377db983802404e54"}, + {file = "lxml-6.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73becf6d8c81d4c76b1014dbd3584cb26d904492dcf73ca85dc8bff08dcd6d2d"}, + {file = "lxml-6.1.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1ae225f66e5938f4fa29d37e009a3bb3b13032ac57eb4eb42afa44f6e4054e69"}, + {file = "lxml-6.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:690022c7fae793b0489aa68a658822cea83e0d5933781811cabbf5ea3bcfe73d"}, + {file = "lxml-6.1.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:63aeafc26aac0be8aff14af7871249e87ea1319be92090bfd632ec68e03b16a5"}, + {file = "lxml-6.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:264c605ab9c0e4aa1a679636f4582c4d3313700009fac3ec9c3412ed0d8f3e1d"}, + {file = "lxml-6.1.0-cp312-cp312-win32.whl", hash = "sha256:56971379bc5ee8037c5a0f09fa88f66cdb7d37c3e38af3e45cf539f41131ac1f"}, + {file = "lxml-6.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:bba078de0031c219e5dd06cf3e6bf8fb8e6e64a77819b358f53bb132e3e03366"}, + {file = "lxml-6.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:c3592631e652afa34999a088f98ba7dfc7d6aff0d535c410bea77a71743f3819"}, + {file = "lxml-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a0092f2b107b69601adf562a57c956fbb596e05e3e6651cabd3054113b007e45"}, + {file = "lxml-6.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fc7140d7a7386e6b545d41b7358f4d02b656d4053f5fa6859f92f4b9c2572c4d"}, + {file = "lxml-6.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:419c58fc92cc3a2c3fa5f78c63dbf5da70c1fa9c1b25f25727ecee89a96c7de2"}, + {file = "lxml-6.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:37fabd1452852636cf38ecdcc9dd5ca4bba7a35d6c53fa09725deeb894a87491"}, + {file = "lxml-6.1.0-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2853c8b2170cc6cd54a6b4d50d2c1a8a7aeca201f23804b4898525c7a152cfc"}, + {file = "lxml-6.1.0-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8e369cbd690e788c8d15e56222d91a09c6a417f49cbc543040cba0fe2e25a79e"}, + {file = "lxml-6.1.0-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e69aa6805905807186eb00e66c6d97a935c928275182eb02ee40ba00da9623b2"}, + {file = "lxml-6.1.0-cp313-cp313-manylinux_2_28_i686.whl", hash = "sha256:4bd1bdb8a9e0e2dd229de19b5f8aebac80e916921b4b2c6ef8a52bc131d0c1f9"}, + {file = "lxml-6.1.0-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:cbd7b79cdcb4986ad78a2662625882747f09db5e4cd7b2ae178a88c9c51b3dfe"}, + {file = "lxml-6.1.0-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:43e4d297f11080ec9d64a4b1ad7ac02b4484c9f0e2179d9c4ef78e886e747b88"}, + {file = "lxml-6.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cc16682cc987a3da00aa56a3aa3075b08edb10d9b1e476938cfdbee8f3b67181"}, + {file = "lxml-6.1.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:d6d8efe71429635f0559579092bb5e60560d7b9115ee38c4adbea35632e7fa24"}, + {file = "lxml-6.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7e39ab3a28af7784e206d8606ec0e4bcad0190f63a492bca95e94e5a4aef7f6e"}, + {file = "lxml-6.1.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:9eb667bf50856c4a58145f8ca2d5e5be160191e79eb9e30855a476191b3c3495"}, + {file = "lxml-6.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7f4a77d6f7edf9230cee3e1f7f6764722a41604ee5681844f18db9a81ea0ec33"}, + {file = "lxml-6.1.0-cp313-cp313-win32.whl", hash = "sha256:28902146ffbe5222df411c5d19e5352490122e14447e98cd118907ee3fd6ee62"}, + {file = "lxml-6.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:4a1503c56e4e2b38dc76f2f2da7bae69670c0f1933e27cfa34b2fa5876410b16"}, + {file = "lxml-6.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:e0af85773850417d994d019741239b901b22c6680206f46a34766926e466141d"}, + {file = "lxml-6.1.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:ab863fd37458fed6456525f297d21239d987800c46e67da5ef04fc6b3dd93ac8"}, + {file = "lxml-6.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6fd8b1df8254ff4fd93fd31da1fc15770bde23ac045be9bb1f87425702f61cc9"}, + {file = "lxml-6.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:47024feaae386a92a146af0d2aeed65229bf6fff738e6a11dda6b0015fb8fd03"}, + {file = "lxml-6.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3f00972f84450204cd5d93a5395965e348956aaceaadec693a22ec743f8ae3eb"}, + {file = "lxml-6.1.0-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97faa0860e13b05b15a51fb4986421ef7a30f0b3334061c416e0981e9450ca4c"}, + {file = "lxml-6.1.0-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:972a6451204798675407beaad97b868d0c733d9a74dafefc63120b81b8c2de28"}, + {file = "lxml-6.1.0-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fe022f20bc4569ec66b63b3fb275a3d628d9d32da6326b2982584104db6d3086"}, + {file = "lxml-6.1.0-cp314-cp314-manylinux_2_28_i686.whl", hash = "sha256:75c4c7c619a744f972f4451bf5adf6d0fb00992a1ffc9fd78e13b0bc817cc99f"}, + {file = "lxml-6.1.0-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:3648f20d25102a22b6061c688beb3a805099ea4beb0a01ce62975d926944d292"}, + {file = "lxml-6.1.0-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:77b9f99b17cbf14026d1e618035077060fc7195dd940d025149f3e2e830fbfcb"}, + {file = "lxml-6.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:32662519149fd7a9db354175aa5e417d83485a8039b8aaa62f873ceee7ea4cad"}, + {file = "lxml-6.1.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:73d658216fc173cf2c939e90e07b941c5e12736b0bf6a99e7af95459cfe8eabb"}, + {file = "lxml-6.1.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ac4db068889f8772a4a698c5980ec302771bb545e10c4b095d4c8be26749616f"}, + {file = "lxml-6.1.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:45e9dfbd1b661eb64ba0d4dbe762bd210c42d86dd1e5bd2bdf89d634231beb43"}, + {file = "lxml-6.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:89e8d73d09ac696a5ba42ec69787913d53284f12092f651506779314f10ba585"}, + {file = "lxml-6.1.0-cp314-cp314-win32.whl", hash = "sha256:ebe33f4ec1b2de38ceb225a1749a2965855bffeef435ba93cd2d5d540783bf2f"}, + {file = "lxml-6.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:398443df51c538bd578529aa7e5f7afc6c292644174b47961f3bf87fe5741120"}, + {file = "lxml-6.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:8c8984e1d8c4b3949e419158fda14d921ff703a9ed8a47236c6eb7a2b6cb4946"}, + {file = "lxml-6.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1081dd10bc6fa437db2500e13993abf7cc30716d0a2f40e65abb935f02ec559c"}, + {file = "lxml-6.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:dabecc48db5f42ba348d1f5d5afdc54c6c4cc758e676926c7cd327045749517d"}, + {file = "lxml-6.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e3dd5fe19c9e0ac818a9c7f132a5e43c1339ec1cbbfecb1a938bd3a47875b7c9"}, + {file = "lxml-6.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9e7b0a4ca6dcc007a4cef00a761bba2dea959de4bd2df98f926b33c92ca5dfb9"}, + {file = "lxml-6.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d27bbe326c6b539c64b42638b18bc6003a8d88f76213a97ac9ed4f885efeab7"}, + {file = "lxml-6.1.0-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4e425db0c5445ef0ad56b0eec54f89b88b2d884656e536a90b2f52aecb4ca86"}, + {file = "lxml-6.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4b89b098105b8599dc57adac95d1813409ac476d3c948a498775d3d0c6124bfb"}, + {file = "lxml-6.1.0-cp314-cp314t-manylinux_2_28_i686.whl", hash = "sha256:c4a699432846df86cc3de502ee85f445ebad748a1c6021d445f3e514d2cd4b1c"}, + {file = "lxml-6.1.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:30e7b2ed63b6c8e97cca8af048589a788ab5c9c905f36d9cf1c2bb549f450d2f"}, + {file = "lxml-6.1.0-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:022981127642fe19866d2907d76241bb07ed21749601f727d5d5dd1ce5d1b773"}, + {file = "lxml-6.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:23cad0cc86046d4222f7f418910e46b89971c5a45d3c8abfad0f64b7b05e4a9b"}, + {file = "lxml-6.1.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:21c3302068f50d1e8728c67c87ba92aa87043abee517aa2576cca1855326b405"}, + {file = "lxml-6.1.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:be10838781cb3be19251e276910cd508fe127e27c3242e50521521a0f3781690"}, + {file = "lxml-6.1.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2173a7bffe97667bbf0767f8a99e587740a8c56fdf3befac4b09cb29a80276fd"}, + {file = "lxml-6.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c6854e9cf99c84beb004eecd7d3a3868ef1109bf2b1df92d7bc11e96a36c2180"}, + {file = "lxml-6.1.0-cp314-cp314t-win32.whl", hash = "sha256:00750d63ef0031a05331b9223463b1c7c02b9004cef2346a5b2877f0f9494dd2"}, + {file = "lxml-6.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:80410c3a7e3c617af04de17caa9f9f20adaa817093293d69eae7d7d0522836f5"}, + {file = "lxml-6.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:26dd9f57ee3bd41e7d35b4c98a2ffd89ed11591649f421f0ec19f67d50ec67ac"}, + {file = "lxml-6.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b6c2f225662bc5ad416bdd06f72ca301b31b39ce4261f0e0097017fc2891b940"}, + {file = "lxml-6.1.0-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a86f06f059e22a0d574990ee2df24ede03f7f3c68c1336293eee9536c4c776cd"}, + {file = "lxml-6.1.0-cp38-cp38-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:468479e52ecf3ec23799c863336d02c05fc2f7ffd1a1424eeeb9a28d4eb69d13"}, + {file = "lxml-6.1.0-cp38-cp38-manylinux_2_28_i686.whl", hash = "sha256:a02ca8fe48815bddcfca3248efe54451abb9dbf2f7d1c5744c8aa4142d476919"}, + {file = "lxml-6.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:bb40648d96157f9081886defe13eac99253e663be969ff938a9289eff6e47b72"}, + {file = "lxml-6.1.0-cp38-cp38-win32.whl", hash = "sha256:1dd6a1c3ad4cb674f44525d9957f3e9c209bb6dd9213245195167a281fcc2bdc"}, + {file = "lxml-6.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:4e2c54d6b47361d0f1d3bc8d4e082ad87201e56ccdcca4d3b9ee3644ff595ec8"}, + {file = "lxml-6.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:920354904d1cb86577d4b3cfe2830c2dbe81d6f4449e57ada428f1609b5985f7"}, + {file = "lxml-6.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c871299c595ee004d186f61840f0bfc4941aa3f17c8ba4a565ead7e4f4f820ee"}, + {file = "lxml-6.1.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d0d799ff958655781296ec870d5e2448e75150da2b3d07f13ff5b0c2c35beefd"}, + {file = "lxml-6.1.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7ba11752e346bd804ea312ec2eea2532dfa8b8d3261d81a32ef9e6ab16256280"}, + {file = "lxml-6.1.0-cp39-cp39-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:26c5272c6a4bf4cf32d3f5a7890c942b0e04438691157d341616d02cca74d4bd"}, + {file = "lxml-6.1.0-cp39-cp39-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c53fa3a5a52122d590e847a57ccf955557b9634a7f99ff5a35131321b0a85317"}, + {file = "lxml-6.1.0-cp39-cp39-manylinux_2_28_i686.whl", hash = "sha256:76b958b4ea3104483c20f74866d55aa056546e15ebe83dd7aecd63698f43b755"}, + {file = "lxml-6.1.0-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:8c11b984b5ce6add4dccc7144c7be5d364d298f15b0c6a57da1991baedc750ce"}, + {file = "lxml-6.1.0-cp39-cp39-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d3829a6e6fd550a219564912d4002c537f65da4c6ae4e093cc34462f4fa027ad"}, + {file = "lxml-6.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:52b0ac6903cf74ebf997eb8c682d2fbac7d1ab7e4c552413eec55868a9b73f39"}, + {file = "lxml-6.1.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:29f5c00cb7d752bce2c70ebd2d31b0a42f9499ffdd3ecb2f31a5b73ee43031ad"}, + {file = "lxml-6.1.0-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:c748ebcb6877de89f48ab90ca96642ac458fff5dec291a2b9337cd4d0934e383"}, + {file = "lxml-6.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:08950a23f296b3f83521577274e3d3b0f3d739bf2e68d01a752e4288bc50d286"}, + {file = "lxml-6.1.0-cp39-cp39-win32.whl", hash = "sha256:11a873c77a181b4fef9c2e357d08ed399542c2af1390101da66720a19c7c9618"}, + {file = "lxml-6.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:81ff55c70b67d19d52b6fd118a114c0a4c97d799cd3089ff9bd9e2ff4b414ee2"}, + {file = "lxml-6.1.0-cp39-cp39-win_arm64.whl", hash = "sha256:481d6e2104285d9add34f41b42b247b76b61c5b5c26c303c2e9707bbf8bd9a64"}, + {file = "lxml-6.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:546b66c0dd1bb8d9fa89d7123e5fa19a8aff3a1f2141eb22df96112afb17b842"}, + {file = "lxml-6.1.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5cfa1a34df366d9dc0d5eaf420f4cf2bb1e1bebe1066d1c2fc28c179f8a4004c"}, + {file = "lxml-6.1.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:db88156fcf544cdbf0d95588051515cfdfd4c876fc66444eb98bceb5d6db76de"}, + {file = "lxml-6.1.0-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:07f98f5496f96bf724b1e3c933c107f0cbf2745db18c03d2e13a291c3afd2635"}, + {file = "lxml-6.1.0-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4642e04449a1e164b5ff71ffd901ddb772dfabf5c9adf1b7be5dffe1212bc037"}, + {file = "lxml-6.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:7da13bb6fbadfafb474e0226a30570a3445cfd47c86296f2446dafbd77079ace"}, + {file = "lxml-6.1.0.tar.gz", hash = "sha256:bfd57d8008c4965709a919c3e9a98f76c2c7cb319086b3d26858250620023b13"}, ] [package.extras] cssselect = ["cssselect (>=0.7)"] +html-clean = ["lxml_html_clean"] html5 = ["html5lib"] htmlsoup = ["BeautifulSoup4"] -source = ["Cython (>=3.0.7)"] [[package]] name = "markdown" @@ -2295,28 +2355,49 @@ files = [ {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, ] +[[package]] +name = "sklearn-compat" +version = "0.1.5" +description = "Ease support for compatible scikit-learn estimators across versions" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "sklearn_compat-0.1.5-py3-none-any.whl", hash = "sha256:dddd00c442027b6a2c2fd4a86667b804a7353cdb5093bfd0d5431f5e3c135fce"}, + {file = "sklearn_compat-0.1.5.tar.gz", hash = "sha256:1a0c3a2f384100e034def49ee5a6cfe984a826f79d032eb559f10445e012b02c"}, +] + +[package.dependencies] +scikit-learn = ">=1.2,<1.9" + +[package.extras] +dev = ["ipython"] +docs = ["mkdocs", "mkdocs-material"] +lint = ["pre-commit"] +tests = ["pandas", "polars", "pyarrow", "pytest", "pytest-cov", "pytest-xdist", "pytz"] + [[package]] name = "somef" -version = "0.10.1" +version = "0.10.3" description = "SOftware Metadata Extraction Framework: A tool for automatically extracting relevant software metadata from a source code repository (README, package files, etc)." optional = false python-versions = "<3.13,>=3.11" groups = ["main"] files = [ - {file = "somef-0.10.1-py3-none-any.whl", hash = "sha256:f2635ef8d4902731031c20a8bf115aba95be9ee55cc22cd98c3b91472690e91e"}, - {file = "somef-0.10.1.tar.gz", hash = "sha256:0126c8e5a8f80c1c8d0063b72d1797973362df372ece77699332c354c44fbe52"}, + {file = "somef-0.10.3-py3-none-any.whl", hash = "sha256:768605714816d5a2508ea5c25cac66290f6a6dba0b9850a92bdd339840035625"}, + {file = "somef-0.10.3.tar.gz", hash = "sha256:f5be4bdcf5dcf5c3315721883846814bdce61e42205ff6b6a24658d27d0c8fd8"}, ] [package.dependencies] bibtexparser = ">=1.4.1,<2.0.0" bs4 = ">=0.0.1,<0.0.2" -chardet = ">=5.2.0,<6.0.0" +chardet = ">=5.2,<8.0" click = ">=8.1.7,<9.0.0" click-option-group = ">=0.5.6,<0.6.0" contractions = ">=0.1.73,<0.2.0" -imbalanced-learn = ">=0.12.0,<0.13.0" +imbalanced-learn = ">=0.14.1,<0.15.0" inflect = ">=7.0.0,<8.0.0" -lxml = ">=5.1.0,<6.0.0" +lxml = ">=6.1.0,<7.0.0" markdown = ">=3.5.2,<4.0.0" markdown-it-py = ">=3.0,<4.0" matplotlib = ">=3.8.2,<4.0.0" @@ -2325,7 +2406,7 @@ nbformat = ">=5.9.2,<6.0.0" nltk = ">=3.9.0,<4.0.0" numpy = ">=1.26.3,<2.0.0" pandas = ">=2.1.4,<3.0.0" -pytest = ">=8.0.0,<9.0.0" +pytest = ">=8,<10" pyyaml = ">=6.0.2,<7.0.0" rdflib = ">7.0.0" requests = ">=2.31.0,<3.0.0" @@ -2611,4 +2692,4 @@ scikit-learn = ["scikit-learn"] [metadata] lock-version = "2.1" python-versions = ">=3.11,<3.13" -content-hash = "d456c241e91b4289bdd12b25c282b5d8195c9c9f0ec4243402d17c65d924913c" +content-hash = "f7dc61fd3cf788e45135899b11842ff2f16e9116751534777e355483a87d2c47" diff --git a/pyproject.toml b/pyproject.toml index 9526c6f..863af21 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "rsfc" -version = "0.1.5" +version = "0.1.6" description = "EVERSE Research Software Fairness Checks" authors = [ "Andres Montero ", @@ -15,7 +15,7 @@ homepage = "https://github.com/oeg-upm/rsfc" [tool.poetry.dependencies] python = ">=3.11,<3.13" -somef = "0.10.1" +somef = "0.10.3" regex = "2024.11.6" requests = "2.32.4" anyascii = "0.3.2" @@ -38,7 +38,7 @@ falcon = "3.1.3" fastjsonschema = "2.21.1" fonttools = "4.58.4" idna = "3.10" -imbalanced-learn = "0.12.4" +imbalanced-learn = ">=0.14.1,<0.15.0" inflect = "7.5.0" iniconfig = "2.1.0" jinja2 = "3.1.6" @@ -48,7 +48,7 @@ jsonschema = "4.24.0" jsonschema-specifications = "2025.4.1" jupyter-core = "5.8.1" kiwisolver = "1.4.8" -lxml = "5.1.0" +lxml = "^6.1.0" markdown = "3.8.1" markupsafe = "3.0.2" matplotlib = "3.10.3" diff --git a/requirements.txt b/requirements.txt index 676a9ea..75c72fb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -71,7 +71,7 @@ scikit-learn==1.5.0 scipy==1.15.3 six==1.17.0 snowballstemmer==3.0.1 -somef==0.10.1 +somef==0.10.3 soupsieve==2.7 tabulate==0.9.0 textblob==0.17.1 diff --git a/src/rsfc/harvesters/cff_harvester.py b/src/rsfc/harvesters/cff_harvester.py deleted file mode 100644 index aa82f9e..0000000 --- a/src/rsfc/harvesters/cff_harvester.py +++ /dev/null @@ -1,35 +0,0 @@ -class CFFHarvester: - - def __init__(self, gh): - self.cff_data = self.harvest_cff(gh.cff) - - - def harvest_cff(self, cff): - - if cff != None: - cff_info = { - "license": None, - "authors": None, - "version": None, - "identifiers": None, - "preferred-citation": None - } - - if "license" in cff: - cff_info["license"] = cff["license"] - - if "authors" in cff: - cff_info["authors"] = cff["authors"] - - if "version" in cff: - cff_info["version"] = cff["version"] - - if "identifiers" in cff: - cff_info["identifiers"] = cff["identifiers"] - - if "preferred-citation" in cff: - cff_info["preferred-citation"] = cff["preferred-citation"] - - return cff_info - else: - return None \ No newline at end of file diff --git a/src/rsfc/harvesters/codemeta_harvester.py b/src/rsfc/harvesters/codemeta_harvester.py deleted file mode 100644 index ae8c7e7..0000000 --- a/src/rsfc/harvesters/codemeta_harvester.py +++ /dev/null @@ -1,39 +0,0 @@ -class CodemetaHarvester: - - def __init__(self, gh): - self.codemeta_data = self.harvest_codemeta(gh.codemeta) - - - def harvest_codemeta(self, codemeta): - if codemeta != None: - codemeta_info = { - "license": None, - "author": None, - "contributor": None, - "identifier": None, - "referencePublication": None, - "version": None - } - - if "license" in codemeta: - codemeta_info["license"] = codemeta["license"] - - if "identifier" in codemeta: - codemeta_info["identifier"] = codemeta["identifier"] - - if "referencePublication" in codemeta: - codemeta_info["referencePublication"] = codemeta["referencePublication"] - - if "author" in codemeta: - codemeta_info["author"] = codemeta["author"] - - if "contributor" in codemeta: - codemeta_info["contributor"] = codemeta["contributor"] - - if "version" in codemeta: - codemeta_info["version"] = codemeta["version"] - - return codemeta_info - else: - return None - \ No newline at end of file diff --git a/src/rsfc/harvesters/github_harvester.py b/src/rsfc/harvesters/github_harvester.py index 9aa36e5..707dd28 100644 --- a/src/rsfc/harvesters/github_harvester.py +++ b/src/rsfc/harvesters/github_harvester.py @@ -1,5 +1,5 @@ import requests -from datetime import datetime +from datetime import datetime, timedelta, timezone import urllib import yaml from rsfc.utils import constants @@ -19,6 +19,7 @@ def __init__(self, repo_url, branch, tag, token): self.codemeta = self.get_codemeta_file() self.commits = self.get_commits() self.issues = self.get_issues() + self.bug_issues = self.get_bugs() self.tests = self.get_tests() @@ -153,13 +154,16 @@ def get_soft_version(self): def get_commits(self): + since = (datetime.now(timezone.utc) - timedelta(days=90)).isoformat() + commits_url = "" + if self.repo_type == "GITHUB": - commits_url = f"{self.api_url}/commits?sha={self.repo_branch}&per_page=100" + commits_url = f"{self.api_url}/commits?sha={self.repo_branch}&since={since}&per_page=100" headers = {'Accept': 'application/vnd.github.v3.raw'} response = self.safe_request("GET", commits_url, headers=headers) elif self.repo_type == "GITLAB": - commits_url = f"{self.api_url}/repository/commits?ref_name={self.repo_branch}&per_page=100" + commits_url = f"{self.api_url}/repository/commits?ref_name={self.repo_branch}&since={since}&per_page=100" response = self.safe_request("GET", commits_url) else: @@ -171,31 +175,64 @@ def get_commits(self): print(f"Error getting commits: {response.status_code}") commits = [] - return commits + return commits_url, commits def get_issues(self): + since = (datetime.now(timezone.utc) - timedelta(days=90)).isoformat() + if self.repo_type == "GITHUB": - issues_url = f"{self.api_url}/issues?state=all&per_page=100" + issues_url = f"{self.api_url}/issues?state=all&since={since}&per_page=100" headers = {'Accept': 'application/vnd.github.v3.raw'} response = self.safe_request("GET", issues_url, headers=headers) elif self.repo_type == "GITLAB": - issues_url = f"{self.api_url}/issues?state=all&per_page=100" + issues_url = f"{self.api_url}/issues?state=all&updated_after={since}&per_page=100" response = self.safe_request("GET", issues_url) else: raise ValueError(f"Not supported repository: {self.repo_type}") issues = [] + if response.status_code == 200: data = response.json() - issues = [issue for issue in data if "pullsafe_request" not in issue] + if self.repo_type == "GITHUB": + issues = [issue for issue in data if "pull_request" not in issue] + else: + issues = data else: print(f"Error getting issues: {response.status_code}") return issues + + def get_bugs(self): + + if self.repo_type == "GITHUB": + issues_url = f"{self.api_url}/issues?state=all&labels=bug&per_page=100" + headers = {'Accept': 'application/vnd.github.v3+json'} + response = self.safe_request("GET", issues_url, headers=headers) + + elif self.repo_type == "GITLAB": + issues_url = f"{self.api_url}/issues?labels=bug&per_page=100" + response = self.safe_request("GET", issues_url) + + else: + raise ValueError(f"Not supported repository: {self.repo_type}") + + if response.status_code == 200: + bugs = response.json() + + if self.repo_type == "GITHUB": + bugs = [issue for issue in bugs if "pull_request" not in issue] + + else: + print(f"Error getting bugs: {response.status_code}") + bugs = [] + + return bugs + def get_tests(self): diff --git a/src/rsfc/harvesters/somef_harvester.py b/src/rsfc/harvesters/somef_harvester.py index 9a82563..ec85c46 100644 --- a/src/rsfc/harvesters/somef_harvester.py +++ b/src/rsfc/harvesters/somef_harvester.py @@ -1,70 +1,83 @@ import io -import contextlib import json -from somef import somef_cli -import subprocess import os +import tempfile +import contextlib +import subprocess + +from somef.somef_cli import run_cli + class SomefHarvester: - - def __init__(self, repo_url, branch, tag, token): + + def __init__(self, repo_url, branch=None, tag=None, token=None): + self.somef_configure(token) - self.somef_data = self.somef_assessment(repo_url, branch, tag, 0.8) - - + + self.somef_data = self.somef_assessment(repo_url=repo_url, branch=branch, tag=tag, threshold=0.8) + def somef_configure(self, token): - + print("Configuring SOMEF...") - + if token: + configure = ["somef", "configure"] + stdin_data = ( - f"{token}\n" #To deal with the inputs asked by somef configure - "\n" - "\n" - "\n" - "\n" - "\n" + f"{token}\n" + "\n" + "\n" + "\n" + "\n" + "\n" ) + else: + configure = ["somef", "configure", "-a"] stdin_data = None try: - subprocess.run( - configure, - input=stdin_data, - text=True, - check=True, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL - ) + subprocess.run(configure, input=stdin_data, text=True, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) except subprocess.CalledProcessError as e: raise RuntimeError("SOMEF configuration failed") from e def somef_assessment(self, repo_url, branch=None, tag=None, threshold=0.8): - + print("Extracting repository metadata with SOMEF...") - + + os.makedirs("./rsfc_output/", exist_ok=True) + + output_json = "./rsfc_output/somef_assessment.json" + somef_kwargs = { "threshold": threshold, "ignore_classifiers": True, "repo_url": repo_url, - "readme_only": False + "readme_only": False, + "output": output_json, + "pretty": True } if branch is not None: somef_kwargs["branch"] = branch + elif tag is not None: somef_kwargs["tag"] = tag - with contextlib.redirect_stdout(io.StringIO()), contextlib.redirect_stderr(io.StringIO()): - repo_data = somef_cli.cli_get_data(**somef_kwargs) - - repo_data = json.loads(json.dumps(repo_data.results)) - - '''os.makedirs('./rsfc_output/', exist_ok=True) - with open('./rsfc_output/somef_assessment.json', 'w', encoding='utf-8') as f: - json.dump(repo_data, f, indent=4, ensure_ascii=False)''' - + with (contextlib.redirect_stdout(io.StringIO()), contextlib.redirect_stderr(io.StringIO())): + + run_cli(**somef_kwargs) + + if not os.path.exists(output_json): + + raise RuntimeError( + "SOMEF did not generate the expected JSON output" + ) + + with open(output_json, "r", encoding="utf-8") as f: + + repo_data = json.load(f) + return repo_data \ No newline at end of file diff --git a/src/rsfc/model/indicator.py b/src/rsfc/model/indicator.py index 9a26831..71281a0 100644 --- a/src/rsfc/model/indicator.py +++ b/src/rsfc/model/indicator.py @@ -1,15 +1,15 @@ -from rsfc.rsfc_tests import rsfc_tests as rt +from rsfc.rsfc_checks import rsfc_checks as rt class Indicator: - def __init__(self, somef, cd, cf, gh): + def __init__(self, somef, gh): self.test_functions = { "RSFC-01-1": [ (rt.test_id_presence_and_resolves, [somef.somef_data]) ], "RSFC-01-2": [ - (rt.test_id_associated_with_software, [somef.somef_data, cd.codemeta_data, cf.cff_data]) + (rt.test_id_associated_with_software, [somef.somef_data]) ], "RSFC-01-3": [ (rt.test_id_common_schema, [somef.somef_data]) @@ -30,10 +30,10 @@ def __init__(self, somef, cd, cf, gh): (rt.test_latest_release_consistency, [somef.somef_data]) ], "RSFC-03-6": [ - (rt.test_version_number_in_metadata, [somef.somef_data, cd.codemeta_data, cf.cff_data]) + (rt.test_version_number_in_metadata, [somef.somef_data]) ], "RSFC-04-1": [ - (rt.test_metadata_exists, [somef.somef_data, cd.codemeta_data, cf.cff_data]) + (rt.test_metadata_exists, [somef.somef_data, gh]) ], "RSFC-04-2": [ (rt.test_readme_exists, [somef.somef_data]) @@ -45,7 +45,7 @@ def __init__(self, somef, cd, cf, gh): (rt.test_descriptive_metadata, [somef.somef_data]) ], "RSFC-04-5": [ - (rt.test_codemeta_exists, [cd.codemeta_data]) + (rt.test_codemeta_exists, [gh]) ], "RSFC-05-1": [ (rt.test_repo_status, [somef.somef_data]) @@ -57,22 +57,19 @@ def __init__(self, somef, cd, cf, gh): (rt.test_software_documentation, [somef.somef_data]) ], "RSFC-06-1": [ - (rt.test_authors, [somef.somef_data, cd.codemeta_data, cf.cff_data]) + (rt.test_authors, [somef.somef_data]) ], "RSFC-06-2": [ - (rt.test_contributors, [somef.somef_data, cd.codemeta_data]) + (rt.test_contributors, [somef.somef_data]) ], "RSFC-06-3": [ - (rt.test_authors_orcids, [cd.codemeta_data, cf.cff_data]) - ], - "RSFC-06-4": [ - (rt.test_author_roles, [cd.codemeta_data]) + (rt.test_authors_orcids, [somef.somef_data]) ], "RSFC-07-1": [ - (rt.test_identifier_in_readme_citation, [somef.somef_data, cf.cff_data]) + (rt.test_identifier_in_readme_citation, [somef.somef_data]) ], "RSFC-07-2": [ - (rt.test_identifier_resolves_to_software, [somef.somef_data, cd.codemeta_data, cf.cff_data, gh.repo_url]) + (rt.test_identifier_resolves_to_software, [somef.somef_data, gh.repo_url]) ], "RSFC-08-1": [ (rt.test_metadata_record_in_zenodo_or_software_heritage, [somef.somef_data]) @@ -81,7 +78,7 @@ def __init__(self, somef, cd, cf, gh): (rt.test_is_github_repository, [gh.repo_url]) ], "RSFC-12-1": [ - (rt.test_reference_publication, [somef.somef_data, cd.codemeta_data]) + (rt.test_reference_publication, [somef.somef_data]) ], "RSFC-13-1": [ (rt.test_dependencies_declared, [somef.somef_data]) @@ -108,11 +105,11 @@ def __init__(self, somef, cd, cf, gh): (rt.test_license_spdx_compliant, [somef.somef_data]) ], "RSFC-16-1": [ - (rt.test_license_info_in_metadata_files, [somef.somef_data, cd.codemeta_data, cf.cff_data]) + (rt.test_license_info_in_metadata_files, [somef.somef_data]) ], - "RSFC-17-1": [ + '''"RSFC-17-1": [ (rt.test_repo_enabled_and_commits, [somef.somef_data, gh]) - ], + ],''' "RSFC-17-2": [ (rt.test_commit_history, [gh]) ], @@ -130,6 +127,9 @@ def __init__(self, somef, cd, cf, gh): ], "RSFC-21-1": [ (rt.test_has_contribution_guidelines, [somef.somef_data]) + ], + "RSFC-22-1": [ + (rt.test_containerized, [somef.somef_data]) ] } diff --git a/src/rsfc/model/markdownReportGenerator.py b/src/rsfc/model/markdownReportGenerator.py index 38c6476..fca1489 100644 --- a/src/rsfc/model/markdownReportGenerator.py +++ b/src/rsfc/model/markdownReportGenerator.py @@ -211,6 +211,12 @@ def table_to_markdown(self): md.append("| " + " | ".join(["---"] * len(header)) + " |") for row in data_rows: + row = row.copy() + + test_id = row[0].strip() + + if test_id in constants.TEST_ID_DICT: + row[0] = f"[{test_id}]({constants.TEST_ID_DICT[test_id]})" md.append("| " + " | ".join(row) + " |") return "\n".join(md) diff --git a/src/rsfc/rsfc_tests/__init__.py b/src/rsfc/rsfc_checks/__init__.py similarity index 100% rename from src/rsfc/rsfc_tests/__init__.py rename to src/rsfc/rsfc_checks/__init__.py diff --git a/src/rsfc/rsfc_checks/rsfc_checks.py b/src/rsfc/rsfc_checks/rsfc_checks.py new file mode 100644 index 0000000..d680a93 --- /dev/null +++ b/src/rsfc/rsfc_checks/rsfc_checks.py @@ -0,0 +1,1620 @@ +from rsfc.utils import constants +from rsfc.model import check as ch +import regex as re +import requests +from rsfc.utils import rsfc_helpers + + +################################################### FRSM_01 ################################################### + +def test_id_presence_and_resolves(somef_data): + + if "identifier" not in somef_data: + output = "false" + evidence = constants.EVIDENCE_NO_IDENTIFIER_FOUND + suggest = constants.SUGGEST_NO_IDENTIFIER + else: + for item in somef_data["identifier"]: + sources = item.get("source", []) + sources_list = sources if isinstance(sources, list) else [sources] + + readme_source = any("README" in source for source in sources_list) + + if not readme_source: + output = "false" + evidence = constants.EVIDENCE_NO_IDENTIFIER_FOUND_README + suggest = constants.SUGGEST_NO_IDENTIFIER_README + else: + identifier = item["result"]["value"] + + if (identifier.startswith("http://") or identifier.startswith("https://")): + try: + response = requests.get(identifier, allow_redirects=True, timeout=10, stream=True) + + if response.status_code == 200: + output = "true" + evidence = constants.EVIDENCE_ID_FOUND_AND_RESOLVES.format(id=identifier) + suggest = "N/A" + + else: + output = "false" + evidence = constants.EVIDENCE_NO_ID_RESOLVE.format(id=identifier) + suggest = constants.SUGGEST_IDENTIFIER_NO_RESOLVE + + except requests.RequestException: + output = "error" + evidence = "Something went wrong when trying to resolve the identifier" + suggest = None + + else: + output = "false" + evidence = constants.EVIDENCE_ID_NOT_URL.format(id=identifier) + suggest = constants.SUGGEST_IDENTIFIER_NOT_HTTP + + check = ch.Check(constants.INDICATORS_DICT['persistent_and_unique_identifier'], 'RSFC-01-1', "There is an identifier and resolves", constants.PROCESS_IDENTIFIER, output, evidence, suggest) + + return check.convert() + + +def test_id_common_schema(somef_data): + output = "true" + evidence = constants.EVIDENCE_ID_COMMON_SCHEMA + suggest = "N/A" + + compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in constants.ID_SCHEMA_REGEX_LIST] + failed_identifiers = [] + any_identifier_found = False + + if 'identifier' in somef_data and isinstance(somef_data['identifier'], list): + for item in somef_data['identifier']: + if "source" in item and "result" in item and "value" in item["result"]: + any_identifier_found = True + value = item['result']['value'] + + if value and not any(pattern.match(str(value)) for pattern in compiled_patterns): + sources = item['source'] + sources_str = ", ".join(sources) if isinstance(sources, list) else sources + failed_identifiers.append(f"\n\t- Identifier '{value}' found in: {sources_str}") + + if 'citation' in somef_data and isinstance(somef_data['citation'], list): + for item in somef_data['citation']: + if "source" in item and "result" in item and "identifier" in item["result"]: + citation_ids = item["result"]["identifier"] + citation_ids_list = citation_ids if isinstance(citation_ids, list) else [citation_ids] + + for cid in citation_ids_list: + if isinstance(cid, dict) and "value" in cid: + any_identifier_found = True + value = cid['value'] + + if value and not any(pattern.search(str(value)) for pattern in compiled_patterns): + sources = item['source'] + sources_str = ", ".join(sources) if isinstance(sources, list) else sources + failed_identifiers.append(f"\n\t- Identifier '{value}' found in: {sources_str}") + + if not any_identifier_found: + output = "false" + evidence = constants.EVIDENCE_NO_IDENTIFIER_FOUND + suggest = constants.SUGGEST_NO_IDENTIFIER + elif failed_identifiers: + output = "false" + suggest = constants.SUGGEST_IDENTIFIER_SCHEME + evidence = constants.EVIDENCE_NO_ID_COMMON_SCHEMA + "".join(failed_identifiers) + else: + output = "true" + evidence = constants.EVIDENCE_ID_COMMON_SCHEMA + suggest = "N/A" + + check = ch.Check(constants.INDICATORS_DICT['persistent_and_unique_identifier'], 'RSFC-01-3', "Software identifier follows a proper schema", constants.PROCESS_ID_PROPER_SCHEMA, output, evidence, suggest) + + return check.convert() + + +def test_id_associated_with_software(somef_data): + id_locations = { + 'codemeta.json': False, + 'CITATION.cff': False, + 'README.md': False + } + + if "identifier" in somef_data and isinstance(somef_data['identifier'], list): + for item in somef_data['identifier']: + if 'source' in item: + sources = item["source"] + sources_list = sources if isinstance(sources, list) else [sources] + + for s in sources_list: + if 'README.md' in str(s): + id_locations['README.md'] = True + if "codemeta.json" in str(s): + id_locations["codemeta.json"] = True + + if "citation" in somef_data and isinstance(somef_data['citation'], list): + for item in somef_data['citation']: + if "result" in item and "identifier" in item["result"] and item["result"]["identifier"]: + if 'source' in item: + sources = item["source"] + sources_list = sources if isinstance(sources, list) else [sources] + + for s in sources_list: + if ".cff" in str(s) or "CITATION.cff" in str(s): + id_locations["CITATION.cff"] = True + + if any(id_locations.values()): + output = "true" + suggest = "N/A" + + existing_id_locations = [key for key, value in id_locations.items() if value] + existing_id_locations_txt = ', '.join(sorted(existing_id_locations)) + evidence = constants.EVIDENCE_SOME_ID_ASSOCIATED_WITH_SOFTWARE.format(source=existing_id_locations_txt) + + missing_id_locations = [key for key, value in id_locations.items() if not value] + if missing_id_locations: + missing_id_locations_txt = ', '.join(sorted(missing_id_locations)) + evidence += constants.EVIDENCE_MISSING_IDS.format(missing_sources=missing_id_locations_txt) + + else: + output = "false" + evidence = constants.EVIDENCE_NO_ID_ASSOCIATED_WITH_SOFTWARE + suggest = constants.SUGGEST_NO_IDENTIFIER_ASSOCIATED + + + check = ch.Check(constants.INDICATORS_DICT['persistent_and_unique_identifier'], 'RSFC-01-2', "There is an identifier associated with the software", constants.PROCESS_ID_ASSOCIATED_WITH_SOFTWARE, output, evidence, suggest) + + return check.convert() + + + +################################################### FRSM_03 ################################################### + + +def test_version_number_in_metadata(somef_data): + + if 'version' in somef_data: + output = "true" + suggest = "N/A" + + sources = set() + + for item in somef_data["version"]: + source = item.get("source", []) + + if isinstance(source, list): + sources.update(source) + else: + sources.add(source) + + valid_sources = ''.join(f'\n\t- {source}' for source in sorted(sources)) + evidence = constants.EVIDENCE_VERSION_IN_METADATA + valid_sources + + else: + output = "false" + evidence = constants.EVIDENCE_NO_VERSION_IN_METADATA + suggest = constants.SUGGEST_NO_VERSION_IN_METADATA + + check = ch.Check(constants.INDICATORS_DICT['descriptive_metadata'], 'RSFC-03-6', "Version number in metadata", constants.PROCESS_VERSION_IN_METADATA, output, evidence, suggest) + + return check.convert() + + +def test_has_releases(somef_data): + if 'releases' not in somef_data: + output = "false" + evidence = constants.EVIDENCE_NO_RELEASES + suggest = constants.SUGGEST_NO_RELEASES + else: + output = "true" + evidence = constants.EVIDENCE_RELEASES + suggest = "N/A" + for item in somef_data['releases']: + if 'type' in item['result']: + if item['result']['type'] == 'Release': + evidence += f'\n\t- {item["result"]["html_url"]}' + + check = ch.Check(constants.INDICATORS_DICT['has_releases'], 'RSFC-03-1', "Software has releases", constants.PROCESS_RELEASES, output, evidence, suggest) + + return check.convert() + + +def test_release_id_and_version(somef_data): + if 'releases' not in somef_data: + output = "false" + evidence = constants.EVIDENCE_NO_RELEASES + suggest = constants.SUGGEST_NO_RELEASES + else: + output = "true" + evidence = constants.EVIDENCE_RELEASE_ID_AND_VERSION + suggest = "N/A" + + bad_releases = "" + + results = somef_data['releases'] + for item in results: + if not (item['result']['url'] and item['result']['tag']): + if output != "false": + output = "false" + suggest = constants.SUGGEST_NO_RELEASE_ID_AND_VERSION + bad_releases + f"\t\n- {item["result"]["html_url"]}" + + if output == "false": + evidence = constants.EVIDENCE_NO_RELEASE_ID_AND_VERSION + bad_releases + + check = ch.Check(constants.INDICATORS_DICT['has_releases'], 'RSFC-03-2', "Releases have an id and version number", constants.PROCESS_RELEASE_ID_VERSION, output, evidence, suggest) + + return check.convert() + + +def test_semantic_versioning_standard(somef_data): + if 'releases' not in somef_data or not isinstance(somef_data['releases'], list) or len(somef_data['releases']) == 0: + output = "false" + evidence = constants.EVIDENCE_NO_RELEASES + suggest = constants.SUGGEST_NO_RELEASES + else: + compiled_patterns = [re.compile(pattern) for pattern in constants.VERSIONING_REGEX_LIST] + bad_versions_list = [] + total_valid_tags = 0 + + results = somef_data['releases'] + for item in results: + if 'result' in item and 'tag' in item['result']: + tag_value = item['result']['tag'] + if tag_value: + total_valid_tags += 1 + if not any(pattern.match(str(tag_value)) for pattern in compiled_patterns): + bad_versions_list.append(str(tag_value)) + + if total_valid_tags == 0: + output = "false" + evidence = constants.EVIDENCE_NO_RELEASES + suggest = constants.SUGGEST_NO_RELEASES + else: + successful_versions = total_valid_tags - len(bad_versions_list) + success_rate = successful_versions / total_valid_tags + + bad_versions_txt = "".join([f"\n\t- {tag}" for tag in bad_versions_list]) + + if success_rate >= 0.80: + output = "true" + suggest = "N/A" + evidence = constants.EVIDENCE_VERSIONING_STANDARD + + if bad_versions_list: + evidence += f"\nNote: Some versions did not follow the convention but passed the 80% threshold:{bad_versions_txt}" + else: + output = "false" + suggest = constants.SUGGEST_NO_VERSIONING_STANDARD + evidence = constants.EVIDENCE_NO_VERSIONING_STANDARD + bad_versions_txt + + check = ch.Check(constants.INDICATORS_DICT['versioning_standards_use'], 'RSFC-03-3', "Release versions follow a community established convention", constants.PROCESS_SEMANTIC_VERSIONING, output, evidence, suggest) + + return check.convert() + + +def test_version_scheme(somef_data): + if 'releases' not in somef_data: + output = "false" + evidence = constants.EVIDENCE_NO_RELEASES + suggest = constants.SUGGEST_NO_RELEASES + else: + output = "true" + evidence = constants.EVIDENCE_IDENTIFIER_SCHEME_COMPLIANT + suggest = "N/A" + + scheme = '' + bad_urls = "" + + results = somef_data['releases'] + for item in results: + if 'result' in item and 'url' in item['result'] and item['result']['url']: + url = item['result']['url'] + + if not scheme: + scheme = rsfc_helpers.build_url_pattern(url) + + if scheme and not scheme.match(url): + if output != "false": + output = "false" + suggest = constants.SUGGEST_NO_IDENTIFIER_SCHEME_COMPLIANT + + bad_urls += f"\n\t- {url}" + + if output == "false": + evidence = constants.EVIDENCE_NO_IDENTIFIER_SCHEME_COMPLIANT + bad_urls + + check = ch.Check(constants.INDICATORS_DICT['has_releases'], 'RSFC-03-4', "Release identifiers follow the same scheme", constants.PROCESS_VERSION_SCHEME, output, evidence, suggest) + + return check.convert() + + + +def test_latest_release_consistency(somef_data): + latest_release = None + version = None + + if 'releases' in somef_data: + latest_release = rsfc_helpers.get_latest_release(somef_data) + + if 'version' in somef_data: + version_data = somef_data['version'][0]['result'] + version = version_data.get('tag') or version_data.get('value') + + norm_version = str(version).strip().lstrip('vV') + norm_latest = str(latest_release).strip().lstrip('vV') + + if version == None or latest_release == None: + output = "error" + evidence = constants.EVIDENCE_NOT_ENOUGH_RELEASE_INFO + suggest = None + elif norm_version == norm_latest: + output = "true" + evidence = constants.EVIDENCE_RELEASE_CONSISTENCY + suggest = "N/A" + else: + output = "false" + evidence = constants.EVIDENCE_NO_RELEASE_CONSISTENCY + suggest = constants.SUGGEST_NO_RELEASE_CONSISTENCY + + + check = ch.Check(constants.INDICATORS_DICT['has_releases'], 'RSFC-03-5', "Last release consistency", constants.PROCESS_RELEASE_CONSISTENCY, output, evidence, suggest) + + return check.convert() + +################################################### FRSM_04 ################################################### + +def test_metadata_exists(somef_data, gh_data): + metadata_files = { + 'CITATION.cff': False, + 'codemeta.json': False, + 'package_file': False + } + + if gh_data.cff is not None: + metadata_files['CITATION.cff'] = True + + if gh_data.codemeta is not None: + metadata_files['codemeta.json'] = True + + if 'has_package_file' in somef_data: + metadata_files['package_file'] = True + + if any(metadata_files.values()): + output = "true" + suggest = "N/A" + + existing_metadata = [key for key, value in metadata_files.items() if value] + existing_metadata_txt = ', '.join(existing_metadata) + + evidence = constants.EVIDENCE_METADATA_EXISTS.format(source=existing_metadata_txt) + else: + output = "false" + evidence = constants.EVIDENCE_NO_METADATA_EXISTS + suggest = constants.SUGGEST_NO_METADATA_FILES + + check = ch.Check(constants.INDICATORS_DICT['descriptive_metadata'], 'RSFC-04-1', "Metadata exists", constants.PROCESS_METADATA_EXISTS, output, evidence, suggest) + + return check.convert() + + +def test_readme_exists(somef_data): + if 'readme_url' in somef_data: + output = "true" + evidence = constants.EVIDENCE_DOCUMENTATION_README + suggest = "N/A" + else: + output = "false" + evidence = constants.EVIDENCE_NO_DOCUMENTATION_README + suggest = constants.SUGGEST_NO_README + + check = ch.Check(constants.INDICATORS_DICT['software_has_documentation'], 'RSFC-04-2', "There is a README", constants.PROCESS_README, output, evidence, suggest) + + return check.convert() + + +def test_title_description(somef_data): + title_evidence_part = None + desc_evidence_part = None + + if 'full_title' in somef_data and isinstance(somef_data['full_title'], list) and len(somef_data['full_title']) > 0: + item = somef_data['full_title'][0] + if "source" in item: + sources = item["source"] + sources_list = sources if isinstance(sources, list) else [sources] + title_txt = ", ".join(sorted([str(s).strip() for s in sources_list if s and str(s).strip()])) + title_evidence_part = f"title in {title_txt}" + elif "technique" in item: + tech = item["technique"] + title_evidence_part = f"title (no source found, obtained via {tech})" + else: + title_evidence_part = "title (no source or technique found)" + + if 'description' in somef_data and isinstance(somef_data['description'], list) and len(somef_data['description']) > 0: + item = somef_data['description'][0] + if "source" in item: + sources = item["source"] + sources_list = sources if isinstance(sources, list) else [sources] + desc_txt = ", ".join(sorted([str(s).strip() for s in sources_list if s and str(s).strip()])) + desc_evidence_part = f"description in {desc_txt}" + elif "technique" in item: + tech = item["technique"] + desc_evidence_part = f"description (no source found, obtained via {tech})" + else: + desc_evidence_part = "description (no source or technique found)" + + if title_evidence_part and desc_evidence_part: + output = "true" + suggest = "N/A" + if "in " in title_evidence_part and "in " in desc_evidence_part: + t_clean = title_evidence_part.replace("title in ", "") + d_clean = desc_evidence_part.replace("description in ", "") + evidence = constants.EVIDENCE_TITLE_AND_DESCRIPTION.format(title_sources=t_clean, desc_sources=d_clean) + else: + evidence = f"Found {title_evidence_part} and {desc_evidence_part}." + + elif title_evidence_part and not desc_evidence_part: + output = "false" + suggest = constants.SUGGEST_NO_DESCRIPTION + evidence = f"Found {title_evidence_part}. However, no description was found." + + elif desc_evidence_part and not title_evidence_part: + output = "false" + suggest = constants.SUGGEST_NO_TITLE + evidence = f"Found {desc_evidence_part}. However, no title was found." + + else: + output = "false" + evidence = constants.EVIDENCE_NO_TITLE_AND_DESCRIPTION + suggest = constants.SUGGEST_NO_TITLE_DESCRIPTION + + check = ch.Check(constants.INDICATORS_DICT['descriptive_metadata'], 'RSFC-04-3', "There are title and description", constants.PROCESS_TITLE_DESCRIPTION, output, evidence, suggest) + + return check.convert() + + +def test_descriptive_metadata(somef_data): + desc_sources = set() + lang_sources = set() + date_sources = set() + keyword_sources = set() + + if 'description' in somef_data and isinstance(somef_data['description'], list): + for item in somef_data['description']: + if "source" in item: + sources = item["source"] + sources_list = sources if isinstance(sources, list) else [sources] + for s in sources_list: + if s and str(s).strip(): + desc_sources.add(str(s).strip()) + + if 'programming_languages' in somef_data and isinstance(somef_data['programming_languages'], list): + for item in somef_data['programming_languages']: + if "source" in item: + sources = item["source"] + sources_list = sources if isinstance(sources, list) else [sources] + for s in sources_list: + if s and str(s).strip(): + lang_sources.add(str(s).strip()) + + if 'date_created' in somef_data and isinstance(somef_data['date_created'], list): + for item in somef_data['date_created']: + if "source" in item: + sources = item["source"] + sources_list = sources if isinstance(sources, list) else [sources] + for s in sources_list: + if s and str(s).strip(): + date_sources.add(str(s).strip()) + + if 'keywords' in somef_data and isinstance(somef_data['keywords'], list): + for item in somef_data['keywords']: + if "source" in item: + sources = item["source"] + sources_list = sources if isinstance(sources, list) else [sources] + for s in sources_list: + if s and str(s).strip(): + keyword_sources.add(str(s).strip()) + + has_all_metadata = all([desc_sources, lang_sources, date_sources, keyword_sources]) + + txt_desc = ", ".join(sorted(desc_sources)) if desc_sources else "None" + txt_lang = ", ".join(sorted(lang_sources)) if lang_sources else "None" + txt_date = ", ".join(sorted(date_sources)) if date_sources else "None" + txt_key = ", ".join(sorted(keyword_sources)) if keyword_sources else "None" + + if has_all_metadata: + output = "true" + suggest = "N/A" + evidence = constants.EVIDENCE_DESCRIPTIVE_METADATA.format(desc_sources=txt_desc, lang_sources=txt_lang, date_sources=txt_date, keyword_sources=txt_key) + else: + output = "false" + suggest = constants.SUGGEST_NO_DESCRIPTIVE_METADATA + evidence = constants.EVIDENCE_NO_DESCRIPTIVE_METADATA.format(desc_sources=txt_desc, lang_sources=txt_lang, date_sources=txt_date, keyword_sources=txt_key) + + check = ch.Check(constants.INDICATORS_DICT['descriptive_metadata'], 'RSFC-04-4', "Software has descriptive metadata", constants.PROCESS_DESCRIPTIVE_METADATA, output, evidence, suggest) + + return check.convert() + + + +def test_codemeta_exists(gh_data): + if gh_data.codemeta != None: + output = "true" + evidence = constants.EVIDENCE_METADATA_CODEMETA + suggest = "N/A" + else: + output = "false" + evidence = constants.EVIDENCE_NO_METADATA_CODEMETA + suggest = constants.SUGGEST_NO_CODEMETA + + check = ch.Check(constants.INDICATORS_DICT['descriptive_metadata'], 'RSFC-04-5', "There is a codemeta file", constants.PROCESS_CODEMETA, output, evidence, suggest) + + return check.convert() + +################################################### FRSM_05 ################################################### + +def test_repo_status(somef_data): + unique_sources = set() + + if 'repository_status' in somef_data and isinstance(somef_data['repository_status'], list): + for item in somef_data['repository_status']: + if "source" in item: + sources = item["source"] + sources_list = sources if isinstance(sources, list) else [sources] + for s in sources_list: + if s and str(s).strip(): + unique_sources.add(str(s).strip()) + + if unique_sources: + output = "true" + suggest = "N/A" + + formatted_sources = "".join([f"\n\t- {src}" for src in sorted(unique_sources)]) + evidence = constants.EVIDENCE_REPO_STATUS + formatted_sources + else: + output = "false" + evidence = constants.EVIDENCE_NO_REPO_STATUS + suggest = constants.SUGGEST_NO_REPO_STATUS + + check = ch.Check(constants.INDICATORS_DICT['version_control_use'], 'RSFC-05-1', "There is a repostatus badge", constants.PROCESS_REPO_STATUS, output, evidence, suggest) + + return check.convert() + + +def test_contact_support_documentation(somef_data): + unique_sources = set() + + keys_to_check = ['contact', 'support', 'support_channels'] + + for key in keys_to_check: + if key in somef_data and isinstance(somef_data[key], list): + for item in somef_data[key]: + if "source" in item: + sources = item["source"] + sources_list = sources if isinstance(sources, list) else [sources] + for s in sources_list: + if s and str(s).strip(): + unique_sources.add(str(s).strip()) + + if unique_sources: + output = "true" + suggest = "N/A" + + formatted_sources = "".join([f"\n\t- {src}" for src in sorted(unique_sources)]) + evidence = constants.EVIDENCE_CONTACT_INFO + formatted_sources + else: + output = "false" + evidence = constants.EVIDENCE_NO_CONTACT_INFO + suggest = constants.SUGGEST_NO_CONTACT_INFO + + check = ch.Check(constants.INDICATORS_DICT['software_has_documentation'], 'RSFC-05-2', "There is contact and/or support metadata", constants.PROCESS_CONTACT_SUPPORT_DOCUMENTATION, output, evidence, suggest) + + return check.convert() + + +def test_software_documentation(somef_data): + rtd = False + readme = False + sources = set() + + if 'documentation' in somef_data and isinstance(somef_data['documentation'], list): + for item in somef_data['documentation']: + if 'result' in item: + result_val = str(item['result'].get('value', '')).lower() + result_format = str(item['result'].get('format', '')).lower() + + if 'readthedocs' in result_val or 'readthedocs' in result_format: + rtd = True + if 'source' in item: + source_field = item["source"] + sources_list = source_field if isinstance(source_field, list) else [source_field] + + for s in sources_list: + if s and str(s).strip(): + sources.add(str(s).strip()) + + if 'readme_url' in somef_data and isinstance(somef_data['readme_url'], list): + for item in somef_data['readme_url']: + if 'result' in item and 'value' in item['result']: + val = item['result']['value'] + if val and str(val).strip(): + readme = True + sources.add(str(val).strip()) + + if not readme and not rtd: + output = "false" + evidence = constants.EVIDENCE_NO_README_AND_READTHEDOCS + suggest = constants.SUGGEST_NO_README_AND_READTHEDOCS + else: + output = "true" + suggest = "N/A" + + formatted_sources = ''.join(f"\n\t- {source}" for source in sorted(sources)) + evidence = constants.EVIDENCE_DOCUMENTATION + formatted_sources + + check = ch.Check(constants.INDICATORS_DICT['software_has_documentation'], 'RSFC-05-3', "Software documentation", constants.PROCESS_DOCUMENTATION, output, evidence, suggest) + + return check.convert() + +################################################### FRSM_06 ################################################### + +def test_authors(somef_data): + unique_sources = set() + + if "author" in somef_data and isinstance(somef_data["author"], list): + for item in somef_data["author"]: + if "source" in item: + sources = item["source"] + sources_list = sources if isinstance(sources, list) else [sources] + for s in sources_list: + if s and str(s).strip(): + unique_sources.add(str(s).strip()) + + if "citation" in somef_data and isinstance(somef_data["citation"], list): + for item in somef_data["citation"]: + if "result" in item and "author" in item["result"] and item["result"]["author"]: + if "source" in item: + sources = item["source"] + sources_list = sources if isinstance(sources, list) else [sources] + for s in sources_list: + if s and str(s).strip(): + unique_sources.add(str(s).strip()) + + if unique_sources: + output = "true" + suggest = "N/A" + + formatted_sources = "".join([f"\n\t- {src}" for src in sorted(unique_sources)]) + evidence = constants.EVIDENCE_AUTHORS + formatted_sources + else: + output = "false" + evidence = constants.EVIDENCE_NO_AUTHORS + suggest = constants.SUGGEST_NO_AUTHORS + + check = ch.Check(constants.INDICATORS_DICT['descriptive_metadata'], 'RSFC-06-1', "Authors are declared", constants.PROCESS_AUTHORS, output, evidence, suggest) + + return check.convert() + + +def test_contributors(somef_data): + unique_sources = set() + + if "contributor" in somef_data and isinstance(somef_data["contributor"], list): + for item in somef_data["contributor"]: + if "source" in item: + sources = item["source"] + sources_list = sources if isinstance(sources, list) else [sources] + for s in sources_list: + if s and str(s).strip(): + unique_sources.add(str(s).strip()) + + if unique_sources: + output = "true" + suggest = "N/A" + + formatted_sources = "".join([f"\n\t- {src}" for src in sorted(unique_sources)]) + evidence = constants.EVIDENCE_CONTRIBUTORS + formatted_sources + else: + output = "false" + evidence = constants.EVIDENCE_NO_CONTRIBUTORS + suggest = constants.SUGGEST_NO_CONTRIBUTORS + + check = ch.Check(constants.INDICATORS_DICT['descriptive_metadata'], 'RSFC-06-2', "Contributors are declared", constants.PROCESS_CONTRIBUTORS, output, evidence, suggest) + + return check.convert() + + +def test_authors_orcids(somef_data): + missing_orcid_sources = set() + + has_codemeta_authors = False + has_cff_authors = False + + if "author" in somef_data and isinstance(somef_data["author"], list): + for item in somef_data["author"]: + if "result" in item: + sources = item.get("source", []) + sources_list = sources if isinstance(sources, list) else [sources] + + is_codemeta = any("codemeta" in str(s) for s in sources_list) + + if is_codemeta: + has_codemeta_authors = True + orcid_id = item["result"].get("identifier", "") + + if not orcid_id or "https://orcid.org/" not in str(orcid_id): + for s in sources_list: + if "codemeta" in str(s): + missing_orcid_sources.add(str(s).strip()) + + if "citation" in somef_data and isinstance(somef_data["citation"], list): + for item in somef_data["citation"]: + sources = item.get("source", []) + sources_list = sources if isinstance(sources, list) else [sources] + + if not any("CITATION.cff" in str(s) for s in sources_list): + continue + + authors = item.get("result", {}).get("author", []) + if authors: + has_cff_authors = True + + for author in authors: + orcid_url = author.get("url", "") + if not orcid_url or "orcid.org" not in str(orcid_url): + for s in sources_list: + if "CITATION.cff" in str(s): + missing_orcid_sources.add(str(s).strip()) + + if (has_codemeta_authors or has_cff_authors) and not missing_orcid_sources: + output = "true" + evidence = constants.EVIDENCE_AUTHOR_ORCIDS + suggest = "N/A" + else: + output = "false" + suggest = constants.SUGGEST_NO_AUTHOR_ORCIDS + + if missing_orcid_sources: + formatted_sources = "".join([f"\n\t- {src}" for src in sorted(missing_orcid_sources)]) + evidence = constants.EVIDENCE_NO_AUTHOR_ORCIDS + formatted_sources + else: + evidence = constants.EVIDENCE_NO_AUTHOR_ORCIDS + "\n\t- No author sources found to analyze" + + check = ch.Check(constants.INDICATORS_DICT['descriptive_metadata'], 'RSFC-06-3', "Authors have an ORCID", constants.PROCESS_AUTHOR_ORCIDS, output, evidence, suggest) + + return check.convert() + + +'''def test_author_roles(codemeta_data): + + if codemeta_data != None: + if codemeta_data["author"] != None: + author_roles = rsfc_helpers.subtest_author_roles(codemeta_data["author"]) + + if all(value is not None for value in author_roles.values()): + output = "true" + evidence = constants.EVIDENCE_AUTHOR_ROLES + suggest = "N/A" + else: + output = "false" + evidence = constants.EVIDENCE_NO_ALL_AUTHOR_ROLES + suggest = constants.SUGGEST_NO_ALL_AUTHOR_ROLES + else: + output = "false" + evidence = constants.EVIDENCE_NO_AUTHORS_IN_CODEMETA + suggest = constants.SUGGEST_NO_AUTHORS_IN_CODEMETA + else: + output = "false" + evidence = constants.EVIDENCE_NO_CODEMETA_FOUND + suggest = constants.SUGGEST_NO_CODEMETA + + check = ch.Check(constants.INDICATORS_DICT['descriptive_metadata'], 'RSFC-06-4', "Authors have roles", constants.PROCESS_AUTHOR_ROLES, output, evidence, suggest) + + return check.convert()''' + +################################################### FRSM_07 ################################################### + +def test_identifier_in_readme_citation(somef_data): + readme_ids = [] + citation_ids = [] + + if "identifier" in somef_data: + for item in somef_data["identifier"]: + if "source" in item and "result" in item and "value" in item["result"]: + sources = item["source"] + sources_list = sources if isinstance(sources, list) else [sources] + + if any("README" in str(s) for s in sources_list): + val = item["result"]["value"] + if val: + readme_ids.append(val) + + if "citation" in somef_data: + for item in somef_data["citation"]: + if "result" in item and "identifier" in item["result"]: + citations_list = item["result"]["identifier"] + + if isinstance(citations_list, list): + for ident in citations_list: + if isinstance(ident, dict) and "value" in ident and ident["value"]: + citation_ids.append(ident["value"]) + + if citation_ids and readme_ids: + output = "true" + suggest = "N/A" + + all_ids = readme_ids + citation_ids + formatted_ids = "".join([f"\n\t- {id_val}" for id_val in all_ids]) + evidence = constants.EVIDENCE_IDENTIFIER_IN_README_AND_CITATION + formatted_ids + + elif citation_ids: + output = "true" + suggest = "N/A" + + formatted_ids = "".join([f"\n\t- {id_val}" for id_val in citation_ids]) + evidence = constants.EVIDENCE_IDENTIFIER_IN_CITATION + formatted_ids + + elif readme_ids: + output = "true" + suggest = "N/A" + + formatted_ids = "".join([f"\n\t- {id_val}" for id_val in readme_ids]) + evidence = constants.EVIDENCE_IDENTIFIER_IN_README + formatted_ids + + else: + output = "false" + evidence = constants.EVIDENCE_NO_IDENTIFIER_IN_README_OR_CITATION + suggest = constants.SUGGEST_NO_IDENTIFIER_IN_README_OR_CITATION + + check = ch.Check(constants.INDICATORS_DICT['persistent_and_unique_identifier'], 'RSFC-07-1', "There is an identifier in README or CITATION.cff", constants.PROCESS_IDENTIFIER_IN_README_CITATION, output, evidence, suggest) + + return check.convert() + + +def test_identifier_resolves_to_software(somef_data, repo_url): + output = "false" + evidence = constants.EVIDENCE_NO_IDENTIFIER_FOUND + suggest = constants.SUGGEST_NO_IDENTIFIER + identifier = None + pause = False + + if "identifier" in somef_data: + for item in somef_data["identifier"]: + if "source" in item and "result" in item and "value" in item["result"]: + sources = item["source"] + sources_list = sources if isinstance(sources, list) else [sources] + + if any("README" in str(s) or "codemeta.json" in str(s) for s in sources_list): + if item['result']['value']: + identifier = item['result']['value'] + pause = True + break + + if not pause: + if "citation" in somef_data: + for item in somef_data["citation"]: + if "result" in item and "identifier" in item["result"]: + citations_list = item["result"]["identifier"] + + if isinstance(citations_list, list) and citations_list: + first_id = citations_list[0] + if isinstance(first_id, dict) and "value" in first_id and first_id["value"]: + identifier = first_id["value"] + break + + if identifier: + doi_url = rsfc_helpers.normalize_identifier_url(identifier) + try: + resp = requests.get(doi_url, allow_redirects=True, timeout=10) + html = resp.text + + if rsfc_helpers.landing_page_links_back(html, repo_url): + output = "true" + evidence = constants.EVIDENCE_DOI_LINKS_BACK_TO_REPO.format(identifier=identifier) + suggest = "N/A" + else: + output = "false" + evidence = constants.EVIDENCE_DOI_NO_LINK_BACK_TO_REPO + suggest = constants.SUGGEST_DOI_NO_LINK_BACK_TO_REPO + + except requests.RequestException: + output = "false" + evidence = constants.EVIDENCE_NO_RESOLVE_DOI_IDENTIFIER + suggest = constants.SUGGEST_IDENTIFIER_NO_RESOLVE + + check = ch.Check(constants.INDICATORS_DICT['persistent_and_unique_identifier'], 'RSFC-07-2', "Software identifier resolves to software", constants.PROCESS_ID_RESOLVES_TO_SOFTWARE, output, evidence, suggest) + + return check.convert() + +################################################### FRSM_08 ################################################### + +def test_metadata_record_in_zenodo_or_software_heritage(somef_data): + zenodo_identifiers = [] + swh_identifiers = [] + + if "identifier" in somef_data: + for item in somef_data['identifier']: + if 'result' in item and 'value' in item['result'] and item['result']['value']: + val = item['result']['value'] + + if 'zenodo' in val: + zenodo_identifiers.append(val) + elif 'softwareheritage' in val: + swh_identifiers.append(val) + + if zenodo_identifiers and swh_identifiers: + output = "true" + suggest = "N/A" + + all_ids = zenodo_identifiers + swh_identifiers + formatted_ids = "".join([f"\n\t- {id_val}" for id_val in all_ids]) + evidence = constants.EVIDENCE_ZENODO_DOI_AND_SOFTWARE_HERITAGE + formatted_ids + + elif swh_identifiers: + output = "true" + suggest = "N/A" + + formatted_ids = "".join([f"\n\t- {id_val}" for id_val in swh_identifiers]) + evidence = constants.EVIDENCE_SOFTWARE_HERITAGE_BADGE + formatted_ids + + elif zenodo_identifiers: + output = "true" + suggest = "N/A" + + formatted_ids = "".join([f"\n\t- {id_val}" for id_val in zenodo_identifiers]) + evidence = constants.EVIDENCE_ZENODO_DOI + formatted_ids + + else: + output = "false" + evidence = constants.EVIDENCE_NO_ZENODO_DOI_OR_SOFTWARE_HERITAGE + suggest = constants.SUGGEST_ARCHIVE_SOFTWARE + + check = ch.Check(constants.INDICATORS_DICT['archived_in_software_heritage'], 'RSFC-08-1', "Metadata record in Software Heritage or Zenodo", constants.PROCESS_ZENODO_SOFTWARE_HERITAGE, output, evidence, suggest) + + return check.convert() + +################################################### FRSM_09 ################################################### + +def test_is_github_repository(repo_url): + + if 'github.com' in repo_url or 'gitlab.com' in repo_url: + response = requests.head(repo_url, allow_redirects=True, timeout=5) + if response.status_code == 200: + output = "true" + evidence = constants.EVIDENCE_IS_IN_GITHUB_OR_GITLAB + suggest = "N/A" + elif response.status_code == 404: + output = "false" + evidence = constants.EVIDENCE_NO_RESOLVE_GITHUB_OR_GITLAB_URL + suggest = "N/A" + else: + output = "error" + evidence = 'Connection error' + suggest = "N/A" + else: + output = "false" + evidence = constants.EVIDENCE_NO_GITHUB_OR_GITLAB_URL + suggest = "N/A" + + check = ch.Check(constants.INDICATORS_DICT['version_control_use'], 'RSFC-09-1', "Repository is from Github/Gitlab", constants.PROCESS_IS_GITHUB_OR_GITLAB_REPOSITORY, output, evidence, suggest) + + return check.convert() + +################################################### FRSM_12 ################################################### + +def test_reference_publication(somef_data): + ref_pub_found = [] + article_citations_found = [] + + if "citation" in somef_data: + for item in somef_data["citation"]: + if "source" in item and "result" in item: + sources = item["source"] + sources_list = sources if isinstance(sources, list) else [sources] + + is_ref_pub = any("codemeta" in str(s) for s in sources_list) + + result_data = item["result"] + is_article = False + if 'format' in result_data and result_data['format'] == 'bibtex': + is_article = True + elif 'type' in result_data and (result_data['type'] == 'ScholarlyArticle' or result_data['type'] == 'article'): + is_article = True + + title = result_data.get("title", "Untitled Citation") + + if is_ref_pub: + ref_pub_found.append(title) + if is_article: + article_citations_found.append(title) + + if article_citations_found and ref_pub_found: + output = "true" + suggest = "N/A" + + all_found = ref_pub_found + article_citations_found + formatted_sources = "".join([f"\n\t- {title}" for title in all_found]) + evidence = constants.EVIDENCE_REFERENCE_PUBLICATION_OR_CITATION_TO_ARTICLE + formatted_sources + + elif article_citations_found: + output = "true" + suggest = "N/A" + + formatted_sources = "".join([f"\n\t- {title}" for title in article_citations_found]) + evidence = constants.EVIDENCE_ARTICLE_CITATION + formatted_sources + + elif ref_pub_found: + output = "true" + suggest = "N/A" + + formatted_sources = "".join([f"\n\t- {title}" for title in ref_pub_found]) + evidence = constants.EVIDENCE_REFERENCE_PUBLICATION + formatted_sources + + else: + output = "false" + evidence = constants.EVIDENCE_NO_REFERENCE_PUBLICATION_OR_CITATION_TO_ARTICLE + suggest = constants.SUGGEST_NO_REFPUB_OR_ARTICLE + + check = ch.Check(constants.INDICATORS_DICT['software_has_citation'], 'RSFC-12-1', "There is an article citation or reference publication", constants.PROCESS_REFERENCE_PUBLICATION, output, evidence, suggest) + + return check.convert() + +################################################### FRSM_13 ################################################### + +def test_dependencies_declared(somef_data): + if 'requirements' not in somef_data: + output = "false" + evidence = constants.EVIDENCE_NO_DEPENDENCIES + suggest = constants.SUGGEST_NO_DEPENDENCIES + else: + output = "true" + evidence = constants.EVIDENCE_DEPENDENCIES + suggest = "N/A" + + for item in somef_data['requirements']: + sources = item.get("source", []) + sources_list = sources if isinstance(sources, list) else [sources] + + sources_str = ", ".join(sources_list) + + if sources_str not in evidence: + evidence += f'\n\t- {sources_str}' + + check = ch.Check(constants.INDICATORS_DICT['requirements_specified'], 'RSFC-13-1', "Dependencies are declared", constants.PROCESS_REQUIREMENTS, output, evidence, suggest) + + return check.convert() + + +def test_installation_instructions(somef_data): + if 'installation' in somef_data and somef_data['installation']: + output = "false" + evidence = constants.EVIDENCE_NO_INSTALLATION + suggest = constants.SUGGEST_NO_INSTALL_INSTRUCTIONS + unique_sources = set() + + for item in somef_data['installation']: + if "source" in item: + sources = item["source"] + + if isinstance(sources, list): + for s in sources: + if s and str(s).strip(): + unique_sources.add(str(s).strip()) + elif isinstance(sources, str) and sources.strip(): + unique_sources.add(sources.strip()) + + if unique_sources: + output = "true" + suggest = "N/A" + formatted_sources = "".join([f"\n\t- {src}" for src in sorted(unique_sources)]) + evidence = constants.EVIDENCE_INSTALLATION + formatted_sources + else: + output = "false" + evidence = constants.EVIDENCE_NO_INSTALLATION + suggest = constants.SUGGEST_NO_INSTALL_INSTRUCTIONS + + check = ch.Check(constants.INDICATORS_DICT['software_has_documentation'], 'RSFC-13-2', "There are installation instructions", constants.PROCESS_INSTALLATION, output, evidence, suggest) + + return check.convert() + + +def test_dependencies_have_version(somef_data): + if 'requirements' not in somef_data: + output = "false" + evidence = constants.EVIDENCE_NO_DEPENDENCIES + suggest = constants.SUGGEST_NO_DEPENDENCIES + else: + output = "true" + evidence = constants.EVIDENCE_DEPENDENCIES_VERSION + suggest = "N/A" + + bad_dependencies = "" + + for item in somef_data['requirements']: + if "source" in item and "result" in item: + sources = item['source'] + sources_list = sources if isinstance(sources, list) else [sources] + + if any('README' in str(s) for s in sources_list): + continue + + version = item["result"].get("version") + + if not version or (isinstance(version, str) and not version.strip()): + if output != "false": + output = "false" + suggest = constants.SUGGEST_NO_DEPENDENCIES_VERSION + + dep_name = item["result"].get("name", "Unknown dependency") + bad_dependencies += f"\n\t- {dep_name}" + + if output == "false": + evidence = constants.EVIDENCE_NO_DEPENDENCIES_VERSION + bad_dependencies + + check = ch.Check(constants.INDICATORS_DICT['requirements_specified'], 'RSFC-13-3', "Dependencies have version numbers", constants.PROCESS_DEPENDENCIES_VERSION, output, evidence, suggest) + + return check.convert() + + +def test_dependencies_in_machine_readable_file(somef_data): + if 'requirements' not in somef_data: + output = "false" + evidence = constants.EVIDENCE_NO_DEPENDENCIES + suggest = constants.SUGGEST_NO_DEPENDENCIES + else: + output = "false" + evidence = constants.EVIDENCE_NO_DEPENDENCIES_MACHINE_READABLE_FILE + suggest = constants.SUGGEST_NO_MACHINE_READABLE_DEPENDENCIES + + valid_sources = "" + + for item in somef_data['requirements']: + if "source" in item: + sources = item['source'] + + if isinstance(sources, list): + for s in sources: + if 'README' not in s: + if output != "true": + output = "true" + suggest = "N/A" + valid_sources += f"\n\t- {s}" + + elif isinstance(sources, str) and sources: + if 'README' not in sources: + if output != "true": + output = "true" + suggest = "N/A" + valid_sources += f"\n\t- {sources}" + + if output == "true": + evidence = constants.EVIDENCE_DEPENDENCIES_MACHINE_READABLE_FILE + valid_sources + + check = ch.Check(constants.INDICATORS_DICT['requirements_specified'], 'RSFC-13-4', "There is a dependencies machine-readable file", constants.PROCESS_DEPENDENCIES_MACHINE_READABLE_FILE, output, evidence, suggest) + + return check.convert() + + +################################################### FRSM_14 ################################################### + +def test_presence_of_tests(gh): + test_evidences = gh.tests + + if test_evidences: + rx = re.compile(r'tests?', re.IGNORECASE) + sources = "" + for e in test_evidences: + path = e["path"] + path_lower = path.lower() + + if "doc" in path_lower or "docs" in path_lower: + continue + if rx.search(path): + sources += f"\n\t- {path}" + + if sources: + output = "true" + evidence = constants.EVIDENCE_TESTS + sources + suggest = "N/A" + else: + output = "false" + evidence = constants.EVIDENCE_NO_TESTS + suggest = constants.SUGGEST_NO_TESTS + else: + output = "error" + evidence = None + suggest = constants.SUGGEST_NO_TESTS + + check = ch.Check(constants.INDICATORS_DICT['software_has_tests'], 'RSFC-14-1', "Presence of tests in repository", constants.PROCESS_TESTS, output, evidence, suggest) + + return check.convert() + + +def test_github_action_tests(somef_data): + sources = '' + + if 'continuous_integration' not in somef_data: + output = "false" + evidence = constants.EVIDENCE_NO_WORKFLOWS + suggest = constants.SUGGEST_NO_WORKFLOWS + + else: + for item in somef_data['continuous_integration']: + if item['result']['value'] and ('.github/workflows' in item['result']['value'] or '.gitlab-ci.yml' in item['result']['value']): + if any(keyword in item['result']['value'] for keyword in ["test", "validate", "check"]): + sources += f'\n\t- {item["result"]["value"]}' + + if sources: + output = "true" + evidence = constants.EVIDENCE_AUTOMATED_TESTS + sources + suggest = "N/A" + + else: + output = "false" + evidence = constants.EVIDENCE_NO_AUTOMATED_TESTS + suggest = constants.SUGGEST_NO_TEST_ACTIONS + + + check = ch.Check(constants.INDICATORS_DICT['repository_workflows'], 'RSFC-14-2', "There are actions to automate tests", constants.PROCESS_AUTOMATED_TESTS, output, evidence, suggest) + + return check.convert() + +'''def test_has_no_known_bugs(gh_data): + if len(gh_data.bug_issues) == 0: + output = "true" + evidence = constants.EVIDENCE_NO_ISSUES_BUG + suggest = "N/A" + else: + output = "false" + evidence = constants.EVIDENCE_ISSUES_BUGS + suggest = constants.SUGGEST_ISSUES_BUGS + + check = ch.Check(constants.INDICATORS_DICT['software_has_no_known_bugs'], 'RSFC-14-3', "Software has no issues tagged as bugs", constants.PROCESS_ISSUES_BUGS, output, evidence, suggest) + + return check.convert()''' + +################################################### FRSM_15 ################################################### + +def test_has_license(somef_data): + if 'license' not in somef_data: + output = "false" + evidence = constants.EVIDENCE_NO_LICENSE + suggest = constants.SUGGEST_NO_LICENSE + else: + output = "true" + evidence = constants.EVIDENCE_LICENSE + suggest = "N/A" + + for item in somef_data['license']: + if 'source' in item: + sources = item["source"] + if isinstance(sources, list): + for s in sources: + evidence += f'\n\t- {s}' + elif isinstance(sources, str) and sources: + evidence += f'\n\t- {sources}' + + check = ch.Check(constants.INDICATORS_DICT['software_has_license'], 'RSFC-15-1', "Software has license", constants.PROCESS_LICENSE, output, evidence, suggest) + + return check.convert() + + +def test_license_spdx_compliant(somef_data): + output = "false" + evidence = None + + if 'license' not in somef_data: + output = "false" + evidence = constants.EVIDENCE_NO_LICENSE + suggest = constants.SUGGEST_NO_LICENSE + else: + output = "true" + suggest = "N/A" + no_spdx = "" + + evaluated_any = False + + for item in somef_data['license']: + if 'result' in item and 'spdx_id' in item['result']: + evaluated_any = True + spdx_id = item['result']['spdx_id'] + + if spdx_id not in constants.SPDX_LICENSE_WHITELIST: + if output != "false": + output = "false" + suggest = constants.SUGGEST_NO_LICENSE_SPDX + + no_spdx += f"\n\t- {spdx_id}" + + if output == "true" and evaluated_any: + evidence = constants.EVIDENCE_SPDX_COMPLIANT + elif output == "false" and no_spdx: + evidence = constants.EVIDENCE_NO_SPDX_COMPLIANT + no_spdx + else: + output = "false" + evidence = constants.EVIDENCE_LICENSE_NOT_CLEAR + suggest = "N/A" + + check = ch.Check(constants.INDICATORS_DICT['software_has_license'], 'RSFC-15-2', "License is SPDX compliant", constants.PROCESS_LICENSE_SPDX_COMPLIANT, output, evidence, suggest) + + return check.convert() + +'''def test_license_information_provided(somef_data): + + if 'license' not in somef_data: + output = "false" + evidence = constants.EVIDENCE_NO_LICENSE + suggest = constants.SUGGEST_NO_LICENSE + else: + output = "false" + evidence = constants.EVIDENCE_NO_LICENSE_INFORMATION_PROVIDED + suggest = constants.SUGGEST_NO_LICENSE_INFO + for item in somef_data['license']: + if 'source' in item: + if 'README' in item['source']: + output = "true" + evidence = constants.EVIDENCE_LICENSE_INFORMATION_PROVIDED + suggest = "N/A" + + + check = ch.Check(constants.INDICATORS_DICT['software_has_license'], 'RSFC-15-3', "License information is provided", constants.PROCESS_LICENSE_INFORMATION_PROVIDED, output, evidence, suggest) + + return check.convert()''' + +################################################### FRSM_16 ################################################### + +def test_license_info_in_metadata_files(somef_data): + license_info = { + 'codemeta': None, + 'CITATION.cff': None, + 'package': None + } + + if 'license' in somef_data: + for item in somef_data['license']: + sources = item["source"] + sources_list = sources if isinstance(sources, list) else [sources] + for s in sources_list: + if 'pyproject.toml' in s or 'setup.py' in s or 'node.json' in s or 'pom.xml' in s or 'package.json' in s: + license_info['package'] = item["result"]["value"] + if "codemeta" in s: + license_info["codemeta"] = item["result"]["value"] + if "CITATION.cff" in s: + license_info["CITATION.cff"] = item["result"]["value"] + + if all(license_info.values()): + output = "true" + suggest = "N/A" + + existing_list = [f"{key} ({value})" for key, value in license_info.items()] + existing_txt = ', '.join(existing_list) + + evidence = constants.EVIDENCE_LICENSE_INFO_ALL.format(existing=existing_txt) + + elif any(license_info.values()): + output = "true" + suggest = "N/A" + + existing_list = [f"{key} ({value})" for key, value in license_info.items() if value] + existing_txt = ', '.join(existing_list) + + missing_list = [key for key, value in license_info.items() if not value] + missing_txt = ', '.join(missing_list) + + evidence = constants.EVIDENCE_LICENSE_INFO_IN_METADATA.format(existing=existing_txt, missing=missing_txt) + + else: + output = "false" + suggest = constants.SUGGEST_NO_LICENSE_INFO_METADATA + + missing_list = [key for key, value in license_info.items()] + missing_txt = ', '.join(missing_list) + + evidence = constants.EVIDENCE_NO_LICENSE_INFO_IN_METADATA + ": " + missing_txt + + check = ch.Check(constants.INDICATORS_DICT['software_has_license'], 'RSFC-16-1', "License referenced in metadata files", constants.PROCESS_LICENSE_INFO_IN_METADATA_FILES, output, evidence, suggest) + + return check.convert() + +################################################### FRSM_17 ################################################### + +'''def test_repo_enabled_and_commits(somef_data, gh): + + if 'repository_status' in somef_data and somef_data['repository_status'][0]['result']['value']: + if '#active' in somef_data['repository_status'][0]['result']['value']: + repo = True + else: + repo = False + else: + repo = False + + commits = gh.commits + + if repo: + if commits: + output = "true" + evidence = constants.EVIDENCE_REPO_ENABLED_AND_HAS_COMMITS + suggest = "N/A" + else: + output = "false" + evidence = constants.EVIDENCE_NO_COMMITS + suggest = constants.SUGGEST_NO_COMMITS + + else: + output = "false" + evidence = constants.EVIDENCE_NO_REPO_STATUS + suggest = constants.SUGGEST_NO_ACTIVE_REPO + + + check = ch.Check(constants.INDICATORS_DICT['project_is_active'], 'RSFC-17-1', "Repository is active", constants.PROCESS_REPO_ENABLED_AND_COMMITS, output, evidence, suggest) + + return check.convert()''' + + +def test_commit_history(gh): + + commits = gh.commits + + if commits[1] != []: + output = "true" + evidence = constants.EVIDENCE_COMMITS + f"\n\t- {commits[0]}" + suggest = "N/A" + else: + output = "false" + evidence = constants.EVIDENCE_NO_COMMITS + suggest = constants.SUGGEST_NO_COMMITS + + check = ch.Check(constants.INDICATORS_DICT['version_control_use'], 'RSFC-17-2', "Commit history", constants.PROCESS_COMMITS_HISTORY, output, evidence, suggest) + + return check.convert() + +def test_commits_linked_issues(gh): + commits = gh.commits + issues = gh.issues + commits_list = commits[1] + + if commits_list == [] or issues == []: + output = "false" + evidence = constants.EVIDENCE_NOT_ENOUGH_ISSUES_COMMITS_INFO + suggest = constants.SUGGEST_NO_COMMITS_OR_ISSUES + else: + linked_pairs = rsfc_helpers.cross_check_any_issue(issues, commits_list) + + if linked_pairs: + output = "true" + suggest = "N/A" + + formatted_pairs = "".join([f"\n\t- {pair}" for pair in linked_pairs]) + evidence = constants.EVIDENCE_COMMITS_LINKED_TO_ISSUES + formatted_pairs + else: + output = "false" + evidence = constants.EVIDENCE_NO_COMMITS_LINKED_TO_ISSUES + suggest = constants.SUGGEST_NO_ISSUES_LINK_COMMITS + + + check = ch.Check(constants.INDICATORS_DICT['version_control_use'], 'RSFC-17-3', "Commits are linked to issues", constants.PROCESS_COMMITS_LINKED_TO_ISSUES, output, evidence, suggest) + + return check.convert() + + +################################################### MISC ################################################### + + +def test_has_citation(somef_data): + + if 'citation' not in somef_data: + output = "false" + evidence = constants.EVIDENCE_NO_CITATION + suggest = constants.SUGGEST_NO_CITATION + else: + output = "true" + suggest = "N/A" + + sources = set() + + for item in somef_data['citation']: + if 'source' not in item: + continue + + source = item['source'] + + if isinstance(source, list): + sources.update(source) + else: + sources.add(source) + + formatted_sources = ''.join(f'\n\t- {source}' for source in sorted(sources)) + evidence = constants.EVIDENCE_CITATION + formatted_sources + + check = ch.Check(constants.INDICATORS_DICT['software_has_citation'], 'RSFC-18-1', "Repository has citation", constants.PROCESS_CITATION, output, evidence, suggest) + + return check.convert() + + +def test_repository_workflows(somef_data): + + if 'continuous_integration' not in somef_data: + output = "false" + evidence = constants.EVIDENCE_NO_WORKFLOWS + suggest = constants.SUGGEST_NO_WORKFLOWS + else: + output = "true" + evidence = constants.EVIDENCE_WORKFLOWS + suggest = "N/A" + + for item in somef_data['continuous_integration']: + evidence += f'\n\t- {item["result"]["value"]}' + + check = ch.Check(constants.INDICATORS_DICT['repository_workflows'], 'RSFC-19-1', "Repository has workflows", constants.PROCESS_WORKFLOWS, output, evidence, suggest) + + return check.convert() + + +def test_has_issue_tracker(somef_data): + + if "issue_tracker" not in somef_data: + output = "false" + evidence = constants.EVIDENCE_NO_ISSUE_TRACKER + suggest = constants.SUGGEST_NO_ISSUE_TRACKER + else: + for item in somef_data["issue_tracker"]: + sources = "" + if "source" in item: + sources += f"\n\t- {item["source"]}" + + if sources: + evidence = constants.EVIDENCE_ISSUE_TRACKER_SOURCE + sources + else: + evidence = constants.EVIDENCE_ISSUE_TRACKER_NO_SOURCE + + output = "true" + suggest = "N/A" + + check = ch.Check(constants.INDICATORS_DICT['support_issue_tracking'], 'RSFC-20-1', "Repository has an issue tracker", constants.PROCESS_ISSUE_TRACKER, output, evidence, suggest) + + return check.convert() + + +def test_has_contribution_guidelines(somef_data): + if "contributing_guidelines" not in somef_data: + output = "false" + evidence = constants.EVIDENCE_NO_CONTRIBUTION_GUIDELINES + suggest = constants.SUGGEST_NO_CONTRIBUTION_GUIDELINES + else: + output = "true" + evidence = constants.EVIDENCE_CONTRIBUTION_GUIDELINES + suggest = "N/A" + + for item in somef_data["contributing_guidelines"]: + evidence += f'\n\t- {item["source"]}' + + check = ch.Check(constants.INDICATORS_DICT['has_contribution_guidelines'], 'RSFC-21-1', "Repository has contribution guidelines", constants.PROCESS_CONTRIBUTION_GUIDELINES, output, evidence, suggest) + + return check.convert() + +def test_containerized(somef_data): + + unique_sources = set() + + if "has_build_file" in somef_data and isinstance(somef_data["has_build_file"], list): + for item in somef_data["has_build_file"]: + if "source" in item and "result" in item and "format" in item["result"]: + fmt = str(item["result"]["format"]).lower().strip() + + if fmt in constants.VALID_CONTAINER_FORMATS: + sources = item["source"] + sources_list = sources if isinstance(sources, list) else [sources] + for s in sources_list: + if s and str(s).strip(): + unique_sources.add(str(s).strip()) + + if unique_sources: + output = "true" + suggest = "N/A" + formatted_sources = "".join([f"\n\t- {src}" for src in sorted(unique_sources)]) + evidence = constants.EVIDENCE_CONTAINER_FILE + formatted_sources + else: + output = "false" + evidence = constants.EVIDENCE_NO_CONTAINER_FILE + suggest = constants.SUGGEST_NO_CONTAINER_FILE + + check = ch.Check(constants.INDICATORS_DICT['software_is_containerized'], 'RSFC-22-1', "Software is containerized", constants.PROCESS_CONTAINER_FILE, output, evidence, suggest) + + return check.convert() \ No newline at end of file diff --git a/src/rsfc/rsfc_core.py b/src/rsfc/rsfc_core.py index bbd8926..0e12d60 100644 --- a/src/rsfc/rsfc_core.py +++ b/src/rsfc/rsfc_core.py @@ -3,8 +3,6 @@ from rsfc.model import assessment as asmt from rsfc.model import markdownReportGenerator as mdRep from rsfc.harvesters import somef_harvester as som -from rsfc.harvesters import codemeta_harvester as cm -from rsfc.harvesters import cff_harvester as cf from rsfc.harvesters import github_harvester as gt from rsfc.utils import rsfc_helpers @@ -14,12 +12,10 @@ def start_assessment(repo_url, branch, tag, ftr, test_id, token): gh = gt.GithubHarvester(repo_url, branch, tag, token) sw = soft.AssessedSoftware(repo_url, gh) somef = som.SomefHarvester(repo_url, branch, tag, token) - code = cm.CodemetaHarvester(gh) - cff = cf.CFFHarvester(gh) print("Assessing repository...") - indi = ind.Indicator(somef, code, cff, gh) + indi = ind.Indicator(somef, gh) checks = indi.assess_indicators(test_id) assess = asmt.Assessment(checks) diff --git a/src/rsfc/rsfc_tests/rsfc_tests.py b/src/rsfc/rsfc_tests/rsfc_tests.py deleted file mode 100644 index 86a73ed..0000000 --- a/src/rsfc/rsfc_tests/rsfc_tests.py +++ /dev/null @@ -1,1211 +0,0 @@ -from rsfc.utils import constants -from rsfc.model import check as ch -import regex as re -import requests -from rsfc.utils import rsfc_helpers - - -################################################### FRSM_01 ################################################### - -def test_id_presence_and_resolves(somef_data): - if 'identifier' in somef_data: - for item in somef_data['identifier']: - if "source" in item: - if 'README' in item['source']: - id = item['result']['value'] - - if id.startswith('http://') or id.startswith('https://'): - try: - response = requests.head(id, allow_redirects=True, timeout=10) - if response.status_code == 200: - output = "true" - evidence = constants.EVIDENCE_ID_FOUND_AND_RESOLVES.format(id=id) - suggest = "No suggestions" - else: - output = "false" - evidence = constants.EVIDENCE_NO_ID_RESOLVE.format(id=id) - suggest = constants.SUGGEST_IDENTIFIER_NO_RESOLVE - except requests.RequestException: - output = "error" - evidence = "Something went wrong when trying to resolve the identifier" - suggest = None - else: - output = "false" - evidence = constants.EVIDENCE_ID_NOT_URL.format(id=id) - suggest = constants.SUGGEST_IDENTIFIER_NOT_HTTP - else: - output = "false" - evidence = constants.EVIDENCE_NO_IDENTIFIER_FOUND_README - suggest = constants.SUGGEST_NO_IDENTIFIER_README - else: - output = "false" - evidence = constants.EVIDENCE_NO_IDENTIFIER_FOUND - suggest = constants.SUGGEST_NO_IDENTIFIER - - - check = ch.Check(constants.INDICATORS_DICT['persistent_and_unique_identifier'], 'RSFC-01-1', "There is an identifier and resolves", constants.PROCESS_IDENTIFIER, output, evidence, suggest) - - return check.convert() - - -def test_id_common_schema(somef_data): - if 'identifier' in somef_data: - compiled_patterns = [] - for pattern in constants.ID_SCHEMA_REGEX_LIST: - compiled = re.compile(pattern, re.IGNORECASE) - compiled_patterns.append(compiled) - - output = "true" - evidence = constants.EVIDENCE_ID_COMMON_SCHEMA - suggest = "No suggestions" - - for item in somef_data['identifier']: - if item['source']: - if 'README' in item['source']: - if not any(pattern.match(item['result']['value']) for pattern in compiled_patterns): - output = "false" - evidence = constants.EVIDENCE_NO_ID_COMMON_SCHEMA - suggest = constants.SUGGEST_IDENTIFIER_SCHEME - break - else: - output = "false" - evidence = constants.EVIDENCE_NO_IDENTIFIER_FOUND_README - suggest = constants.SUGGEST_NO_IDENTIFIER - - - check = ch.Check(constants.INDICATORS_DICT['persistent_and_unique_identifier'], 'RSFC-01-3', "Software identifier follows a proper schema", constants.PROCESS_ID_PROPER_SCHEMA, output, evidence, suggest) - - return check.convert() - - -def test_id_associated_with_software(somef_data, codemeta_data, cff_data): - - id_locations = { - 'codemeta': False, - 'cff': False, - 'readme': False - } - - if codemeta_data != None and codemeta_data["identifier"] != None: - id_locations["codemeta"] = True - - if cff_data != None and cff_data["identifiers"] != None: - id_locations["cff"] = True - - if 'identifier' in somef_data: - for item in somef_data['identifier']: - if 'source' in item: - if 'README.md' in item['source']: - id_locations['readme'] = True - - - if any(id_locations.values()): - output = "true" - evidence = constants.EVIDENCE_SOME_ID_ASSOCIATED_WITH_SOFTWARE - suggest = "No suggestions" - - existing_id_locations = [key for key, value in id_locations.items() if not value] - existing_id_locations_txt = ', '.join(existing_id_locations) - - evidence += existing_id_locations_txt - else: - output = "false" - evidence = constants.EVIDENCE_NO_ID_ASSOCIATED_WITH_SOFTWARE - suggest = constants.SUGGEST_IDENTIFIER_ASSOCIATED - - - check = ch.Check(constants.INDICATORS_DICT['persistent_and_unique_identifier'], 'RSFC-01-2', "There is an identifier associated with the software", constants.PROCESS_ID_ASSOCIATED_WITH_SOFTWARE, output, evidence, suggest) - - return check.convert() - - - -################################################### FRSM_03 ################################################### - - -def test_version_number_in_metadata(somef_data, codemeta_data, cff_data): - - cff = False - codemeta = False - somef = False - - if cff_data != None and cff_data['version'] != None: - cff = True - - if codemeta_data != None and codemeta_data['version'] != None: - codemeta = True - - if 'version' in somef_data: - somef = True - - if cff or codemeta or somef: - output = "true" - evidence = constants.EVIDENCE_VERSION_IN_METADATA - suggest = "No suggestions" - else: - output = "false" - evidence = constants.EVIDENCE_NO_VERSION_IN_METADATA - suggest = constants.SUGGEST_NO_VERSION_IN_METADATA - - check = ch.Check(constants.INDICATORS_DICT['descriptive_metadata'], 'RSFC-03-6', "Version number in metadata", constants.PROCESS_VERSION_IN_METADATA, output, evidence, suggest) - - return check.convert() - - -def test_has_releases(somef_data): - if 'releases' not in somef_data: - output = "false" - evidence = constants.EVIDENCE_NO_RELEASES - suggest = constants.SUGGEST_NO_RELEASES - else: - output = "true" - evidence = constants.EVIDENCE_RELEASES - suggest = "No suggestions" - for item in somef_data['releases']: - if 'type' in item['result']: - if item['result']['type'] == 'Release': - if 'name' in item['result']: - evidence += f'\n\t- {item["result"]["name"]}' - elif 'tag' in item['result']: - evidence += f'\n\t- {item["result"]["tag"]}' - else: - evidence += f'\n\t- {item["result"]["url"]}' - - check = ch.Check(constants.INDICATORS_DICT['has_releases'], 'RSFC-03-1', "Software has releases", constants.PROCESS_RELEASES, output, evidence, suggest) - - return check.convert() - - -def test_release_id_and_version(somef_data): - if 'releases' not in somef_data: - output = "false" - evidence = constants.EVIDENCE_NO_RELEASES - suggest = constants.SUGGEST_NO_RELEASES - else: - results = somef_data['releases'] - for item in results: - if item['result']['url'] and item['result']['tag']: - output = "true" - evidence = constants.EVIDENCE_RELEASE_ID_AND_VERSION - suggest = "No suggestions" - else: - output = "false" - evidence = constants.EVIDENCE_NO_RELEASE_ID_AND_VERSION - suggest = constants.SUGGEST_NO_RELEASE_ID_AND_VERSION - break - - check = ch.Check(constants.INDICATORS_DICT['has_releases'], 'RSFC-03-2', "Releases have an id and version number", constants.PROCESS_RELEASE_ID_VERSION, output, evidence, suggest) - - return check.convert() - - -def test_semantic_versioning_standard(somef_data): - - if 'releases' not in somef_data: - output = "false" - evidence = constants.EVIDENCE_NO_RELEASES - suggest = constants.SUGGEST_NO_RELEASES - else: - compiled_patterns = [] - for pattern in constants.VERSIONING_REGEX_LIST: - compiled = re.compile(pattern) - compiled_patterns.append(compiled) - - results = somef_data['releases'] - for item in results: - if item['result']['tag']: - if any(pattern.match(item['result']['tag']) for pattern in compiled_patterns): - output = "true" - else: - output = "false" - evidence = constants.EVIDENCE_NO_VERSIONING_STANDARD - suggest = constants.SUGGEST_NO_VERSIONING_STANDARD - break - - if output == "true": - evidence = constants.EVIDENCE_VERSIONING_STANDARD - suggest = "No suggestions" - - check = ch.Check(constants.INDICATORS_DICT['versioning_standards_use'], 'RSFC-03-3', "Release versions follow a community established convention", constants.PROCESS_SEMANTIC_VERSIONING, output, evidence, suggest) - - return check.convert() - - -def test_version_scheme(somef_data): - if 'releases' not in somef_data: - output = "false" - evidence = constants.EVIDENCE_NO_RELEASES - suggest = constants.SUGGEST_NO_RELEASES - else: - scheme = '' - results = somef_data['releases'] - for item in results: - if item['result']['url']: - url = item['result']['url'] - if not scheme: - scheme = rsfc_helpers.build_url_pattern(url) - if not scheme.match(url): - output = "false" - evidence = constants.EVIDENCE_NO_IDENTIFIER_SCHEME_COMPLIANT - suggest = constants.SUGGEST_NO_IDENTIFIER_SCHEME_COMPLIANT - else: - output = "true" - - if output == "true": - evidence = constants.EVIDENCE_IDENTIFIER_SCHEME_COMPLIANT - suggest = "No suggestions" - - check = ch.Check(constants.INDICATORS_DICT['has_releases'], 'RSFC-03-4', "Release identifiers follow the same scheme", constants.PROCESS_VERSION_SCHEME, output, evidence, suggest) - - return check.convert() - - - -def test_latest_release_consistency(somef_data): - latest_release = None - version = None - - if 'releases' in somef_data: - latest_release = rsfc_helpers.get_latest_release(somef_data) - - if 'version' in somef_data: - version_data = somef_data['version'][0]['result'] - version = version_data.get('tag') or version_data.get('value') - - if version == None or latest_release == None: - output = "error" - evidence = constants.EVIDENCE_NOT_ENOUGH_RELEASE_INFO - suggest = None - elif version == latest_release: - output = "true" - evidence = constants.EVIDENCE_RELEASE_CONSISTENCY - suggest = "No suggestions" - else: - output = "false" - evidence = constants.EVIDENCE_NO_RELEASE_CONSISTENCY - suggest = constants.SUGGEST_NO_RELEASE_CONSISTENCY - - - check = ch.Check(constants.INDICATORS_DICT['has_releases'], 'RSFC-03-5', "Last release consistency", constants.PROCESS_RELEASE_CONSISTENCY, output, evidence, suggest) - - return check.convert() - -################################################### FRSM_04 ################################################### - -def test_metadata_exists(somef_data, codemeta_data, cff_data): - - metadata_files = { - 'cff': False, - 'codemeta': False, - 'package_file': False - } - - if cff_data != None: - metadata_files['cff'] = True - - if codemeta_data != None: - metadata_files['codemeta'] = True - - if 'has_package_file' in somef_data: - metadata_files['package_file'] = True - - if all(metadata_files.values()): - output = "true" - evidence = constants.EVIDENCE_METADATA_EXISTS - suggest = "No suggestions" - else: - output = "false" - evidence = constants.EVIDENCE_NO_METADATA_EXISTS - suggest = constants.SUGGEST_NO_METADATA_FILES - - missing_metadata = [key for key, value in metadata_files.items() if not value] - missing_metadata_txt = ', '.join(missing_metadata) - - evidence += missing_metadata_txt - - - check = ch.Check(constants.INDICATORS_DICT['descriptive_metadata'], 'RSFC-04-1', "Metadata exists", constants.PROCESS_METADATA_EXISTS, output, evidence, suggest) - - return check.convert() - - -def test_readme_exists(somef_data): - if 'readme_url' in somef_data: - output = "true" - evidence = constants.EVIDENCE_DOCUMENTATION_README - suggest = "No suggestions" - else: - output = "false" - evidence = constants.EVIDENCE_NO_DOCUMENTATION_README - suggest = constants.SUGGEST_NO_README - - check = ch.Check(constants.INDICATORS_DICT['software_has_documentation'], 'RSFC-04-2', "There is a README", constants.PROCESS_README, output, evidence, suggest) - - return check.convert() - - -def test_title_description(somef_data): - if 'full_title' in somef_data: - title = True - else: - title = False - - if 'description' in somef_data: - desc = True - else: - desc = False - - if title and desc: - output = "true" - evidence = constants.EVIDENCE_TITLE_AND_DESCRIPTION - suggest = "No suggestions" - elif title and not desc: - output = "false" - evidence = constants.EVIDENCE_NO_DESCRIPTION - suggest = constants.SUGGEST_NO_DESCRIPTION - elif desc and not title: - output = "false" - evidence = constants.EVIDENCE_NO_TITLE - suggest = constants.SUGGEST_NO_TITLE - else: - output = "false" - evidence = constants.EVIDENCE_NO_TITLE_AND_DESCRIPTION - suggest = constants.SUGGEST_NO_TITLE_DESCRIPTION - - - check = ch.Check(constants.INDICATORS_DICT['descriptive_metadata'], 'RSFC-04-3', "There are title and description", constants.PROCESS_TITLE_DESCRIPTION, output, evidence, suggest) - - return check.convert() - - -def test_descriptive_metadata(somef_data): - - metadata = { - 'description': None, - 'programming_languages': None, - 'date_created': None, - 'keywords': None - } - - metadata = {key: key in somef_data for key in metadata} - - - if all(metadata.values()): - output = "true" - evidence = constants.EVIDENCE_DESCRIPTIVE_METADATA - suggest = "No suggestions" - else: - output = "false" - evidence = constants.EVIDENCE_NO_DESCRIPTIVE_METADATA - suggest = constants.SUGGEST_NO_DESCRIPTIVE_METADATA - - missing_metadata = [key for key, value in metadata.items() if not value] - missing_metadata_txt = ', '.join(missing_metadata) - - evidence += missing_metadata_txt - - check = ch.Check(constants.INDICATORS_DICT['descriptive_metadata'], 'RSFC-04-4', "Software has descriptive metadata", constants.PROCESS_DESCRIPTIVE_METADATA, output, evidence, suggest) - - return check.convert() - - - -def test_codemeta_exists(codemeta_data): - if codemeta_data != None: - output = "true" - evidence = constants.EVIDENCE_METADATA_CODEMETA - suggest = "No suggestions" - else: - output = "false" - evidence = constants.EVIDENCE_NO_METADATA_CODEMETA - suggest = constants.SUGGEST_NO_CODEMETA - - check = ch.Check(constants.INDICATORS_DICT['descriptive_metadata'], 'RSFC-04-5', "There is a codemeta file", constants.PROCESS_CODEMETA, output, evidence, suggest) - - return check.convert() - -################################################### FRSM_05 ################################################### - -def test_repo_status(somef_data): - if 'repository_status' in somef_data: - output = "true" - evidence = constants.EVIDENCE_REPO_STATUS - suggest = "No suggestions" - else: - output = "false" - evidence = constants.EVIDENCE_NO_REPO_STATUS - suggest = constants.SUGGEST_NO_REPO_STATUS - - - check = ch.Check(constants.INDICATORS_DICT['version_control_use'], 'RSFC-05-1', "There is a repostatus badge", constants.PROCESS_REPO_STATUS, output, evidence, suggest) - - return check.convert() - - -def test_contact_support_documentation(somef_data): - sources = { - 'contact': None, - 'support': None, - 'support_channels': None - } - - sources = {key: key in somef_data for key in sources} - - - if all(sources.values()): - output = "true" - evidence = constants.EVIDENCE_CONTACT_INFO - suggest = "No suggestions" - else: - output = "false" - evidence = constants.EVIDENCE_NO_CONTACT_INFO - suggest = constants.SUGGEST_NO_CONTACT_INFO - - missing_sources = [key for key, value in sources.items() if not value] - missing_sources_txt = ', '.join(missing_sources) - - evidence += missing_sources_txt - - check = ch.Check(constants.INDICATORS_DICT['software_has_documentation'], 'RSFC-05-2', "There is contact and/or support metadata", constants.PROCESS_CONTACT_SUPPORT_DOCUMENTATION, output, evidence, suggest) - - return check.convert() - - -def test_software_documentation(somef_data): - rtd = False - readme = False - - sources = '' - - if 'documentation' in somef_data: - for item in somef_data['documentation']: - if 'readthedocs' in item['result']['value']: - rtd = True - if item['source'] not in sources: - sources += f"\t\n- {item['source']}" - if 'readme_url' in somef_data: - readme = True - for item in somef_data['readme_url']: - if item['result']['value'] not in sources: - sources += f"\t\n- {item['result']['value']}" - - - if not readme and not rtd: - output = "false" - evidence = constants.EVIDENCE_NO_README_AND_READTHEDOCS - suggest = constants.SUGGEST_NO_README_AND_READTHEDOCS - else: - evidence = constants.EVIDENCE_DOCUMENTATION + sources - output = "true" - suggest = "No suggest" - - - check = ch.Check(constants.INDICATORS_DICT['software_has_documentation'], 'RSFC-05-3', "Software documentation", constants.PROCESS_DOCUMENTATION, output, evidence, suggest) - - return check.convert() - -################################################### FRSM_06 ################################################### - -def test_authors(somef_data, codemeta_data, cff_data): - - if 'authors' in somef_data: - evidence = constants.EVIDENCE_AUTHORS - output = "true" - suggest = "No suggestions" - elif codemeta_data != None and codemeta_data["author"] != None: - output = "true" - evidence = constants.EVIDENCE_AUTHORS - suggest = "No suggestions" - elif cff_data != None and cff_data["authors"] != None: - output = "true" - evidence = constants.EVIDENCE_AUTHORS - suggest = "No suggestions" - else: - evidence = constants.EVIDENCE_NO_AUTHORS - output = "false" - suggest = constants.SUGGEST_NO_AUTHORS - - - check = ch.Check(constants.INDICATORS_DICT['descriptive_metadata'], 'RSFC-06-1', "Authors are declared", constants.PROCESS_AUTHORS, output, evidence, suggest) - - return check.convert() - - -def test_contributors(somef_data, codemeta_data): - - if 'contributors' in somef_data: - output = "true" - evidence = constants.EVIDENCE_CONTRIBUTORS - suggest = "No suggestions" - elif codemeta_data != None and codemeta_data["contributor"] != None: - output = "true" - evidence = constants.EVIDENCE_CONTRIBUTORS - suggest = "No suggestions" - else: - evidence = constants.EVIDENCE_NO_CONTRIBUTORS - output = "false" - suggest = constants.SUGGEST_NO_CONTRIBUTORS - - check = ch.Check(constants.INDICATORS_DICT['descriptive_metadata'], 'RSFC-06-2', "Contributors are declared", constants.PROCESS_CONTRIBUTORS, output, evidence, suggest) - - return check.convert() - - -def test_authors_orcids(codemeta_data, cff_data): - author_orcids_codemeta = False - author_orcids_cff = None - - if codemeta_data != None: - if codemeta_data["author"] != None: - author_orcids_codemeta = rsfc_helpers.subtest_author_orcids(codemeta_data) - - if cff_data != None: - if cff_data["authors"] != None: - author_orcids_cff = rsfc_helpers.subtest_author_orcids(cff_data) - - if author_orcids_codemeta and author_orcids_cff: - output = "true" - evidence = constants.EVIDENCE_AUTHOR_ORCIDS_BOTH - suggest = "No suggestions" - elif author_orcids_codemeta: - output = "true" - evidence = constants.EVIDENCE_AUTHOR_ORCIDS_CODEMETA - suggest = "No suggestions" - elif author_orcids_cff: - output = "true" - evidence = constants.EVIDENCE_AUTHOR_ORCIDS_CFF - suggest = "No suggestions" - else: - output = "false" - evidence = constants.EVIDENCE_NO_AUTHOR_ORCIDS - suggest = constants.SUGGEST_NO_AUTHOR_ORCIDS - - check = ch.Check(constants.INDICATORS_DICT['descriptive_metadata'], 'RSFC-06-3', "Authors have an ORCID", constants.PROCESS_AUTHOR_ORCIDS, output, evidence, suggest) - - return check.convert() - - -def test_author_roles(codemeta_data): - - if codemeta_data != None: - if codemeta_data["author"] != None: - author_roles = rsfc_helpers.subtest_author_roles(codemeta_data["author"]) - - if all(value is not None for value in author_roles.values()): - output = "true" - evidence = constants.EVIDENCE_AUTHOR_ROLES - suggest = "No suggestions" - else: - output = "false" - evidence = constants.EVIDENCE_NO_ALL_AUTHOR_ROLES - suggest = constants.SUGGEST_NO_ALL_AUTHOR_ROLES - else: - output = "false" - evidence = constants.EVIDENCE_NO_AUTHORS_IN_CODEMETA - suggest = constants.SUGGEST_NO_AUTHORS_IN_CODEMETA - else: - output = "false" - evidence = constants.EVIDENCE_NO_CODEMETA_FOUND - suggest = constants.SUGGEST_NO_CODEMETA - - check = ch.Check(constants.INDICATORS_DICT['descriptive_metadata'], 'RSFC-06-4', "Authors have roles", constants.PROCESS_AUTHOR_ROLES, output, evidence, suggest) - - return check.convert() - -################################################### FRSM_07 ################################################### - -def test_identifier_in_readme_citation(somef_data, cff_data): - readme = False - citation = False - - if 'identifier' in somef_data: - readme = True - - if cff_data != None: - if cff_data["identifiers"] != None: - citation = True - - if readme and not citation: - output = "true" - evidence = constants.EVIDENCE_IDENTIFIER_IN_README - suggest = "No suggestions" - elif citation and not readme: - output = "true" - evidence = constants.EVIDENCE_IDENTIFIER_IN_CITATION - suggest = "No suggestions" - elif citation and readme: - output = "true" - evidence = constants.EVIDENCE_IDENTIFIER_IN_README_AND_CITATION - suggest = "No suggestions" - else: - output = "false" - evidence = constants.EVIDENCE_NO_IDENTIFIER_IN_README_OR_CITATION - suggest = constants.SUGGEST_NO_IDENTIFIER_IN_README_OR_CITATION - - - check = ch.Check(constants.INDICATORS_DICT['persistent_and_unique_identifier'], 'RSFC-07-1', "There is an identifier in README or CITATION.cff", constants.PROCESS_IDENTIFIER_IN_README_CITATION, output, evidence, suggest) - - return check.convert() - - -def test_identifier_resolves_to_software(somef_data, codemeta_data, cff_data, repo_url): - - output = "false" - evidence = constants.EVIDENCE_NO_IDENTIFIER_FOUND - suggest = constants.SUGGEST_NO_IDENTIFIER - identifier = None - pause = False - - if 'identifier' in somef_data: - for item in somef_data['identifier']: - if item['source']: - if 'README' in item['source']: - identifier = item['result']['value'] - pause = True - break - - if not pause and codemeta_data != None and codemeta_data['identifier']: - identifier = codemeta_data['identifier'] - - if not pause and cff_data != None and cff_data['identifiers'] != None: - identifier = cff_data['identifiers'][0]['value'] - - if identifier: - doi_url = rsfc_helpers.normalize_identifier_url(identifier) - try: - resp = requests.get(doi_url, allow_redirects=True, timeout=10) - html = resp.text - - if rsfc_helpers.landing_page_links_back(html, repo_url): - output = "true" - evidence = constants.EVIDENCE_DOI_LINKS_BACK_TO_REPO - suggest = "No suggestions" - else: - output = "false" - evidence = constants.EVIDENCE_DOI_NO_LINK_BACK_TO_REPO - suggest = constants.SUGGEST_DOI_NO_LINK_BACK_TO_REPO - - except requests.RequestException: - output = "false" - evidence = constants.EVIDENCE_NO_RESOLVE_DOI_IDENTIFIER - suggest = constants.SUGGEST_IDENTIFIER_NO_RESOLVE - - - check = ch.Check(constants.INDICATORS_DICT['persistent_and_unique_identifier'], 'RSFC-07-2', "Software identifier resolves to software", constants.PROCESS_ID_RESOLVES_TO_SOFTWARE, output, evidence, suggest) - - return check.convert() - -################################################### FRSM_08 ################################################### - -def test_metadata_record_in_zenodo_or_software_heritage(somef_data): #CAMBIAR - zenodo = False - swh = False - - if 'identifier' in somef_data: - for item in somef_data['identifier']: - if item['result']['value'] and ('zenodo' in item['result']['value'] or 'softwareheritage' in item['result']['value']): - if 'zenodo' in item['result']['value']: - zenodo = True - elif 'softwareheritage' in item['result']['value']: - swh = True - else: - continue - - if zenodo and swh: - output = "true" - evidence = constants.EVIDENCE_ZENODO_DOI_AND_SOFTWARE_HERITAGE - suggest = "No suggestions" - elif swh: - output = "true" - evidence = constants.EVIDENCE_SOFTWARE_HERITAGE_BADGE - suggest = "No suggestions" - elif zenodo: - output = "true" - evidence = constants.EVIDENCE_ZENODO_DOI - suggest = "No suggestions" - else: - output = "false" - evidence = constants.EVIDENCE_NO_ZENODO_DOI_OR_SOFTWARE_HERITAGE - suggest = constants.SUGGEST_ARCHIVE_SOFTWARE - - - check = ch.Check(constants.INDICATORS_DICT['archived_in_software_heritage'], 'RSFC-08-1', "Metadata record in Software Heritage or Zenodo", constants.PROCESS_ZENODO_SOFTWARE_HERITAGE, output, evidence, suggest) - - return check.convert() - -################################################### FRSM_09 ################################################### - -def test_is_github_repository(repo_url): - - if 'github.com' in repo_url or 'gitlab.com' in repo_url: - response = requests.head(repo_url, allow_redirects=True, timeout=5) - if response.status_code == 200: - output = "true" - evidence = constants.EVIDENCE_IS_IN_GITHUB_OR_GITLAB - suggest = "No suggestions" - elif response.status_code == 404: - output = "false" - evidence = constants.EVIDENCE_NO_RESOLVE_GITHUB_OR_GITLAB_URL - suggest = "No suggestions" - else: - output = "error" - evidence = 'Connection error' - suggest = "No suggestions" - else: - output = "false" - evidence = constants.EVIDENCE_NO_GITHUB_OR_GITLAB_URL - suggest = "No suggestions" - - check = ch.Check(constants.INDICATORS_DICT['version_control_use'], 'RSFC-09-1', "Repository is from Github/Gitlab", constants.PROCESS_IS_GITHUB_OR_GITLAB_REPOSITORY, output, evidence, suggest) - - return check.convert() - -################################################### FRSM_12 ################################################### - -def test_reference_publication(somef_data, codemeta_data): - - referencePub = False - - if codemeta_data != None and codemeta_data["referencePublication"] != None: - referencePub = True - - article_citation = False - - if 'citation' in somef_data: - for item in somef_data['citation']: - if 'format' in item['result'] and item['result']['format'] == 'bibtex': - article_citation = True - break - - - if article_citation and referencePub: - output = "true" - evidence = constants.EVIDENCE_REFERENCE_PUBLICATION_AND_CITATION_TO_ARTICLE - suggest = "No suggestions" - elif article_citation: - output = "true" - evidence = constants.EVIDENCE_CITATION_TO_ARTICLE - suggest = "No suggestions" - elif referencePub: - output = "true" - evidence = constants.EVIDENCE_REFERENCE_PUBLICATION - suggest = "No suggestions" - else: - output = "false" - evidence = constants.EVIDENCE_NO_REFERENCE_PUBLICATION_OR_CITATION_TO_ARTICLE - suggest = constants.SUGGEST_NO_REFPUB_OR_ARTICLE - - - check = ch.Check(constants.INDICATORS_DICT['software_has_citation'], 'RSFC-12-1', "There is an article citation or reference publication", constants.PROCESS_REFERENCE_PUBLICATION, output, evidence, suggest) - - return check.convert() - -################################################### FRSM_13 ################################################### - -def test_dependencies_declared(somef_data): - if 'requirements' not in somef_data: - output = "false" - evidence = constants.EVIDENCE_NO_DEPENDENCIES - suggest = constants.SUGGEST_NO_DEPENDENCIES - else: - output = "true" - evidence = constants.EVIDENCE_DEPENDENCIES - suggest = "No suggestions" - - for item in somef_data['requirements']: - if 'source' in item: - if item['source'] not in evidence: - evidence += f'\n\t- {item["source"]}' - - check = ch.Check(constants.INDICATORS_DICT['requirements_specified'], 'RSFC-13-1', "Dependencies are declared", constants.PROCESS_REQUIREMENTS, output, evidence, suggest) - - return check.convert() - - -def test_installation_instructions(somef_data): - if 'installation' in somef_data: - output = "true" - evidence = constants.EVIDENCE_INSTALLATION - suggest = "No suggestions" - else: - output = "false" - evidence = constants.EVIDENCE_NO_INSTALLATION - suggest = constants.SUGGEST_NO_INSTALL_INSTRUCTIONS - - check = ch.Check(constants.INDICATORS_DICT['software_has_documentation'], 'RSFC-13-2', "There are installation instructions", constants.PROCESS_INSTALLATION, output, evidence, suggest) - - return check.convert() - - -def test_dependencies_have_version(somef_data): - if 'requirements' not in somef_data: - output = "false" - evidence = constants.EVIDENCE_NO_DEPENDENCIES - suggest = constants.SUGGEST_NO_DEPENDENCIES - else: - output = "true" - evidence = constants.EVIDENCE_DEPENDENCIES_VERSION - suggest = "No suggestions" - for item in somef_data['requirements']: - if 'README' not in item['source']: - if not item["result"].get("version"): - output = "false" - evidence = constants.EVIDENCE_NO_DEPENDENCIES_VERSION - suggest = constants.SUGGEST_NO_DEPENDENCIES_VERSION - break - - check = ch.Check(constants.INDICATORS_DICT['requirements_specified'], 'RSFC-13-3', "Dependencies have version numbers", constants.PROCESS_DEPENDENCIES_VERSION, output, evidence, suggest) - - return check.convert() - - -def test_dependencies_in_machine_readable_file(somef_data): - if 'requirements' not in somef_data: - output = "false" - evidence = constants.EVIDENCE_NO_DEPENDENCIES - suggest = constants.SUGGEST_NO_DEPENDENCIES - else: - output = "false" - evidence = constants.EVIDENCE_NO_DEPENDENCIES_MACHINE_READABLE_FILE - suggest = constants.SUGGEST_NO_MACHINE_READABLE_DEPENDENCIES - - for item in somef_data['requirements']: - if item['source'] and 'README' not in item['source']: - output = "true" - evidence = constants.EVIDENCE_DEPENDENCIES_MACHINE_READABLE_FILE - suggest = "No suggestions" - break - - check = ch.Check(constants.INDICATORS_DICT['requirements_specified'], 'RSFC-13-4', "There is a dependencies machine-readable file", constants.PROCESS_DEPENDENCIES_MACHINE_READABLE_FILE, output, evidence, suggest) - - return check.convert() - - -################################################### FRSM_14 ################################################### - -def test_presence_of_tests(gh): - - test_evidences = gh.tests - - if test_evidences: - rx = re.compile(r'tests?', re.IGNORECASE) - sources = "" - - for e in test_evidences: - path = e["path"] - if rx.search(path): - sources += f"\t\n- {path}" - - if sources: - output = "true" - evidence = constants.EVIDENCE_TESTS + sources - suggest = "No suggestions" - else: - output = "false" - evidence = constants.EVIDENCE_NO_TESTS - suggest = constants.SUGGEST_NO_TESTS - else: - output = "error" - evidence = None - suggest = constants.SUGGEST_NO_TESTS - - check = ch.Check(constants.INDICATORS_DICT['software_has_tests'], 'RSFC-14-1', "Presence of tests in repository", constants.PROCESS_TESTS, output, evidence, suggest) - - return check.convert() - - -def test_github_action_tests(somef_data): - sources = '' - - if 'continuous_integration' not in somef_data: - output = "false" - evidence = constants.EVIDENCE_NO_WORKFLOWS - suggest = constants.SUGGEST_NO_WORKFLOWS - - else: - for item in somef_data['continuous_integration']: - if item['result']['value'] and ('.github/workflows' in item['result']['value'] or '.gitlab-ci.yml' in item['result']['value']): - if 'test' in item['result']['value'] or 'tests' in item['result']['value']: - sources += f'\t\n- {item["result"]["value"]}' - - if sources: - output = "true" - evidence = constants.EVIDENCE_AUTOMATED_TESTS + sources - suggest = "No suggestions" - - else: - output = "false" - evidence = constants.EVIDENCE_NO_AUTOMATED_TESTS - suggest = constants.SUGGEST_NO_TEST_ACTIONS - - - check = ch.Check(constants.INDICATORS_DICT['repository_workflows'], 'RSFC-14-2', "There are actions to automate tests", constants.PROCESS_AUTOMATED_TESTS, output, evidence, suggest) - - return check.convert() - -################################################### FRSM_15 ################################################### - -def test_has_license(somef_data): - if 'license' not in somef_data: - output = "false" - evidence = constants.EVIDENCE_NO_LICENSE - suggest = constants.SUGGEST_NO_LICENSE - else: - output = "true" - evidence = constants.EVIDENCE_LICENSE - suggest = "No suggestions" - for item in somef_data['license']: - if 'source' in item: - evidence += f'\n\t- {item["source"]}' - - check = ch.Check(constants.INDICATORS_DICT['software_has_license'], 'RSFC-15-1', "Software has license", constants.PROCESS_LICENSE, output, evidence, suggest) - - return check.convert() - - -def test_license_spdx_compliant(somef_data): - output = "false" - evidence = None - if 'license' not in somef_data: - output = "false" - evidence = constants.EVIDENCE_NO_LICENSE - suggest = constants.SUGGEST_NO_LICENSE - else: - for item in somef_data['license']: - if 'result' in item and 'spdx_id' in item['result']: - if item['result']['spdx_id'] in constants.SPDX_LICENSE_WHITELIST: - output = "true" - else: - output = "false" - evidence = constants.EVIDENCE_NO_SPDX_COMPLIANT - suggest = constants.SUGGEST_NO_LICENSE_SPDX - break - - if output == "true": - evidence = constants.EVIDENCE_SPDX_COMPLIANT - suggest = "No suggestions" - elif output == "false" and evidence == None: - evidence = constants.EVIDENCE_LICENSE_NOT_CLEAR - suggest = "No suggestions" - - check = ch.Check(constants.INDICATORS_DICT['software_has_license'], 'RSFC-15-2', "License is SPDX compliant", constants.PROCESS_LICENSE_SPDX_COMPLIANT, output, evidence, suggest) - - return check.convert() - - -'''def test_license_information_provided(somef_data): - - if 'license' not in somef_data: - output = "false" - evidence = constants.EVIDENCE_NO_LICENSE - suggest = constants.SUGGEST_NO_LICENSE - else: - output = "false" - evidence = constants.EVIDENCE_NO_LICENSE_INFORMATION_PROVIDED - suggest = constants.SUGGEST_NO_LICENSE_INFO - for item in somef_data['license']: - if 'source' in item: - if 'README' in item['source']: - output = "true" - evidence = constants.EVIDENCE_LICENSE_INFORMATION_PROVIDED - suggest = "No suggestions" - - - check = ch.Check(constants.INDICATORS_DICT['software_has_license'], 'RSFC-15-3', "License information is provided", constants.PROCESS_LICENSE_INFORMATION_PROVIDED, output, evidence, suggest) - - return check.convert()''' - -################################################### FRSM_16 ################################################### - -def test_license_info_in_metadata_files(somef_data, codemeta_data, cff_data): - - license_info = { - 'codemeta': False, - 'citation': False, - 'package': False - } - - if 'license' in somef_data: - for item in somef_data['license']: - if 'source' in item: - if 'pyproject.toml' in item['source'] or 'setup.py' in item['source'] or 'node.json' in item['source'] or 'pom.xml' in item['source'] or 'package.json' in item['source']: - license_info['package'] = True - break - - if cff_data != None and cff_data["license"] != None: - license_info["citation"] = True - - if codemeta_data != None and codemeta_data["license"] != None: - license_info["codemeta"] = True - - - if all(license_info.values()): - output = "true" - evidence = constants.EVIDENCE_LICENSE_INFO_IN_METADATA - suggest = "No suggestions" - else: - output = "false" - evidence = constants.EVIDENCE_NO_LICENSE_INFO_IN_METADATA - suggest = constants.SUGGEST_NO_LICENSE_INFO_METADATA - - missing_license_info= [key for key, value in license_info.items() if not value] - missing_license_info_txt = ', '.join(missing_license_info) - - evidence += missing_license_info_txt - - - check = ch.Check(constants.INDICATORS_DICT['software_has_license'], 'RSFC-16-1', "License referenced in metadata files", constants.PROCESS_LICENSE_INFO_IN_METADATA_FILES, output, evidence, suggest) - - return check.convert() - -################################################### FRSM_17 ################################################### - -def test_repo_enabled_and_commits(somef_data, gh): - - if 'repository_status' in somef_data and somef_data['repository_status'][0]['result']['value']: - if '#active' in somef_data['repository_status'][0]['result']['value']: - repo = True - else: - repo = False - else: - repo = False - - commits = gh.commits - - if repo: - if commits: - output = "true" - evidence = constants.EVIDENCE_REPO_ENABLED_AND_HAS_COMMITS - suggest = "No suggestions" - else: - output = "false" - evidence = constants.EVIDENCE_NO_COMMITS - suggest = constants.SUGGEST_NO_COMMITS - - else: - output = "false" - evidence = constants.EVIDENCE_NO_REPO_STATUS - suggest = constants.SUGGEST_NO_ACTIVE_REPO - - - check = ch.Check(constants.INDICATORS_DICT['version_control_use'], 'RSFC-17-1', "Repository active", constants.PROCESS_REPO_ENABLED_AND_COMMITS, output, evidence, suggest) - - return check.convert() - - -def test_commit_history(gh): - - commits = gh.commits - - if commits != []: - output = "true" - evidence = constants.EVIDENCE_COMMITS - suggest = "No suggestions" - else: - output = "false" - evidence = constants.EVIDENCE_NO_COMMITS - suggest = constants.SUGGEST_NO_COMMITS - - check = ch.Check(constants.INDICATORS_DICT['version_control_use'], 'RSFC-17-2', "Commit history", constants.PROCESS_COMMITS_HISTORY, output, evidence, suggest) - - return check.convert() - -def test_commits_linked_issues(gh): - - commits = gh.commits - issues = gh.issues - - if commits == [] or issues == []: - output = "false" - evidence = constants.EVIDENCE_NOT_ENOUGH_ISSUES_COMMITS_INFO - suggest = constants.SUGGEST_NO_COMMITS_OR_ISSUES - else: - linked = rsfc_helpers.cross_check_any_issue(issues, commits) - - if linked: - output = "true" - evidence = constants.EVIDENCE_COMMITS_LINKED_TO_ISSUES - suggest = "No suggestions" - else: - output = "false" - evidence = constants.EVIDENCE_NO_COMMITS_LINKED_TO_ISSUES - suggest = constants.SUGGEST_NO_ISSUES_LINK_COMMITS - - - check = ch.Check(constants.INDICATORS_DICT['version_control_use'], 'RSFC-17-3', "Commits are linked to issues", constants.PROCESS_COMMITS_LINKED_TO_ISSUES, output, evidence, suggest) - - return check.convert() - - -################################################### MISC ################################################### - - -def test_has_citation(somef_data): - if 'citation' not in somef_data: - output = "false" - evidence = constants.EVIDENCE_NO_CITATION - suggest = constants.SUGGEST_NO_CITATION - else: - output = "true" - evidence = constants.EVIDENCE_CITATION - suggest = "No suggestions" - for item in somef_data['citation']: - if 'source' in item: - if item['source'] not in evidence: - evidence += f'\n\t- {item["source"]}' - - check = ch.Check(constants.INDICATORS_DICT['software_has_citation'], 'RSFC-18-1', "Repository has citation", constants.PROCESS_CITATION, output, evidence, suggest) - - return check.convert() - - -def test_repository_workflows(somef_data): - - if 'continuous_integration' not in somef_data: - output = "false" - evidence = constants.EVIDENCE_NO_WORKFLOWS - suggest = constants.SUGGEST_NO_WORKFLOWS - else: - output = "true" - evidence = constants.EVIDENCE_WORKFLOWS - suggest = "No suggestions" - - for item in somef_data['continuous_integration']: - evidence += f'\n\t- {item["result"]["value"]}' - - check = ch.Check(constants.INDICATORS_DICT['repository_workflows'], 'RSFC-19-1', "Repository has workflows", constants.PROCESS_WORKFLOWS, output, evidence, suggest) - - return check.convert() - - -def test_has_issue_tracker(somef_data): - - if "issue_tracker" not in somef_data: - output = "false" - evidence = constants.EVIDENCE_NO_ISSUE_TRACKER - suggest = constants.SUGGEST_NO_ISSUE_TRACKER - else: - output = "true" - evidence = constants.EVIDENCE_ISSUE_TRACKER - suggest = "No suggestions" - - check = ch.Check(constants.INDICATORS_DICT['support_issue_tracking'], 'RSFC-20-1', "Repository has an issue tracker", constants.PROCESS_ISSUE_TRACKER, output, evidence, suggest) - - return check.convert() - -def test_has_contribution_guidelines(somef_data): - if "contributing_guidelines" not in somef_data: - output = "false" - evidence = constants.EVIDENCE_NO_CONTRIBUTION_GUIDELINES - suggest = constants.SUGGEST_NO_CONTRIBUTION_GUIDELINES - else: - output = "true" - evidence = constants.EVIDENCE_CONTRIBUTION_GUIDELINES - suggest = "No suggestions" - - for item in somef_data["contributing_guidelines"]: - evidence += f'\n\t- {item["source"]}' - - check = ch.Check(constants.INDICATORS_DICT['has_contribution_guidelines'], 'RSFC-21-1', "Repository has contribution guidelines", constants.PROCESS_CONTRIBUTION_GUIDELINES, output, evidence, suggest) - - return check.convert() \ No newline at end of file diff --git a/src/rsfc/utils/constants.py b/src/rsfc/utils/constants.py index 89a01c3..3b7c355 100644 --- a/src/rsfc/utils/constants.py +++ b/src/rsfc/utils/constants.py @@ -31,237 +31,301 @@ DOI_SCHEMA_REGEX = r'^10\.\d{4,9}/[-._;()/:A-Z0-9]+$' ORCID_SCHEMA_REGEX = r'^https?://orcid\.org/\d{4}-\d{4}-\d{4}-\d{3}[0-9X]$' -SWHID_SCHEMA_REGEX = r'^swh:1:[a-z]+:[0-9a-f]{40}$' +SWHID_SCHEMA_REGEX = r'(?:https?://archive\.softwareheritage\.org/)?swh:1:[a-z]+:[0-9a-f]{40}' URN_SCHEMA_REGEX = r'^urn:[a-z0-9][a-z0-9-]{1,31}:[\w\-.:\/?#\[\]@!$&\'()*+,;=]+$' GITHUB_SCHEMA_REGEX = r'^https?://github\.com/[^/]+/[^/]+/?$' ZENODO_BADGE_REGEX = r'^https?://zenodo\.org/badge/latestdoi/\d+$' DOI_URL_REGEX = r'^https?://doi\.org/10\.\d{4,9}/[-._;()/:A-Z0-9]+$' -#Processes - -PROCESS_LICENSE = "Searches for a file named 'LICENSE' or 'LICENSE.md' in the root of the repository." -PROCESS_LICENSE_INFO_IN_METADATA_FILES = 'Searches for licensing information in the codemeta, citation and package files if they exist' -PROCESS_LICENSE_SPDX_COMPLIANT = 'Checks if the licenses detected are SPDX compliant' -PROCESS_CITATION = "Searches for a CITATION.cff file and README file in the repository" -PROCESS_REQUIREMENTS = "Searches for dependencies in project configuration files, README and dependencies files such as requirements.txt" -PROCESS_RELEASES = "Searches for release tags in the repository" -PROCESS_RELEASE_ID_VERSION = 'Checks if all of the releases have an identifier and a version' -PROCESS_SEMANTIC_VERSIONING = 'Checks if all of the releases versions follow the SemVer or CalVer versioning standards' -PROCESS_VERSION_SCHEME = 'Checks if all of the version identifiers follow the same scheme' -PROCESS_VERSION_CONTROL_USE = "Searches for commits and branches in the repository" -PROCESS_WORKFLOWS = "Searches for workflows in the repository" -PROCESS_IDENTIFIER = "Searches for an identifier (i.e. DOI or SWHID) in the README file of the repository" -PROCESS_DESCRIPTIVE_METADATA = "Searches for description, programming languages, date of creation and keywords in the repository" -PROCESS_README = 'Searches for a README file in the repository' -PROCESS_DOCUMENTATION = "Searches for a README file in the root repository and other forms of documentation such as a Read The Docs badge or url" -PROCESS_CONTACT_SUPPORT_DOCUMENTATION = 'Searches for contact and support information in the repository' -PROCESS_INSTALLATION = 'Searches for installation instructions in the README file of the repository' -PROCESS_DEPENDENCIES_VERSION = 'Checks if all of the dependencies stated in the machine-readable file (e.g. requirements.txt, pyproject.toml, etc.) of the repository have a version indicated' -PROCESS_DEPENDENCIES_MACHINE_READABLE_FILE = 'Checks if dependencies are indicated in a machine-readable file' -PROCESS_ID_PROPER_SCHEMA = 'Checks if the identifiers associated with the software follow any of these schemas: DOI, URN, GITHUB and SWHID' -PROCESS_ID_ASSOCIATED_WITH_SOFTWARE = 'Searches for an identifier in the CITATION.cff, codemeta.json and README files' -PROCESS_AUTOMATED_TESTS = 'Searches for workflows that contain test or tests in their names' -PROCESS_TESTS = 'Searches for files and/or directories that mention test in their names' -PROCESS_RELEASE_CONSISTENCY = 'Checks if the latest release tag matches the version stated in the package file of the repository' -PROCESS_METADATA_EXISTS = 'Searches for codemeta, citation and package files in the repository' -PROCESS_TITLE_DESCRIPTION = 'Checks if there is a title and a description for the software in the metadata' -PROCESS_CODEMETA = 'Searches for a codemeta.json file in the repository' -PROCESS_REPO_STATUS = 'Searches for a repo status badge in the README file of the repository' -PROCESS_REPO_ENABLED_AND_COMMITS = 'Checks if there is a repo_status badge with value Active and if there are commits in the repository' -PROCESS_TICKETS = 'Searches for tickets or issues in the repository' -PROCESS_REFERENCE_PUBLICATION = 'Searches for an article citation or a reference publication in the codemeta and citation files' -PROCESS_IS_GITHUB_OR_GITLAB_REPOSITORY = 'Checks if the URL provided is indeed a Github or Gitlab repository' -PROCESS_ZENODO_SOFTWARE_HERITAGE = 'Searches for Zenodo and Software Heritage badges in the README file of the repository' -PROCESS_IDENTIFIER_IN_README_CITATION = 'Searches for an identifier in the README or CITATION.cff files of the repository' -PROCESS_ID_RESOLVES_TO_SOFTWARE = 'Checks if the identifier found in the README file or metadata files (i.e. codemeta.json, CITATION.cff) resolves to a page that links back to the software repository' -PROCESS_AUTHORS = 'Searches for authors in various files of the repository (i.e. CITATION.cff, AUTHORS.md, codemeta.json)' -PROCESS_CONTRIBUTORS = "Searches for contributors in various files of the repository (i.e. codemeta.json, pyproject.toml, pom.xml)'" -PROCESS_AUTHOR_ORCIDS = 'Checks if all authors stated in the CITATION.cff file have an ORCID assigned' -PROCESS_AUTHOR_ROLES = 'Checks if all authors stated in a codemeta.json file have a role assigned ' -PROCESS_VERSION_IN_METADATA = 'Checks if a version number for the software is indicated in the CITATION.cff, codemeta.json or package files(i.e. pyproject.toml, pom.xml, etc.)' -PROCESS_COMMITS_LINKED_TO_ISSUES = 'Checks if there is at least one of the existing issues (opened or closed) referenced in any of the commits made in the default branch of the repository' -PROCESS_COMMITS_HISTORY = 'Checks if the software repository has a commits history' +#Processes (not used rn) PROCESS_LICENSE_INFORMATION_PROVIDED = 'Checks if license information is found in the README file of the repository' -PROCESS_ISSUE_TRACKER = "Checks if there is an issue tracker in the repository." -PROCESS_CONTRIBUTION_GUIDELINES = "Checks if there are contribution guidelines either in the README file or if there is a CONTRIBUTING.md file" - - -#Evidences +PROCESS_REPO_ENABLED_AND_COMMITS = 'Checks if there is a repo_status badge with value Active and if there are commits in the repository' -EVIDENCE_LICENSE = 'A license was found in:' -EVIDENCE_CITATION = 'A citation was found in:' -EVIDENCE_COMMITS = 'Commits were found in the repository' -EVIDENCE_BRANCHES = 'Branches were found in the repository' -EVIDENCE_DOCUMENTATION = 'Documentation was found in: ' -EVIDENCE_DOCUMENTATION_README = 'There is a README file in the repository' -EVIDENCE_DOCUMENTATION_ONLY_README = 'A README file was found in: ' -EVIDENCE_DOCUMENTATION_ONLY_READTHEDOCS = 'A Read The Docs badge/url was found in: ' -EVIDENCE_METADATA_CODEMETA = 'A codemeta.json file was found in the root of the repository' -EVIDENCE_DESCRIPTIVE_METADATA = 'Descriptive metadata was found in the repository' -EVIDENCE_RELEASES = 'These releases were found:' -EVIDENCE_DOI_IDENTIFIER = 'A valid DOI was found in:' -EVIDENCE_DOI_RESOLVES = 'All of the DOIs in the README file resolve' -EVIDENCE_WORKFLOWS = 'Workflows were found in:' -EVIDENCE_DEPENDENCIES = 'Requirements were found in:' -EVIDENCE_RELEASE_ID_AND_VERSION = 'All of the releases have an id and a version' -EVIDENCE_VERSIONING_STANDARD = 'All of the releases follow a versioning standard' -EVIDENCE_IDENTIFIER_SCHEME_COMPLIANT = 'All of the releases URLs follow the same scheme' -EVIDENCE_TITLE_AND_DESCRIPTION = 'Title and description were found in the repository' -EVIDENCE_REPO_STATUS = 'A repo status badge was found in the repository' -EVIDENCE_CONTACT_INFO = 'Contact and support information was found in the repository' -EVIDENCE_SPDX_COMPLIANT = 'Licenses are SPDX compliant' -EVIDENCE_LICENSE_INFO_IN_METADATA = 'License information was found in metadata files' +#Evidences (not used rn) EVIDENCE_LICENSE_INFORMATION_PROVIDED = 'License information was found in the README file of the repository' -EVIDENCE_TICKETS = 'Tickets/Issues were found in the repository' EVIDENCE_REPO_ENABLED_AND_HAS_COMMITS = 'Repository is enabled and has commits' -EVIDENCE_AUTHOR_ORCIDS_CODEMETA = 'All authors in the codemeta.json file have an orcid identifier' -EVIDENCE_AUTHOR_ORCIDS_CFF = 'All authors in the CITATION.cff file have an orcid identifier' -EVIDENCE_AUTHOR_ORCIDS_BOTH = 'All authors in both the codemeta.json and CITATION.cff files have an orcid identifier' -EVIDENCE_AUTHORS = 'Authors were found in the repository' -EVIDENCE_CONTRIBUTORS = "Contributors were found in the repository" -EVIDENCE_AUTHOR_ROLES = 'All authors defined in the codemeta file have roles assigned' -EVIDENCE_REFERENCE_PUBLICATION = 'A reference publication was found in the codemeta file of the repository' -EVIDENCE_CITATION_TO_ARTICLE = 'A citation to an article was found in the repository' -EVIDENCE_REFERENCE_PUBLICATION_AND_CITATION_TO_ARTICLE = 'Both a citation to an article and a reference publication were found in the repository' -EVIDENCE_IS_IN_GITHUB_OR_GITLAB = 'URL provided is a Github or Gitlab repository' -EVIDENCE_IDENTIFIER_IN_README = 'An identifier was found in the README file of the repository' -EVIDENCE_IDENTIFIER_IN_CITATION = 'An identifier was found in the CITATION.cff file of the repository' -EVIDENCE_IDENTIFIER_IN_README_AND_CITATION = 'An identifier was found in both the README and CITATION.cff files of the repository' -EVIDENCE_ZENODO_DOI = 'A Zenodo DOI identifier was found in the repository' -EVIDENCE_SOFTWARE_HERITAGE_BADGE = 'A Software Heritage badge was found in the repository' -EVIDENCE_ZENODO_DOI_AND_SOFTWARE_HERITAGE = 'A Zenodo DOI identifier and a Software Heritage badge were found in the repository' -EVIDENCE_INSTALLATION = 'Installation instructions were found in the repository' -EVIDENCE_DEPENDENCIES_VERSION = 'All of the dependencies have a version stated' -EVIDENCE_DEPENDENCIES_MACHINE_READABLE_FILE = 'There is a machine-readable file for dependencies' -EVIDENCE_ID_FOUND_AND_RESOLVES = "Found the identifier {id} in the README and it resolves" -EVIDENCE_ID_COMMON_SCHEMA = 'All of the identifiers detected follow a common schema' -EVIDENCE_ID_ASSOCIATED_WITH_SOFTWARE = 'There is an identifier in the CITATION, codemeta and README files' -EVIDENCE_AUTOMATED_TESTS = 'There are workflows or actions that perform automated tests' -EVIDENCE_TESTS = 'Files and/or directories that mention test were found at:' -EVIDENCE_RELEASE_CONSISTENCY = 'Latest release matches the latest version stated' -EVIDENCE_METADATA_EXISTS = 'Found codemeta, citation and package files in the repository' -EVIDENCE_VERSION_IN_METADATA = 'Found the software version in one of the specified files' -EVIDENCE_CONTRIBUTORS = 'Found contributors metadata in the codemeta or package files' -EVIDENCE_COMMITS_LINKED_TO_ISSUES = 'There is at least one commit linked to an issue' -EVIDENCE_DOI_LINKS_BACK_TO_REPO = "The landing page of the software's identifier links back to the software repository" -EVIDENCE_ISSUE_TRACKER = "Found an issue tracker in the repository" -EVIDENCE_CONTRIBUTION_GUIDELINES = "Found contribution guidelines at: " +EVIDENCE_NO_LICENSE_INFORMATION_PROVIDED = 'Could not find license information in the README file of the repository' +EVIDENCE_NO_REPO_ENABLED = 'Repository is not enabled' +#Suggestions +SUGGEST_NO_LICENSE_INFO = "You should provide license information in your README file. More information at https://everse.software/RSQKit/licensing_software" +SUGGEST_NO_ACTIVE_REPO = "You should keep your repository active and indicate it with a repostatus badge" -EVIDENCE_NO_LICENSE = 'Could not find any license in the repository' -EVIDENCE_LICENSE_NOT_CLEAR = 'Could not recognize a license clearer enough to detect which one it is' -EVIDENCE_NO_DOI_IDENTIFIER = 'Could not find any DOI in the README file of the repository' + +#RSFC-01-1 +PROCESS_IDENTIFIER = "Searches for an identifier (i.e. DOI or SWHID) in the README file of the repository" +EVIDENCE_ID_FOUND_AND_RESOLVES = "Found the identifier {id} in the README and it resolves" EVIDENCE_NO_IDENTIFIER_FOUND = 'Could not find any identifier in the repository' EVIDENCE_NO_IDENTIFIER_FOUND_README = 'Could not find any identifier in the README file' -EVIDENCE_NO_RESOLVE_DOI_IDENTIFIER = 'DOI found but not resolvable' -EVIDENCE_NO_CITATION = 'Could not find any citation in the repository' -EVIDENCE_NO_WORKFLOWS = 'Could not find any workflows in the repository' -EVIDENCE_NO_DEPENDENCIES = 'Could not find any dependencies indicated in the repository' -EVIDENCE_NO_METADATA_CODEMETA = 'Could not find a codemeta.json file in the repository' -EVIDENCE_NO_DESCRIPTIVE_METADATA = 'Could not find any of the following metadata: ' -EVIDENCE_NO_README_AND_READTHEDOCS = 'Could not find neither README file or Read The Docs badge' -EVIDENCE_NO_DOCUMENTATION_README = 'Could not find a README file in the repository' -EVIDENCE_NO_RELEASES = 'Could not find any releases in the repository' -EVIDENCE_NO_RELEASE_ID_AND_VERSION = 'There is one or many releases that do not have an id and a version' -EVIDENCE_NO_VERSIONING_STANDARD = 'There is one version number of a release that does not follow either SemVer or CalVer' -EVIDENCE_NO_IDENTIFIER_SCHEME_COMPLIANT = 'There is one or more releases URLs that do not follow the same scheme as the rest of the release\'s URLs' -EVIDENCE_NO_TITLE = 'Could not find a title for the project in the repository' -EVIDENCE_NO_DESCRIPTION = 'Could not find a description for the project in the repository' -EVIDENCE_NO_TITLE_AND_DESCRIPTION = 'Could not find neither title or description in the repository' -EVIDENCE_NO_REPO_STATUS = 'Could not find a repo status badge in the repository' -EVIDENCE_NO_CONTACT_INFO = 'Could not find any of the following information: ' -EVIDENCE_NO_SPDX_COMPLIANT = 'There is one or more licenses that are not SPDX compliant' -EVIDENCE_NO_LICENSE_INFO_IN_METADATA = 'Could not find any licensing information in the following metadata files: ' -EVIDENCE_NO_LICENSE_INFORMATION_PROVIDED = 'Could not find license information in the README file of the repository' -EVIDENCE_NO_TICKETS = 'Could not find tickets/issues in the repository' -EVIDENCE_NO_REPO_ENABLED = 'Repository is not enabled' -EVIDENCE_NO_COMMITS = 'Could not find any commits in the repository' -EVIDENCE_NO_CONTRIBUTORS = 'Found authors but could not find any contributors in the repository' -EVIDENCE_NO_AUTHORS = 'Could not find any authors in the repository' -EVIDENCE_NO_AUTHOR_ORCIDS = 'One or more authors do not have an ORCID assigned' -EVIDENCE_NO_AUTHORS_IN_CODEMETA = 'There are no authors defined in the codemeta file' -EVIDENCE_NO_ALL_AUTHOR_ROLES = 'There are one or more authors in the codemeta file that do not have roles assigned' -EVIDENCE_NO_VALID_JSON = 'Codemeta file is not valid' -EVIDENCE_NO_CODEMETA_FOUND = 'Could not find codemeta file' -EVIDENCE_NO_REFERENCE_PUBLICATION_OR_CITATION_TO_ARTICLE = 'Could not find neither a reference publication or citation to an article in the repository' -EVIDENCE_NO_RESOLVE_GITHUB_OR_GITLAB_URL = 'Github/Gitlab URL provided does not resolve' -EVIDENCE_NO_GITHUB_OR_GITLAB_URL = 'URL provided is not from Github or Gitlab' -EVIDENCE_NO_IDENTIFIER_IN_README_OR_CITATION = 'Could not find an identifier in neither of the README or CITATION files in the repository' -EVIDENCE_NO_ZENODO_DOI_OR_SOFTWARE_HERITAGE = 'Could not find neither a Zenodo DOI identifier or a Software Heritage badge in the repository' -EVIDENCE_NO_INSTALLATION = 'Could not find any installation instructions in the repository' -EVIDENCE_NO_DEPENDENCIES_VERSION = 'One or more dependencies do not have a version stated' -EVIDENCE_NO_DEPENDENCIES_MACHINE_READABLE_FILE = 'Could not find a machine-readable file for dependencies' EVIDENCE_NO_ID_RESOLVE = 'Found the identifier {id} in the README but it does not resolve or is not resolvable' EVIDENCE_ID_NOT_URL = 'Found the identifier {id} in the README but it is not an URL' -EVIDENCE_NO_ID_COMMON_SCHEMA = 'One or more of the detected identifiers do not follow a common schema' +SUGGEST_NO_IDENTIFIER = "You should include a resolvable, unique and persistent identifier in your repository. More information at https://everse.software/RSQKit/software_identifiers" +SUGGEST_NO_IDENTIFIER_README = "You should include a resolvable, unique and persistent identifier in your README file. More information at https://everse.software/RSQKit/software_identifiers" +SUGGEST_IDENTIFIER_NO_RESOLVE = "You should make sure that your identifier is resolvable and persistent. More information at https://everse.software/RSQKit/software_identifiers" +SUGGEST_IDENTIFIER_NOT_HTTP = "The repository your identifier resolves to should use a standard communication protocol. More information at https://everse.software/RSQKit/software_identifiers" + +#RSFC-01-2 +PROCESS_ID_PROPER_SCHEMA = 'Checks if the identifiers associated with the software follow any of these schemas: DOI, URN, GITHUB and SWHID' +EVIDENCE_ID_COMMON_SCHEMA = 'All of the identifiers detected follow a common schema' +EVIDENCE_NO_ID_COMMON_SCHEMA = 'The following identifiers found do not follow a common schema:' +SUGGEST_IDENTIFIER_SCHEME = "Your identifier should follow a common schema like URN, DOI or SWHID. More information at https://everse.software/RSQKit/software_identifiers" + +#RSFC-01-3 +PROCESS_ID_ASSOCIATED_WITH_SOFTWARE = 'Searches for an identifier in the CITATION.cff, codemeta.json and README files' +EVIDENCE_SOME_ID_ASSOCIATED_WITH_SOFTWARE = 'An identifier was found in {source}.' +EVIDENCE_MISSING_IDS = ' However, no identifier was found in {missing_sources}.' EVIDENCE_NO_ID_ASSOCIATED_WITH_SOFTWARE = 'Could not find an identifier in any of the CITATION, codemeta or README files' -EVIDENCE_SOME_ID_ASSOCIATED_WITH_SOFTWARE = 'An identifier was found but could not find it in the following locations: ' -EVIDENCE_NO_TESTS = 'Could not find any files or directories that mention test' -EVIDENCE_NO_AUTOMATED_TESTS = 'Could not find any workflows or actions that mention test in their names' +SUGGEST_NO_IDENTIFIER_ASSOCIATED = "Remember that identifiers should be included in other files aside from README like codemeta.json, CITATION.cff. More information at https://everse.software/RSQKit/software_identifiers" + +#RSFC-03-1 +PROCESS_RELEASES = "Searches for release tags in the repository" +EVIDENCE_RELEASES = 'These releases were found:' +EVIDENCE_NO_RELEASES = 'Could not find any releases in the repository' +SUGGEST_NO_RELEASES = "You should often launch releases of your software that contain new updates. More information at https://everse.software/RSQKit/releasing_software" + +#RSFC-03-2 +PROCESS_RELEASE_ID_VERSION = 'Checks if all of the releases have an identifier and a version' +EVIDENCE_RELEASE_ID_AND_VERSION = 'All of the releases have an id and a version' +EVIDENCE_NO_RELEASE_ID_AND_VERSION = 'There are releases that do not have an id and a version at:' +SUGGEST_NO_RELEASE_ID_AND_VERSION = "The releases that you launch should have an id and a version to describe them. More information at https://everse.software/RSQKit/releasing_software" + +#RSFC-03-3 +PROCESS_SEMANTIC_VERSIONING = 'Checks if all of the releases versions follow the SemVer or CalVer versioning standards' +EVIDENCE_VERSIONING_STANDARD = 'All of the releases follow a versioning standard' +EVIDENCE_NO_VERSIONING_STANDARD = 'The following release versions do not follow either SemVer or CalVer:' +SUGGEST_NO_VERSIONING_STANDARD = "You should use a versioning standard for all of your releases. More information at https://everse.software/RSQKit/releasing_software" + +#RSFC-03-4 +PROCESS_VERSION_SCHEME = 'Checks if all of the version identifiers follow the same scheme' +EVIDENCE_IDENTIFIER_SCHEME_COMPLIANT = 'All of the releases URLs follow the same scheme' +EVIDENCE_NO_IDENTIFIER_SCHEME_COMPLIANT = 'The following release URLs do not follow the same scheme as the rest of the release\'s URLs:' +SUGGEST_NO_IDENTIFIER_SCHEME_COMPLIANT = "The identifiers or URLs of your releases should follow a common scheme. More information at https://everse.software/RSQKit/releasing_software" + +#RSFC-03-5 +PROCESS_RELEASE_CONSISTENCY = 'Checks if the latest release tag matches the version stated in the codemeta or package files of the repository' +EVIDENCE_RELEASE_CONSISTENCY = 'Latest release matches the latest version stated in the metadata files' EVIDENCE_NO_RELEASE_CONSISTENCY = 'Latest release does not match the latest version stated' EVIDENCE_NOT_ENOUGH_RELEASE_INFO = 'Could not get the necessary information to perform the test, it being releases and/or version in package file' -EVIDENCE_NO_METADATA_EXISTS = 'Could not find any of the following metadata files: ' +SUGGEST_NO_RELEASE_CONSISTENCY = "It is good practice to keep consistency between the version of your latest release and the version in your metadata files" + + +#RSFC-03-6 +PROCESS_VERSION_IN_METADATA = 'Checks if a version number for the software is indicated in the CITATION.cff, codemeta.json or package files(i.e. pyproject.toml, pom.xml, etc.)' +EVIDENCE_VERSION_IN_METADATA = 'Found the software version in:' EVIDENCE_NO_VERSION_IN_METADATA = 'Could not find a version number for the software in any of the specified files' -EVIDENCE_NOT_ENOUGH_ISSUES_COMMITS_INFO = 'Could not get the necessary information to perform the test, it being the commits record or repository issues' -EVIDENCE_NO_COMMITS_LINKED_TO_ISSUES = 'There is not any commits linked to any issues in the repository' -EVIDENCE_DOI_NO_LINK_BACK_TO_REPO = "The landing page of the software's identifier does not link back to the software repository" -EVIDENCE_NO_ISSUE_TRACKER = "Could not find an issue tracker in the repository" -EVIDENCE_NO_CONTRIBUTION_GUIDELINES = "Could not find contribution guidelines in the repository" +SUGGEST_NO_VERSION_IN_METADATA = "You should include the version of your software in its metadata. More information at https://everse.software/RSQKit/software_metadata" +#RSFC-04-1 +PROCESS_METADATA_EXISTS = 'Searches for codemeta, citation and package files in the repository' +EVIDENCE_METADATA_EXISTS = 'Found {source} in the repository' +EVIDENCE_NO_METADATA_EXISTS = 'Could not find any of the metadata files' +SUGGEST_NO_METADATA_FILES = "You should describe your software in metadata files. More information at https://everse.software/RSQKit/software_metadata" -#Suggestions +#RSFC-04-2 +PROCESS_README = 'Searches for a README file in the repository' +EVIDENCE_DOCUMENTATION_README = 'There is a README file in the repository' +EVIDENCE_NO_DOCUMENTATION_README = 'Could not find a README file in the repository' +SUGGEST_NO_README = "You should include an informative README file in your repository. More information at https://everse.software/RSQKit/creating_good_readme" -SUGGEST_NO_IDENTIFIER = "You should include a resolvable, unique and persistent identifier in your repository. More information at https://everse.software/RSQKit/software_identifiers" -SUGGEST_NO_IDENTIFIER_README = "You should include a resolvable, unique and persistent identifier in your README file. More information at https://everse.software/RSQKit/software_identifiers" -SUGGEST_IDENTIFIER_NO_RESOLVE = "You should make sure that your identifier is resolvable and persistent. More information at https://everse.software/RSQKit/software_identifiers" -SUGGEST_IDENTIFIER_NOT_HTTP = "The repository your identifier resolves to should use a standard communication protocol. More information at https://everse.software/RSQKit/software_identifiers" -SUGGEST_IDENTIFIER_SCHEME = "Your identifier should follow a common schema like URN, DOI or SWHID. More information at https://everse.software/RSQKit/software_identifiers" -SUGGEST_IDENTIFIER_ASSOCIATED = "Remember that identifiers should be included in other files aside from README like codemeta.json, CITATION.cff. More information at https://everse.software/RSQKit/software_identifiers" -SUGGEST_NO_IDENTIFIER_IN_README_OR_CITATION = "You should include your software's identifier in your README or CITATION.cff files. More information at " -SUGGEST_DOI_NO_LINK_BACK_TO_REPO = "Your software's identifier should resolve to a page that links back to itself." +#RSFC-04-3 +PROCESS_TITLE_DESCRIPTION = 'Checks if there is a title and a description for the software in the metadata' +EVIDENCE_TITLE_AND_DESCRIPTION = 'A title was found in [{title_sources}] and a description was found in [{desc_sources}]' +EVIDENCE_NO_DESCRIPTION = 'A title was found in [{title_sources}], but could not find a description in the repository' +EVIDENCE_NO_TITLE = 'A description was found in [{desc_sources}], but could not find a title in the repository' +EVIDENCE_NO_TITLE_AND_DESCRIPTION = 'Could not find neither title and/or description in the repository' +EVIDENCE_NO_SOURCE = "Found {resource} but could not get a source. Data obtained via {technique}" SUGGEST_NO_TITLE_DESCRIPTION = "You should add a title and a description to your software's metadata. More information at https://everse.software/RSQKit/software_metadata" SUGGEST_NO_DESCRIPTION = "Remember to add a description to your software's metadata. More information at https://everse.software/RSQKit/software_metadata" SUGGEST_NO_TITLE = "A title would be of much help to describe your software. More information at https://everse.software/RSQKit/software_metadata" + +#RSFC-04-4 (REVISAR) +PROCESS_DESCRIPTIVE_METADATA = "Searches for description, programming languages, date of creation and keywords in the repository" +EVIDENCE_DESCRIPTIVE_METADATA = 'Descriptive metadata found in: Description [{desc_sources}], Languages [{lang_sources}], Date Created [{date_sources}], Keywords [{keyword_sources}]' +EVIDENCE_NO_DESCRIPTIVE_METADATA = 'Missing some descriptive metadata. Found so far: Description [{desc_sources}], Languages [{lang_sources}], Date Created [{date_sources}], Keywords [{keyword_sources}]' SUGGEST_NO_DESCRIPTIVE_METADATA = "You should describe your software using metadata. More information at https://everse.software/RSQKit/software_metadata" + +#RSFC-04-5 +PROCESS_CODEMETA = 'Searches for a codemeta.json file in the repository' +EVIDENCE_METADATA_CODEMETA = 'A codemeta.json file was found in the root of the repository' +EVIDENCE_NO_METADATA_CODEMETA = 'Could not find a codemeta.json file in the repository' SUGGEST_NO_CODEMETA = "You should create a codemeta file to describe your software. More information at https://everse.software/RSQKit/software_metadata" -SUGGEST_NO_METADATA_FILES = "You should describe your software in metadata files. More information at https://everse.software/RSQKit/software_metadata" -SUGGEST_NO_VERSION_IN_METADATA = "You should include the version of your software in its metadata. More information at https://everse.software/RSQKit/software_metadata" -SUGGEST_NO_RELEASES = "You should often launch releases of your software that contain new updates. More information at https://everse.software/RSQKit/releasing_software" -SUGGEST_NO_VERSIONING_STANDARD = "You should use a versioning standard for all of your releases. More information at https://everse.software/RSQKit/releasing_software" -SUGGEST_NO_RELEASE_CONSISTENCY = "It is good practice to keep consistency between the version of your latest release and the version in your metadata files" -SUGGEST_NO_RELEASE_ID_AND_VERSION = "The releases that you launch should have an id and a version to describe them. More information at https://everse.software/RSQKit/releasing_software" -SUGGEST_NO_IDENTIFIER_SCHEME_COMPLIANT = "The identifiers or URLs of your releases should follow a common scheme. More information at https://everse.software/RSQKit/releasing_software" + +#RSFC-05-1 +PROCESS_REPO_STATUS = 'Searches for a repo status badge in the README file of the repository' +EVIDENCE_REPO_STATUS = 'A repo status badge was found in:' +EVIDENCE_NO_REPO_STATUS = 'Could not find a repo status badge in the repository' SUGGEST_NO_REPO_STATUS = "You should include the state of your repository in the README file" -SUGGEST_NO_README_AND_READTHEDOCS = "Your software should be well documented via a README file or a Read the Docs page. More information at https://everse.software/RSQKit/software_documentation" + +#RSFC-05-2 +PROCESS_CONTACT_SUPPORT_DOCUMENTATION = 'Searches for contact and support information in the repository' +EVIDENCE_CONTACT_INFO = 'Contact and support information was found in:' +EVIDENCE_NO_CONTACT_INFO = 'Could not find any contact or support information in the repository' SUGGEST_NO_CONTACT_INFO = "You should include contact information in your software's metadata in case someone wants to ask for information." + +#RSFC-05-3 +PROCESS_DOCUMENTATION = "Searches for a README file in the root repository and other forms of documentation such as a Read The Docs badge or url" +EVIDENCE_DOCUMENTATION = 'Documentation was found in:' +EVIDENCE_NO_README_AND_READTHEDOCS = 'Could not find neither README file or Read The Docs badge' +SUGGEST_NO_README_AND_READTHEDOCS = "Your software should be well documented via a README file or a Read the Docs page. More information at https://everse.software/RSQKit/software_documentation" + +#RSFC-06-1 +PROCESS_AUTHORS = 'Searches for authors in various files of the repository (i.e. CITATION.cff, AUTHORS.md, codemeta.json)' +EVIDENCE_AUTHORS = 'Authors were found in:' +EVIDENCE_NO_AUTHORS = 'Could not find any authors in the repository' SUGGEST_NO_AUTHORS = "Your software should document its authors. More information at https://everse.software/RSQKit/documenting_software_project" + +#RSFC-06-2 +PROCESS_CONTRIBUTORS = "Searches for contributors in various files of the repository (i.e. codemeta.json, pyproject.toml, pom.xml)'" +EVIDENCE_CONTRIBUTORS = "Contributors were found in:" +EVIDENCE_NO_CONTRIBUTORS = 'Could not find any contributors in the repository' SUGGEST_NO_CONTRIBUTORS = "Your software should also document its contributors if there are any. More information at https://everse.software/RSQKit/documenting_software_project" + +#RSFC-06-3 +PROCESS_AUTHOR_ORCIDS = 'Checks if all authors stated in the CITATION.cff file have an ORCID assigned' +EVIDENCE_AUTHOR_ORCIDS= 'All authors in both the codemeta.json and CITATION.cff files have an orcid identifier' +EVIDENCE_NO_AUTHOR_ORCIDS = 'Authors that do not have an orcid were found in:' SUGGEST_NO_AUTHOR_ORCIDS = "When documenting your software's authors, you should include their ORCIDs if possible." -SUGGEST_NO_ALL_AUTHOR_ROLES = "When documenting your software's authors, you should include their roles if possible." -SUGGEST_NO_AUTHORS_IN_CODEMETA = "You should include your software's authors metadata in the codemeta.json file. More information at https://everse.software/RSQKit/software_metadata" -SUGGEST_NO_LICENSE = "You should have your software under a public license. More information at https://everse.software/RSQKit/licensing_software" -SUGGEST_NO_LICENSE_INFO = "You should provide license information in your README file. More information at https://everse.software/RSQKit/licensing_software" -SUGGEST_NO_LICENSE_INFO_METADATA = "Information about your license should be present in other metadata files like codemeta.json, package files or CITATION. More information on https://everse.software/RSQKit/software_metadata" -SUGGEST_NO_LICENSE_SPDX = "You should include SPDX tags to ensure that your licenses are machine-readable. More information at https://everse.software/RSQKit/licensing_software" -SUGGEST_NO_CITATION = "You should include a citation so other people can citate your research software. More information at https://everse.software/RSQKit/citing_software" -SUGGEST_NO_REFPUB_OR_ARTICLE = "You should include other forms of citation like article citations and reference publications in your software's metadata. More information at https://everse.software/RSQKit/creating_good_readme" -SUGGEST_NO_WORKFLOWS = "Your software should include workflows to automate tasks. More information at https://everse.software/RSQKit/task_automation_github_actions" -SUGGEST_NO_TESTS = "Your software should include tests to prove its functionability. More information at https://everse.software/RSQKit/testing_software" -SUGGEST_NO_TEST_ACTIONS = "You should include github actions that run tests to ensure quality. More information at https://everse.software/RSQKit/task_automation_github_actions" -SUGGEST_NO_README = "You should include an informative README file in your repository. More information at https://everse.software/RSQKit/creating_good_readme" + +#RSFC-07-1 +PROCESS_IDENTIFIER_IN_README_CITATION = 'Searches for an identifier in the README or CITATION.cff files of the repository' +EVIDENCE_IDENTIFIER_IN_README = 'An identifier was found in the README file of the repository' +EVIDENCE_IDENTIFIER_IN_CITATION = 'An identifier was found in the CITATION.cff file of the repository' +EVIDENCE_IDENTIFIER_IN_README_AND_CITATION = 'An identifier was found in both the README and CITATION.cff files of the repository' +EVIDENCE_NO_IDENTIFIER_IN_README_OR_CITATION = 'Could not find an identifier in neither of the README or CITATION files in the repository' +SUGGEST_NO_IDENTIFIER_IN_README_OR_CITATION = "You should include your software's identifier in your README or CITATION.cff files" + +#RSFC-07-2 +PROCESS_ID_RESOLVES_TO_SOFTWARE = 'Checks if the identifier found in the README file or metadata files (i.e. codemeta.json, CITATION.cff) resolves to a page that links back to the software repository' +EVIDENCE_DOI_LINKS_BACK_TO_REPO = "The landing page of the software's identifier {identifier} links back to the software repository" +EVIDENCE_DOI_NO_LINK_BACK_TO_REPO = "None of the identifiers found (if any) link back to the software repository" +EVIDENCE_NO_RESOLVE_DOI_IDENTIFIER = 'DOI found but not resolvable' +SUGGEST_DOI_NO_LINK_BACK_TO_REPO = "Your software's identifier should resolve to a page that links back to itself." + +#RSFC-08-1 +PROCESS_ZENODO_SOFTWARE_HERITAGE = 'Searches for Zenodo and Software Heritage badges in the README file of the repository' +EVIDENCE_ZENODO_DOI = 'A Zenodo DOI identifier was found in:' +EVIDENCE_SOFTWARE_HERITAGE_BADGE = 'A Software Heritage badge was found in:' +EVIDENCE_ZENODO_DOI_AND_SOFTWARE_HERITAGE = 'A Zenodo DOI identifier and a Software Heritage badge were found in:' +EVIDENCE_NO_ZENODO_DOI_OR_SOFTWARE_HERITAGE = 'Could not find neither a Zenodo DOI identifier or a Software Heritage badge in the repository' SUGGEST_ARCHIVE_SOFTWARE = "You should archive your software not only in Github/Gitlab. More information at https://everse.software/RSQKit/archiving_software" + +#RSFC-09-1 +PROCESS_IS_GITHUB_OR_GITLAB_REPOSITORY = 'Checks if the URL provided is indeed a Github or Gitlab repository' +EVIDENCE_IS_IN_GITHUB_OR_GITLAB = 'URL provided is a Github or Gitlab repository' +EVIDENCE_NO_GITHUB_OR_GITLAB_URL = 'URL provided is not from Github or Gitlab' +EVIDENCE_NO_RESOLVE_GITHUB_OR_GITLAB_URL = 'Github/Gitlab URL provided does not resolve' + +#RSFC-12-1 +PROCESS_REFERENCE_PUBLICATION = 'Searches for an article citation or a reference publication in the codemeta and citation files' +EVIDENCE_REFERENCE_PUBLICATION = "A reference publication was found in:" +EVIDENCE_ARTICLE_CITATION = "An article citation was found in:" +EVIDENCE_REFERENCE_PUBLICATION_OR_CITATION_TO_ARTICLE = 'Both a citation to an article and a reference publication were found in:' +EVIDENCE_NO_REFERENCE_PUBLICATION_OR_CITATION_TO_ARTICLE = 'Could not find neither a reference publication or citation to an article in the repository' +SUGGEST_NO_REFPUB_OR_ARTICLE = "You should include other forms of citation like article citations and reference publications in your software's metadata. More information at https://everse.software/RSQKit/creating_good_readme" + +#RSFC-13-1 +PROCESS_REQUIREMENTS = "Searches for dependencies in project configuration files, README and dependencies files such as requirements.txt" +EVIDENCE_DEPENDENCIES = 'Requirements were found in:' +EVIDENCE_NO_DEPENDENCIES = 'Could not find any dependencies indicated in the repository' SUGGEST_NO_DEPENDENCIES = "You should have your dependencies stated somewhere to enable reproducibility. More information at https://everse.software/RSQKit/reproducible_software_environments" -SUGGEST_NO_MACHINE_READABLE_DEPENDENCIES = "You should have your dependencies stated in a machine-readable file. More information at https://everse.software/RSQKit/reproducible_software_environments" + +#RSFC-13-2 +PROCESS_INSTALLATION = 'Searches for installation instructions in the README file of the repository' +EVIDENCE_INSTALLATION = 'Installation instructions were found in:' +EVIDENCE_NO_INSTALLATION = 'Could not find any installation instructions in the repository' SUGGEST_NO_INSTALL_INSTRUCTIONS = "You should include instructions to facilitate the use of your software. More information at https://everse.software/RSQKit/creating_good_readme" + +#RSFC-13-3 +PROCESS_DEPENDENCIES_VERSION = 'Checks if all of the dependencies stated in the machine-readable file (e.g. requirements.txt, pyproject.toml, etc.) of the repository have a version indicated' +EVIDENCE_DEPENDENCIES_VERSION = 'All of the dependencies have a version stated' +EVIDENCE_NO_DEPENDENCIES_VERSION = 'The following dependencies do not have a version stated:' SUGGEST_NO_DEPENDENCIES_VERSION = "All of your dependencies should have their versions stated to ensure its reproducibility. More information at https://everse.software/RSQKit/reproducible_software_environments" + +#RSFC-13-4 +PROCESS_DEPENDENCIES_MACHINE_READABLE_FILE = 'Checks if dependencies are indicated in a machine-readable file' +EVIDENCE_DEPENDENCIES_MACHINE_READABLE_FILE = 'There is a machine-readable file for dependencies at:' +EVIDENCE_NO_DEPENDENCIES_MACHINE_READABLE_FILE = 'Could not find a machine-readable file for dependencies' +SUGGEST_NO_MACHINE_READABLE_DEPENDENCIES = "You should have your dependencies stated in a machine-readable file. More information at https://everse.software/RSQKit/reproducible_software_environments" + +#RSFC-14-1 +PROCESS_TESTS = 'Searches for files and/or directories that mention test in their names. Also, ignores doc and docs directories' +EVIDENCE_TESTS = 'Files and/or directories that mention test were found at:' +EVIDENCE_NO_TESTS = 'Could not find any files or directories that mention test' +SUGGEST_NO_TESTS = "Your software should include tests to prove its functionability. More information at https://everse.software/RSQKit/testing_software" + +#RSFC-14-2 +PROCESS_AUTOMATED_TESTS = 'Searches for workflows that contain test or tests in their names' +EVIDENCE_AUTOMATED_TESTS = 'There are workflows or actions that perform automated tests' +EVIDENCE_NO_AUTOMATED_TESTS = 'Could not find any workflows or actions that mention test in their names' +SUGGEST_NO_TEST_ACTIONS = "You should include github actions that run tests to ensure quality. More information at https://everse.software/RSQKit/task_automation_github_actions" +SUGGEST_NO_WORKFLOWS = "Your software should include workflows to automate tasks. More information at https://everse.software/RSQKit/task_automation_github_actions" + +#RSFC-15-1 +PROCESS_LICENSE = "Searches for a file named 'LICENSE' or 'LICENSE.md' in the root of the repository." +EVIDENCE_LICENSE = 'A license was found in:' +EVIDENCE_NO_LICENSE = 'Could not find any license in the repository' +SUGGEST_NO_LICENSE = "You should have your software under a public license. More information at https://everse.software/RSQKit/licensing_software" + +#RSFC-15-2 +PROCESS_LICENSE_SPDX_COMPLIANT = 'Checks if the licenses detected are SPDX compliant' +EVIDENCE_SPDX_COMPLIANT = 'All licenses are SPDX compliant' +EVIDENCE_LICENSE_NOT_CLEAR = 'Could not recognize a license clearer enough to detect which one it is' +EVIDENCE_NO_SPDX_COMPLIANT = 'The following licenses are not SPDX compliant:' +SUGGEST_NO_LICENSE_SPDX = "You should include SPDX tags to ensure that your licenses are machine-readable. More information at https://everse.software/RSQKit/licensing_software" + +#RSFC-16-1 +PROCESS_LICENSE_INFO_IN_METADATA_FILES = 'Searches for licensing information in the codemeta, CITATION.cff and package files if they exist' +EVIDENCE_LICENSE_INFO_ALL = "License information was found in {existing}" +EVIDENCE_LICENSE_INFO_IN_METADATA = 'Found license information in {existing} but could not find any in {missing}' +EVIDENCE_NO_LICENSE_INFO_IN_METADATA = 'Could not find any licensing information in the metadata files' +SUGGEST_NO_LICENSE_INFO_METADATA = "Information about your license should be present in other metadata files like codemeta.json, package files or CITATION. More information on https://everse.software/RSQKit/software_metadata" + +#RSFC-17-2 +PROCESS_COMMITS_HISTORY = 'Checks if the software repository has a commits history' +EVIDENCE_COMMITS = 'A commit history was found in:' +EVIDENCE_NO_COMMITS = 'Could not find any commits in the repository' SUGGEST_NO_COMMITS = "Remember to keep track of your changes making commits to your repository. More information at https://everse.software/RSQKit/using_version_control" + +#RSFC-17-3 +PROCESS_COMMITS_LINKED_TO_ISSUES = 'Checks if there is at least one of the existing issues (opened or closed) referenced in any of the commits made in the default branch of the repository' +EVIDENCE_COMMITS_LINKED_TO_ISSUES = 'There is at least one commit linked to an issue' +EVIDENCE_NO_COMMITS_LINKED_TO_ISSUES = 'There is not any commits linked to any issues in the repository' +EVIDENCE_NOT_ENOUGH_ISSUES_COMMITS_INFO = 'Could not get the necessary information to perform the test, it being the commits record or repository issues' SUGGEST_NO_ISSUES_LINK_COMMITS = "It is good practice to indicate in your commits which issues you are targeting or solving" -SUGGEST_NO_ACTIVE_REPO = "You should keep your repository active and indicate it with a repostatus badge" -SUGGEST_NO_ISSUE_TRACKER = "You should have an issue tracker in your repository to help you manage your ideas, next steps and bugs to fix." SUGGEST_NO_COMMITS_OR_ISSUES = "Don't forget to make commits of your source code to your git repository and issues to improve version control. More information at https://everse.software/RSQKit/using_version_control" + + +#RSFC-18-1 +PROCESS_CITATION = "Searches for a CITATION.cff file and README file in the repository" +EVIDENCE_CITATION = 'A citation was found in:' +EVIDENCE_NO_CITATION = 'Could not find any citation in the repository' +SUGGEST_NO_CITATION = "You should include a citation so other people can citate your research software. More information at https://everse.software/RSQKit/citing_software" + +#RSFC-19-1 +PROCESS_WORKFLOWS = "Searches for workflows in the repository" +EVIDENCE_WORKFLOWS = 'Workflows were found in:' +EVIDENCE_NO_WORKFLOWS = 'Could not find any workflows in the repository' +SUGGEST_NO_WORKFLOWS = "Your software should include workflows to automate tasks. More information at https://everse.software/RSQKit/task_automation_github_actions" + +#RSFC-20-1 +PROCESS_ISSUE_TRACKER = "Checks if there is an issue tracker in the repository." +EVIDENCE_ISSUE_TRACKER_SOURCE = "Found an issue tracker in the repository at:" +EVIDENCE_ISSUE_TRACKER_NO_SOURCE = "There is an issue tracking system in the repository but it isn't stated in any of the files" +EVIDENCE_NO_ISSUE_TRACKER = "Could not find an issue tracking system in the repository" +SUGGEST_NO_ISSUE_TRACKER = "You should have an issue tracker in your repository to help you manage your ideas, next steps and bugs to fix." + +#RSFC-21-1 +PROCESS_CONTRIBUTION_GUIDELINES = "Checks if there are contribution guidelines either in the README file or if there is a CONTRIBUTING.md file" +EVIDENCE_CONTRIBUTION_GUIDELINES = "Found contribution guidelines at:" +EVIDENCE_NO_CONTRIBUTION_GUIDELINES = "Could not find contribution guidelines in the repository" SUGGEST_NO_CONTRIBUTION_GUIDELINES = "If you want to properly keep track of the colaborations your project receives to ensure its quality and fiability, you should add some contribution guidelines so the colaborators know how you want contributions to be made" +#RSFC-22-1 +PROCESS_CONTAINER_FILE = "Searches in the root of the repository for container files such as dockerfile, apptainer, podman, etc." +EVIDENCE_CONTAINER_FILE = "Found container files at:" +EVIDENCE_NO_CONTAINER_FILE = "Could not find any container file in the repository" +SUGGEST_NO_CONTAINER_FILE = "You should allow interopertability when other users want to execute your software easily" #RSFC test identifiers @@ -308,7 +372,55 @@ RSFC_19_1_ID = "https://w3id.org/rsfc/test/RSFC-19-1" RSFC_20_1_ID = "https://w3id.org/rsfc/test/RSFC-20-1" RSFC_21_1_ID = "https://w3id.org/rsfc/test/RSFC-21-1" - +RSFC_22_1_ID = "https://w3id.org/rsfc/test/RSFC-22-1" + +TEST_ID_DICT = { + + "RSFC-01-1": RSFC_01_1_ID, + "RSFC-01-2": RSFC_01_2_ID, + "RSFC-01-3": RSFC_01_3_ID, + "RSFC-03-1": RSFC_03_1_ID, + "RSFC-03-2": RSFC_03_2_ID, + "RSFC-03-3": RSFC_03_3_ID, + "RSFC-03-4": RSFC_03_4_ID, + "RSFC-03-5": RSFC_03_5_ID, + "RSFC-03-6": RSFC_03_6_ID, + "RSFC-04-1": RSFC_04_1_ID, + "RSFC-04-2": RSFC_04_2_ID, + "RSFC-04-3": RSFC_04_3_ID, + "RSFC-04-4": RSFC_04_4_ID, + "RSFC-04-5": RSFC_04_5_ID, + "RSFC-05-1": RSFC_05_1_ID, + "RSFC-05-2": RSFC_05_2_ID, + "RSFC-05-3": RSFC_05_3_ID, + "RSFC-06-1": RSFC_06_1_ID, + "RSFC-06-2": RSFC_06_2_ID, + "RSFC-06-3": RSFC_06_3_ID, + "RSFC-06-4": RSFC_06_4_ID, + "RSFC-07-1": RSFC_07_1_ID, + "RSFC-07-2": RSFC_07_2_ID, + "RSFC-08-1": RSFC_08_1_ID, + "RSFC-09-1": RSFC_09_1_ID, + "RSFC-12-1": RSFC_12_1_ID, + "RSFC-13-1": RSFC_13_1_ID, + "RSFC-13-2": RSFC_13_2_ID, + "RSFC-13-3": RSFC_13_3_ID, + "RSFC-13-4": RSFC_13_4_ID, + "RSFC-14-1": RSFC_14_1_ID, + "RSFC-14-2": RSFC_14_2_ID, + "RSFC-15-1": RSFC_15_1_ID, + "RSFC-15-2": RSFC_15_2_ID, + "RSFC-15-3": RSFC_15_3_ID, + "RSFC-16-1": RSFC_16_1_ID, + "RSFC-17-1": RSFC_17_1_ID, + "RSFC-17-2": RSFC_17_2_ID, + "RSFC-17-3": RSFC_17_3_ID, + "RSFC-18-1": RSFC_18_1_ID, + "RSFC-19-1": RSFC_19_1_ID, + "RSFC-20-1": RSFC_20_1_ID, + "RSFC-21-1": RSFC_21_1_ID, + "RSFC-22-1": RSFC_22_1_ID +} #Short descriptions @@ -355,6 +467,7 @@ DESC_RSFC_19_1 = "Repository has continuous integration workflows" DESC_RSFC_20_1 = "Repository has an issue tracker" DESC_RSFC_21_1 = "Repository has contribution guidelines" +DESC_RSFC_22_1 = "Software offers a container file to run it" TEST_DESC_DICT = { @@ -400,7 +513,8 @@ "RSFC-18-1": DESC_RSFC_18_1, "RSFC-19-1": DESC_RSFC_19_1, "RSFC-20-1": DESC_RSFC_20_1, - "RSFC-21-1": DESC_RSFC_21_1 + "RSFC-21-1": DESC_RSFC_21_1, + "RSFC-22-1": DESC_RSFC_22_1 } @@ -421,14 +535,16 @@ 'versioning_standards_use': 'https://w3id.org/everse/i/indicators/versioning_standards_use', 'archived_in_software_heritage': 'https://w3id.org/everse/i/indicators/archived_in_software_heritage', 'support_issue_tracking': 'https://w3id.org/everse/i/indicators/support_issue_tracking', - 'has_contribution_guidelines': 'https://w3id.org/everse/i/indicators/has_contribution_guidelines' + 'has_contribution_guidelines': 'https://w3id.org/everse/i/indicators/has_contribution_guidelines', + 'project_is_active': 'https://w3id.org/everse/i/indicators/project_is_active', + 'software_is_containerized': 'https://w3id.org/everse/i/indicators/software_is_containerized' } CHECKERS_DICT = { 'rsfc' : { 'name' : 'RSFC', 'id' : 'https://w3id.org/rsfc/', - 'version' : '0.1.5' + 'version' : '0.1.6' } } @@ -496,4 +612,6 @@ GITHUB_SCHEMA_REGEX, ZENODO_BADGE_REGEX, DOI_URL_REGEX -] \ No newline at end of file +] + +VALID_CONTAINER_FORMATS = ['dockerfile', 'docker', 'docker-compose', 'singularity', 'apptainer', 'containerfile', 'podman'] \ No newline at end of file diff --git a/src/rsfc/utils/rsfc_helpers.py b/src/rsfc/utils/rsfc_helpers.py index 8647670..0144e90 100644 --- a/src/rsfc/utils/rsfc_helpers.py +++ b/src/rsfc/utils/rsfc_helpers.py @@ -16,41 +16,6 @@ def decode_github_content(content_json): else: return encoded_content -def subtest_author_roles(authors): - - #Follows codemeta standards v2.0 and v3.0 - - author_roles = {} - for item in authors: - type_field = None - id_field = None - - if 'type' in item: - type_field = 'type' - elif '@type' in item: - type_field = '@type' - - if 'id' in item: - id_field = 'id' - elif '@id' in item: - id_field = '@id' - - - if type_field != None and id_field != None: - if item[type_field] == 'Person': - if item[id_field] not in author_roles: - author_roles[item[id_field]] = None - elif item[type_field] == 'Role' or item[type_field] == 'schema:Role': - if item['schema:author'] in author_roles: - if 'roleName' in item: - author_roles[item['schema:author']] = item['roleName'] - elif 'schema:roleName' in item: - author_roles[item['schema:author']] = item['schema:roleName'] - else: - continue - - return author_roles - def subtest_author_orcids(file_data): @@ -137,15 +102,20 @@ def check_issue(issue, issue_refs): return issue_id in issue_refs -def cross_check_any_issue(issues, commits): - issue_refs = extract_issue_refs(commits) - - for issue in issues: - issue_id = str(issue.get("number") or issue.get("iid")) - if issue_id in issue_refs: - return True - - return False +def cross_check_any_issue(issues, commits_list): + found_links = [] + + for commit in commits_list: + commit_message = commit['commit']['message'] + commit_sha = commit['sha'][:7] + + for issue in issues: + issue_number = issue['number'] + + if f"#{issue_number}" in commit_message: + found_links.append(f"Commit '{commit_sha}' linked to Issue #{issue_number}") + + return found_links def normalize_identifier_url(identifier): diff --git a/tests/test_main.py b/tests/test_main.py new file mode 100644 index 0000000..e69de29