diff --git a/.github/workflows/action-test-before-PR.yml b/.github/workflows/action-test-before-PR.yml
new file mode 100644
index 0000000..026a601
--- /dev/null
+++ b/.github/workflows/action-test-before-PR.yml
@@ -0,0 +1,37 @@
+name: Run SOMEF tests before PR
+
+on:
+ pull_request:
+ branches:
+ - main #should be this branch or master?
+ workflow_dispatch:
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ python-version: ["3.11", "3.12"]
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v2
+
+ - name: Set up Python
+ uses: actions/setup-python@v2
+ with:
+ python-version: ${{ matrix.python-version }}
+
+ - name: Install Poetry
+ run: curl -sSL https://install.python-poetry.org | python3 -
+
+ - name: Install dependencies
+ run: poetry install
+
+ - name: Download NLTK data
+ run: poetry run python -m nltk.downloader wordnet omw-1.4 punkt punkt_tab stopwords
+
+ - name: Configure SOMEF
+ run: poetry run somef_core configure -a
+
+ - name: Run pytest
+ run: poetry run pytest -v src/somef_core/test/
diff --git a/.gitignore b/.gitignore
index 9523bbe..af8bdf7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,10 +1,12 @@
build/*
dist/*
env*/*
+somef_core_env/
.vscode/
.ipynb_checkpoints/
config.json
__pycache__/
+**/__pycache__/
.DS_Store
Lib/*
Scripts/*
@@ -13,7 +15,7 @@ Scripts/*
*.ttl
env_3.9/*
src/somef_core/create_corpus_for_NER.py
-src/somef.egg-info/*
+src/somef_core.egg-info/*
local_tests/*
Dockerfile_old
repos.txt
diff --git a/CITATION.cff b/CITATION.cff
index b806beb..2b0dd79 100644
--- a/CITATION.cff
+++ b/CITATION.cff
@@ -1,9 +1,16 @@
-title: "SOMEF: Software metadata extraction framework"
-license: Apache-2.0
+# This CITATION.cff file was generated with cffinit.
+# Visit https://bit.ly/cffinit to generate yours today!
+
+cff-version: 1.2.0
+title: 'SOMEF: Software metadata extraction framework'
+message: >-
+ If you use this software, please cite both the article
+ from preferred-citation and the software itself.
+type: software
authors:
- family-names: Garijo
given-names: Daniel
- orcid: "http://orcid.org/0000-0003-0454-7145"
+ orcid: 'https://orcid.org/0000-0003-0454-7145'
- family-names: Mao
given-names: Allen
- family-names: Dharmala
@@ -20,18 +27,16 @@ authors:
given-names: Jenifer
- family-names: Mendoza
given-names: Juanje
-
-cff-version: 1.2.0
-message: "If you use this software, please cite both the article from preferred-citation and the software itself."
+license: Apache-2.0
preferred-citation:
authors:
- family-names: Kelley
given-names: Aidan
- family-names: Garijo
given-names: Daniel
- title: "A Framework for Creating Knowledge Graphs of Scientific Software Metadata"
+ title: A Framework for Creating Knowledge Graphs of Scientific Software Metadata
type: article
- journal: "Quantitative Science Studies"
- pages: "1-37"
+ journal: Quantitative Science Studies
+ pages: 1-37
year: 2021
doi: 10.1162/qss_a_00167
diff --git a/Dockerfile b/Dockerfile
index a986e1f..2f23ee2 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -6,7 +6,7 @@ RUN curl -sSL https://install.python-poetry.org | python3 -
RUN pip install poetry-plugin-shell
-WORKDIR "/somef-core"
+WORKDIR "/somef_core"
RUN poetry install
diff --git a/README.md b/README.md
index 45c57b6..9bde117 100644
--- a/README.md
+++ b/README.md
@@ -1,16 +1,15 @@
# Software Metadata Extraction Framework CORE (SOMEF-core)
-TO DO: Add other badges.
-
-[](https://somef-core.readthedocs.io/en/latest/?badge=latest)
-
+[](https://somef.readthedocs.io/en/latest/?badge=latest)
+[](https://badge.fury.io/py/somef) [](https://badge.fury.io/py/somef) [](https://zenodo.org/badge/latestdoi/190487675) [](https://mybinder.org/v2/gh/KnowledgeCaptureAndDiscovery/somef/HEAD?filepath=notebook%2FSOMEF%20Usage%20Example.ipynb) [](https://www.repostatus.org/#active)
A command line interface for automatically extracting relevant metadata from code repositories (readme, configuration files, documentation, etc.).
-This repository is extracted from https://github.com/SciCodes/somef-core/issues
+This repository is extracted from https://github.com/SciCodes/somef-core/
+**Demo:** See a [demo running somef as a service](https://somef.linkeddata.es), through the [SOMEF-Vider tool](https://github.com/SoftwareUnderstanding/SOMEF-Vider/).
**Authors:** Daniel Garijo, Allen Mao, Miguel Ángel García Delgado, Haripriya Dharmala, Vedant Diwanji, Jiaying Wang, Aidan Kelley, Jenifer Tabita Ciuciu-Kiss, Luca Angheluta and Juanje Mendoza.
@@ -19,6 +18,13 @@ This repository is extracted from https://github.com/SciCodes/somef-core/issues
Given a readme file (or a GitHub/Gitlab repository) SOMEF will extract the following categories (if present), listed in alphabetical order:
- **Acknowledgement**: Text acknowledging funding sources or contributors
+- **Application domain**: The application domain of the repository. Current supported domains include: Astrophysics, Audio, Computer vision, Graphs, Natural language processing, Reinforcement learning, Semantc web, Sequential. Domains are not mutually exclusive. These domains have been extracted from [awesome lists](https://github.com/topics/awesome-list) and [Papers with code](https://paperswithcode.com/). Find more information in our [documentation](https://somef.readthedocs.io/en/latest/)
+- **Authors**: Person(s) or organization(s) responsible for the project. We recognize the following properties:
+ - Name: name of the author (including last name)
+ - Given name: First name of an author
+ - Family name: Last name of an author
+ - Email: email of author
+ - URL: website or ORCID associated with the author
- **Build file**: Build file(s) of the project. For example, files used to create a Docker image for the target software, package files, etc.
- **Citation**: Preferred citation as the authors have stated in their readme file. SOMEF recognizes Bibtex, Citation File Format files and other means by which authors cite their papers (e.g., by in-text citation). We aim to recognize the following properties:
- Title: Title of the publication
@@ -43,7 +49,7 @@ Given a readme file (or a GitHub/Gitlab repository) SOMEF will extract the follo
- **Forks url**: Links to forks made of the project
- **Full name**: Name + owner (owner/name)
- **Full title**: If the repository is a short name, we will attempt to extract the longer version of the repository name
-- **Identifier**: Identifier associated with the software (if any), such as Digital Object Identifiers. DOIs associated with publications will also be detected.
+- **Identifier**: Identifier associated with the software (if any), such as Digital Object Identifiers and Software Heritage identifiers (SWH). DOIs associated with publications will also be detected.
- **Images**: Images used to illustrate the software component
- **Installation instructions**: A set of instructions that indicate how to install a target repository
- **Invocation**: Execution command(s) needed to run a scientific software component
@@ -52,6 +58,7 @@ Given a readme file (or a GitHub/Gitlab repository) SOMEF will extract the follo
- **License**: License and usage terms of a software component
- **Logo**: Main logo used to represent the target software component
- **Name**: Name identifying a software component
+- **Ontologies**: URL and path to the ontology files present in the repository
- **Owner**: Name and type of the user or organization in charge of the repository
- **Package distribution**: Links to package sites like pypi in case the repository has a package available.
- **Package files**: Links to package files used to wrap the project in a package.
@@ -69,18 +76,23 @@ Given a readme file (or a GitHub/Gitlab repository) SOMEF will extract the follo
- Link to the tarball zip and code of the release
- **Repository status**: Repository status as it is described in [repostatus.org](https://www.repostatus.org/).
- **Requirements**: Pre-requisites and dependencies needed to execute a software component
+- **Run**: Running instructions of a software component. It may be wider than the `invocation` category, as it may include several steps and explanations.
+- **Runtime platform**: specifies runtime platform or script interpreter dependencies required to run the project..
+- **Script files**: Bash script files contained in the repository
- **Stargazers count**: Total number of stargazers of the project
- **Support**: Guidelines and links of where to obtain support for a software component
- **Support channels**: Help channels one can use to get support about the target software component
+- **Type**: type of software (command line application, notebook, ontology, scientific workflow, etc.)
- **Usage examples**: Assumptions and considerations recorded by the authors when executing a software component, or examples on how to use it
+- **Workflows**: URL and path to the computational workflow files present in the repository
We use different supervised classifiers, header analysis, regular expressions, the GitHub/Gitlab API to retrieve all these fields (more than one technique may be used for each field) and language specific metadata parsers (e.g., for package files). Each extraction records its provenance, with the confidence and technique used on each step. For more information check the [output format description](https://somef.readthedocs.io/en/latest/output/)
## Documentation
-The documentation for somef-core is available at [https://somef-core.readthedocs.io/en/latest/](https://somef-core.readthedocs.io/en/latest/)
+See full documentation for somef-core is available at [https://somef-core.readthedocs.io/en/latest/](https://somef-core.readthedocs.io/en/latest/)
-## Cite SOMEF and SOMEF-core:
+## Cite SOMEF-core:
Journal publication (preferred):
@@ -132,7 +144,7 @@ To run somef_core, please follow the next steps:
Clone this GitHub repository
```
-git clone https://github.com/KnowledgeCaptureAndDiscovery/somef-core.git
+git clone https://github.com/SciCodes/somef-core.git
```
We use [Poetry](https://python-poetry.org/) to ensure library compatibility. It can be installed as follows:
@@ -145,32 +157,36 @@ This option is recommended over installing Poetry with pip install.
Now Poetry will handle the installation of SOMEF-core and all its dependencies configured in the `toml` file.
-To test the correct installation of poetry run:
+To test the correct installation of poetry run (poetry version `> 2.0.0`):
```
poetry --version
```
-Install somef and all their dependencies.
+Install somef-core and all their dependencies.
```
cd /somef_core
poetry install
```
-Now we need to access our virtual environment, to do so you have to install the [poetry plugin shell](https://github.com/python-poetry/poetry-plugin-shell) and run the following command:
+Now we need to access our virtual environment, to do so you can run the following command:
+```bash
+poetry env activate
```
-pip install poetry-plugin-shell
-```
-After `shell` is set up, you can run the following comand to access the virtual environment
-```
-poetry shell
+If the environment is not active, paste the command shown when `poetry env activate` is run, typically something like the command below:
+
+```bash
+source /path_to_env/ENV_NAME/bin/activate
```
-Test SOMEF installation
+
+To learn more about poetry environment management, visit their official documentation [here](https://python-poetry.org/docs/managing-environments/).
+
+Test the SOMEF-core installation run:
```bash
-somef --help
+somef_core --help
```
If everything goes fine, you should see:
@@ -189,7 +205,31 @@ Commands:
## Installing through Docker
-We are working on this section
+We provide a Docker image with SOMEF already installed. To run through Docker, you may build the Dockerfile provided in the repository by running:
+
+```bash
+docker build -t somef .
+```
+
+Or just use the Docker image already built in [DockerHub](https://hub.docker.com/r/kcapd/somef):
+
+```bash
+docker pull kcapd/somef
+```
+
+Then, to run your image just type:
+
+```bash
+docker run --rm -it kcapd/somef
+```
+
+And you will be ready to use SOMEF (see section below). If you want to have access to the results we recommend [mounting a volume](https://docs.docker.com/storage/volumes/). For example, the following command will mount the current directory as the `out` folder in the Docker image:
+
+```bash
+docker run -it --rm -v $PWD/:/out kcapd/somef
+```
+
+If you move any files produced by somef into `/out`, then you will be able to see them in your current directory.
## Configure
@@ -228,6 +268,9 @@ Options:
-h, --help Show this message and exit.
```
+### Updating SOMEF
+
+If you update SOMEF to a newer version, we recommend you `configure` again the library (by running `somef configure`). The rationale is that different versions may rely on classifiers which may be stored in a different path.
## Usage
@@ -239,6 +282,7 @@ Usage: somef_core describe [OPTIONS]
Running the Command Line Interface
Options:
+ -t, --threshold FLOAT Threshold to classify the text [required]
Input: [mutually_exclusive, required]
-r, --repo_url URL Github/Gitlab Repository URL
-d, --doc_src PATH Path to the README file source
@@ -250,6 +294,15 @@ Options:
output will be in JSON
-c, --codemeta_out PATH Path to an output codemeta file
+ -g, --graph_out PATH Path to the output Knowledge Graph export
+ file. If supplied, the output will be a
+ Knowledge Graph, in the format given in the
+ --format option chosen (turtle, json-ld)
+
+ -f, --graph_format [turtle|json-ld]
+ If the --graph_out option is given, this is
+ the format that the graph will be stored in
+
-p, --pretty Pretty print the JSON output file so that it
is easy to compare to another JSON output
file.
@@ -264,6 +317,11 @@ Options:
will be stored at the
desired path
+ -all, --requirements_all Export all detected requirements, including
+ text and libraries (default).
+
+ -v, --requirements_v Export only requirements from structured
+ sources (pom.xml, requirements.txt, etc.)
-h, --help Show this message and exit.
```
@@ -273,9 +331,29 @@ Options:
The following command extracts all metadata available from [https://github.com/dgarijo/Widoco/](https://github.com/dgarijo/Widoco/).
```bash
-somef_core describe -r https://github.com/dgarijo/Widoco/ -o test.json
+somef_core describe -r https://github.com/dgarijo/Widoco/ -o test.json -t 0.8
```
+Try SOMEF in Binder with our sample notebook: [](https://mybinder.org/v2/gh/KnowledgeCaptureAndDiscovery/somef/HEAD?filepath=notebook%2FSOMEF%20Usage%20Example.ipynb)
+
## Contribute:
If you want to contribute with a pull request, please do so by submitting it to the `dev` branch.
+
+## Next features:
+
+To see upcoming features, please have a look at our [open issues](https://github.com/SciCodes/somef-core/issues) and [milestones](https://github.com/SciCodes/somef-core/milestones)
+
+## Extending SOMEF categories:
+
+To run a classifier with an additional category or remove an existing one, a corresponding path entry in the config.json should be provided and the category type should be added/removed in the category variable in `cli.py`.
+
+## Metadata Support
+
+SOMEF supports the extraction and analysis of metadata in package files of several programming languages. Current support includes: `setup.py` and `pyproject.toml` for Python, `pom.xml` for Java, `.gemspec` for Ruby, `DESCRIPTION` for R, `bower.json` for JavaScript, HTML or CSS, `.cabal` for Haskell, `cargo.toml` for RUST, `composer` for PHP, `.juliaProject.toml` for Julia , `AUTHORS`, `codemeta.json`, `publiccode.yml`, `dockerfile` and `citation.cff`
+This includes identifying dependencies, runtime requirements, and development tools specified in project configuration files.
+
+## Limitations
+
+SOMEF is designed to work primarily with repositories written in English.
+Repositories in other languages may not be processed as effectively, and results could be incomplete or less accurate.
diff --git a/docs/author.md b/docs/author.md
new file mode 100644
index 0000000..fd00768
--- /dev/null
+++ b/docs/author.md
@@ -0,0 +1,113 @@
+The following metadata fields can be extracted from a AUTHORS file.
+These fields are defined in the [Authors file specification](https://opensource.google/documentation/reference/releasing/authors/), and are mapped according to the [CodeMeta crosswalk for AUTHORS files](https://github.com/codemeta/codemeta/blob/master/crosswalks/codemeta-V2.csv).
+
+| Software metadata category | SOMEF metadata JSON path | AUTHORS metadata file field |
+|-------------------------------|---------------------------------------------|------------------------------|
+| authors - value | authors[i].result.value | *(1)* value |
+| authors - name | authors[i].result.name | *(2)* name |
+| authors - email | authors[i].result.email | *(3)* email |
+| authors - given name | authors[i].result.given_name | *(4)* if type person |
+| authors - last name | authors[i].result.last_name | *(5)* if type person |
+
+---
+
+*(1)*
+- Regex: line.strip()
+- Example: `Jane Doe `
+- Result: `Jane Doe `
+
+*(2)*
+- line[:email_match.start()].strip()
+- Example: `Jane Doe `
+- Result: `Jane Doe`
+
+*(3)*
+- Regex: re.search(r'<([^>]+)>', line)
+- Example: `Jane Doe `
+- Result: `jane.doe@example.org`
+
+*(4)*
+- First part of name
+- Example: `Jane Doe `
+- Result: `Jane`
+
+*(5)*
+- Second part of name
+- Example: `Jane Doe `
+- Result: `Doe`
+
+## Supported files of authors.
+
+The following filenames are recognized and processed automatically:
+
+* `AUTHORS`
+* `AUTHORS.md`
+* `AUTHORS.txt`
+
+These files are expected to be located at the root of the repository. Filenames are matched case-insensitively.
+
+## Purpose and Format
+
+These files typically contain a list of individuals and/or organizations that have contributed to the project. While there is no universal standard for formatting, a widely referenced convention is Google's guidance:
+
+🔗 [Google Open Source: Authors Files Protocol](https://opensource.google/documentation/reference/releasing/authors/)
+
+The content may be structured as:
+
+* Simple plain text, with one contributor per line.
+* Markdown-formatted text (`.md` files).
+* Lines including contributor names, emails (e.g., `Name `), and sometimes affiliations.
+
+### Examples of Valid Entries
+
+```text
+Jane Doe
+John Smith
+Acme Corporation
+Google Inc.
+```
+
+### Examples of NON Valid Entries
+
+```text
+JetBrains <>
+Microsoft
+Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung
+scrawl - Top contributor
+Tom
+```
+## What Is Read vs. Discarded
+
+When processing these files, the parser will:
+
+**Include** lines that:
+
+* Contain person names, optionally with emails (`Name `).
+* Clearly refer to organizations (e.g., "Google LLC", "OpenAI Inc.").
+
+**Discard** lines that:
+
+* Are headers, decorative separators, or markdown formatting (`#`, `*`, `=`, etc.).
+* Contain only URLs or links.
+* Are single words with no email and no organizational keyword (e.g., `JetBrains <>`).
+* Are markdown or structured noise (`---`, `{}`, etc.).
+* Contain more than four words and are not recognized as organizations — to avoid capturing generic or descriptive sentences (e.g., This line not is an author).
+
+### Special Cases
+
+* Entries with only a first name and an email are accepted but must not assign an empty `last_name`.
+* Lines starting with `-` or `*` are considered lists, but only parsed if the content matches expected author patterns.
+* Blocks enclosed in `{}` are stripped before parsing.
+* Any line matching known organization suffixes (`Inc.`, `LLC`, `Ltd.`, `Corporation`) is treated as an organization, even if no email is present.
+* Some organization names (e.g., Open Source Initiative) may be mistakenly treated as person names if they do not contain a company designator or email. To improve detection, it is recommended to use names like Open Source Initiative Inc.
+* In such cases, only the meaningful part (typically the name) is extracted before any descriptive annotations.
+For example, the line:
+Tom Smith (Tom) - Project leader 2010-2018
+Will be interpreted as:
+{
+ "type": "Person",
+ "name": "Tom Smith",
+ "value": "Tom Smith",
+ "given_name": "Tom",
+ "last_name": "Smith"
+}
diff --git a/docs/bower.md b/docs/bower.md
new file mode 100644
index 0000000..8318067
--- /dev/null
+++ b/docs/bower.md
@@ -0,0 +1,39 @@
+The following metadata fields can be extracted from a bower.json file.
+These fields are defined in the [Bower specification](https://github.com/bower/spec/blob/master/json.md), currently at version **4.0.0**, and are mapped according to the [CodeMeta crosswalk for bower.json](https://github.com/codemeta/codemeta/blob/master/crosswalks/NodeJS.csv).
+
+| Software metadata category | SOMEF metadata JSON path | BOWER.JSON metadata file field |
+|-----------------------------|---------------------------------------|---------------------|
+| authors | authors[i].result.value | authors[] |
+| description | description[i].result.value | description |
+| has_package_file | has_package_file[i].result.value | URL of the bower.json file |
+| homepage | homepage[i].result.value | homepage |
+| license | license[i].result.value | license |
+| name | name[i].result.value | name |
+| keywords | keywords[i].result.value | keywords |
+| requirements - value | requirements[i].result.value | "dependencies": {"paq":"version"} -> paq: version *(1)* |
+| requirements - name | requirements[i].result.name | "dependencies": {"paq":"version"} -> paq |
+| requirements - version | requirements[i].result.version | "dependencies": {"paq":"version"} -> version |
+| requirements - dependency type | requirements[i].result.dependency_type | dependencies -> runtime , devDependencies -> dev |
+| version | version[i].result.value | version |
+
+
+---
+
+*(1)*
+
+- Example:
+```
+ "dependencies": {
+ "jquery": "^3.1.1",
+ "bootstrap": "^3.3.7",
+ "moment": "^2.17.1",
+ "components-font-awesome": "^4.7.0",
+ "font-awesome": "^4.7.0",
+ "materialize": "^0.97.8"
+ }
+ ```
+ First item results:
+ - Result value: "jquery: ^3.1.1"
+ - Result name": "jquery"
+ - Result version": "^3.1.1"
+ - Result dependency_type": "runtime" because it is "dependencies"s
\ No newline at end of file
diff --git a/docs/cabal.md b/docs/cabal.md
new file mode 100644
index 0000000..d72be9f
--- /dev/null
+++ b/docs/cabal.md
@@ -0,0 +1,61 @@
+The following metadata fields can be extracted from a cabal file.
+These fields are defined in the [Cabal specification](https://cabal.readthedocs.io/en/3.10/cabal-package.html), currently at version **3.10** and are mapped according to the [Codemeta crosswalk for cabal](https://github.com/codemeta/codemeta/blob/master/crosswalks/Cabal.csv).
+
+| Software metadata category | SOMEF metadata JSON path | CABAL metadata file field |
+|-------------------------------|---------------------------------------|-----------------------------------------|
+| description | description[i].result.value | synopsis / description *(1)*|
+| development_status | development_status[i].result.value | stability |
+| has_package_file | has_package_file[i].result.value | URL of the filename.cabal file |
+| homepage | homepage[i].result.value | homepage *(2)* |
+| issue_tracker | issue_tracker[i].result.value | bug-reports |
+| license | license[i].result.value | Regex license *(3)* |
+| package_id | package_id[i].value | name *(4)* |
+| requirements - value | requirements[i].result.value | *(5)* |
+| requirements - name | requirements[i].result.name | library.build-depends name *(5.1)* |
+| requirements - version | requirements[i].result.version | library.build-depends version *(5.2)* |
+| requirements - development type | requirements[i].result.development_type | "runtime" |
+| version | version[i].result.value | version *(6)* |
+
+---
+
+*(1)*
+- Regex: `r'description:\s*(.*?)(?=\n\S|\Z)' → group[1]`
+- Example: `description: text description`
+- Result: `text description`
+
+*(2)*
+- Regex: `re.search(r'homepage:\s*(.*)' → group[1]`
+- Example: `homepage: https://github.com/joshuaclayton/unused#readme`
+- Result: `https://github.com/joshuaclayton/unused#readme`
+
+*(3)*
+- Regex: `r'license:\s*(.*)' → group[1]`
+- Example: `license: BSD-3-Clause`
+- Result: `BSD-3-Clause`
+
+*(4)*
+- Regex: `r'name:\s*(.*)' → group[1]`
+- Example: `name: unused`
+- Result: `unused`
+
+*(5)*, *(5.1)*, *(5.2)*
+
+- Regex 1: `r'library\s*\n(.*?)(?=\n\S|\Z)' → group[1]` --> first look into library
+- Regex 2: `r'build-depends:\s*(.*?)(?=\n\s*(?:[A-Za-z0-9_-]+\s*:|if\s|\Z)) ' → group[1]` --> build-depends into library content
+- Regex 3: `r'^([a-zA-Z0-9-_]+)\s*(.*?)$'` --> extract groups for name and version
+- Example:
+```
+library
+ build-depends:
+ Cabal-syntax ^>= 3.17,
+ array >= 0.4.0.1 && < 0.6
+```
+- Result value: `Cabal-syntax ^>= 3.17`
+- Result name: `Cabal-syntax`
+- Result version: `^>= 3.17`
+
+*(6)*
+- Regex: `r'version:\s*(.*)' → group[1]`
+- Example: `version: 0.10.0.0`
+- Result: `0.10.0.0`
+
diff --git a/docs/cargo.md b/docs/cargo.md
new file mode 100644
index 0000000..f50fc83
--- /dev/null
+++ b/docs/cargo.md
@@ -0,0 +1,41 @@
+The following metadata fields can be extracted from a cargo.toml file.
+These fields are defined in the [Cargo.toml specification](https://doc.rust-lang.org/cargo/reference/manifest.html), and are mapped according to the [CodeMeta crosswalk for cargo.toml](https://github.com/codemeta/codemeta/blob/master/crosswalks/Cargo.csv).
+
+| Software metadata category | SOMEF metadata JSON path | CARGO.TOML metadata file field |
+|-------------------------------|--------------------------------------------|------------------------------|
+| authors - value | authors[i].result.value | package.authors |
+| authors - name | authors[i].result.name | package.authors *(1)* |
+| authors - email | authors[i].result.email | package.authors *(2)* |
+| code_repository | code_repository[i].result.value | package.repository |
+| description | description[i].result.value | package.description |
+| has_package_file | has_package_file[i].result.value | URL of the cargo.toml file |
+| keywords | keywords[i].result.value | package.keywords |
+| license - value | license[i].result.value | package.license |
+| license - spdx id | license[i].result.spdx_id | package.license *(3)* |
+| license - name | license[i].result.name | package.license *(4)* |
+| package_id | package_id[i].result.value | package.name |
+| requirements - value | requirements[i].result.value | target.dependencies or depencencies name = version |
+| requirements - name | requirements[i].result.name | target.dependencies or depencencies name |
+| requirements - version | requirements[i].result.version | target.dependencies or depencencies version |
+| requirements - development type | requirements[i].result.development_type | target.dependencies or dependencies (version, git, path, other) |
+| version - value | version[i].result.value | package.version |
+| version - tag | version[i].result.tag | package.version |
+
+---
+
+*(1)*
+- Regex: `re.search(r'<([^>]+)>', author_str)`--> look in package.authors, them from init until email init.
+- Example: `authors = ["rustdesk "]`
+- Result: `rustdesk`
+
+*(2)*
+- Regex: `re.search(r'<([^>]+)>', author_str)`--> look in package.authors
+- Example: `authors = ["rustdesk "]`
+- Result: `info@rustdesk.com`
+
+*(3)*, *(4)*
+- Look for expressions in a local dictionary with all the reference and spdx_id
+- Example: `license = "Apache-2.0"`
+- Result spdx_id: `Apache-2.0`
+- Result name: `Apache License 2.0`
+
diff --git a/docs/codemetajson.md b/docs/codemetajson.md
new file mode 100644
index 0000000..6fb84c8
--- /dev/null
+++ b/docs/codemetajson.md
@@ -0,0 +1,46 @@
+The following metadata fields can be extracted from a codemeta.json file.
+These fields are defined in the [Codemeta specification](https://github.com/codemeta/codemeta/blob/master/crosswalk.csv), currently at version **3.0**, and are mapped according to the properties and classes defined in the CodeMeta vocabulary.
+
+| Software metadata category | SOMEF metadata JSON path | CODEMETA.JSON metadata file field |
+|-------------------------------|------------------------------|--------------------------------------------|
+| application_domain | application_domain[i].result.value | applicationCategory |
+| authors - value | authors[i].result.value | author.name or author.givenName + author.familyName |
+| authors - email | authors[i].result.email | author.email |
+| authors - affiliation | authors[i].result.affiliation | author.affiliation |
+| authors - identifier | authors[i].result.identifier | author.identifier or author.@id |
+| citation - value | citation[i].result.value | referencePublication.name or referencePublication.title |
+| citation - title | citation[i].result.title | referencePublication.name or referencePublication.title |
+| citation - url | citation[i].result.url | referencePublication.url |
+| citation - date published | citation[i].result.date_published | referencePublication.datePublished
+| citation - doi | citation[i].result.doi | referencePublication.identifier |
+| code_repository | code_repository[i].result.value | codeRepository |
+| continuous_integration | continuous_integration[i].result.value | contIntegration |
+| date_created | date_created[i].result.value | dateCreated |
+| date_updated | date_updated[i].result.value | dateModified |
+| date_published | date_published[i].result .value | datePublished |
+| description | description[i].result.value | description |
+| development_status | development_status[i].result.value | developmentStatus |
+| download_url | download_url[i].result.value | downloadUrl |
+| has_package_file | has_package_file[i].result.value | URL of the codemeta.json file |
+| funding - funder | funding[i].result.funder | funding.funder or funding.funder.name |
+| funding - funding | funding[i].result.funding | String.fundingIdentifier |
+| identifier | identifier[i].result.value | identifier |
+| issue_tracker | issue_tracker[i].result.value | issueTracker |
+| keywords | keywords[i].result.value | keywords |
+| license - value | license[i].result .value | license.name |
+| license - url | license[i].result.url | license.url |
+| license - spdx id | license[i].result.spdx_id | license.identifier if "spdx.org/licenses/ |
+| license - identifier | license[i].result.identifier | license.identifier or https://spdx.org/licenses/{license} |
+| name | name[i].result.value | name |
+| programming_languages - value | programming_languages[i].result.value | programmingLanguage.name |
+| programming_languages - name | programming_languages[i].result.name | programmingLanguage.name |
+| programming_languages - version | programming_languages[i].result.version | programmingLanguage.version |
+| programming_languages - url | programming_languages[i].result.url | programmingLanguage.url |
+| readme_url | readme_url[i].result.value | readme |
+| requirements - value | requirements[i].result.value | softwareRequirements or softwareRequirements.name == softwareRequirements.version |
+| requirements - name | requirements[i].result.name | softwareRequirements or softwareRequirements.name or softwareRequirements.identifier |
+| requirements - version | requirements[i].result.version | softwareRequirements or softwareRequirements.version |
+| version | version[i].result.value | softwareVersion or version |
+
+
+
\ No newline at end of file
diff --git a/docs/composer.md b/docs/composer.md
new file mode 100644
index 0000000..a3ac6e6
--- /dev/null
+++ b/docs/composer.md
@@ -0,0 +1,25 @@
+The following metadata fields can be extracted from a composer.json file.
+These fields are defined in the [Composer.json specification](https://getcomposer.org/doc/04-schema.md), currently at version **2.8.12**, and are mapped to CodeMeta properties following the internal mapping implemented in the parser, since no dedicated CodeMeta crosswalk for composer.json exists.
+
+| Software metadata category | SOMEF metadata JSON path | COMPOSER.JSON metadata file field |
+|-------------------------------|-----------------------------------|-----------------------------------|
+| authors - value | authors[i].result.value | authors.name |
+| authors - name | authors[i].result.name | authors.name |
+| authors - name | authors[i].result email | authors.email |
+| authors - url | authors[i].result.url | authors.homepage |
+| authors - role | authors[i].result.role | authors.role |
+| code_repository | code_repository[i].result.value | repository or repository.url |
+| description | description[i].result.value | description |
+| has_package_file | has_package_file[i].result.value | URL of the composer.json file |
+| homepage | homepage[i].result.value | homepage |
+| keywords | keywords[i].result.value | keywords |
+| license - value | license[i].result.value | license |
+| license - spdx id | license[i].result.spdx_id | license(name:value) -> license.value if spdx_id |
+| license - name | license[i].result.name | license(name:value) -> license.name |
+| package_id | package_id[i].result.value | name |
+| requirements - value | requirements[i].result.value | require.name require.version or require-dev.name reire-dev.version |
+| requirements - name | requirements[i].result.name | require.name or require-dev.name |
+| requirements - version | requirements[i].result.version | require.version or require-dev.version |
+| requirements - dependency type | requirements[i].result.dependency_type | require = runtime or require-dev = dev |
+| version - value | version[i].result.value | version |
+| version - tag | version[i].result.tag | version |
diff --git a/docs/description.md b/docs/description.md
new file mode 100644
index 0000000..87c5eb8
--- /dev/null
+++ b/docs/description.md
@@ -0,0 +1,86 @@
+The following metadata fields can be extracted from a DESCRIPTION file.
+These fields are defined in the [DESCRIPTON specification](https://r-pkgs.org/description.html), and are mapped according to the [CodeMeta crosswalk for DESCRIPTION files based in R Package](https://github.com/codemeta/codemeta/blob/master/crosswalks/R%20Package%20Description.csv).
+
+| Software metadata category | SOMEF metadata JSON path | DESCRIPTION metadata file field |
+|-------------------------------|---------------------------------|---------------------|
+| authors | authors[i].result.value | Authors *(1)* |
+| authors | authors[i].result.email | Authors *(2)* |
+| code_repository | code_repository[i].result.value | URL *(3)* |
+| description | description[i].result.value | Description *(3)* |
+| has_package_file | has_package_file[i].result.value | URL of the DESCRIPTION file |
+| homepage | homepage[i].result.value | URL *(3)* |
+| issue_tracker | issue_tracker[i].result.value | BugReports *(5)* |
+| license | license[i].result.value | License *(6)* |
+| package_id | package_id[i].result.value | Package *(6)* |
+| version | version[i].result.value | Version *(7)* |
+
+---
+
+*(1)*, *(2)* ,
+- Regex 1: `r'Authors@R:\s*c\(([\s\S]*?)\)\s*$' → group[1]`
+- Regex 2: `find in group[1] all persons and extract first name (or organition), last name and email`
+- Example:
+```
+ Authors@R: c(
+ person("Hadley", "Wickham", , "hadley@posit.co", role = "aut",
+ comment = c(ORCID = "0000-0003-4757-117X")),
+ person("Winston", "Chang", role = "aut",
+ comment = c(ORCID = "0000-0002-1576-2126"))
+ )
+```
+
+- Result:
+```
+{'result': {'value': 'Hadley Wickham', 'type': 'Agent', 'email': 'hadley@posit.co'}, 'confidence': 1, 'technique': 'code_parser', 'source': 'https://example.org/DESCRIPTION'}, {'result': {'value': 'Winston Chang', 'type': 'Agent'}, 'confidence': 1, 'technique': 'code_parser', 'source': 'https://example.org/DESCRIPTION'}
+```
+
+*(3)*
+- Regex: `'URL:\s*([^\n]+(?:\n\s+[^\n]+)*)'`
+- if github.com or gitlab.com --> code_repository
+- if not --> homepage
+
+- Example:
+```
+URL: https://ggplot2.tidyverse.org,
+ https://github.com/tidyverse/ggplot2
+```
+
+- Result code_repository: `'result': {'value': 'https://github.com/tidyverse/tidyverse', 'type': 'Url'}`
+- Result hompeage: `'result': {'value': 'https://tidyverse.tidyverse.org', 'type': 'Url'}}`
+
+
+*(3)*
+- Regex: `r'Description:\s*([^\n]+(?:\n\s+[^\n]+)*)', content)`
+- Example:
+```Description: A system for 'declaratively' creating graphics, based on "The
+ Grammar of Graphics". You provide the data, tell 'ggplot2' how to map
+ variables to aesthetics, what graphical primitives to use, and it
+ takes care of the details.
+```
+- Result:
+```
+A system for 'declaratively' creating graphics, based on "The
+ Grammar of Graphics". You provide the data, tell 'ggplot2' how to map
+ variables to aesthetics, what graphical primitives to use, and it
+ takes care of the details.
+```
+
+*(5)*
+- Regex: `'BugReports:\s*([^\n]+)'`
+- Example: `BugReports: https://github.com/tidyverse/ggplot2/issues`
+- Result: `https://github.com/tidyverse/ggplot2/issues`
+
+*(5)*
+- Regex: `r'License:\s*([^\n]+)'``
+- Example: `License: MIT + file LICENSE`
+- Result: `MIT + file LICENSE`
+
+*(6)*
+- Regex: `r'Package:\s*([^\n]+)`
+- Example: `Package: ggplot2`
+- Result: `ggplot2`
+
+*(6)*
+- Regex: `r'Version:\s*([^\n]+)'`
+- Example: `Version: 2.0.0.9000`
+- Result: `2.0.0.9000`
diff --git a/docs/dockerfiledoc.md b/docs/dockerfiledoc.md
new file mode 100644
index 0000000..6dc668f
--- /dev/null
+++ b/docs/dockerfiledoc.md
@@ -0,0 +1,30 @@
+The following metadata fields can be extracted from a Dockerfile.
+These fields are defined using Dockerfile `LABEL` instructions as described in the
+[Dockerfile reference](https://docs.docker.com/reference/dockerfile/) and are interpreted
+according to the OCI Image Specification, following the
+[mapping for OCI image annotations](https://github.com/opencontainers/image-spec/blob/main/annotations.md#pre-defined-annotation-keys).
+
+| Software metadata category | SOMEF metadata JSON path | DOCKERFILE metadata file field |
+|-----------------------------|-----------------------------------------|------------------------------------|
+| authors | authors[i].result.value | org.opencontainers.image.authors *(1)* |
+| authors | authors[i].result.value | LABEL maintainer *(1)* |
+| code_repository | code_repository[i].result.value | org.opencontainers.image.url |
+| description | description[i].result.value | org.opencontainers.image.description |
+| documentation | documentation[i].result.value | org.opencontainers.image.documentation |
+| license | license[i].result.value | org.opencontainers.image.licenses |
+| name | name[i].result.value | org.opencontainers.image.ref.name |
+| owner | owner[i].result.value | org.opencontainers.image.vendor |
+| version | version[i].result.value | org.opencontainers.image.version |
+
+
+---
+
+
+*(1)*
+- Example:
+```
+LABEL maintainer="The Prometheus Authors "
+LABEL org.opencontainers.image.authors="The Prometheus Authors" \
+```
+
+
diff --git a/docs/gemspec.md b/docs/gemspec.md
new file mode 100644
index 0000000..b55cbd0
--- /dev/null
+++ b/docs/gemspec.md
@@ -0,0 +1,86 @@
+The following metadata fields can be extracted from a gemspec file.
+These fields are defined in the [Ruby Gems specification](https://guides.rubygems.org/specification-reference/) currently at version **2.0**, and are mapped according to the [CodeMeta crosswalk for ruby gems](https://github.com/codemeta/codemeta/blob/master/crosswalks/Ruby%20Gem.csv).
+
+| Software metadata category | SOMEF metadata JSON path | .gemspec metadata file field |
+|-------------------------------|--------------------------------|--------------------------------------|
+| authors | authors[i].result.value | gem.authors *(1)* |
+| description | description[i].resultvalue | description *(2)* |
+| has_package_file | has_package_file[i].result.value | URL of the filename.gemspec file |
+| homepage | homepage[i].result.value | homepage *(3)* |
+| license - value | license[i].result.value | license/licenses *(4)* |
+| license - spdx id | license[i].result.spdx_id | license/licenses *(4)* spdx_id |
+| license - name | license[i].result.name | llicense/licenses name *(4)* |
+| package_id | package_id[i].result.value | name *(5)* |
+| requirements - value | requirements[i].result.value | requirements/add_dependency/add_development_dependency name:version *(6)* |
+| requirements - name | requirements[i].result.name | requirements/add_dependency/add_development_dependency name *(6)* |
+| requirements - version | requirements[i].result.version | requirements/add_dependency/add_development_dependency version *(6)* |
+| requirements - development type | requirements[i].result.development_type | add_dependency -> runtime *(6)* |
+| requirements - development type | requirements[i].result.development_type | add_development_dependency -> dev *(6)* |
+
+---
+
+*(1)*
+- Regex: `r'gem\.author[s]?\s*=\s*(?P"[^"]*"|\[[^\]]*\])'`
+- Example:
+```
+ s.authors = [
+ "André Arko", "Samuel Giddins", "Colby Swandale", "Hiroshi Shibata"
+ ]
+```
+```
+ s.author = "Daniel Garijo"
+```
+
+*(2)*
+- Regex: `r'gem\.description\s*=\s*%q{([^}]+)}|gem\.description\s*=\s*["\']([^"\']+)["\']'`
+
+
+*(3)*
+- Regex: `r'gem\.homepage\s*=\s*["\']([^"\']+)["\']`
+- Example: `s.homepage = "https://bundler.io"`
+- Result: `https://bundler.io`
+
+*(4)*
+- Regex: `r'gem\.license[s]?\s*=\s*["\']([^"\']+)["\']'`
+- if license with spdx_id:
+```
+ name
+ value
+ spdx_id
+```
+- if not just value
+- Example: `gem.license = 'MIT'`
+- Result: `MIT`
+
+*(5)*
+- Regex: `r'gem\.name\s*=\s*["\']([^"\']+)["\']`
+- Example: `gem.name = "bootstrap-datepicker-rails"`
+- Resutl: `bootstrap-datepicker-rails`
+
+*(5)*
+- Regex1: `r'gem\.requirements\s*=\s*(\[.*?\])'`
+- Example:
+```
+spec.requirements = [
+ "Java Development Kit (JDK) 1.6 or newer is required.",
+ "The environment variable JAVA_HOME must be correctly defined."
+]
+
+```
+- Result: `["Java Development Kit (JDK) 1.6 or newer is required.","The environment variable JAVA_HOME must be correctly defined."]`
+- Regex2: `r'gem\.add_dependency\s*["\']([^"\']+)["\'](?:\s*,\s*["\']([^"\']+)["\'])?'`
+- Regex3: `'gem\.add_development_dependency\s*["\']([^"\']+)["\'](?:\s*,\s*["\']([^"\']+)["\'])?'`
+- Example:
+```
+ gem.add_dependency "railties", ">= 3.0"
+ gem.add_development_dependency "bundler", ">= 1.0"
+```
+Result: add_depency --> type runtime; add_development_dependencyd --> type dev
+```
+ [{'result': {'value': 'railties: >= 3.0', 'name': 'railties', 'version': '>= 3.0', 'type': 'Software_application', 'dependency_type': 'runtime'}, 'confidence': 1, 'technique': 'code_parser', 'source': 'https://example.org/bootstrap-datepicker-rails.gemspec'}, {'result': {'value': 'bundler: >= 1.0', 'name': 'bundler', 'version': '>= 1.0', 'type': 'Software_application', 'dependency_type': 'dev'}, 'confidence': 1, 'technique': 'code_parser', 'source': 'https://example.org/bootstrap-datepicker-rails.gemspec'}]
+```
+
+
+
+
+
diff --git a/docs/index.md b/docs/index.md
index f6e1671..fdf438d 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,19 +1,35 @@
-# Software Metadata Extraction Framework (SOMEF-core) [](https://badge.fury.io/py/somef) [](https://badge.fury.io/py/somef) [](https://zenodo.org/badge/latestdoi/190487675) [](https://mybinder.org/v2/gh/KnowledgeCaptureAndDiscovery/somef/HEAD?filepath=notebook%2FSOMEF%20Usage%20Example.ipynb) [](https://www.repostatus.org/#active)
+# Software Metadata Extraction Framework (SOMEF) [](https://badge.fury.io/py/somef) [](https://badge.fury.io/py/somef) [](https://zenodo.org/badge/latestdoi/190487675) [](https://mybinder.org/v2/gh/KnowledgeCaptureAndDiscovery/somef/HEAD?filepath=notebook%2FSOMEF%20Usage%20Example.ipynb) [](https://www.repostatus.org/#active)
**Authors:** Daniel Garijo, Allen Mao, Miguel Ángel García Delgado, Haripriya Dharmala, Vedant Diwanji, Jiaying Wang, Aidan Kelley and Jenifer Tabita Ciuciu-Kiss.
-The aim of SOMEF-core is to help automatically extract metadata from scientific software from their readme files and GitHub repositories and make it available in a machine-readable manner. Thanks to SOMEF, we can populate knowedge graphs of scientific software metadata and relate different software together.
+The aim of SOMEF is to help automatically extract metadata from scientific software from their readme files and GitHub repositories and make it available in a machine-readable manner. Thanks to SOMEF, we can populate knowedge graphs of scientific software metadata and relate different software together.
-SOMEF-core has currently been tested with GitHub repositories, but it can extract metadata from any readme file written in mardown syntax.
+SOMEF has currently been tested with GitHub repositories, but it can extract metadata from any readme file written in mardown syntax.
!!! info
If you experience any issues when using SOMEF, please open an issue on our [GitHub repository](https://github.com/KnowledgeCaptureAndDiscovery/somef/issues).
## Features
-Given a readme file (or a GitHub repository) SOMEF-core will extract the following categories (if present):
+Given a readme file (or a GitHub repository) SOMEF will extract the following categories (if present):
- **Acknowledgement**: Text acknowledging funding sources or contributors
+- **Application domain**: The application domain of the repository. This may be related to the research area of a software component (e.g., Astrophysics) or the general domain/functionality of the tool (i.e., machine learning projects)[1](#myfootnote1)
+- **Assets**: files attached to the release
+ - url: URL of the publication of the file
+ - name: name of the file
+ - content_size: file size
+ - content_url: direct download link for the release file
+ - encoding_format: format of the file
+ - upload_date: date of publishing
+ - download_count: numbers of downloads
+- **Authors**: Person(s) or organization(s) responsible for the project. We recognize the following properties:
+ - Name: name of the author (including last name)
+ - Given name: First name of an author
+ - Family name: Last name of an author
+ - Email: email of author
+ - URL: website or ORCID associated with the author
+ - Affiliation: name of organization or affiliation
- **Build file**: Build file(s) of the project. For example, files used to create a Docker image for the target software, package files, etc.
- **Citation**: Preferred citation as the authors have stated in their readme file. SOMEF recognizes Bibtex, Citation File Format files and other means by which authors cite their papers (e.g., by in-text citation). We aim to recognize the following properties:
- Title: Title of the publication
@@ -38,7 +54,8 @@ Given a readme file (or a GitHub repository) SOMEF-core will extract the followi
- **Forks url**: Links to forks made of the project
- **Full name**: Name + owner (owner/name)
- **Full title**: If the repository is a short name, we will attempt to extract the longer version of the repository name
-- **Identifier**: Identifier associated with the software (if any), such as Digital Object Identifiers. DOIs associated with publications will also be detected.
+- **Homepage**: URL of the item.
+- **Identifier**: Identifier associated with the software (if any), such as Digital Object Identifiers and Software Heritage identifiers (SWH). DOIs associated with publications will also be detected.
- **Images**: Images used to illustrate the software component
- **Installation instructions**: A set of instructions that indicate how to install a target repository
- **Invocation**: Execution command(s) needed to run a scientific software component
@@ -47,12 +64,14 @@ Given a readme file (or a GitHub repository) SOMEF-core will extract the followi
- **License**: License and usage terms of a software component
- **Logo**: Main logo used to represent the target software component
- **Name**: Name identifying a software component
+- **Ontologies**: URL and path to the ontology files present in the repository
- **Owner**: Name and type of the user or organization in charge of the repository
- **Package distribution**: Links to package sites like pypi in case the repository has a package available.
- **Package files**: Links to package files used to wrap the project in a package.
- **Programming languages**: Languages used in the repository
- **Related papers**: URL to possible related papers within the repository stated within the readme file (from Arxiv)
-- **Releases** (GitHub only): Pointer to the available versions of a software component. For each release, somef will track the following properties:
+- **Releases** (GitHub and Gitlab): Pointer to the available versions of a software component. For each release, somef will track the following properties:
+ - Assets: files attached to the release
- Description: Release notes
- Author: Agent responsible of creating the release
- Name: Name of the release
@@ -64,12 +83,17 @@ Given a readme file (or a GitHub repository) SOMEF-core will extract the followi
- Link to the tarball zip and code of the release
- **Repository status**: Repository status as it is described in [repostatus.org](https://www.repostatus.org/).
- **Requirements**: Pre-requisites and dependencies needed to execute a software component
+- **Run**: Running instructions of a software component. It may be wider than the `invocation` category, as it may include several steps and explanations.
+- **Runtime platform**: specifies runtime platform or script interpreter dependencies required to run the project.
+- **Script files**: Bash script files contained in the repository
- **Stargazers count**: Total number of stargazers of the project
- **Support**: Guidelines and links of where to obtain support for a software component
- **Support channels**: Help channels one can use to get support about the target software component
+- **Type**: type of software (command line application, notebook, ontology, scientific workflow, etc.)
- **Usage examples**: Assumptions and considerations recorded by the authors when executing a software component, or examples on how to use it
+- **Workflows**: URL and path to the computational workflow files present in the repository
-We use different supervised classifiers, header analysis, regular expressions, the GitHub/Gitlab API to retrieve all these fields (more than one technique may be used for each field) and language specific metadata parsers (e.g., for package files). Each extraction records its provenance, with the confidence and technique used on each step. For more information check the [output format description](https://somef-core.readthedocs.io/en/latest/)
+We use different supervised classifiers, header analysis, regular expressions, the GitHub/Gitlab API to retrieve all these fields (more than one technique may be used for each field) and language specific metadata parsers (e.g., for package files). Each extraction records its provenance, with the confidence and technique used on each step. For more information check the [output format description](https://somef.readthedocs.io/en/latest/output/)
1 The available application domains currently are:
@@ -87,6 +111,9 @@ We use different supervised classifiers, header analysis, regular expressions, t
### GitHub API
We use the [Github's API](https://developer.github.com/v3/) to retrieve some of metadata fields indicated above, like name, license changelog and releases of a software component.
+### Scikit Learn
+[Scikit Learn](https://scikit-learn.org/stable/about.html) is a powerful machine learning framework that provides a variety of methods for supervised and unsupervised learning. We use some of these classifiers to train sentence-based models to detect software description, citation, installation instructions and invocation commands.
+
### Wordnet
[Wordnet](https://wordnet.princeton.edu/) is a public lexical database which we use to find synonyms of common headers used when describe software.
@@ -96,5 +123,5 @@ The JavaScript Object Notation (JSON) is a syntax for storing and exchanging dat
### RDF
We use the Resource Description Framework (RDF) to serialize our results. We currently provide two different serializations: [JSON-LD](https://www.w3.org/TR/json-ld11/) and [Turtle](https://www.w3.org/TR/turtle/). The main difference between the JSON and RDF serializations is that they are simpler and do not incorporate the confidence of the results.
-SOMEF-core uses the [Software Description Ontology](https://w3id.org/okn/o/sd), which extends [Schema.org](https://schema.org) and [Codemeta](https://codemeta.github.io/terms/). We also support a Codemeta-specific export.
+SOMEF uses the [Software Description Ontology](https://w3id.org/okn/o/sd), which extends [Schema.org](https://schema.org) and [Codemeta](https://codemeta.github.io/terms/). We also support a Codemeta-specific export.
diff --git a/docs/install.md b/docs/install.md
index 5187b21..7be3c4b 100644
--- a/docs/install.md
+++ b/docs/install.md
@@ -4,11 +4,8 @@
## Install from Pypi
-SOMEF [is available in Pypi!](https://pypi.org/project/somef/). To install it just type:
-```
-pip install somef
-```
+We are currently working on this
## Install from GitHub
To run SOMEF, please follow the next steps:
diff --git a/docs/julia.md b/docs/julia.md
new file mode 100644
index 0000000..1f09193
--- /dev/null
+++ b/docs/julia.md
@@ -0,0 +1,36 @@
+The following metadata fields can be extracted from a Pyproject.toml file with Julia.
+These fields are defined in the [Julia projects specification](https://docs.julialang.org/en/v1/) currently at version **1.12**, and are mapped according to the [CodeMeta crosswalk for julia projects](https://github.com/codemeta/codemeta/blob/master/crosswalks/Julia%20Project.csv).
+
+| Software metadata category | SOMEF metadata JSON path | julia pyproject.toml metadata file field |
+|-------------------------------|--------------------------------|---------------------------------------------------|
+| authors - name | authors[i].result.name | authors *(1)* |
+| authors - email | authors[i].result.email | authors *(1)* |
+| package_id | package_id[i].result.value | name |
+| has_package_file | has_package_file[i].result.value | URL of the Project.toml file |
+| identifier | identifier[i].result .value | uuid |
+| package_id | package_id[i].result.value | name |
+| requirements | requirements[i].result.value | deeps |
+| runtime_platform - value | runtime_platform[i].result.value | properties compat |
+| runtime_platform - name | runtime_platform[i].result.name | compat |
+| version | version[i].result .value | version |
+
+
+---
+
+
+*(1)*
+- Regex: `r'^(.+?)\s*<(.+?)>$''`
+- Example:
+```
+authors = [
+ "Author1 ",
+ ....
+ ]
+
+```
+- Result:
+```
+"name": "Author1",
+"email": "author1@example.com",
+```
+
diff --git a/docs/output.md b/docs/output.md
index 45f5a7f..7a04879 100644
--- a/docs/output.md
+++ b/docs/output.md
@@ -1,9 +1,9 @@
-SOMEF-core supports three main output formats. Each of them contains different information with different levels of granularity. Below we enumerate them from more granular to less granular:
+SOMEF supports three main output formats. Each of them contains different information with different levels of granularity. Below we enumerate them from more granular to less granular:
## JSON format
**Version:** 1.0.1
-Default SOMEF-core response (and more complete in terms of metadata). The JSON format returns a set of categories, as shown in the snippet below:
+Default SOMEF response (and more complete in terms of metadata). The JSON format returns a set of categories, as shown in the snippet below:
```json
{
@@ -66,6 +66,7 @@ The `confidence` depends on the `technique` used. In this case, the confidence i
SOMEF aims to recognize the following categories (in alphabetical order):
- `acknowledgement`: Any text that the authors have prepared to acknnowledge the contribution from others, or project funding.
+- `application_domain`: The application domain of the repository. This may be related to the research area of a software component (e.g., Astrophysics) or the general domain/functionality of the tool (i.e., machine learning projects). See all current recognized application domains [here](https://somef.readthedocs.io/en/latest/#myfootnote1).
- `authors`: Person or organization responsible of the project. This property is also used to indicate the responsible entities of a publication associated with the code repository.
- `citation`: Software citation (usually in `.bib` form) as the authors have stated in their readme file, or through a `CFF` file.
- `code_of_conduct`: Link to the code of conduct file of the project
@@ -77,6 +78,7 @@ SOMEF aims to recognize the following categories (in alphabetical order):
- `date_created`: Date when the software component was created.
- `date_updated`: Date when the software component was last updated (note that this will always be older than the date of the extraction).
- `description`: A description of what the software component does.
+- `development_status`: The project’s development stage: beta, deprecated...
- `documentation`: Where to find additional documentation about a software component.
- `download_url`: URL where to download the target software (typically the installer, package or a tarball to a stable version)
- `executable_example`: Jupyter notebooks ready for execution (e.g., through myBinder, colab or files)
@@ -87,6 +89,7 @@ SOMEF aims to recognize the following categories (in alphabetical order):
- `full_title`: If the repository has a short name, we will attempt to extract the longer version of the repository name. For example, a repository may be called "Widoco", but the longer title is "Wizard for documenting ontologies".
- `has_build_file`: Build file to create a Docker image for the target software
- `has_script_file`: Snippets of code contained in the repository.
+- `homepage`: URL of the item.
- `identifier`: Identifiers detected within a repository (e.g., Digital Object Identifier).
- `images`: Images used to illustrate the software component.
- `installation`: A set of instructions that indicate how to install a target repository
@@ -96,6 +99,7 @@ SOMEF aims to recognize the following categories (in alphabetical order):
- `license`: License and usage terms of a software component
- `logo`: Main logo used to represent the target software component.
- `name`: Name identifying a software component
+- `ontologies`: URL and path to the ontology files present in the repository.
- `owner`: Name of the user or organization in charge of the repository
- `package_distribution`: Link to official package repositories where the software can be downloaded from (e.g., `pypi`).
- `package_file`: Link to a package file used in the repository (e.g., `pyproject.toml`, `setup.py`).
@@ -112,6 +116,11 @@ SOMEF aims to recognize the following categories (in alphabetical order):
- `type`: Software type: Commandline Application, Notebook Application, Ontology, Scientific Workflow. Non-Software types: Static Website, Uncategorized
- `usage`: Usage examples and considerations of a code repository.
- `workflows`: URL and path to the computational workflow files present in the repository.
+- `homepage`: URL to the homepage of the software or organization.
+- `reference_publication`: URL to the paper associated with the code repository.
+- `package_id`: Identifier extracted from packages. (e.g., `packages.json`)
+- `funding`: Funding code for the related project.
+- `has_package_file`: Specifies what package file is present in the code repository.
The following table summarized the properties used to describe a `category`:
@@ -162,7 +171,7 @@ The following object `types` are currently supported:
- `Agent`: user (typically, a person) or organization responsible for authoring a software release or a paper.
- `Publication`: Scientific paper associated with the code repository.
- `SoftwareApplication`: Class to represent software dependencies between projects.
-
+- `Runtime_platform`: specifies runtime platform or script interpreter dependencies required to run the project..
The following literal types are currently supported:
- `Number`: A numerical value. We do not distinguish between integer, long or float.
@@ -172,6 +181,8 @@ The following literal types are currently supported:
- `File_dump`: The value is a string with the contents of a file (e.g., a `citation.cff` file, or a `license.md` file).
- `Url`: uniform resource locator of a file.
+
+
+
+
+The tables below summarizes all types and their corresponding properties-
+
+An AGENT has the following properties:
+
+| Property | Expected value | Definition |
+|---|---|---|
+| **email** | String | Email of an author |
+| **family_name** | String | Last name of an author |
+| **given_name** | String | First name of an author |
+| **name** | String | Name used to designate the person or organization|
+| **url** | Url | Uniform resource locator of the resource |
+| **affiliation** | String | name of organization or affiliation |
+| **identifier** | String | id of an agent |
+| **role** | String | role of agent |
+
+An ASSET has the following properties:
+
+| Property | Expected value | Definition |
+|---|---|---|
+| **content_size** | Integer | size of file |
+| **content_url** | String | direct download link for the release file |
+| **download_count** | Integer | numbers of downloads |
+| **encoding_format** | String | format of the file |
+| **name** | String | Title or name of the file |
+| **upload_date** | Date | Date of creation of a release |
+| **url** | Url | Uniform resource locator of the resource |
+
+
+
+A LICENSE has the following properties:
+
+| Property | Expected value | Definition |
+|---|---|---|
+| **name** | String | Title or name of the license |
+| **spdx_id** | String | Spdx id corresponding to this license |
+| **url** | Url | Uniform resource locator of the license |
+| **identifier** | String | id of licence |
+
+A PROGRAMMING_LANGUAGE has the following properties:
+
+| Property | Expected value | Definition |
+|---|---|---|
+| **name** | String | Name of the language |
+| **size** | Integer | File size content (bytes) of a code repository using a given programming language |
+
+
+A PUBLICATION has the following properties:
+
+| Property | Expected value | Definition |
+|---|---|---|
+| **author** | Agent, Organization | Person or organization responsible for creating an article or a software release. |
+| **doi** | Url | When a publication is detected, but the format is in bibtek or CFF, SOMEF will add a `doi` field with the detected DOI value. The result includes a full URL. |
+| **title** | String | Title of the publication |
+| **url** | Url | Uniform resource locator of the resource |
+
+
+A RELEASE has the following properties:
+
+| Property | Expected value | Definition |
+|---|---|---|
+| **assets** | Asset | Files attached to the release
+| **author** | Agent, Organization | Person or organization responsible for creating an article or a software release. |
+| **description** | String | Descriptive text with the purpose of the release |
+| **date_created** | Date | Date of creation of a release |
+| **date_published** | Date | Date of publication of a release |
+| **html_url** | Url | link to the HTML representation of a release |
+| **name** | String | Title or name used to designate the release, license user or programming language. |
+| **release_id** | String | Id of a software release. |
+| **tag** | String | named version of a release |
+| **tarball_url** | Url | URL to the tar ball file where to download a software release |
+| **url** | Url | Uniform resource locator of the resource |
+| **zipball_url** | Url | URL to the zip file where to download a software release |
+
+
+
+
+
+A RUNTIME_PLATFORM has the following properties:
+
+| Property | Expected value | Definition |
+|---|---|---|
+| **name** | String | Name of the runtime platform (e.g., Java) |
+ **version** | String | version of the runtime platform |
+| **value** | String | name and version of the runtime platform |
+
+
+A SCHOLARLY_ARTICLE has the following properties:
+
+| Property | Expected value | Definition |
+|---|---|---|
+| **title** | String | Title of reference or citation |
+| **value** | String | Title of reference or citation |
+| **url** | String | Link to reference or citation |
+| **date_published** | String | date of publication reference or citation |
+| **doi** | String | Identifier of reference|
+
+
+A SOFTWARE_APPLICATION has the following properties:
+
+| Property | Expected value | Definition |
+|---|---|---|
+| **name** | String | Name of the software |
+| **value** | String | Name and version of the software |
+| **version** | String | version of software |
+| **development_type** | String | runtime or dev |
+
+A TEXT_EXCERPT has the following properties:
+
+| Property | Expected value | Definition |
+|---|---|---|
+| **original_header** | String | If the result value is extracted from a markdown file like a README, the original header of that section is also returned. |
+| **parent_header** | [String] | If the result value is extracted from a markdown file like a README, the parent header(s) of the current section are also returned (in case they exist). |
+
### Format
@@ -208,10 +340,16 @@ The following formats for a result value are currently recognized:
- `docker_compose`: [orchestration file](https://docs.docker.com/compose/compose-file/) used to communicate multiple containers.
- `readthedocs`: documentation format used by many repositories in order to describe their projects.
- `wiki`: documentation format used in GitHub repositories.
-- `setup.py`: package file format used in python projects
-- `pyproject.toml`: package file format used in python projects
-- `pom.xml`: package file used in Java projects
-- `package.json`: package file used in Javascript projects
+- `setup.py`: package file format used in python projects.
+- `publiccode.yml`: metadata file used to describe public sector software projects.
+- `pyproject.toml`: package file format used in python projects.
+- `pom.xml`: package file used in Java projects.
+- `package.json`: package file used in Javascript projects.
+- `bower.json`: package descriptor used for configuring packages that can be used as a dependency for Bower-managed front-end projects.
+- `composer.json`: manifest file serves as the package descriptor used in PHP projects.
+- `cargo.toml.json`: manifest file serves as the package descriptor used in Rust projects.
+- `[name].gemspec`:manifest file serves as the package descriptor used in Ruby gem projects.
+
### Technique
The techniques can be of several types:
diff --git a/docs/packagejson.md b/docs/packagejson.md
new file mode 100644
index 0000000..da5fcc1
--- /dev/null
+++ b/docs/packagejson.md
@@ -0,0 +1,92 @@
+The following metadata fields can be extracted from a package.json file.
+These fields are defined in the [Package.json specification](https://docs.npmjs.com/cli/v10/configuring-npm/package-json), currently at version **10.9.4**, and are mapped according to the [CodeMeta crosswalk for package.json](https://github.com/codemeta/codemeta/blob/master/crosswalks/NodeJS.csv).
+
+| Software metadata category | SOMEF metadata JSON path | PACKAGE.JSON metadata file field |
+|-------------------------------|----------------------------------------|--------------------- |
+| authors - value | authors[i].result.value | author.name |
+| authors - email | authors[i].result.email | author.email |
+| authors - url | authors[i].result.url | author.url |
+| authors - name | authors[i].result.name | author.name |
+| code_repository | code_repository[i].result.value | repository/repository.url/repository.directory *(1)*|
+| description | description[i].result.value | description |
+| has_package_file | has_package_file[i].result.value | URL of the package.json file |
+| homepage | homepage[i].result.value | homepage |
+| issue_tracker | issue_tracker[i].result.value | bugs or bugs.url *(2)* |
+| keywords | keywords[i].result.value | keywords |
+| license | license[i].result.value | license or license.type *(3)* |
+| package_id | package_id[i].result.value | name |
+| requirements - value | requirements[i].result.value | dependencies/devDependencies name@sversion *(4)* |
+| requirements - name | requirements[i].result.name | dependencies/devDependencies name *(4)* |
+| requirements - version | requirements[i].result.version | dependencies/devDependencies version *(4)* |
+| runtime_platform - value | runtime_platform[i].result.value | engines(package:version) -> version *(5)* |
+| runtime_platform - name | runtime_platform[i].result.name engines(package:version) -> package *(5)* |
+| version | version[i].result.value | version |
+
+---
+
+*(1)*
+- Example:
+```
+"repository": "npm/npm",
+
+or
+
+"repository": {
+"type": "git",
+"url": "git+https://github.com/npm/cli.git"
+}
+
+or
+
+"repository": {
+"type": "git",
+"directory": "workspaces/libnpmpublish"
+}
+```
+
+*(2)*
+- Example:
+```
+"bugs": {
+"url": "https://github.com/npm/cli/issues"
+}
+
+or
+
+"bugs": "https://github.com/npm/cli/issues"
+
+```
+
+*(2)*
+- Example:
+```
+ "license": "Artistic-2.0"
+
+ or
+
+ "license" : {
+ "type" : "ISC",
+ "url" : "https://opensource.org/licenses/ISC"
+ }
+ ```
+- Result: `Artistic-2.0" or "ISC" (license.type)`
+
+*(3)*
+- Fist part of item -> name; second part --> version
+- Example:
+```
+ "devDependencies":
+ "foo": "1.0.0 - 2.9999.9999",
+ ...
+```
+Resutl:
+```{'result': {'value': 'foo@1.0.0 - 2.9999.9999', 'name': 'foo', 'version': '1.0.0 - 2.9999.9999', 'type': 'Software_application'}, 'confidence': 1, 'technique': 'code_parser', 'source': 'http://example.com/package_neors.json'}```
+
+*(5)*
+- Example:
+```
+ "engines": {
+ "node": ">=0.10.3 <15"
+ }
+```
+- Result: `"name": "Node", "version": "">=0.10.3 <15"`
\ No newline at end of file
diff --git a/docs/pom.md b/docs/pom.md
new file mode 100644
index 0000000..b03dcda
--- /dev/null
+++ b/docs/pom.md
@@ -0,0 +1,114 @@
+The following metadata fields can be extracted from a pom.xml file.
+These fields are defined in the [Maven POM specification](https://maven.apache.org/pom.html), currently at version **4.0.0**, and are mapped according to the [CodeMeta crosswalk for Java (Maven)](https://github.com/codemeta/codemeta/blob/master/crosswalks/Java%20(Maven).csv).
+
+| Software metadata category | SOMEF metadata JSON path | POM.XML metadata file field |
+|-------------------------------|---------------------------------------------|----------------------------------|
+| authors - value | authors[i].result.value | developers.developer.name |
+| authors - name | authors[i].result.name | developers.developer.name |
+| authors - email | Aauthors[i].result.email | developers.developer.email |
+| authors - url | authors[i].result.url | developers.developer.url |
+| authors - affiliation | authors[i].result.affiliation | developers.developer.organization |
+| has_package_file | has_package_file[i].result .value | URL of the pom.xml file |
+| homepage | homepage[i].result.value | homepage |
+| issue_tracker | issue_tracker[i].result .value | issueManagement.url |
+| package_distribution - value | package_distribution[i].result value | scm.url *(1)*|
+| package_distribution - value | package_distribution[i].result.value | repositories.repository(id) *(2)*|
+| package_distribution - name | package_distribution[i].result.name | repositories.repository(name) *(2)*|
+| package_distribution - url | package_distribution[i].result.url | repositories.repository(url) *(2)*|
+| requirements - value | requirements[i].result.value | dependencies.dependency.grpId.arfifactId *(3)* |
+| requirements - name | requirements[i].result.name | dependencies.dependency.arfifactId *(3)* |
+| requirements - version | requirements[i].result.version | dependencies.dependency.version *(3)* |
+| package_id | package_id[i].result .value | group_id.artifact_id *(4)* |
+| runtime_platform - value | runtime_platform[i].result.value | properties extract name version *(5)* |
+| runtime_platform - name | runtime_platform[i].result.name | properties extract name *(5)*|
+| runtime_platform - value | runtime_platform[i].result.value | properties extract version *(5)* |
+| version | version[i].result .value | version |
+
+---
+
+*(1)*
+- Example:
+```
+
+ scm:svn:http://127.0.0.1/svn/my-project
+ scm:svn:https://127.0.0.1/svn/my-project
+ HEAD
+ http://127.0.0.1/websvn/my-project
+
+```
+- Result:
+```
+package_distribution': [{'result': {'value': 'http://127.0.0.1/websvn/my-project', 'type': 'Url'}, 'confidence': 1, 'technique': 'code_parser', 'source': 'https://example.org/pom.xml'}]
+```
+
+*(2)*
+- Example:
+```
+
+
+ jitpack.io
+ https://jitpack.io
+
+
+```
+- Result:
+```
+'package_distribution': [{
+ 'result': {
+ 'value': 'jitpack.io',
+ 'url': 'https://jitpack.io',
+ 'type': 'Url'
+ }
+```
+
+*(3)*
+- Example:
+```
+
+
+ org.apache.maven
+ maven-model
+ 3.9.0
+
+
+
+```
+- Result:
+```
+'requirements': [{
+ 'result':
+ {'value': 'org.apache.maven.maven-model',
+ 'name': 'maven-model',
+ 'version': '3.9.0',
+ 'type': 'Software_application'},
+
+```
+
+*(4)*
+- Example:
+```
+ es.oeg
+ widoco
+ ```
+- Result:
+```
+ 'package_id': [{
+ 'result': {
+ 'value': 'es.oeg.widoco',
+
+```
+
+ *(5)*
+ - Example:
+```
+
+ 1.8
+
+```
+
+- Result:
+```
+[{'value': 'Java: 1.8', 'name': 'Java', 'version': '1.8'}]
+```
+
+
diff --git a/docs/publiccode.md b/docs/publiccode.md
new file mode 100644
index 0000000..84ab402
--- /dev/null
+++ b/docs/publiccode.md
@@ -0,0 +1,133 @@
+The following metadata fields can be extracted from a publiccode.yml file.
+These fields are defined in the [PublicCode specification](https://yml.publiccode.tools/), currently at version **0.5.0**, and are mapped according to the [CodeMeta crosswalk for publiccode.yml](https://codemeta.github.io/crosswalk/publiccode/) and [csv CodeMeta crosswalk for publiccode.yml](https://github.com/codemeta/codemeta/blob/master/crosswalks/publiccode.csv)
+
+| Software metadata category | SOMEF metadata JSON path | PUBLICCODE.YML metadata file field |
+|-----------------------------|---------------------------------------|----------------------------------------|
+| application_domain | application_domain[i].result.value | categories or description.[lang].genericName *(1)* |
+| code_repository | code_repository[i].result.value | url |
+| date_published | date_published[i].result.value | releaseDate |
+| date_updated | date_updated[i].result.value | releaseDate |
+| description | description[i].result.value | description.[lang].shortDescription or description.[lang].longDescription *(2)* |
+| development_status | development_status[i].result.value | developmentStatus |
+| has_package_file | has_package_file[i].result.value | URL of the publiccode.yml file |
+| keywords | keywords[i].result.value | description.[lang].features *(3)* |
+| license - value | license[i].result.value | legal.license *(4)* |
+| license - spdx id | license[i].result.spdx_id | legal.license extract spdx id *(4)*|
+| license - name | license[i].result.name | legal.license extract name *(4)* |
+| name | name[i].result.value | name or description.[lang].localisedName *(5)* |
+| requirements - value | requirements[i].result.value | dependsOn.open / dependsOn.proprietary / dependsOn.hardware name + version *(6)* |
+| requirements - name | requirements[i].result.name | dependsOn.open / dependsOn.proprietary / dependsOn.hardware name *(6)* |
+| requiriments - version | requirements[i].result.version | dependsOn.open / dependsOn.proprietary / dependsOn.hardware more than one label of version *(6)* |
+| runtime_platform | runtime_platform[i].result.value | platforms |
+| version | version[i].result.value | softwareVersion |
+
+---
+
+*(1)*
+- Example:
+```
+categories:
+ - data-collection
+ - it-development
+```
+or
+```
+description:
+ nl:
+ genericName: API component
+```
+
+*(2)*
+- Example:
+```
+description:
+ nl:
+ shortDescription: API voor het beheren van objecten
+ longDescription: >
+ De **Objecten API** heeft als doel om uiteenlopende objecten eenvoudig te kunnen
+ registreren en ontsluiten in een gestandaardiseerd formaat. De Objecten API kan
+ ....`_.
+
+ en:
+ shortDescription: API to manage objects
+ longDescription: >
+ The **Objects API** aims to easily store various objects and make them available in
+ standardized format. The Objects API can be used by any organization to manage
+ relevant objects. An organization can also choose to use the Objects API to
+ ....`_.
+```
+
+*(3)*
+- Example:
+```
+description:
+ nl:
+ features:
+ - Objecten API
+ - Minimalistische objecten beheerinterface
+ en:
+ features:
+ - Objects API
+ - Minimalistic object management interface
+```
+
+
+*(4)*
+- Look for expressions in a local dictionary with all the reference and spdx_id
+- Example:
+```
+legal:
+ license: AGPL-3.0-or-later
+ mainCopyrightOwner: City of Chicago
+ repoOwner: City of Chicago
+```
+-Result:
+```
+'result':
+ {'value': 'AGPL-3.0-or-later',
+ 'spdx_id': 'AGPL-3.0',
+ 'name': 'GNU Affero General Public License v3.0',
+ 'type': 'License'},
+
+```
+
+*(5)*
+- Example:
+`name: Medusa`
+or
+```
+description:
+ en:
+ localisedName: Medusa
+```
+
+
+*(6)*
+- Examples:
+```
+dependsOn:
+ open:
+ - name: Objecttypes API
+ optional: false
+ version: '1.0'
+ - name: MySQL
+ versionMin: "1.1"
+ versionMax: "1.3"
+ optional: true
+ - name: PostgreSQL
+ versionMin: "14.0"
+ optional: true
+```
+
+- Result PostgreSQL:
+```
+ "result": {
+ "value": "PostgreSQL>=14.0",
+ "name": "PostgreSQL",
+ "version": ">=14.0",
+ "type": "Software_application",
+ "dependency_type": "runtime"
+ },
+```
+
+
diff --git a/docs/pyprojecttoml.md b/docs/pyprojecttoml.md
new file mode 100644
index 0000000..f6c1233
--- /dev/null
+++ b/docs/pyprojecttoml.md
@@ -0,0 +1,78 @@
+The following metadata fields can be extracted from a pyproject.toml file.
+These fields are defined in the [pyproject.toml specification](https://packaging.python.org/en/latest/guides/writing-pyproject-toml), and are mapped according to the [CodeMeta crosswalk for python](https://github.com/codemeta/codemeta/blob/master/crosswalks/Python%20Distutils%20(PyPI).csv).
+
+| Software metadata category | SOMEF metadata JSON path | PYPROJECT.TOML metadata file field |
+|--------------------------------|-----------------------------|----------------------------------------|
+| authors - value | authors[i].result.value | authors.name |
+| authors - email | authors[i].result.email | authors.email |
+| authors - name | authors[i].result.name | authors.name |
+| authors - url | authors[i].result.url | authors.url |
+| code_repository | code_repository[i].result.value | project.urls.repository or tool.poetry.repository |
+| description | description[i].result.value | project.description or tool.poetry.description |
+| documentation | documentation[i].result.value | project.urls.documentation or tool.poetry.documentation |
+| download_url | download_url[i].result.value | project.urls.download or tool.poetry.download|
+| license - value | license[i].result.value | license.file or license.type or license |
+| license - name | license[i].result.name | license.name *(1)* |
+| license - spdx id | license[i].result.spdx_id | license.identifier if "spdx.org/licenses/ *(1)* |
+| has_package_file | has_package_file[i].result.value | URL of the pyproject.toml file |
+| homepage | homepage[i].result.value | project.homepage or tool.poetry.homepage |
+| issue_tracker | issue_tracker[i].result.value | projects.urls.issue or tool.poetry.issue |
+| keywords | keywords[i].result.value | keywords |
+| package_id | package_id[i].result.value | project.name or tool.poetry.name |
+| readme_url | readme_url[i].result.value | projects.urls.readme or tool.poetry.readme|
+| related_documentation | download_url[i].result.value | projects.urls or tool.poetry |
+| requirements - value | requirements[i].result.value | dependencies(name=version) or build-system.requires[i] *(2)* |
+| requirements - name | requirements[i].result.name | dependencies(name=version) -> dependencies.name or build-system.requires[i] parsing -> name *(2)* |
+| requirements - version | requirements[i].result.version | dependencies[i](name=version) -> dependencies.version or build-system.requires[i] parsing -> version *(2)* |
+| runtime_platform - value | runtime_platform[i].result.value | depeendencies or requires-python -> version *(3)* |
+| runtime_platform - name | runtime_platform[i].result.name | depeendencies or requires-python -> name *(3)* |
+| version - value | version[i].result.value | project.version or tool.poetry.version |
+| version - tag | version[i].result.tag | project.version or tool.poetry.version |
+
+---
+
+*(1)*
+- Look for the name and spdx_id in a local dictionary with all licenses
+
+*(2)*
+- Examples: allows formats
+```
+dependencies = [
+ "astropy",
+ "ctapipe",
+ "ctaplot",
+ "dl1_data_handler",
+ "h5py",
+........
+
+[tool.poetry.dependencies]
+ python = ">=3.9,<=3.13"
+ bs4 = "^0.0.1"
+........
+
+[build-system]
+ requires = ["poetry-core>=1.1.10"]
+```
+
+- If it is possible we obtain name and version
+ 'value' : 'python>=3.9,<=3.13'
+ 'name' : 'python'
+ 'version' : '>=3.9,<=3.13'
+
+*(3)*
+- Always "Python" and version if exists.
+- Examenple:
+```
+requires-python = ">=3.11"
+
+or
+
+[tool.poetry.dependencies]
+ python = ">=3.9,<=3.13"
+
+or
+
+[dependencies]
+ python = ">=3.9,<=3.13"
+
+```
diff --git a/docs/readmefile.md b/docs/readmefile.md
new file mode 100644
index 0000000..adad749
--- /dev/null
+++ b/docs/readmefile.md
@@ -0,0 +1,163 @@
+The following metadata fields can be extracted from a readme.md file.
+Unlike others files formats (pom, cargo, cabal...), README documents do not follow a formal specification. They are free‑form text files, usually written in markdown or restructuredtext, and their structure varies widely across projects. SOMEF applies heuristics to identify common sections (e.g., Title, Description, Installation, Usage, License...) and extracts metadata accordingly.
+
+| Software metadata category | SOMEF metadata JSON path | README.MD metadata file field |
+|--------------------------------|----------------------------------------|----------------------------------------|
+| acknowledgement | acknowledgement[i].result.value | hearders with acknowledgement |
+| citation | citation[i].result.value | headers with citation, reference, cite. Extract bibtext **(1)** |
+| contact | contact[i].result.value | headers with contact |
+| contributing_guidelines | contributing_guidelines[i].result.value | headers with contributing |
+| contributors | contributors[i].result.value | headers with contributor |
+| description | description[i].result.value | headers with description, introduction, basics, initiation, overview |
+| documentation | documentation[i].result.value | github or gitlab url documentation **(2)**, headers with documentation, readthedocs same name project, readthedocs in badges, wiki links in badges and text |
+| download | download[i].result.value | headers with download |
+| executable_example | executable_example[i].result.value | extracts Binder from badgets **(3)** |
+| faq | faq[i].result.value | headers with faq, errors, problems |
+| full_title | full_title[i].result.value | extract full title **(4)** |
+| homepage | homepage[i].result.value | homepage from badgets **(5)** |
+| identifier | idenfier[i].result.value | extract from badgets directly or get from zenodo with latest doi **(6)**, swh identifiers **(7)** |
+| images | images[i].result.value | other images in the README apart from the logo |
+| installation | installation[i].result.value | headers with installation, install, setup, prepare, preparation, manual, guide |
+| license | license[i].result.value | headers with license |
+| logo | logo[i].result.value | look images in badges and text **(8)** |
+| package_distribution | package_distribution[i].result.value | Pypi or latest Pypi version in badges **(9)** |
+| related_documentation | dorelated_documentationumentation[i].result.value | readthedocs diferent name project |
+| run | run[i].result.value | headers with run, execute |
+| readme_url | readme_url[i].result.value | url in raw githubuser content **(10)** |
+| related_papers | related_papers[i].result.value | look for arXiv reference in all the text **(11)** |
+| repository_status | repository_status[i].result.value | badges with Project status **(12)** |
+| requirements | requirements[i].result.value | headers with requirement, prerequisite, dependency, dependent |
+| support | support[i].result.value | headers with support, help, report |
+| support_channels | support_channels[i].result.value | extract information of gitter, reddit and discord in badges and text **(13)** |
+| usage | usage[i].result.value | headers with usage, example, implement, implementation, demo, tutorial, start, started |
+
+
+------
+
+**(1)**
+- Example:
+```bib
+@inproceedings{garijo2017widoco,
+ title={WIDOCO: a wizard for documenting ontologies},
+ author={Garijo, Daniel},
+ booktitle={International Semantic Web Conference},
+ pages={94--102},
+ year={2017},
+ organization={Springer, Cham},
+ doi = {10.1007/978-3-319-68204-4_9},
+ funding = {USNSF ICER-1541029, NIH 1R01GM117097-01},
+ url={http://dgarijo.com/papers/widoco-iswc2017.pdf}
+}
+```
+- Result:
+```
+{
+ "result": {
+ "value": "@inproceedings{garijo2017widoco,\n url = {http://dgarijo.com/papers/widoco-iswc2017.pdf},\n funding = {USNSF ICER-1541029, NIH 1R01GM117097-01},\n doi = {10.1007/978-3-319-68204-4_9},\n organization = {Springer, Cham},\n year = {2017},\n pages = {94--102},\n booktitle = {International Semantic Web Conference},\n author = {Garijo, Daniel},\n title = {WIDOCO: a wizard for documenting ontologies},\n}",
+ "type": "Text_excerpt",
+ "format": "bibtex",
+ "doi": "10.1007/978-3-319-68204-4_9",
+ "title": "WIDOCO: a wizard for documenting ontologies",
+ "author": "Garijo, Daniel",
+ "url": "http://dgarijo.com/papers/widoco-iswc2017.pdf"
+ },
+}
+```
+
+
+**(2)**
+- Example if github:
+```
+f"https://github.com/{owner}/{repo_name}/tree/{urllib.parse.quote(repo_default_branch)}/{docs_path}"
+```
+- Example if gitlab:
+```
+f"https://{domain_gitlab}/{owner}/{repo_name}/-/tree/{urllib.parse.quote(repo_default_branch)}/{docs_path}"
+```
+
+**(3)**
+- Example: `[](https://mybinder.org/v2/gh/user/repo/HEAD)`
+- Result: `"value": "https://mybinder.org/v2/gh/user/repo/HEAD"`
+
+**(4)**
+- Example: `# WIzard for DOCumenting Ontologies (WIDOCO)`
+- Result:
+```
+"full_title": [
+ {
+ "result": {
+ "type": "String",
+ "value": "WIzard for DOCumenting Ontologies (WIDOCO)"
+ },
+ "confidence": 1,
+ "technique": "regular_expression",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/README.md"
+ }
+]
+```
+
+**(5)**
+- Example: `[](https://myproject.org)`
+- Result: `"value": "https://myproject.org"`
+
+
+**(6)**
+- Example: `[](https://doi.org/10.5281/zenodo.11093793)`
+- Result: `"value": "https://doi.org/10.5281/zenodo.11093793"`
+
+**(7)**
+- Example: `[](https://archive.softwareheritage.org/swh:1:dir:40d462bbecefc3a9c3e810567d1f0d7606e0fae7;origin=...)`
+- Result: ` "value": "https://archive.softwareheritage.org/swh:1:dir:40d462bbecefc3a9c3e810567d1f0d7606e0fae7",`
+
+
+**(8)**
+- Example: ``
+- Result: `"value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/src/main/resources/logo/logo2.png"``
+
+**(9)**
+- Example: `[](https://badge.fury.io/py/somef) `
+- Result: `"value": "https://pypi.org/project/somef"`
+
+
+**(10)**
+- Example:
+```
+[Yulun Zhang](http://yulunzhang.com/), [Yapeng Tian](http://yapengtian.org/), [Yu Kong](http://www1.ece.neu.edu/~yukong/), [Bineng Zhong](https://scholar.google.de/citations?user=hvRBydsAAAAJ&hl=en), and [Yun Fu](http://www1.ece.neu.edu/~yunfu/), "Residual Dense Network for Image Super-Resolution", CVPR 2018 (spotlight), [[arXiv]](https://arxiv.org/abs/1802.08797)
+```
+- Result: `"value": "https://arxiv.org/abs/1802.08797"`
+
+
+**(11)**
+- Example:
+```
+f"https://raw.githubusercontent.com/{owner}/{repo_name}/{repo_ref}/{urllib.parse.quote(partial)}"
+```
+
+**(12)**
+- Example:
+```
+ [](https://www.repostatus.org/#active)
+```
+- Result:
+```
+"value": "https://www.repostatus.org/#active",
+"description": "Active \u2013 The project has reached a stable, usable state and is being actively developed."
+```
+
+**(13)**
+- Example:
+```
+[](https://gitter.im/myproject/community)
+[Reddit](https://www.reddit.com/r/myproject)
+[Discord](https://discord.com/invite/xyz789)
+```
+- Result:
+```
+"value": "https://gitter.im/myproject/community"
+....
+"value": "https://www.reddit.com/r/myproject"
+.....
+"value": "https://discord.com/invite/xyz789"
+```
+
+
diff --git a/docs/requirementstxt.md b/docs/requirementstxt.md
new file mode 100644
index 0000000..591eabc
--- /dev/null
+++ b/docs/requirementstxt.md
@@ -0,0 +1,26 @@
+The following metadata fields can be extracted from a requirements file.
+These fields are defined in the [Requirements specification](https://pip.pypa.io/en/stable/reference/requirements-file-format/), currently at version **25.2**, and are mapped according to the [CodeMeta crosswalk for requirements files](https://github.com/codemeta/codemeta/blob/master/crosswalks/Python%20Distutils%20(PyPI).csv).
+
+| Software metadata category | SOMEF metadata JSON path | REQUIREMENTS.TXT metadata file field |
+|----------------------------------|------------------------------------|------------------------------------------|
+| requirements - value | requirements[i].result.value | *(1)* |
+| requirements - name | requirements[i].result.name | *(1)* |
+| requirements - version | requirements[i].result.version| *(1)* |
+| runtime_platform - value | runtime_platform[i].result.value | if Python *(1)* |
+| runtime_platform - name | runtime_platform[i].result.name | "Python" |
+
+
+---
+
+*(1)*
+- Example: `docstring_parser==0.7`
+- Result:
+```
+value : docstring_parser==0.7
+name : docstring_parser=
+version : 0.7
+```
+
+*(2)*
+- Always "Python" and version if exists.
+- Example: `python= ">=3.11"`
\ No newline at end of file
diff --git a/docs/setuppy.md b/docs/setuppy.md
new file mode 100644
index 0000000..6dbf8d7
--- /dev/null
+++ b/docs/setuppy.md
@@ -0,0 +1,35 @@
+The following metadata fields can be extracted from a setup.py file.
+These fields are defined in the [Setup.py specification](https://packaging.python.org/en/latest/guides/distributing-packages-using-setuptools/#setup-args), and are mapped according to the [CodeMeta crosswalk for python](https://github.com/codemeta/codemeta/blob/master/crosswalks/Python%20Distutils%20(PyPI).csv).
+
+| Software metadata category | SOMEF metadata JSON path | SETUP.PY metadata file field |
+|-------------------------------|------------------------------|------------------------------|
+| authors - value | authors[i].result.value | author *(1)* |
+| authors - email | authors[i].result.email | author_email or EMAIL *(1)* |
+| code_repository | code_repository[i].result.value | url or URL |
+| description | description[i].result .value | description or DESCRIPTION |
+| keywords | keywords[i].result.value | keywords |
+| license | license[i].result.value | license |
+| package_id | package_id[i].result.value | name |
+| programming_languages | programming_languages[i].result.value | if classifiers -> "python" *(2)* |
+
+---
+
+*(1)*
+- Example:
+```
+ author='Colin Raffel',
+ author_email='craffel@gmail.com',
+```
+- Result: `result {'name': 'Colin Raffel', 'email':'craffel@gmail.com'}`
+
+*(2)*
+- Example:
+```
+ classifiers=[
+ "Development Status :: 3 - Alpha",
+ "Programming Language :: Python",
+ ],
+```
+- Result: always `"value": "Python"` if 'Programming Language :: Python' in classifiers.
+
+
diff --git a/docs/supported_languages.md b/docs/supported_languages.md
new file mode 100644
index 0000000..5b4b313
--- /dev/null
+++ b/docs/supported_languages.md
@@ -0,0 +1,33 @@
+
+SOMEF recognizes the programming languages used in a software repository by inspecting
+well-known configuration files, dependency descriptors and executable artifacts.
+To know more about the extraction details for each type of file, click on it.
+
+
+
+| Language | Supported Files |
+|-----------|----------------------------|
+| Haskell | [`*.cabal`](./cabal.md) |
+| Java | [`pom.xml`](./pom.md) |
+| JavaScript | [`package.json`](./packagejson.md), [`bower.json`](./bower.md) |
+| Julia | [`Project.toml`](./julia.md) |
+| PHP | [`composer.json`](./composer.md) |
+| Python | [`setup.py`](./setuppy.md), [`pyproject.toml`](./pyprojecttoml.md), [`requirements.txt`](./requirementstxt.md) |
+| R | [`DESCRIPTION`](./description.md) |
+| Ruby | [`*.gemspec`](./gemspec.md) |
+| Rust | [`Cargo.toml`](./cargo.md) |
+
+---
+
+SoMEF also detects the following files to recognize build instructions, workflows or executable examples:
+
+
+| Language | Supported Files | Software metadata category |
+|-----------|------------------------------------|-----------------------------|
+| Docker | `Dockerfile`, `docker-compose.yml` | has_built_file
+| Jupyter Notebook | `*.ipynb` | executable_example |
+| Ontologies | `*.ttl`, `*.owl`, `*.nt`, `*.xml`, `*.jsonld` | ontologies |
+| Shell | `*.sh` | has_script_file |
+| YAML | `*.yml`, `*.yaml` | continuous_integration, workflows, publiccode |
+
+
diff --git a/docs/supported_metadata_files.md b/docs/supported_metadata_files.md
new file mode 100644
index 0000000..36c8700
--- /dev/null
+++ b/docs/supported_metadata_files.md
@@ -0,0 +1,112 @@
+# Supported Metadata Files
+
+This project supports extracting metadata from specific types of files commonly used to declare authorship and contribution in open source repositories.
+
+
+## Supported Metadata Files in SOMEF
+
+SOMEF can extract metadata from a wide range of files commonly found in software repositories. Below is a list of supported file types, along with clickable examples from real projects:
+
+| File Name | Language | Description | Detail | Source Spec. | Version Spec.| Example |
+|--------------------|----------------|-------------|--------|--------------|--------------|---------|
+| `AUTHORS.md` | General | Lists contributors, authors, and affiliations relevant to the project |
[🔍](./pom.md) | [📄](https://maven.apache.org/pom.html) | [4.0.0](https://maven.apache.org/xsd/maven-4.0.0.xsd) | [Example](https://github.com/apache/maven/blob/master/pom.xml) |
+| `bower.json` | JavaScript (Bower) | Package descriptor used for configuring packages that can be used as a dependency for Bower-managed front-end projects. |
[🔍](./bower.md)
| [📄](https://github.com/bower/spec/blob/master/json.md)| |[Example](https://github.com/juanjemdIos/somef/blob/master/src/somef/test/test_data/repositories/js-template/bower.json) |
+| `package.json` | JavaScript / Node.js | Defines metadata, scripts, and dependencies for Node.js projects |
[🔍](./packagejson.md)| [📄](https://docs.npmjs.com/cli/v10/configuring-npm/package-json)| 10.9.4|[Example](https://github.com/npm/cli/blob/latest/package.json) |
+| `codemeta.json` | JSON-LD | Metadata file for research software using JSON-LD vocabulary |
[🔍](./codemetajson.md)
| [📄](https://github.com/codemeta/codemeta/blob/master/crosswalk.csv)| [v3.0](https://w3id.org/codemeta/3.0)|[Example](https://github.com/codemeta/codemeta/blob/master/codemeta.json) |
+| `readme.me` | Markdown | Main documentation file of repository |
[🔍](./readmefile.md)
| | |[Example](https://github.com/KnowledgeCaptureAndDiscovery/somef/blob/master/README.md) |
+| `composer.json` | PHP | Manifest file serves as the package descriptor used in PHP projects. |
[🔍](./composer.md)
| [📄](https://getcomposer.org/doc/04-schema.md)| [2.8.12](https://getcomposer.org/changelog/2.8.12)|[Example](https://github.com/composer/composer/blob/main/composer.json) |
+| `juliaProject.toml` | Python | Defines the package metadata and dependencies for Julia projects, used by the Pkg package manager.|
[🔍](./julia.md)
| [📄](https://docs.julialang.org/en/v1/)| |[Example](https://github.com/JuliaLang/TOML.jl/blob/master/Project.toml) |
+| `pyproject.toml` | Python | Modern Python project configuration file used by tools like Poetry and Flit |
| [📄](https://pip.pypa.io/en/stable/reference/requirements-file-format/)| 25.2|[Example](https://github.com/oeg-upm/FAIR-Research-Object/blob/main/requirements.txt) |
+| `setup.py` | Python | Package file format used in python projects |
[🔍](./setuppy.md)
| [📄](https://packaging.python.org/en/latest/guides/distributing-packages-using-setuptools/#setup-args)| |[Example](https://github.com/oeg-upm/soca/blob/main/setup.py) |
+| `DESCRIPTION` | R | Metadata file for R packages including title, author, and version |
[🔍](./description.md)
| [📄](https://r-pkgs.org/description.html)| | [Example](https://github.com/cran/ggplot2/blob/master/DESCRIPTION) |
+| `*.gemspec` | Ruby | Manifest file serves as the package descriptor used in Ruby gem projects. |
[🔍](./gemspec.md)
| [📄](https://guides.rubygems.org/specification-reference/)| |[Example](https://github.com/rubygems/rubygems/blob/master/bundler/bundler.gemspec) |
+| `cargo.toml` | Rust | Manifest file serves as the package descriptor used in Rust projects |
[🔍](./cargo.md)
| [📄](https://doc.rust-lang.org/cargo/reference/manifest.html)| |[Example](https://github.com/rust-lang/cargo/blob/master/Cargo.toml) |
+| `*.cabal` | Haskell | Manifest file serving as the package descriptor for Haskell projects.|
[🔍](./cabal.md)
| [📄](https://cabal.readthedocs.io/en/3.10/cabal-package.html)| |[Example](https://github.com/haskell/cabal/blob/master/Cabal/Cabal.cabal) |
+| `dockerfile` | Dockerfile | Build specification file for container images that can include software metadata via LABEL instructions (OCI specification).|
[🔍](./dockerfiledoc.md)
| [📄](https://docs.docker.com/reference/dockerfile/)| |[Example](https://github.com/FairwindsOps/nova/blob/master/Dockerfile) |
+| `publiccode.yml` | YAML | YAML metadata file for public sector software projects|
[🔍](./publiccode.md)
| [📄](https://yml.publiccode.tools//)| |[Example](https://github.com/maykinmedia/objects-api/blob/master/publiccode.yaml) |
+
+> **Note:** The general principles behind metadata mapping in SOMEF are based on the [CodeMeta crosswalk](https://github.com/codemeta/codemeta/blob/master/crosswalk.csv) and the [CodeMeta JSON-LD context](https://github.com/codemeta/codemeta/blob/master/codemeta.jsonld).
+> However, each supported file type may have specific characteristics and field interpretations.
+
+
+
+
+## Types of metadata in SOMEF
+
+| Type | Metadata Category |
+|----------------------|---------------------------|
+| Agent | authors |
+| Keywords | keywords |
+| License | license |
+| Release | version |
+| Software_application | requirements |
+| String | description |
+| String | name |
+| String | package_id |
+| String | runtime_platform |
+| Url | has_package_file |
+| Url | homepage |
+| Url | issue_tracker |
+| Url | package_distribution |
+
+
+## Example: Dependency Metadata Extraction from Configuration Files
+
+SOMEF parses configuration files like `pom.xml` to extract **structured metadata** about software dependencies and other requirements.
+
+---
+
+### Source File: Snippet from `Widoco/pom.xml`
+
+Below is the XML fragment for the Maven dependency that is being parsed:
+
+```xml
+
+
+ org.apache.maven
+ maven-model
+ 3.9.0
+
+
+```
+
+
+The following Python code snippet show the logic used by the SOMEF parser to transform the XML elements to the JSON metadata structure:
+
+```
+ if project_data["dependencies"]:
+ for dependency in project_data["dependencies"]:
+ metadata_result.add_result(
+ constants.CAT_REQUIREMENTS,
+ {
+ "value": f'{dependency.get("groupId", "")}.{dependency.get("artifactId", "")}'.strip("."),
+ "name": dependency.get("artifactId", ""),
+ "version": dependency.get("version", ""),
+ "type": constants.SOFTWARE_APPLICATION
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+```
+
+
+After applying the mapping logic, the metadata for the dependency is stored under the requirements category (CAT_REQUIREMENTS in this case) with the following JSON structure:
+
+``` somef json
+
+ "requirements": [
+ {
+ "result": {
+ "value": "org.apache.maven.maven-model",
+ "name": "maven-model",
+ "version": "3.9.0",
+ "type": "Software_application"
+ },
+ "confidence": 1,
+ "technique": "code_parser",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/pom.xml"
+ },
+```
diff --git a/docs/usage.md b/docs/usage.md
index 7b51d73..89f3102 100644
--- a/docs/usage.md
+++ b/docs/usage.md
@@ -1,4 +1,4 @@
-To see the available options for SOMEF-core:
+To see the available options for SOMEF:
```bash
somef_core --help
@@ -52,7 +52,7 @@ Options:
file.
-m, --missing JSON report with the missing metadata fields
- SOMEF-core was not able to find. The report will
+ SOMEF was not able to find. The report will
be placed in $PATH_missing.json, where
$PATH is -o, -c or -g.
-kt, --keep_tmp PATH SOMEF will NOT delete the temporary folder
diff --git a/mkdocs.yml b/mkdocs.yml
index e3e2801..6000586 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -4,6 +4,23 @@ nav:
- Install: install.md
- Usage: usage.md
- Output: output.md
+ - Supported metadata files: supported_metadata_files.md
+ - Supported languages: supported_languages.md
+ # - Authors: author.md
+ # - Bower: bower.md
+ # - Cabal: cabal.md
+ # - Cargo: cargo.md
+ # - Codemeta: codemetajson.md
+ # - Composer: composer.md
+ # - Description: description.md
+ # - Gemespec: gemspec.md
+ # - Julia Projects: julia.md
+ # - Package JSON: packagejson.md
+ # - Pom: pom.md
+ # - Pyproject: pyprojecttoml.md
+ # - Requirements: requirementstxt.md
+ # - Setup: setuppy.md
+
theme:
name: material
diff --git a/output.json-ld b/output.json-ld
new file mode 100644
index 0000000..1944e63
--- /dev/null
+++ b/output.json-ld
@@ -0,0 +1,263 @@
+@prefix ns1: .
+@prefix ns2: .
+@prefix xsd: .
+
+ a ns2:Person ;
+ ns2:name "tpronk" .
+
+ a ns2:CreativeWork ;
+ ns1:name "GNU Affero General Public License v3.0" ;
+ ns1:url "https://raw.githubusercontent.com/tpronk/somef-demo-repo/main/LICENSE"^^xsd:anyURI .
+
+ a ns1:Software ;
+ ns2:license ;
+ ns1:contactDetails """Contact person responsible for maintaining a software component
+""" ;
+ ns1:contributingGuidelines """These guidelines were copied from [PychoPy](https://github.com/psychopy/psychopy/edit/dev/CONTRIBUTING.md)
+
+# Have you ever contributed to an Open Source project?
+Your first contribution can be a bit intimidating, but feel free to give it a try. If you get stuck, don't hesitate to ask for help in our [developer forum](https://discourse.psychopy.org/c/dev). This is also a good place to pitch your idea. Next up:
+* **I won't program it myself.** Please file a [GitHub issue](https://github.com/psychopy/psychopy/issues).
+* **I'd like to take a shot.** Read on to find out how!
+
+# How to contribute
+Contributing to PsychoPy consists of four steps:
+1. Getting your own copy
+2. Making your changes
+3. Committing your changes
+4. Submitting a Pull Request
+
+## 1. Getting your own copy of the PsychoPy codebase
+To be sure your improvements can easily be integrated, follow these steps:
+1. **Make a [fork](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo) of the [PsychoPy repo](https://github.com/psychopy/psychopy).** This provides you with your own copy of the PsychoPy source code.
+2. **Inside your fork, make a new [branch](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-branches) for the feature you've got in mind.** If you'd like to fix a bug, base your new branch on the *release* branch. If you'd like to add a new feature, base it on the *dev* branch. We tend to name branches after the feature we're building. For example `olfactory_component`.
+3. **Clone your fork to your hard drive.** Next, switch to the new branch and you're all set up!
+
+Look [here](https://www.psychopy.org/developers/repository.html) to see how the PsychoPy repo is organized.
+
+## 2. Making your changes
+To help you get started with modifying PsychoPy, we've got some [developer guides](https://www.psychopy.org/developers/index.html). To try out your modified PsychoPy, do a [developer's install](https://www.psychopy.org/download.html#developers-install).
+
+## 3. Committing your changes
+Once you're happy with your changes, commit them to your GitHub repo. Please use the tags below in your commit and add an informative message.
+ - **BF:** bug fix. For fixing bugs in the *release* branch.
+ - **FF:** ‘feature’ fix. For fixing bugs in the *dev* branch.
+ - **RF:** refactoring
+ - **NF:** new feature
+ - **ENH:** enhancement (to existing code, but don't worry too much about the difference between this and NF)
+ - **DOC:** for all kinds of documentation related commits
+ - **TEST:** for adding or changing tests
+
+## 4. File a Pull Request
+Once you're done, it's time to add it to the central PsychoPy source code. File a [Pull Request](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request) from your own fork and branch to the PsychoPy repo. Be sure to target the right branch in PsychoPy (*release* or *dev*). Thanks for contributing!
+""" ;
+ ns1:contributionInstructions """These guidelines were copied from [PychoPy](https://github.com/psychopy/psychopy/edit/dev/CONTRIBUTING.md)
+
+# Have you ever contributed to an Open Source project?
+Your first contribution can be a bit intimidating, but feel free to give it a try. If you get stuck, don't hesitate to ask for help in our [developer forum](https://discourse.psychopy.org/c/dev). This is also a good place to pitch your idea. Next up:
+* **I won't program it myself.** Please file a [GitHub issue](https://github.com/psychopy/psychopy/issues).
+* **I'd like to take a shot.** Read on to find out how!
+
+# How to contribute
+Contributing to PsychoPy consists of four steps:
+1. Getting your own copy
+2. Making your changes
+3. Committing your changes
+4. Submitting a Pull Request
+
+## 1. Getting your own copy of the PsychoPy codebase
+To be sure your improvements can easily be integrated, follow these steps:
+1. **Make a [fork](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo) of the [PsychoPy repo](https://github.com/psychopy/psychopy).** This provides you with your own copy of the PsychoPy source code.
+2. **Inside your fork, make a new [branch](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-branches) for the feature you've got in mind.** If you'd like to fix a bug, base your new branch on the *release* branch. If you'd like to add a new feature, base it on the *dev* branch. We tend to name branches after the feature we're building. For example `olfactory_component`.
+3. **Clone your fork to your hard drive.** Next, switch to the new branch and you're all set up!
+
+Look [here](https://www.psychopy.org/developers/repository.html) to see how the PsychoPy repo is organized.
+
+## 2. Making your changes
+To help you get started with modifying PsychoPy, we've got some [developer guides](https://www.psychopy.org/developers/index.html). To try out your modified PsychoPy, do a [developer's install](https://www.psychopy.org/download.html#developers-install).
+
+## 3. Committing your changes
+Once you're happy with your changes, commit them to your GitHub repo. Please use the tags below in your commit and add an informative message.
+ - **BF:** bug fix. For fixing bugs in the *release* branch.
+ - **FF:** ‘feature’ fix. For fixing bugs in the *dev* branch.
+ - **RF:** refactoring
+ - **NF:** new feature
+ - **ENH:** enhancement (to existing code, but don't worry too much about the difference between this and NF)
+ - **DOC:** for all kinds of documentation related commits
+ - **TEST:** for adding or changing tests
+
+## 4. File a Pull Request
+Once you're done, it's time to add it to the central PsychoPy source code. File a [Pull Request](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request) from your own fork and branch to the PsychoPy repo. Be sure to target the right branch in PsychoPy (*release* or *dev*). Thanks for contributing!
+"""^^xsd:anyURI ;
+ ns1:dateCreated "2023-11-28T12:30:28+00:00"^^xsd:dateTime ;
+ ns1:dateModified "2023-12-12T15:35:54+00:00"^^xsd:dateTime ;
+ ns1:description "This repo aims to provide values for each metadata field that SOMEF (v0.9.4) can extract" ;
+ ns1:hasAcknowledgments """This demonstration repo was created during the maSMP hackathon at [ZB MED](https://www.zbmed.de/en) sponsored by [NFDI4DataScience](https://www.nfdi4datascience.de). NFDI4DataScience is a consortium funded by the German Research Foundation (DFG), project number 460234259.
+""" ;
+ ns1:hasBuildFile "https://raw.githubusercontent.com/tpronk/somef-demo-repo/main/Dockerfile"^^xsd:anyURI ;
+ ns1:hasCodeOfConduct """# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+We as members, contributors, and leaders pledge to make participation in our
+community a harassment-free experience for everyone, regardless of age, body
+size, visible or invisible disability, ethnicity, sex characteristics, gender
+identity and expression, level of experience, education, socio-economic status,
+nationality, personal appearance, race, religion, or sexual identity
+and orientation.
+
+We pledge to act and interact in ways that contribute to an open, welcoming,
+diverse, inclusive, and healthy community.
+
+## Our Standards
+
+Examples of behavior that contributes to a positive environment for our
+community include:
+
+* Demonstrating empathy and kindness toward other people
+* Being respectful of differing opinions, viewpoints, and experiences
+* Giving and gracefully accepting constructive feedback
+* Accepting responsibility and apologizing to those affected by our mistakes,
+ and learning from the experience
+* Focusing on what is best not just for us as individuals, but for the
+ overall community
+
+Examples of unacceptable behavior include:
+
+* The use of sexualized language or imagery, and sexual attention or
+ advances of any kind
+* Trolling, insulting or derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or email
+ address, without their explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Enforcement Responsibilities
+
+Community leaders are responsible for clarifying and enforcing our standards of
+acceptable behavior and will take appropriate and fair corrective action in
+response to any behavior that they deem inappropriate, threatening, offensive,
+or harmful.
+
+Community leaders have the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, and will communicate reasons for moderation
+decisions when appropriate.
+
+## Scope
+
+This Code of Conduct applies within all community spaces, and also applies when
+an individual is officially representing the community in public spaces.
+Examples of representing our community include using an official e-mail address,
+posting via an official social media account, or acting as an appointed
+representative at an online or offline event.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported to the community leaders responsible for enforcement at
+.
+All complaints will be reviewed and investigated promptly and fairly.
+
+All community leaders are obligated to respect the privacy and security of the
+reporter of any incident.
+
+## Enforcement Guidelines
+
+Community leaders will follow these Community Impact Guidelines in determining
+the consequences for any action they deem in violation of this Code of Conduct:
+
+### 1. Correction
+
+**Community Impact**: Use of inappropriate language or other behavior deemed
+unprofessional or unwelcome in the community.
+
+**Consequence**: A private, written warning from community leaders, providing
+clarity around the nature of the violation and an explanation of why the
+behavior was inappropriate. A public apology may be requested.
+
+### 2. Warning
+
+**Community Impact**: A violation through a single incident or series
+of actions.
+
+**Consequence**: A warning with consequences for continued behavior. No
+interaction with the people involved, including unsolicited interaction with
+those enforcing the Code of Conduct, for a specified period of time. This
+includes avoiding interactions in community spaces as well as external channels
+like social media. Violating these terms may lead to a temporary or
+permanent ban.
+
+### 3. Temporary Ban
+
+**Community Impact**: A serious violation of community standards, including
+sustained inappropriate behavior.
+
+**Consequence**: A temporary ban from any sort of interaction or public
+communication with the community for a specified period of time. No public or
+private interaction with the people involved, including unsolicited interaction
+with those enforcing the Code of Conduct, is allowed during this period.
+Violating these terms may lead to a permanent ban.
+
+### 4. Permanent Ban
+
+**Community Impact**: Demonstrating a pattern of violation of community
+standards, including sustained inappropriate behavior, harassment of an
+individual, or aggression toward or disparagement of classes of individuals.
+
+**Consequence**: A permanent ban from any sort of public interaction within
+the community.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage],
+version 2.0, available at
+https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
+
+Community Impact Guidelines were inspired by [Mozilla's code of conduct
+enforcement ladder](https://github.com/mozilla/diversity).
+
+[homepage]: https://www.contributor-covenant.org
+
+For answers to common questions about this code of conduct, see the FAQ at
+https://www.contributor-covenant.org/faq. Translations are available at
+https://www.contributor-covenant.org/translations.
+""" ;
+ ns1:hasDownloadUrl "https://github.com/tpronk/somef-demo-repo/releases"^^xsd:anyURI ;
+ ns1:hasExample "https://raw.githubusercontent.com/tpronk/somef-demo-repo/main/executable_example.ipynb"^^xsd:anyURI ;
+ ns1:hasExecutableInstructions """There is no code in this repo that can be run.
+""" ;
+ ns1:hasExecutableNotebook "https://raw.githubusercontent.com/tpronk/somef-demo-repo/main/executable_example.ipynb" ;
+ ns1:hasFAQ """Frequently asked questions about a software component
+""" ;
+ ns1:hasInstallationInstructions """##Installation instructions
+A set of instructions that indicate how to install a target repository
+""" ;
+ ns1:hasLongName "somef-demo-repo" ;
+ ns1:hasSourceCode ;
+ ns1:hasSupportScriptLocation "https://raw.githubusercontent.com/tpronk/somef-demo-repo/main/script_file.sh"^^xsd:anyURI ;
+ ns1:hasUsageNotes """##Usage examples
+Assumptions and considerations recorded by the authors when executing a software component, or examples on how to use it.
+""" ;
+ ns1:hasVersion ;
+ ns1:identifier "https://zenodo.org/badge/latestdoi/450496579"^^xsd:anyURI ;
+ ns1:issueTracker "https://api.github.com/repos/tpronk/somef-demo-repo/issues"^^xsd:anyURI ;
+ ns1:keywords "topic1, topic2, topic3" ;
+ ns1:name "tpronk/somef-demo-repo" ;
+ ns1:readme "https://raw.githubusercontent.com/tpronk/somef-demo-repo/main/README.md"^^xsd:anyURI ;
+ ns1:softwareRequirements """Pre-requisites and dependencies needed to execute a software component.
+""" ;
+ ns1:supportDetails """Guidelines and links of where to obtain support for a software component
+""",
+ """[](https://gitter.im/OpenGeoscience/geonotebook)
+""" .
+
+ a ns2:SoftwareSourceCode ;
+ ns1:codeRepository "https://github.com/tpronk/somef-demo-repo"^^xsd:anyURI ;
+ ns1:name "tpronk/somef-demo-repo" ;
+ ns1:programmingLanguage "Common Workflow Language",
+ "Dockerfile",
+ "Jupyter Notebook",
+ "Shell" .
+
diff --git a/poetry.lock b/poetry.lock
index 9bdb2f2..ef707d1 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -2,46 +2,38 @@
[[package]]
name = "anyascii"
-version = "0.3.2"
+version = "0.3.3"
description = "Unicode to ASCII transliteration"
optional = false
python-versions = ">=3.3"
groups = ["main"]
files = [
- {file = "anyascii-0.3.2-py3-none-any.whl", hash = "sha256:3b3beef6fc43d9036d3b0529050b0c48bfad8bc960e9e562d7223cfb94fe45d4"},
- {file = "anyascii-0.3.2.tar.gz", hash = "sha256:9d5d32ef844fe225b8bc7cba7f950534fae4da27a9bf3a6bea2cb0ea46ce4730"},
+ {file = "anyascii-0.3.3-py3-none-any.whl", hash = "sha256:f5ab5e53c8781a36b5a40e1296a0eeda2f48c649ef10c3921c1381b1d00dee7a"},
+ {file = "anyascii-0.3.3.tar.gz", hash = "sha256:c94e9dd9d47b3d9494eca305fef9447d00b4bf1a32aff85aa746fa3ec7fb95c3"},
]
[[package]]
name = "attrs"
-version = "25.3.0"
+version = "25.4.0"
description = "Classes Without Boilerplate"
optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
groups = ["main"]
files = [
- {file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"},
- {file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"},
+ {file = "attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373"},
+ {file = "attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11"},
]
-[package.extras]
-benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"]
-cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"]
-dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"]
-docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"]
-tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"]
-tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\""]
-
[[package]]
name = "beautifulsoup4"
-version = "4.13.4"
+version = "4.14.2"
description = "Screen-scraping library"
optional = false
python-versions = ">=3.7.0"
groups = ["main"]
files = [
- {file = "beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b"},
- {file = "beautifulsoup4-4.13.4.tar.gz", hash = "sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195"},
+ {file = "beautifulsoup4-4.14.2-py3-none-any.whl", hash = "sha256:5ef6fa3a8cbece8488d66985560f97ed091e22bbc4e9c2338508a9d5de6d4515"},
+ {file = "beautifulsoup4-4.14.2.tar.gz", hash = "sha256:2a98ab9f944a11acee9cc848508ec28d9228abfd522ef0fad6a02a72e0ded69e"},
]
[package.dependencies]
@@ -85,14 +77,14 @@ beautifulsoup4 = "*"
[[package]]
name = "certifi"
-version = "2025.6.15"
+version = "2025.10.5"
description = "Python package for providing Mozilla's CA Bundle."
optional = false
python-versions = ">=3.7"
groups = ["main"]
files = [
- {file = "certifi-2025.6.15-py3-none-any.whl", hash = "sha256:2e0c7ce7cb5d8f8634ca55d2ba7e6ec2689a2fd6537d8dec1296a477a4910057"},
- {file = "certifi-2025.6.15.tar.gz", hash = "sha256:d747aa5a8b9bbbb1bb8c22bb13e22bd1f18e9796defa16bab421f7f7a317323b"},
+ {file = "certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de"},
+ {file = "certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43"},
]
[[package]]
@@ -109,104 +101,91 @@ files = [
[[package]]
name = "charset-normalizer"
-version = "3.4.2"
+version = "3.4.3"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
optional = false
python-versions = ">=3.7"
groups = ["main"]
files = [
- {file = "charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941"},
- {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd"},
- {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6"},
- {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d"},
- {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86"},
- {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c"},
- {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0"},
- {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef"},
- {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6"},
- {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366"},
- {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db"},
- {file = "charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a"},
- {file = "charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509"},
- {file = "charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2"},
- {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645"},
- {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd"},
- {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8"},
- {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f"},
- {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7"},
- {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9"},
- {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544"},
- {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82"},
- {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0"},
- {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5"},
- {file = "charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a"},
- {file = "charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28"},
- {file = "charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7"},
- {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3"},
- {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a"},
- {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214"},
- {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a"},
- {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd"},
- {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981"},
- {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c"},
- {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b"},
- {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d"},
- {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f"},
- {file = "charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c"},
- {file = "charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e"},
- {file = "charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0"},
- {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf"},
- {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e"},
- {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1"},
- {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c"},
- {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691"},
- {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0"},
- {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b"},
- {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff"},
- {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b"},
- {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148"},
- {file = "charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7"},
- {file = "charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980"},
- {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cad5f45b3146325bb38d6855642f6fd609c3f7cad4dbaf75549bf3b904d3184"},
- {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2680962a4848b3c4f155dc2ee64505a9c57186d0d56b43123b17ca3de18f0fa"},
- {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:36b31da18b8890a76ec181c3cf44326bf2c48e36d393ca1b72b3f484113ea344"},
- {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4074c5a429281bf056ddd4c5d3b740ebca4d43ffffe2ef4bf4d2d05114299da"},
- {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9e36a97bee9b86ef9a1cf7bb96747eb7a15c2f22bdb5b516434b00f2a599f02"},
- {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:1b1bde144d98e446b056ef98e59c256e9294f6b74d7af6846bf5ffdafd687a7d"},
- {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:915f3849a011c1f593ab99092f3cecfcb4d65d8feb4a64cf1bf2d22074dc0ec4"},
- {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:fb707f3e15060adf5b7ada797624a6c6e0138e2a26baa089df64c68ee98e040f"},
- {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:25a23ea5c7edc53e0f29bae2c44fcb5a1aa10591aae107f2a2b2583a9c5cbc64"},
- {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:770cab594ecf99ae64c236bc9ee3439c3f46be49796e265ce0cc8bc17b10294f"},
- {file = "charset_normalizer-3.4.2-cp37-cp37m-win32.whl", hash = "sha256:6a0289e4589e8bdfef02a80478f1dfcb14f0ab696b5a00e1f4b8a14a307a3c58"},
- {file = "charset_normalizer-3.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6fc1f5b51fa4cecaa18f2bd7a003f3dd039dd615cd69a2afd6d3b19aed6775f2"},
- {file = "charset_normalizer-3.4.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76af085e67e56c8816c3ccf256ebd136def2ed9654525348cfa744b6802b69eb"},
- {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e45ba65510e2647721e35323d6ef54c7974959f6081b58d4ef5d87c60c84919a"},
- {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:046595208aae0120559a67693ecc65dd75d46f7bf687f159127046628178dc45"},
- {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75d10d37a47afee94919c4fab4c22b9bc2a8bf7d4f46f87363bcf0573f3ff4f5"},
- {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6333b3aa5a12c26b2a4d4e7335a28f1475e0e5e17d69d55141ee3cab736f66d1"},
- {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8323a9b031aa0393768b87f04b4164a40037fb2a3c11ac06a03ffecd3618027"},
- {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:24498ba8ed6c2e0b56d4acbf83f2d989720a93b41d712ebd4f4979660db4417b"},
- {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:844da2b5728b5ce0e32d863af26f32b5ce61bc4273a9c720a9f3aa9df73b1455"},
- {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:65c981bdbd3f57670af8b59777cbfae75364b483fa8a9f420f08094531d54a01"},
- {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:3c21d4fca343c805a52c0c78edc01e3477f6dd1ad7c47653241cf2a206d4fc58"},
- {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dc7039885fa1baf9be153a0626e337aa7ec8bf96b0128605fb0d77788ddc1681"},
- {file = "charset_normalizer-3.4.2-cp38-cp38-win32.whl", hash = "sha256:8272b73e1c5603666618805fe821edba66892e2870058c94c53147602eab29c7"},
- {file = "charset_normalizer-3.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:70f7172939fdf8790425ba31915bfbe8335030f05b9913d7ae00a87d4395620a"},
- {file = "charset_normalizer-3.4.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4"},
- {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7"},
- {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836"},
- {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597"},
- {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7"},
- {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f"},
- {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba"},
- {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12"},
- {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518"},
- {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5"},
- {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3"},
- {file = "charset_normalizer-3.4.2-cp39-cp39-win32.whl", hash = "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471"},
- {file = "charset_normalizer-3.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e"},
- {file = "charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0"},
- {file = "charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63"},
+ {file = "charset_normalizer-3.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72"},
+ {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe"},
+ {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601"},
+ {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c"},
+ {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2"},
+ {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0"},
+ {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0"},
+ {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0"},
+ {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a"},
+ {file = "charset_normalizer-3.4.3-cp310-cp310-win32.whl", hash = "sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f"},
+ {file = "charset_normalizer-3.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669"},
+ {file = "charset_normalizer-3.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b"},
+ {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64"},
+ {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91"},
+ {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f"},
+ {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07"},
+ {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30"},
+ {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14"},
+ {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c"},
+ {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae"},
+ {file = "charset_normalizer-3.4.3-cp311-cp311-win32.whl", hash = "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849"},
+ {file = "charset_normalizer-3.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c"},
+ {file = "charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1"},
+ {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884"},
+ {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018"},
+ {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392"},
+ {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f"},
+ {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154"},
+ {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491"},
+ {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93"},
+ {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f"},
+ {file = "charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37"},
+ {file = "charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc"},
+ {file = "charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe"},
+ {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8"},
+ {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9"},
+ {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31"},
+ {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f"},
+ {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927"},
+ {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9"},
+ {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5"},
+ {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc"},
+ {file = "charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce"},
+ {file = "charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef"},
+ {file = "charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15"},
+ {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db"},
+ {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d"},
+ {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096"},
+ {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa"},
+ {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049"},
+ {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0"},
+ {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92"},
+ {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16"},
+ {file = "charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce"},
+ {file = "charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c"},
+ {file = "charset_normalizer-3.4.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0f2be7e0cf7754b9a30eb01f4295cc3d4358a479843b31f328afd210e2c7598c"},
+ {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c60e092517a73c632ec38e290eba714e9627abe9d301c8c8a12ec32c314a2a4b"},
+ {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:252098c8c7a873e17dd696ed98bbe91dbacd571da4b87df3736768efa7a792e4"},
+ {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3653fad4fe3ed447a596ae8638b437f827234f01a8cd801842e43f3d0a6b281b"},
+ {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8999f965f922ae054125286faf9f11bc6932184b93011d138925a1773830bbe9"},
+ {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d95bfb53c211b57198bb91c46dd5a2d8018b3af446583aab40074bf7988401cb"},
+ {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:5b413b0b1bfd94dbf4023ad6945889f374cd24e3f62de58d6bb102c4d9ae534a"},
+ {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:b5e3b2d152e74e100a9e9573837aba24aab611d39428ded46f4e4022ea7d1942"},
+ {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a2d08ac246bb48479170408d6c19f6385fa743e7157d716e144cad849b2dd94b"},
+ {file = "charset_normalizer-3.4.3-cp38-cp38-win32.whl", hash = "sha256:ec557499516fc90fd374bf2e32349a2887a876fbf162c160e3c01b6849eaf557"},
+ {file = "charset_normalizer-3.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:5d8d01eac18c423815ed4f4a2ec3b439d654e55ee4ad610e153cf02faf67ea40"},
+ {file = "charset_normalizer-3.4.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:70bfc5f2c318afece2f5838ea5e4c3febada0be750fcf4775641052bbba14d05"},
+ {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:23b6b24d74478dc833444cbd927c338349d6ae852ba53a0d02a2de1fce45b96e"},
+ {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:34a7f768e3f985abdb42841e20e17b330ad3aaf4bb7e7aeeb73db2e70f077b99"},
+ {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb731e5deb0c7ef82d698b0f4c5bb724633ee2a489401594c5c88b02e6cb15f7"},
+ {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:257f26fed7d7ff59921b78244f3cd93ed2af1800ff048c33f624c87475819dd7"},
+ {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1ef99f0456d3d46a50945c98de1774da86f8e992ab5c77865ea8b8195341fc19"},
+ {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2c322db9c8c89009a990ef07c3bcc9f011a3269bc06782f916cd3d9eed7c9312"},
+ {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:511729f456829ef86ac41ca78c63a5cb55240ed23b4b737faca0eb1abb1c41bc"},
+ {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:88ab34806dea0671532d3f82d82b85e8fc23d7b2dd12fa837978dad9bb392a34"},
+ {file = "charset_normalizer-3.4.3-cp39-cp39-win32.whl", hash = "sha256:16a8770207946ac75703458e2c743631c79c59c5890c80011d536248f8eaa432"},
+ {file = "charset_normalizer-3.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:d22dbedd33326a4a5190dd4fe9e9e693ef12160c77382d9e87919bce54f3d4ca"},
+ {file = "charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a"},
+ {file = "charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14"},
]
[[package]]
@@ -226,14 +205,14 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""}
[[package]]
name = "click-option-group"
-version = "0.5.7"
+version = "0.5.8"
description = "Option groups missing in Click"
optional = false
python-versions = ">=3.7"
groups = ["main"]
files = [
- {file = "click_option_group-0.5.7-py3-none-any.whl", hash = "sha256:96b9f52f397ef4d916f81929bd6c1f85e89046c7a401a64e72a61ae74ad35c24"},
- {file = "click_option_group-0.5.7.tar.gz", hash = "sha256:8dc780be038712fc12c9fecb3db4fe49e0d0723f9c171d7cda85c20369be693c"},
+ {file = "click_option_group-0.5.8-py3-none-any.whl", hash = "sha256:959065a45ccf58fcc48ec22f18fb50c1e59494612f0a1c13c4e714554434b502"},
+ {file = "click_option_group-0.5.8.tar.gz", hash = "sha256:7dc8d534a066382be748b20dd65270e59e0393bc765a7744df86b059d4e00e62"},
]
[package.dependencies]
@@ -260,86 +239,93 @@ files = [
[[package]]
name = "contourpy"
-version = "1.3.0"
+version = "1.3.3"
description = "Python library for calculating contours of 2D quadrilateral grids"
optional = false
-python-versions = ">=3.9"
-groups = ["main"]
-files = [
- {file = "contourpy-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:880ea32e5c774634f9fcd46504bf9f080a41ad855f4fef54f5380f5133d343c7"},
- {file = "contourpy-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:76c905ef940a4474a6289c71d53122a4f77766eef23c03cd57016ce19d0f7b42"},
- {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92f8557cbb07415a4d6fa191f20fd9d2d9eb9c0b61d1b2f52a8926e43c6e9af7"},
- {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36f965570cff02b874773c49bfe85562b47030805d7d8360748f3eca570f4cab"},
- {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cacd81e2d4b6f89c9f8a5b69b86490152ff39afc58a95af002a398273e5ce589"},
- {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69375194457ad0fad3a839b9e29aa0b0ed53bb54db1bfb6c3ae43d111c31ce41"},
- {file = "contourpy-1.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a52040312b1a858b5e31ef28c2e865376a386c60c0e248370bbea2d3f3b760d"},
- {file = "contourpy-1.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3faeb2998e4fcb256542e8a926d08da08977f7f5e62cf733f3c211c2a5586223"},
- {file = "contourpy-1.3.0-cp310-cp310-win32.whl", hash = "sha256:36e0cff201bcb17a0a8ecc7f454fe078437fa6bda730e695a92f2d9932bd507f"},
- {file = "contourpy-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:87ddffef1dbe5e669b5c2440b643d3fdd8622a348fe1983fad7a0f0ccb1cd67b"},
- {file = "contourpy-1.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0fa4c02abe6c446ba70d96ece336e621efa4aecae43eaa9b030ae5fb92b309ad"},
- {file = "contourpy-1.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:834e0cfe17ba12f79963861e0f908556b2cedd52e1f75e6578801febcc6a9f49"},
- {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbc4c3217eee163fa3984fd1567632b48d6dfd29216da3ded3d7b844a8014a66"},
- {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4865cd1d419e0c7a7bf6de1777b185eebdc51470800a9f42b9e9decf17762081"},
- {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:303c252947ab4b14c08afeb52375b26781ccd6a5ccd81abcdfc1fafd14cf93c1"},
- {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:637f674226be46f6ba372fd29d9523dd977a291f66ab2a74fbeb5530bb3f445d"},
- {file = "contourpy-1.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:76a896b2f195b57db25d6b44e7e03f221d32fe318d03ede41f8b4d9ba1bff53c"},
- {file = "contourpy-1.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e1fd23e9d01591bab45546c089ae89d926917a66dceb3abcf01f6105d927e2cb"},
- {file = "contourpy-1.3.0-cp311-cp311-win32.whl", hash = "sha256:d402880b84df3bec6eab53cd0cf802cae6a2ef9537e70cf75e91618a3801c20c"},
- {file = "contourpy-1.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:6cb6cc968059db9c62cb35fbf70248f40994dfcd7aa10444bbf8b3faeb7c2d67"},
- {file = "contourpy-1.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:570ef7cf892f0afbe5b2ee410c507ce12e15a5fa91017a0009f79f7d93a1268f"},
- {file = "contourpy-1.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:da84c537cb8b97d153e9fb208c221c45605f73147bd4cadd23bdae915042aad6"},
- {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0be4d8425bfa755e0fd76ee1e019636ccc7c29f77a7c86b4328a9eb6a26d0639"},
- {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c0da700bf58f6e0b65312d0a5e695179a71d0163957fa381bb3c1f72972537c"},
- {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb8b141bb00fa977d9122636b16aa67d37fd40a3d8b52dd837e536d64b9a4d06"},
- {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3634b5385c6716c258d0419c46d05c8aa7dc8cb70326c9a4fb66b69ad2b52e09"},
- {file = "contourpy-1.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0dce35502151b6bd35027ac39ba6e5a44be13a68f55735c3612c568cac3805fd"},
- {file = "contourpy-1.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aea348f053c645100612b333adc5983d87be69acdc6d77d3169c090d3b01dc35"},
- {file = "contourpy-1.3.0-cp312-cp312-win32.whl", hash = "sha256:90f73a5116ad1ba7174341ef3ea5c3150ddf20b024b98fb0c3b29034752c8aeb"},
- {file = "contourpy-1.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:b11b39aea6be6764f84360fce6c82211a9db32a7c7de8fa6dd5397cf1d079c3b"},
- {file = "contourpy-1.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3e1c7fa44aaae40a2247e2e8e0627f4bea3dd257014764aa644f319a5f8600e3"},
- {file = "contourpy-1.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:364174c2a76057feef647c802652f00953b575723062560498dc7930fc9b1cb7"},
- {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32b238b3b3b649e09ce9aaf51f0c261d38644bdfa35cbaf7b263457850957a84"},
- {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d51fca85f9f7ad0b65b4b9fe800406d0d77017d7270d31ec3fb1cc07358fdea0"},
- {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:732896af21716b29ab3e988d4ce14bc5133733b85956316fb0c56355f398099b"},
- {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d73f659398a0904e125280836ae6f88ba9b178b2fed6884f3b1f95b989d2c8da"},
- {file = "contourpy-1.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c6c7c2408b7048082932cf4e641fa3b8ca848259212f51c8c59c45aa7ac18f14"},
- {file = "contourpy-1.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f317576606de89da6b7e0861cf6061f6146ead3528acabff9236458a6ba467f8"},
- {file = "contourpy-1.3.0-cp313-cp313-win32.whl", hash = "sha256:31cd3a85dbdf1fc002280c65caa7e2b5f65e4a973fcdf70dd2fdcb9868069294"},
- {file = "contourpy-1.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:4553c421929ec95fb07b3aaca0fae668b2eb5a5203d1217ca7c34c063c53d087"},
- {file = "contourpy-1.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:345af746d7766821d05d72cb8f3845dfd08dd137101a2cb9b24de277d716def8"},
- {file = "contourpy-1.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3bb3808858a9dc68f6f03d319acd5f1b8a337e6cdda197f02f4b8ff67ad2057b"},
- {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:420d39daa61aab1221567b42eecb01112908b2cab7f1b4106a52caaec8d36973"},
- {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d63ee447261e963af02642ffcb864e5a2ee4cbfd78080657a9880b8b1868e18"},
- {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:167d6c890815e1dac9536dca00828b445d5d0df4d6a8c6adb4a7ec3166812fa8"},
- {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:710a26b3dc80c0e4febf04555de66f5fd17e9cf7170a7b08000601a10570bda6"},
- {file = "contourpy-1.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:75ee7cb1a14c617f34a51d11fa7524173e56551646828353c4af859c56b766e2"},
- {file = "contourpy-1.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:33c92cdae89ec5135d036e7218e69b0bb2851206077251f04a6c4e0e21f03927"},
- {file = "contourpy-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a11077e395f67ffc2c44ec2418cfebed032cd6da3022a94fc227b6faf8e2acb8"},
- {file = "contourpy-1.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e8134301d7e204c88ed7ab50028ba06c683000040ede1d617298611f9dc6240c"},
- {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e12968fdfd5bb45ffdf6192a590bd8ddd3ba9e58360b29683c6bb71a7b41edca"},
- {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fd2a0fc506eccaaa7595b7e1418951f213cf8255be2600f1ea1b61e46a60c55f"},
- {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4cfb5c62ce023dfc410d6059c936dcf96442ba40814aefbfa575425a3a7f19dc"},
- {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68a32389b06b82c2fdd68276148d7b9275b5f5cf13e5417e4252f6d1a34f72a2"},
- {file = "contourpy-1.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:94e848a6b83da10898cbf1311a815f770acc9b6a3f2d646f330d57eb4e87592e"},
- {file = "contourpy-1.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d78ab28a03c854a873787a0a42254a0ccb3cb133c672f645c9f9c8f3ae9d0800"},
- {file = "contourpy-1.3.0-cp39-cp39-win32.whl", hash = "sha256:81cb5ed4952aae6014bc9d0421dec7c5835c9c8c31cdf51910b708f548cf58e5"},
- {file = "contourpy-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:14e262f67bd7e6eb6880bc564dcda30b15e351a594657e55b7eec94b6ef72843"},
- {file = "contourpy-1.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fe41b41505a5a33aeaed2a613dccaeaa74e0e3ead6dd6fd3a118fb471644fd6c"},
- {file = "contourpy-1.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eca7e17a65f72a5133bdbec9ecf22401c62bcf4821361ef7811faee695799779"},
- {file = "contourpy-1.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1ec4dc6bf570f5b22ed0d7efba0dfa9c5b9e0431aeea7581aa217542d9e809a4"},
- {file = "contourpy-1.3.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:00ccd0dbaad6d804ab259820fa7cb0b8036bda0686ef844d24125d8287178ce0"},
- {file = "contourpy-1.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ca947601224119117f7c19c9cdf6b3ab54c5726ef1d906aa4a69dfb6dd58102"},
- {file = "contourpy-1.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c6ec93afeb848a0845a18989da3beca3eec2c0f852322efe21af1931147d12cb"},
- {file = "contourpy-1.3.0.tar.gz", hash = "sha256:7ffa0db17717a8ffb127efd0c95a4362d996b892c2904db72428d5b52e1938a4"},
+python-versions = ">=3.11"
+groups = ["main"]
+files = [
+ {file = "contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1"},
+ {file = "contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381"},
+ {file = "contourpy-1.3.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7"},
+ {file = "contourpy-1.3.3-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1"},
+ {file = "contourpy-1.3.3-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a"},
+ {file = "contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db"},
+ {file = "contourpy-1.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620"},
+ {file = "contourpy-1.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f"},
+ {file = "contourpy-1.3.3-cp311-cp311-win32.whl", hash = "sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff"},
+ {file = "contourpy-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42"},
+ {file = "contourpy-1.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470"},
+ {file = "contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb"},
+ {file = "contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6"},
+ {file = "contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7"},
+ {file = "contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8"},
+ {file = "contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea"},
+ {file = "contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1"},
+ {file = "contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7"},
+ {file = "contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411"},
+ {file = "contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69"},
+ {file = "contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b"},
+ {file = "contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc"},
+ {file = "contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5"},
+ {file = "contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1"},
+ {file = "contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286"},
+ {file = "contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5"},
+ {file = "contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67"},
+ {file = "contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9"},
+ {file = "contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659"},
+ {file = "contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7"},
+ {file = "contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d"},
+ {file = "contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263"},
+ {file = "contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9"},
+ {file = "contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d"},
+ {file = "contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216"},
+ {file = "contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae"},
+ {file = "contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20"},
+ {file = "contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99"},
+ {file = "contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b"},
+ {file = "contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a"},
+ {file = "contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e"},
+ {file = "contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3"},
+ {file = "contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8"},
+ {file = "contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301"},
+ {file = "contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a"},
+ {file = "contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77"},
+ {file = "contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5"},
+ {file = "contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4"},
+ {file = "contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36"},
+ {file = "contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3"},
+ {file = "contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b"},
+ {file = "contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36"},
+ {file = "contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d"},
+ {file = "contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd"},
+ {file = "contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339"},
+ {file = "contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772"},
+ {file = "contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77"},
+ {file = "contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13"},
+ {file = "contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe"},
+ {file = "contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f"},
+ {file = "contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0"},
+ {file = "contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4"},
+ {file = "contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f"},
+ {file = "contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae"},
+ {file = "contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc"},
+ {file = "contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b"},
+ {file = "contourpy-1.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497"},
+ {file = "contourpy-1.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8"},
+ {file = "contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e"},
+ {file = "contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989"},
+ {file = "contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77"},
+ {file = "contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880"},
]
[package.dependencies]
-numpy = ">=1.23"
+numpy = ">=1.25"
[package.extras]
bokeh = ["bokeh", "selenium"]
docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"]
-mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.11.1)", "types-Pillow"]
+mypy = ["bokeh", "contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.17.0)", "types-Pillow"]
test = ["Pillow", "contourpy[test-no-images]", "matplotlib"]
test-no-images = ["pytest", "pytest-cov", "pytest-rerunfailures", "pytest-xdist", "wurlitzer"]
@@ -373,35 +359,16 @@ files = [
docs = ["ipython", "matplotlib", "numpydoc", "sphinx"]
tests = ["pytest", "pytest-cov", "pytest-xdist"]
-[[package]]
-name = "exceptiongroup"
-version = "1.3.0"
-description = "Backport of PEP 654 (exception groups)"
-optional = false
-python-versions = ">=3.7"
-groups = ["main"]
-markers = "python_version < \"3.11\""
-files = [
- {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"},
- {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"},
-]
-
-[package.dependencies]
-typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""}
-
-[package.extras]
-test = ["pytest (>=6)"]
-
[[package]]
name = "fastjsonschema"
-version = "2.21.1"
+version = "2.21.2"
description = "Fastest Python implementation of JSON schema"
optional = false
python-versions = "*"
groups = ["main"]
files = [
- {file = "fastjsonschema-2.21.1-py3-none-any.whl", hash = "sha256:c9e5b7e908310918cf494a434eeb31384dd84a98b57a30bcb1f535015b554667"},
- {file = "fastjsonschema-2.21.1.tar.gz", hash = "sha256:794d4f0a58f848961ba16af7b9c85a3e88cd360df008c59aac6fc5ae9323b5d4"},
+ {file = "fastjsonschema-2.21.2-py3-none-any.whl", hash = "sha256:1c797122d0a86c5cace2e54bf4e819c36223b552017172f32c5c024a6b77e463"},
+ {file = "fastjsonschema-2.21.2.tar.gz", hash = "sha256:b1eb43748041c880796cd077f1a07c3d94e93ae84bba5ed36800a33554ae05de"},
]
[package.extras]
@@ -409,58 +376,74 @@ devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benc
[[package]]
name = "fonttools"
-version = "4.58.4"
+version = "4.60.1"
description = "Tools to manipulate font files"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
- {file = "fonttools-4.58.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:834542f13fee7625ad753b2db035edb674b07522fcbdd0ed9e9a9e2a1034467f"},
- {file = "fonttools-4.58.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2e6c61ce330142525296170cd65666e46121fc0d44383cbbcfa39cf8f58383df"},
- {file = "fonttools-4.58.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9c75f8faa29579c0fbf29b56ae6a3660c6c025f3b671803cb6a9caa7e4e3a98"},
- {file = "fonttools-4.58.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:88dedcedbd5549e35b2ea3db3de02579c27e62e51af56779c021e7b33caadd0e"},
- {file = "fonttools-4.58.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ae80a895adab43586f4da1521d58fd4f4377cef322ee0cc205abcefa3a5effc3"},
- {file = "fonttools-4.58.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0d3acc7f0d151da116e87a182aefb569cf0a3c8e0fd4c9cd0a7c1e7d3e7adb26"},
- {file = "fonttools-4.58.4-cp310-cp310-win32.whl", hash = "sha256:1244f69686008e7e8d2581d9f37eef330a73fee3843f1107993eb82c9d306577"},
- {file = "fonttools-4.58.4-cp310-cp310-win_amd64.whl", hash = "sha256:2a66c0af8a01eb2b78645af60f3b787de5fe5eb1fd8348163715b80bdbfbde1f"},
- {file = "fonttools-4.58.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a3841991c9ee2dc0562eb7f23d333d34ce81e8e27c903846f0487da21e0028eb"},
- {file = "fonttools-4.58.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c98f91b6a9604e7ffb5ece6ea346fa617f967c2c0944228801246ed56084664"},
- {file = "fonttools-4.58.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab9f891eb687ddf6a4e5f82901e00f992e18012ca97ab7acd15f13632acd14c1"},
- {file = "fonttools-4.58.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:891c5771e8f0094b7c0dc90eda8fc75e72930b32581418f2c285a9feedfd9a68"},
- {file = "fonttools-4.58.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:43ba4d9646045c375d22e3473b7d82b18b31ee2ac715cd94220ffab7bc2d5c1d"},
- {file = "fonttools-4.58.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33d19f16e6d2ffd6669bda574a6589941f6c99a8d5cfb9f464038244c71555de"},
- {file = "fonttools-4.58.4-cp311-cp311-win32.whl", hash = "sha256:b59e5109b907da19dc9df1287454821a34a75f2632a491dd406e46ff432c2a24"},
- {file = "fonttools-4.58.4-cp311-cp311-win_amd64.whl", hash = "sha256:3d471a5b567a0d1648f2e148c9a8bcf00d9ac76eb89e976d9976582044cc2509"},
- {file = "fonttools-4.58.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:462211c0f37a278494e74267a994f6be9a2023d0557aaa9ecbcbfce0f403b5a6"},
- {file = "fonttools-4.58.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0c7a12fb6f769165547f00fcaa8d0df9517603ae7e04b625e5acb8639809b82d"},
- {file = "fonttools-4.58.4-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2d42c63020a922154add0a326388a60a55504629edc3274bc273cd3806b4659f"},
- {file = "fonttools-4.58.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f2b4e6fd45edc6805f5f2c355590b092ffc7e10a945bd6a569fc66c1d2ae7aa"},
- {file = "fonttools-4.58.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f155b927f6efb1213a79334e4cb9904d1e18973376ffc17a0d7cd43d31981f1e"},
- {file = "fonttools-4.58.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e38f687d5de97c7fb7da3e58169fb5ba349e464e141f83c3c2e2beb91d317816"},
- {file = "fonttools-4.58.4-cp312-cp312-win32.whl", hash = "sha256:636c073b4da9db053aa683db99580cac0f7c213a953b678f69acbca3443c12cc"},
- {file = "fonttools-4.58.4-cp312-cp312-win_amd64.whl", hash = "sha256:82e8470535743409b30913ba2822e20077acf9ea70acec40b10fcf5671dceb58"},
- {file = "fonttools-4.58.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5f4a64846495c543796fa59b90b7a7a9dff6839bd852741ab35a71994d685c6d"},
- {file = "fonttools-4.58.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e80661793a5d4d7ad132a2aa1eae2e160fbdbb50831a0edf37c7c63b2ed36574"},
- {file = "fonttools-4.58.4-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fe5807fc64e4ba5130f1974c045a6e8d795f3b7fb6debfa511d1773290dbb76b"},
- {file = "fonttools-4.58.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b610b9bef841cb8f4b50472494158b1e347d15cad56eac414c722eda695a6cfd"},
- {file = "fonttools-4.58.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2daa7f0e213c38f05f054eb5e1730bd0424aebddbeac094489ea1585807dd187"},
- {file = "fonttools-4.58.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:66cccb6c0b944496b7f26450e9a66e997739c513ffaac728d24930df2fd9d35b"},
- {file = "fonttools-4.58.4-cp313-cp313-win32.whl", hash = "sha256:94d2aebb5ca59a5107825520fde596e344652c1f18170ef01dacbe48fa60c889"},
- {file = "fonttools-4.58.4-cp313-cp313-win_amd64.whl", hash = "sha256:b554bd6e80bba582fd326ddab296e563c20c64dca816d5e30489760e0c41529f"},
- {file = "fonttools-4.58.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ca773fe7812e4e1197ee4e63b9691e89650ab55f679e12ac86052d2fe0d152cd"},
- {file = "fonttools-4.58.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e31289101221910f44245472e02b1a2f7d671c6d06a45c07b354ecb25829ad92"},
- {file = "fonttools-4.58.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90c9e3c01475bb9602cb617f69f02c4ba7ab7784d93f0b0d685e84286f4c1a10"},
- {file = "fonttools-4.58.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e00a826f2bc745a010341ac102082fe5e3fb9f0861b90ed9ff32277598813711"},
- {file = "fonttools-4.58.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:bc75e72e9d2a4ad0935c59713bd38679d51c6fefab1eadde80e3ed4c2a11ea84"},
- {file = "fonttools-4.58.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f57a795e540059ce3de68508acfaaf177899b39c36ef0a2833b2308db98c71f1"},
- {file = "fonttools-4.58.4-cp39-cp39-win32.whl", hash = "sha256:a7d04f64c88b48ede655abcf76f2b2952f04933567884d99be7c89e0a4495131"},
- {file = "fonttools-4.58.4-cp39-cp39-win_amd64.whl", hash = "sha256:5a8bc5dfd425c89b1c38380bc138787b0a830f761b82b37139aa080915503b69"},
- {file = "fonttools-4.58.4-py3-none-any.whl", hash = "sha256:a10ce13a13f26cbb9f37512a4346bb437ad7e002ff6fa966a7ce7ff5ac3528bd"},
- {file = "fonttools-4.58.4.tar.gz", hash = "sha256:928a8009b9884ed3aae17724b960987575155ca23c6f0b8146e400cc9e0d44ba"},
+ {file = "fonttools-4.60.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9a52f254ce051e196b8fe2af4634c2d2f02c981756c6464dc192f1b6050b4e28"},
+ {file = "fonttools-4.60.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c7420a2696a44650120cdd269a5d2e56a477e2bfa9d95e86229059beb1c19e15"},
+ {file = "fonttools-4.60.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee0c0b3b35b34f782afc673d503167157094a16f442ace7c6c5e0ca80b08f50c"},
+ {file = "fonttools-4.60.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:282dafa55f9659e8999110bd8ed422ebe1c8aecd0dc396550b038e6c9a08b8ea"},
+ {file = "fonttools-4.60.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4ba4bd646e86de16160f0fb72e31c3b9b7d0721c3e5b26b9fa2fc931dfdb2652"},
+ {file = "fonttools-4.60.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0b0835ed15dd5b40d726bb61c846a688f5b4ce2208ec68779bc81860adb5851a"},
+ {file = "fonttools-4.60.1-cp310-cp310-win32.whl", hash = "sha256:1525796c3ffe27bb6268ed2a1bb0dcf214d561dfaf04728abf01489eb5339dce"},
+ {file = "fonttools-4.60.1-cp310-cp310-win_amd64.whl", hash = "sha256:268ecda8ca6cb5c4f044b1fb9b3b376e8cd1b361cef275082429dc4174907038"},
+ {file = "fonttools-4.60.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7b4c32e232a71f63a5d00259ca3d88345ce2a43295bb049d21061f338124246f"},
+ {file = "fonttools-4.60.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3630e86c484263eaac71d117085d509cbcf7b18f677906824e4bace598fb70d2"},
+ {file = "fonttools-4.60.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5c1015318e4fec75dd4943ad5f6a206d9727adf97410d58b7e32ab644a807914"},
+ {file = "fonttools-4.60.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e6c58beb17380f7c2ea181ea11e7db8c0ceb474c9dd45f48e71e2cb577d146a1"},
+ {file = "fonttools-4.60.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ec3681a0cb34c255d76dd9d865a55f260164adb9fa02628415cdc2d43ee2c05d"},
+ {file = "fonttools-4.60.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f4b5c37a5f40e4d733d3bbaaef082149bee5a5ea3156a785ff64d949bd1353fa"},
+ {file = "fonttools-4.60.1-cp311-cp311-win32.whl", hash = "sha256:398447f3d8c0c786cbf1209711e79080a40761eb44b27cdafffb48f52bcec258"},
+ {file = "fonttools-4.60.1-cp311-cp311-win_amd64.whl", hash = "sha256:d066ea419f719ed87bc2c99a4a4bfd77c2e5949cb724588b9dd58f3fd90b92bf"},
+ {file = "fonttools-4.60.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7b0c6d57ab00dae9529f3faf187f2254ea0aa1e04215cf2f1a8ec277c96661bc"},
+ {file = "fonttools-4.60.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:839565cbf14645952d933853e8ade66a463684ed6ed6c9345d0faf1f0e868877"},
+ {file = "fonttools-4.60.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8177ec9676ea6e1793c8a084a90b65a9f778771998eb919d05db6d4b1c0b114c"},
+ {file = "fonttools-4.60.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:996a4d1834524adbb423385d5a629b868ef9d774670856c63c9a0408a3063401"},
+ {file = "fonttools-4.60.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a46b2f450bc79e06ef3b6394f0c68660529ed51692606ad7f953fc2e448bc903"},
+ {file = "fonttools-4.60.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6ec722ee589e89a89f5b7574f5c45604030aa6ae24cb2c751e2707193b466fed"},
+ {file = "fonttools-4.60.1-cp312-cp312-win32.whl", hash = "sha256:b2cf105cee600d2de04ca3cfa1f74f1127f8455b71dbad02b9da6ec266e116d6"},
+ {file = "fonttools-4.60.1-cp312-cp312-win_amd64.whl", hash = "sha256:992775c9fbe2cf794786fa0ffca7f09f564ba3499b8fe9f2f80bd7197db60383"},
+ {file = "fonttools-4.60.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6f68576bb4bbf6060c7ab047b1574a1ebe5c50a17de62830079967b211059ebb"},
+ {file = "fonttools-4.60.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:eedacb5c5d22b7097482fa834bda0dafa3d914a4e829ec83cdea2a01f8c813c4"},
+ {file = "fonttools-4.60.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b33a7884fabd72bdf5f910d0cf46be50dce86a0362a65cfc746a4168c67eb96c"},
+ {file = "fonttools-4.60.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2409d5fb7b55fd70f715e6d34e7a6e4f7511b8ad29a49d6df225ee76da76dd77"},
+ {file = "fonttools-4.60.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c8651e0d4b3bdeda6602b85fdc2abbefc1b41e573ecb37b6779c4ca50753a199"},
+ {file = "fonttools-4.60.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:145daa14bf24824b677b9357c5e44fd8895c2a8f53596e1b9ea3496081dc692c"},
+ {file = "fonttools-4.60.1-cp313-cp313-win32.whl", hash = "sha256:2299df884c11162617a66b7c316957d74a18e3758c0274762d2cc87df7bc0272"},
+ {file = "fonttools-4.60.1-cp313-cp313-win_amd64.whl", hash = "sha256:a3db56f153bd4c5c2b619ab02c5db5192e222150ce5a1bc10f16164714bc39ac"},
+ {file = "fonttools-4.60.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:a884aef09d45ba1206712c7dbda5829562d3fea7726935d3289d343232ecb0d3"},
+ {file = "fonttools-4.60.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8a44788d9d91df72d1a5eac49b31aeb887a5f4aab761b4cffc4196c74907ea85"},
+ {file = "fonttools-4.60.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e852d9dda9f93ad3651ae1e3bb770eac544ec93c3807888798eccddf84596537"},
+ {file = "fonttools-4.60.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:154cb6ee417e417bf5f7c42fe25858c9140c26f647c7347c06f0cc2d47eff003"},
+ {file = "fonttools-4.60.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5664fd1a9ea7f244487ac8f10340c4e37664675e8667d6fee420766e0fb3cf08"},
+ {file = "fonttools-4.60.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:583b7f8e3c49486e4d489ad1deacfb8d5be54a8ef34d6df824f6a171f8511d99"},
+ {file = "fonttools-4.60.1-cp314-cp314-win32.whl", hash = "sha256:66929e2ea2810c6533a5184f938502cfdaea4bc3efb7130d8cc02e1c1b4108d6"},
+ {file = "fonttools-4.60.1-cp314-cp314-win_amd64.whl", hash = "sha256:f3d5be054c461d6a2268831f04091dc82753176f6ea06dc6047a5e168265a987"},
+ {file = "fonttools-4.60.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:b6379e7546ba4ae4b18f8ae2b9bc5960936007a1c0e30b342f662577e8bc3299"},
+ {file = "fonttools-4.60.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9d0ced62b59e0430b3690dbc5373df1c2aa7585e9a8ce38eff87f0fd993c5b01"},
+ {file = "fonttools-4.60.1-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:875cb7764708b3132637f6c5fb385b16eeba0f7ac9fa45a69d35e09b47045801"},
+ {file = "fonttools-4.60.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a184b2ea57b13680ab6d5fbde99ccef152c95c06746cb7718c583abd8f945ccc"},
+ {file = "fonttools-4.60.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:026290e4ec76583881763fac284aca67365e0be9f13a7fb137257096114cb3bc"},
+ {file = "fonttools-4.60.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f0e8817c7d1a0c2eedebf57ef9a9896f3ea23324769a9a2061a80fe8852705ed"},
+ {file = "fonttools-4.60.1-cp314-cp314t-win32.whl", hash = "sha256:1410155d0e764a4615774e5c2c6fc516259fe3eca5882f034eb9bfdbee056259"},
+ {file = "fonttools-4.60.1-cp314-cp314t-win_amd64.whl", hash = "sha256:022beaea4b73a70295b688f817ddc24ed3e3418b5036ffcd5658141184ef0d0c"},
+ {file = "fonttools-4.60.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:122e1a8ada290423c493491d002f622b1992b1ab0b488c68e31c413390dc7eb2"},
+ {file = "fonttools-4.60.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a140761c4ff63d0cb9256ac752f230460ee225ccef4ad8f68affc723c88e2036"},
+ {file = "fonttools-4.60.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0eae96373e4b7c9e45d099d7a523444e3554360927225c1cdae221a58a45b856"},
+ {file = "fonttools-4.60.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:596ecaca36367027d525b3b426d8a8208169d09edcf8c7506aceb3a38bfb55c7"},
+ {file = "fonttools-4.60.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2ee06fc57512144d8b0445194c2da9f190f61ad51e230f14836286470c99f854"},
+ {file = "fonttools-4.60.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b42d86938e8dda1cd9a1a87a6d82f1818eaf933348429653559a458d027446da"},
+ {file = "fonttools-4.60.1-cp39-cp39-win32.whl", hash = "sha256:8b4eb332f9501cb1cd3d4d099374a1e1306783ff95489a1026bde9eb02ccc34a"},
+ {file = "fonttools-4.60.1-cp39-cp39-win_amd64.whl", hash = "sha256:7473a8ed9ed09aeaa191301244a5a9dbe46fe0bf54f9d6cd21d83044c3321217"},
+ {file = "fonttools-4.60.1-py3-none-any.whl", hash = "sha256:906306ac7afe2156fcf0042173d6ebbb05416af70f6b370967b47f8f00103bbb"},
+ {file = "fonttools-4.60.1.tar.gz", hash = "sha256:ef00af0439ebfee806b25f24c8f92109157ff3fac5731dc7867957812e87b8d9"},
]
[package.extras]
-all = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "fs (>=2.2.0,<3)", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres ; platform_python_implementation == \"PyPy\"", "pycairo", "scipy ; platform_python_implementation != \"PyPy\"", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0) ; python_version <= \"3.12\"", "xattr ; sys_platform == \"darwin\"", "zopfli (>=0.1.4)"]
+all = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres ; platform_python_implementation == \"PyPy\"", "pycairo", "scipy ; platform_python_implementation != \"PyPy\"", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0) ; python_version <= \"3.12\"", "xattr ; sys_platform == \"darwin\"", "zopfli (>=0.1.4)"]
graphite = ["lz4 (>=1.7.4.2)"]
interpolatable = ["munkres ; platform_python_implementation == \"PyPy\"", "pycairo", "scipy ; platform_python_implementation != \"PyPy\""]
lxml = ["lxml (>=4.0)"]
@@ -469,7 +452,6 @@ plot = ["matplotlib"]
repacker = ["uharfbuzz (>=0.23.0)"]
symfont = ["sympy"]
type1 = ["xattr ; sys_platform == \"darwin\""]
-ufo = ["fs (>=2.2.0,<3)"]
unicode = ["unicodedata2 (>=15.1.0) ; python_version <= \"3.12\""]
woff = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "zopfli (>=0.1.4)"]
@@ -488,55 +470,6 @@ files = [
[package.extras]
all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"]
-[[package]]
-name = "importlib-metadata"
-version = "8.7.0"
-description = "Read metadata from Python packages"
-optional = false
-python-versions = ">=3.9"
-groups = ["main"]
-markers = "python_version == \"3.9\""
-files = [
- {file = "importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd"},
- {file = "importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000"},
-]
-
-[package.dependencies]
-zipp = ">=3.20"
-
-[package.extras]
-check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""]
-cover = ["pytest-cov"]
-doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
-enabler = ["pytest-enabler (>=2.2)"]
-perf = ["ipython"]
-test = ["flufl.flake8", "importlib_resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"]
-type = ["pytest-mypy"]
-
-[[package]]
-name = "importlib-resources"
-version = "6.5.2"
-description = "Read resources from Python packages"
-optional = false
-python-versions = ">=3.9"
-groups = ["main"]
-markers = "python_version == \"3.9\""
-files = [
- {file = "importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec"},
- {file = "importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c"},
-]
-
-[package.dependencies]
-zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""}
-
-[package.extras]
-check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""]
-cover = ["pytest-cov"]
-doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
-enabler = ["pytest-enabler (>=2.2)"]
-test = ["jaraco.test (>=5.4)", "pytest (>=6,!=8.1.*)", "zipp (>=3.17)"]
-type = ["pytest-mypy"]
-
[[package]]
name = "inflect"
version = "7.5.0"
@@ -575,26 +508,26 @@ files = [
[[package]]
name = "joblib"
-version = "1.5.1"
+version = "1.5.2"
description = "Lightweight pipelining with Python functions"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
- {file = "joblib-1.5.1-py3-none-any.whl", hash = "sha256:4719a31f054c7d766948dcd83e9613686b27114f190f717cec7eaa2084f8a74a"},
- {file = "joblib-1.5.1.tar.gz", hash = "sha256:f4f86e351f39fe3d0d32a9f2c3d8af1ee4cec285aafcb27003dda5205576b444"},
+ {file = "joblib-1.5.2-py3-none-any.whl", hash = "sha256:4e1f0bdbb987e6d843c70cf43714cb276623def372df3c22fe5266b2670bc241"},
+ {file = "joblib-1.5.2.tar.gz", hash = "sha256:3faa5c39054b2f03ca547da9b2f52fde67c06240c31853f306aea97f13647b55"},
]
[[package]]
name = "jsonschema"
-version = "4.24.0"
+version = "4.25.1"
description = "An implementation of JSON Schema validation for Python"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
- {file = "jsonschema-4.24.0-py3-none-any.whl", hash = "sha256:a462455f19f5faf404a7902952b6f0e3ce868f3ee09a359b05eca6673bd8412d"},
- {file = "jsonschema-4.24.0.tar.gz", hash = "sha256:0b4e8069eb12aedfa881333004bccaec24ecef5a8a6a4b6df142b2cc9599d196"},
+ {file = "jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63"},
+ {file = "jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85"},
]
[package.dependencies]
@@ -605,18 +538,18 @@ rpds-py = ">=0.7.1"
[package.extras]
format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"]
-format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=24.6.0)"]
+format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "rfc3987-syntax (>=1.1.0)", "uri-template", "webcolors (>=24.6.0)"]
[[package]]
name = "jsonschema-specifications"
-version = "2025.4.1"
+version = "2025.9.1"
description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
- {file = "jsonschema_specifications-2025.4.1-py3-none-any.whl", hash = "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af"},
- {file = "jsonschema_specifications-2025.4.1.tar.gz", hash = "sha256:630159c9f4dbea161a6a2205c3011cc4f18ff381b189fff48bb39b9bf26ae608"},
+ {file = "jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe"},
+ {file = "jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d"},
]
[package.dependencies]
@@ -645,126 +578,113 @@ test = ["ipykernel", "pre-commit", "pytest (<9)", "pytest-cov", "pytest-timeout"
[[package]]
name = "kiwisolver"
-version = "1.4.7"
+version = "1.4.9"
description = "A fast implementation of the Cassowary constraint solver"
optional = false
-python-versions = ">=3.8"
-groups = ["main"]
-files = [
- {file = "kiwisolver-1.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8a9c83f75223d5e48b0bc9cb1bf2776cf01563e00ade8775ffe13b0b6e1af3a6"},
- {file = "kiwisolver-1.4.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:58370b1ffbd35407444d57057b57da5d6549d2d854fa30249771775c63b5fe17"},
- {file = "kiwisolver-1.4.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aa0abdf853e09aff551db11fce173e2177d00786c688203f52c87ad7fcd91ef9"},
- {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8d53103597a252fb3ab8b5845af04c7a26d5e7ea8122303dd7a021176a87e8b9"},
- {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:88f17c5ffa8e9462fb79f62746428dd57b46eb931698e42e990ad63103f35e6c"},
- {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a9ca9c710d598fd75ee5de59d5bda2684d9db36a9f50b6125eaea3969c2599"},
- {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f4d742cb7af1c28303a51b7a27aaee540e71bb8e24f68c736f6f2ffc82f2bf05"},
- {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e28c7fea2196bf4c2f8d46a0415c77a1c480cc0724722f23d7410ffe9842c407"},
- {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e968b84db54f9d42046cf154e02911e39c0435c9801681e3fc9ce8a3c4130278"},
- {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0c18ec74c0472de033e1bebb2911c3c310eef5649133dd0bedf2a169a1b269e5"},
- {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8f0ea6da6d393d8b2e187e6a5e3fb81f5862010a40c3945e2c6d12ae45cfb2ad"},
- {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:f106407dda69ae456dd1227966bf445b157ccc80ba0dff3802bb63f30b74e895"},
- {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:84ec80df401cfee1457063732d90022f93951944b5b58975d34ab56bb150dfb3"},
- {file = "kiwisolver-1.4.7-cp310-cp310-win32.whl", hash = "sha256:71bb308552200fb2c195e35ef05de12f0c878c07fc91c270eb3d6e41698c3bcc"},
- {file = "kiwisolver-1.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:44756f9fd339de0fb6ee4f8c1696cfd19b2422e0d70b4cefc1cc7f1f64045a8c"},
- {file = "kiwisolver-1.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:78a42513018c41c2ffd262eb676442315cbfe3c44eed82385c2ed043bc63210a"},
- {file = "kiwisolver-1.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d2b0e12a42fb4e72d509fc994713d099cbb15ebf1103545e8a45f14da2dfca54"},
- {file = "kiwisolver-1.4.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2a8781ac3edc42ea4b90bc23e7d37b665d89423818e26eb6df90698aa2287c95"},
- {file = "kiwisolver-1.4.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:46707a10836894b559e04b0fd143e343945c97fd170d69a2d26d640b4e297935"},
- {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef97b8df011141c9b0f6caf23b29379f87dd13183c978a30a3c546d2c47314cb"},
- {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ab58c12a2cd0fc769089e6d38466c46d7f76aced0a1f54c77652446733d2d02"},
- {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:803b8e1459341c1bb56d1c5c010406d5edec8a0713a0945851290a7930679b51"},
- {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f9a9e8a507420fe35992ee9ecb302dab68550dedc0da9e2880dd88071c5fb052"},
- {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18077b53dc3bb490e330669a99920c5e6a496889ae8c63b58fbc57c3d7f33a18"},
- {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6af936f79086a89b3680a280c47ea90b4df7047b5bdf3aa5c524bbedddb9e545"},
- {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:3abc5b19d24af4b77d1598a585b8a719beb8569a71568b66f4ebe1fb0449460b"},
- {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:933d4de052939d90afbe6e9d5273ae05fb836cc86c15b686edd4b3560cc0ee36"},
- {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:65e720d2ab2b53f1f72fb5da5fb477455905ce2c88aaa671ff0a447c2c80e8e3"},
- {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3bf1ed55088f214ba6427484c59553123fdd9b218a42bbc8c6496d6754b1e523"},
- {file = "kiwisolver-1.4.7-cp311-cp311-win32.whl", hash = "sha256:4c00336b9dd5ad96d0a558fd18a8b6f711b7449acce4c157e7343ba92dd0cf3d"},
- {file = "kiwisolver-1.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:929e294c1ac1e9f615c62a4e4313ca1823ba37326c164ec720a803287c4c499b"},
- {file = "kiwisolver-1.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:e33e8fbd440c917106b237ef1a2f1449dfbb9b6f6e1ce17c94cd6a1e0d438376"},
- {file = "kiwisolver-1.4.7-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:5360cc32706dab3931f738d3079652d20982511f7c0ac5711483e6eab08efff2"},
- {file = "kiwisolver-1.4.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:942216596dc64ddb25adb215c3c783215b23626f8d84e8eff8d6d45c3f29f75a"},
- {file = "kiwisolver-1.4.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:48b571ecd8bae15702e4f22d3ff6a0f13e54d3d00cd25216d5e7f658242065ee"},
- {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad42ba922c67c5f219097b28fae965e10045ddf145d2928bfac2eb2e17673640"},
- {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:612a10bdae23404a72941a0fc8fa2660c6ea1217c4ce0dbcab8a8f6543ea9e7f"},
- {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e838bba3a3bac0fe06d849d29772eb1afb9745a59710762e4ba3f4cb8424483"},
- {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:22f499f6157236c19f4bbbd472fa55b063db77a16cd74d49afe28992dff8c258"},
- {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693902d433cf585133699972b6d7c42a8b9f8f826ebcaf0132ff55200afc599e"},
- {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4e77f2126c3e0b0d055f44513ed349038ac180371ed9b52fe96a32aa071a5107"},
- {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:657a05857bda581c3656bfc3b20e353c232e9193eb167766ad2dc58b56504948"},
- {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4bfa75a048c056a411f9705856abfc872558e33c055d80af6a380e3658766038"},
- {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:34ea1de54beef1c104422d210c47c7d2a4999bdecf42c7b5718fbe59a4cac383"},
- {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:90da3b5f694b85231cf93586dad5e90e2d71b9428f9aad96952c99055582f520"},
- {file = "kiwisolver-1.4.7-cp312-cp312-win32.whl", hash = "sha256:18e0cca3e008e17fe9b164b55735a325140a5a35faad8de92dd80265cd5eb80b"},
- {file = "kiwisolver-1.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:58cb20602b18f86f83a5c87d3ee1c766a79c0d452f8def86d925e6c60fbf7bfb"},
- {file = "kiwisolver-1.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:f5a8b53bdc0b3961f8b6125e198617c40aeed638b387913bf1ce78afb1b0be2a"},
- {file = "kiwisolver-1.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2e6039dcbe79a8e0f044f1c39db1986a1b8071051efba3ee4d74f5b365f5226e"},
- {file = "kiwisolver-1.4.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a1ecf0ac1c518487d9d23b1cd7139a6a65bc460cd101ab01f1be82ecf09794b6"},
- {file = "kiwisolver-1.4.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7ab9ccab2b5bd5702ab0803676a580fffa2aa178c2badc5557a84cc943fcf750"},
- {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f816dd2277f8d63d79f9c8473a79fe54047bc0467754962840782c575522224d"},
- {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf8bcc23ceb5a1b624572a1623b9f79d2c3b337c8c455405ef231933a10da379"},
- {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dea0bf229319828467d7fca8c7c189780aa9ff679c94539eed7532ebe33ed37c"},
- {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c06a4c7cf15ec739ce0e5971b26c93638730090add60e183530d70848ebdd34"},
- {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:913983ad2deb14e66d83c28b632fd35ba2b825031f2fa4ca29675e665dfecbe1"},
- {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5337ec7809bcd0f424c6b705ecf97941c46279cf5ed92311782c7c9c2026f07f"},
- {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4c26ed10c4f6fa6ddb329a5120ba3b6db349ca192ae211e882970bfc9d91420b"},
- {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c619b101e6de2222c1fcb0531e1b17bbffbe54294bfba43ea0d411d428618c27"},
- {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:073a36c8273647592ea332e816e75ef8da5c303236ec0167196793eb1e34657a"},
- {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3ce6b2b0231bda412463e152fc18335ba32faf4e8c23a754ad50ffa70e4091ee"},
- {file = "kiwisolver-1.4.7-cp313-cp313-win32.whl", hash = "sha256:f4c9aee212bc89d4e13f58be11a56cc8036cabad119259d12ace14b34476fd07"},
- {file = "kiwisolver-1.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:8a3ec5aa8e38fc4c8af308917ce12c536f1c88452ce554027e55b22cbbfbff76"},
- {file = "kiwisolver-1.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:76c8094ac20ec259471ac53e774623eb62e6e1f56cd8690c67ce6ce4fcb05650"},
- {file = "kiwisolver-1.4.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5d5abf8f8ec1f4e22882273c423e16cae834c36856cac348cfbfa68e01c40f3a"},
- {file = "kiwisolver-1.4.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:aeb3531b196ef6f11776c21674dba836aeea9d5bd1cf630f869e3d90b16cfade"},
- {file = "kiwisolver-1.4.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b7d755065e4e866a8086c9bdada157133ff466476a2ad7861828e17b6026e22c"},
- {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08471d4d86cbaec61f86b217dd938a83d85e03785f51121e791a6e6689a3be95"},
- {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7bbfcb7165ce3d54a3dfbe731e470f65739c4c1f85bb1018ee912bae139e263b"},
- {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d34eb8494bea691a1a450141ebb5385e4b69d38bb8403b5146ad279f4b30fa3"},
- {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9242795d174daa40105c1d86aba618e8eab7bf96ba8c3ee614da8302a9f95503"},
- {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a0f64a48bb81af7450e641e3fe0b0394d7381e342805479178b3d335d60ca7cf"},
- {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8e045731a5416357638d1700927529e2b8ab304811671f665b225f8bf8d8f933"},
- {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:4322872d5772cae7369f8351da1edf255a604ea7087fe295411397d0cfd9655e"},
- {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:e1631290ee9271dffe3062d2634c3ecac02c83890ada077d225e081aca8aab89"},
- {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:edcfc407e4eb17e037bca59be0e85a2031a2ac87e4fed26d3e9df88b4165f92d"},
- {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:4d05d81ecb47d11e7f8932bd8b61b720bf0b41199358f3f5e36d38e28f0532c5"},
- {file = "kiwisolver-1.4.7-cp38-cp38-win32.whl", hash = "sha256:b38ac83d5f04b15e515fd86f312479d950d05ce2368d5413d46c088dda7de90a"},
- {file = "kiwisolver-1.4.7-cp38-cp38-win_amd64.whl", hash = "sha256:d83db7cde68459fc803052a55ace60bea2bae361fc3b7a6d5da07e11954e4b09"},
- {file = "kiwisolver-1.4.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3f9362ecfca44c863569d3d3c033dbe8ba452ff8eed6f6b5806382741a1334bd"},
- {file = "kiwisolver-1.4.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e8df2eb9b2bac43ef8b082e06f750350fbbaf2887534a5be97f6cf07b19d9583"},
- {file = "kiwisolver-1.4.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f32d6edbc638cde7652bd690c3e728b25332acbadd7cad670cc4a02558d9c417"},
- {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e2e6c39bd7b9372b0be21456caab138e8e69cc0fc1190a9dfa92bd45a1e6e904"},
- {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dda56c24d869b1193fcc763f1284b9126550eaf84b88bbc7256e15028f19188a"},
- {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79849239c39b5e1fd906556c474d9b0439ea6792b637511f3fe3a41158d89ca8"},
- {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5e3bc157fed2a4c02ec468de4ecd12a6e22818d4f09cde2c31ee3226ffbefab2"},
- {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3da53da805b71e41053dc670f9a820d1157aae77b6b944e08024d17bcd51ef88"},
- {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8705f17dfeb43139a692298cb6637ee2e59c0194538153e83e9ee0c75c2eddde"},
- {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:82a5c2f4b87c26bb1a0ef3d16b5c4753434633b83d365cc0ddf2770c93829e3c"},
- {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce8be0466f4c0d585cdb6c1e2ed07232221df101a4c6f28821d2aa754ca2d9e2"},
- {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:409afdfe1e2e90e6ee7fc896f3df9a7fec8e793e58bfa0d052c8a82f99c37abb"},
- {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5b9c3f4ee0b9a439d2415012bd1b1cc2df59e4d6a9939f4d669241d30b414327"},
- {file = "kiwisolver-1.4.7-cp39-cp39-win32.whl", hash = "sha256:a79ae34384df2b615eefca647a2873842ac3b596418032bef9a7283675962644"},
- {file = "kiwisolver-1.4.7-cp39-cp39-win_amd64.whl", hash = "sha256:cf0438b42121a66a3a667de17e779330fc0f20b0d97d59d2f2121e182b0505e4"},
- {file = "kiwisolver-1.4.7-cp39-cp39-win_arm64.whl", hash = "sha256:764202cc7e70f767dab49e8df52c7455e8de0df5d858fa801a11aa0d882ccf3f"},
- {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:94252291e3fe68001b1dd747b4c0b3be12582839b95ad4d1b641924d68fd4643"},
- {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5b7dfa3b546da08a9f622bb6becdb14b3e24aaa30adba66749d38f3cc7ea9706"},
- {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd3de6481f4ed8b734da5df134cd5a6a64fe32124fe83dde1e5b5f29fe30b1e6"},
- {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a91b5f9f1205845d488c928e8570dcb62b893372f63b8b6e98b863ebd2368ff2"},
- {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40fa14dbd66b8b8f470d5fc79c089a66185619d31645f9b0773b88b19f7223c4"},
- {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:eb542fe7933aa09d8d8f9d9097ef37532a7df6497819d16efe4359890a2f417a"},
- {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bfa1acfa0c54932d5607e19a2c24646fb4c1ae2694437789129cf099789a3b00"},
- {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:eee3ea935c3d227d49b4eb85660ff631556841f6e567f0f7bda972df6c2c9935"},
- {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f3160309af4396e0ed04db259c3ccbfdc3621b5559b5453075e5de555e1f3a1b"},
- {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a17f6a29cf8935e587cc8a4dbfc8368c55edc645283db0ce9801016f83526c2d"},
- {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10849fb2c1ecbfae45a693c070e0320a91b35dd4bcf58172c023b994283a124d"},
- {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:ac542bf38a8a4be2dc6b15248d36315ccc65f0743f7b1a76688ffb6b5129a5c2"},
- {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8b01aac285f91ca889c800042c35ad3b239e704b150cfd3382adfc9dcc780e39"},
- {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:48be928f59a1f5c8207154f935334d374e79f2b5d212826307d072595ad76a2e"},
- {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f37cfe618a117e50d8c240555331160d73d0411422b59b5ee217843d7b693608"},
- {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:599b5c873c63a1f6ed7eead644a8a380cfbdf5db91dcb6f85707aaab213b1674"},
- {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:801fa7802e5cfabe3ab0c81a34c323a319b097dfb5004be950482d882f3d7225"},
- {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0c6c43471bc764fad4bc99c5c2d6d16a676b1abf844ca7c8702bdae92df01ee0"},
- {file = "kiwisolver-1.4.7.tar.gz", hash = "sha256:9893ff81bd7107f7b685d3017cc6583daadb4fc26e4a888350df530e41980a60"},
+python-versions = ">=3.10"
+groups = ["main"]
+files = [
+ {file = "kiwisolver-1.4.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b4b4d74bda2b8ebf4da5bd42af11d02d04428b2c32846e4c2c93219df8a7987b"},
+ {file = "kiwisolver-1.4.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fb3b8132019ea572f4611d770991000d7f58127560c4889729248eb5852a102f"},
+ {file = "kiwisolver-1.4.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84fd60810829c27ae375114cd379da1fa65e6918e1da405f356a775d49a62bcf"},
+ {file = "kiwisolver-1.4.9-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b78efa4c6e804ecdf727e580dbb9cba85624d2e1c6b5cb059c66290063bd99a9"},
+ {file = "kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4efec7bcf21671db6a3294ff301d2fc861c31faa3c8740d1a94689234d1b415"},
+ {file = "kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:90f47e70293fc3688b71271100a1a5453aa9944a81d27ff779c108372cf5567b"},
+ {file = "kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fdca1def57a2e88ef339de1737a1449d6dbf5fab184c54a1fca01d541317154"},
+ {file = "kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9cf554f21be770f5111a1690d42313e140355e687e05cf82cb23d0a721a64a48"},
+ {file = "kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fc1795ac5cd0510207482c3d1d3ed781143383b8cfd36f5c645f3897ce066220"},
+ {file = "kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:ccd09f20ccdbbd341b21a67ab50a119b64a403b09288c27481575105283c1586"},
+ {file = "kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:540c7c72324d864406a009d72f5d6856f49693db95d1fbb46cf86febef873634"},
+ {file = "kiwisolver-1.4.9-cp310-cp310-win_amd64.whl", hash = "sha256:ede8c6d533bc6601a47ad4046080d36b8fc99f81e6f1c17b0ac3c2dc91ac7611"},
+ {file = "kiwisolver-1.4.9-cp310-cp310-win_arm64.whl", hash = "sha256:7b4da0d01ac866a57dd61ac258c5607b4cd677f63abaec7b148354d2b2cdd536"},
+ {file = "kiwisolver-1.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eb14a5da6dc7642b0f3a18f13654847cd8b7a2550e2645a5bda677862b03ba16"},
+ {file = "kiwisolver-1.4.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:39a219e1c81ae3b103643d2aedb90f1ef22650deb266ff12a19e7773f3e5f089"},
+ {file = "kiwisolver-1.4.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2405a7d98604b87f3fc28b1716783534b1b4b8510d8142adca34ee0bc3c87543"},
+ {file = "kiwisolver-1.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dc1ae486f9abcef254b5618dfb4113dd49f94c68e3e027d03cf0143f3f772b61"},
+ {file = "kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a1f570ce4d62d718dce3f179ee78dac3b545ac16c0c04bb363b7607a949c0d1"},
+ {file = "kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb27e7b78d716c591e88e0a09a2139c6577865d7f2e152488c2cc6257f460872"},
+ {file = "kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:15163165efc2f627eb9687ea5f3a28137217d217ac4024893d753f46bce9de26"},
+ {file = "kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bdee92c56a71d2b24c33a7d4c2856bd6419d017e08caa7802d2963870e315028"},
+ {file = "kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:412f287c55a6f54b0650bd9b6dce5aceddb95864a1a90c87af16979d37c89771"},
+ {file = "kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2c93f00dcba2eea70af2be5f11a830a742fe6b579a1d4e00f47760ef13be247a"},
+ {file = "kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f117e1a089d9411663a3207ba874f31be9ac8eaa5b533787024dc07aeb74f464"},
+ {file = "kiwisolver-1.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:be6a04e6c79819c9a8c2373317d19a96048e5a3f90bec587787e86a1153883c2"},
+ {file = "kiwisolver-1.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:0ae37737256ba2de764ddc12aed4956460277f00c4996d51a197e72f62f5eec7"},
+ {file = "kiwisolver-1.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ac5a486ac389dddcc5bef4f365b6ae3ffff2c433324fb38dd35e3fab7c957999"},
+ {file = "kiwisolver-1.4.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2ba92255faa7309d06fe44c3a4a97efe1c8d640c2a79a5ef728b685762a6fd2"},
+ {file = "kiwisolver-1.4.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a2899935e724dd1074cb568ce7ac0dce28b2cd6ab539c8e001a8578eb106d14"},
+ {file = "kiwisolver-1.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f6008a4919fdbc0b0097089f67a1eb55d950ed7e90ce2cc3e640abadd2757a04"},
+ {file = "kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67bb8b474b4181770f926f7b7d2f8c0248cbcb78b660fdd41a47054b28d2a752"},
+ {file = "kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2327a4a30d3ee07d2fbe2e7933e8a37c591663b96ce42a00bc67461a87d7df77"},
+ {file = "kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a08b491ec91b1d5053ac177afe5290adacf1f0f6307d771ccac5de30592d198"},
+ {file = "kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8fc5c867c22b828001b6a38d2eaeb88160bf5783c6cb4a5e440efc981ce286d"},
+ {file = "kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3b3115b2581ea35bb6d1f24a4c90af37e5d9b49dcff267eeed14c3893c5b86ab"},
+ {file = "kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858e4c22fb075920b96a291928cb7dea5644e94c0ee4fcd5af7e865655e4ccf2"},
+ {file = "kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed0fecd28cc62c54b262e3736f8bb2512d8dcfdc2bcf08be5f47f96bf405b145"},
+ {file = "kiwisolver-1.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:f68208a520c3d86ea51acf688a3e3002615a7f0238002cccc17affecc86a8a54"},
+ {file = "kiwisolver-1.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:2c1a4f57df73965f3f14df20b80ee29e6a7930a57d2d9e8491a25f676e197c60"},
+ {file = "kiwisolver-1.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5d0432ccf1c7ab14f9949eec60c5d1f924f17c037e9f8b33352fa05799359b8"},
+ {file = "kiwisolver-1.4.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efb3a45b35622bb6c16dbfab491a8f5a391fe0e9d45ef32f4df85658232ca0e2"},
+ {file = "kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a12cf6398e8a0a001a059747a1cbf24705e18fe413bc22de7b3d15c67cffe3f"},
+ {file = "kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b67e6efbf68e077dd71d1a6b37e43e1a99d0bff1a3d51867d45ee8908b931098"},
+ {file = "kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5656aa670507437af0207645273ccdfee4f14bacd7f7c67a4306d0dcaeaf6eed"},
+ {file = "kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bfc08add558155345129c7803b3671cf195e6a56e7a12f3dde7c57d9b417f525"},
+ {file = "kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:40092754720b174e6ccf9e845d0d8c7d8e12c3d71e7fc35f55f3813e96376f78"},
+ {file = "kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:497d05f29a1300d14e02e6441cf0f5ee81c1ff5a304b0d9fb77423974684e08b"},
+ {file = "kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bdd1a81a1860476eb41ac4bc1e07b3f07259e6d55bbf739b79c8aaedcf512799"},
+ {file = "kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e6b93f13371d341afee3be9f7c5964e3fe61d5fa30f6a30eb49856935dfe4fc3"},
+ {file = "kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d75aa530ccfaa593da12834b86a0724f58bff12706659baa9227c2ccaa06264c"},
+ {file = "kiwisolver-1.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:dd0a578400839256df88c16abddf9ba14813ec5f21362e1fe65022e00c883d4d"},
+ {file = "kiwisolver-1.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:d4188e73af84ca82468f09cadc5ac4db578109e52acb4518d8154698d3a87ca2"},
+ {file = "kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5a0f2724dfd4e3b3ac5a82436a8e6fd16baa7d507117e4279b660fe8ca38a3a1"},
+ {file = "kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b11d6a633e4ed84fc0ddafd4ebfd8ea49b3f25082c04ad12b8315c11d504dc1"},
+ {file = "kiwisolver-1.4.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61874cdb0a36016354853593cffc38e56fc9ca5aa97d2c05d3dcf6922cd55a11"},
+ {file = "kiwisolver-1.4.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:60c439763a969a6af93b4881db0eed8fadf93ee98e18cbc35bc8da868d0c4f0c"},
+ {file = "kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92a2f997387a1b79a75e7803aa7ded2cfbe2823852ccf1ba3bcf613b62ae3197"},
+ {file = "kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31d512c812daea6d8b3be3b2bfcbeb091dbb09177706569bcfc6240dcf8b41c"},
+ {file = "kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:52a15b0f35dad39862d376df10c5230155243a2c1a436e39eb55623ccbd68185"},
+ {file = "kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a30fd6fdef1430fd9e1ba7b3398b5ee4e2887783917a687d86ba69985fb08748"},
+ {file = "kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cc9617b46837c6468197b5945e196ee9ca43057bb7d9d1ae688101e4e1dddf64"},
+ {file = "kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:0ab74e19f6a2b027ea4f845a78827969af45ce790e6cb3e1ebab71bdf9f215ff"},
+ {file = "kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dba5ee5d3981160c28d5490f0d1b7ed730c22470ff7f6cc26cfcfaacb9896a07"},
+ {file = "kiwisolver-1.4.9-cp313-cp313t-win_arm64.whl", hash = "sha256:0749fd8f4218ad2e851e11cc4dc05c7cbc0cbc4267bdfdb31782e65aace4ee9c"},
+ {file = "kiwisolver-1.4.9-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:9928fe1eb816d11ae170885a74d074f57af3a0d65777ca47e9aeb854a1fba386"},
+ {file = "kiwisolver-1.4.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d0005b053977e7b43388ddec89fa567f43d4f6d5c2c0affe57de5ebf290dc552"},
+ {file = "kiwisolver-1.4.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2635d352d67458b66fd0667c14cb1d4145e9560d503219034a18a87e971ce4f3"},
+ {file = "kiwisolver-1.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:767c23ad1c58c9e827b649a9ab7809fd5fd9db266a9cf02b0e926ddc2c680d58"},
+ {file = "kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72d0eb9fba308b8311685c2268cf7d0a0639a6cd027d8128659f72bdd8a024b4"},
+ {file = "kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f68e4f3eeca8fb22cc3d731f9715a13b652795ef657a13df1ad0c7dc0e9731df"},
+ {file = "kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d84cd4061ae292d8ac367b2c3fa3aad11cb8625a95d135fe93f286f914f3f5a6"},
+ {file = "kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a60ea74330b91bd22a29638940d115df9dc00af5035a9a2a6ad9399ffb4ceca5"},
+ {file = "kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ce6a3a4e106cf35c2d9c4fa17c05ce0b180db622736845d4315519397a77beaf"},
+ {file = "kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:77937e5e2a38a7b48eef0585114fe7930346993a88060d0bf886086d2aa49ef5"},
+ {file = "kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:24c175051354f4a28c5d6a31c93906dc653e2bf234e8a4bbfb964892078898ce"},
+ {file = "kiwisolver-1.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:0763515d4df10edf6d06a3c19734e2566368980d21ebec439f33f9eb936c07b7"},
+ {file = "kiwisolver-1.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:0e4e2bf29574a6a7b7f6cb5fa69293b9f96c928949ac4a53ba3f525dffb87f9c"},
+ {file = "kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d976bbb382b202f71c67f77b0ac11244021cfa3f7dfd9e562eefcea2df711548"},
+ {file = "kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2489e4e5d7ef9a1c300a5e0196e43d9c739f066ef23270607d45aba368b91f2d"},
+ {file = "kiwisolver-1.4.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e2ea9f7ab7fbf18fffb1b5434ce7c69a07582f7acc7717720f1d69f3e806f90c"},
+ {file = "kiwisolver-1.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b34e51affded8faee0dfdb705416153819d8ea9250bbbf7ea1b249bdeb5f1122"},
+ {file = "kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8aacd3d4b33b772542b2e01beb50187536967b514b00003bdda7589722d2a64"},
+ {file = "kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7cf974dd4e35fa315563ac99d6287a1024e4dc2077b8a7d7cd3d2fb65d283134"},
+ {file = "kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85bd218b5ecfbee8c8a82e121802dcb519a86044c9c3b2e4aef02fa05c6da370"},
+ {file = "kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0856e241c2d3df4efef7c04a1e46b1936b6120c9bcf36dd216e3acd84bc4fb21"},
+ {file = "kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9af39d6551f97d31a4deebeac6f45b156f9755ddc59c07b402c148f5dbb6482a"},
+ {file = "kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:bb4ae2b57fc1d8cbd1cf7b1d9913803681ffa903e7488012be5b76dedf49297f"},
+ {file = "kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:aedff62918805fb62d43a4aa2ecd4482c380dc76cd31bd7c8878588a61bd0369"},
+ {file = "kiwisolver-1.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:1fa333e8b2ce4d9660f2cda9c0e1b6bafcfb2457a9d259faa82289e73ec24891"},
+ {file = "kiwisolver-1.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:4a48a2ce79d65d363597ef7b567ce3d14d68783d2b2263d98db3d9477805ba32"},
+ {file = "kiwisolver-1.4.9-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4d1d9e582ad4d63062d34077a9a1e9f3c34088a2ec5135b1f7190c07cf366527"},
+ {file = "kiwisolver-1.4.9-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:deed0c7258ceb4c44ad5ec7d9918f9f14fd05b2be86378d86cf50e63d1e7b771"},
+ {file = "kiwisolver-1.4.9-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0a590506f303f512dff6b7f75fd2fd18e16943efee932008fe7140e5fa91d80e"},
+ {file = "kiwisolver-1.4.9-pp310-pypy310_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e09c2279a4d01f099f52d5c4b3d9e208e91edcbd1a175c9662a8b16e000fece9"},
+ {file = "kiwisolver-1.4.9-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c9e7cdf45d594ee04d5be1b24dd9d49f3d1590959b2271fb30b5ca2b262c00fb"},
+ {file = "kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:720e05574713db64c356e86732c0f3c5252818d05f9df320f0ad8380641acea5"},
+ {file = "kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:17680d737d5335b552994a2008fab4c851bcd7de33094a82067ef3a576ff02fa"},
+ {file = "kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:85b5352f94e490c028926ea567fc569c52ec79ce131dadb968d3853e809518c2"},
+ {file = "kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:464415881e4801295659462c49461a24fb107c140de781d55518c4b80cb6790f"},
+ {file = "kiwisolver-1.4.9-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:fb940820c63a9590d31d88b815e7a3aa5915cad3ce735ab45f0c730b39547de1"},
+ {file = "kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d"},
]
[[package]]
@@ -918,23 +838,45 @@ source = ["Cython (>=3.0.11,<3.1.0)"]
[[package]]
name = "markdown"
-version = "3.8.1"
+version = "3.9"
description = "Python implementation of John Gruber's Markdown."
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
- {file = "markdown-3.8.1-py3-none-any.whl", hash = "sha256:46cc0c0f1e5211ab2e9d453582f0b28a1bfaf058a9f7d5c50386b99b588d8811"},
- {file = "markdown-3.8.1.tar.gz", hash = "sha256:a2e2f01cead4828ee74ecca9623045f62216aef2212a7685d6eb9163f590b8c1"},
+ {file = "markdown-3.9-py3-none-any.whl", hash = "sha256:9f4d91ed810864ea88a6f32c07ba8bee1346c0cc1f6b1f9f6c822f2a9667d280"},
+ {file = "markdown-3.9.tar.gz", hash = "sha256:d2900fe1782bd33bdbbd56859defef70c2e78fc46668f8eb9df3128138f2cb6a"},
]
-[package.dependencies]
-importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""}
-
[package.extras]
docs = ["mdx_gh_links (>=0.2)", "mkdocs (>=1.6)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"]
testing = ["coverage", "pyyaml"]
+[[package]]
+name = "markdown-it-py"
+version = "3.0.0"
+description = "Python port of markdown-it. Markdown parsing, done right!"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+files = [
+ {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"},
+ {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"},
+]
+
+[package.dependencies]
+mdurl = ">=0.1,<1.0"
+
+[package.extras]
+benchmarking = ["psutil", "pytest", "pytest-benchmark"]
+code-style = ["pre-commit (>=3.0,<4.0)"]
+compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"]
+linkify = ["linkify-it-py (>=1,<3)"]
+plugins = ["mdit-py-plugins"]
+profiling = ["gprof2dot"]
+rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"]
+testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"]
+
[[package]]
name = "matplotlib"
version = "3.9.4"
@@ -990,7 +932,6 @@ files = [
contourpy = ">=1.0.1"
cycler = ">=0.10"
fonttools = ">=4.22.0"
-importlib-resources = {version = ">=3.2.0", markers = "python_version < \"3.10\""}
kiwisolver = ">=1.3.1"
numpy = ">=1.23"
packaging = ">=20.0"
@@ -1001,16 +942,28 @@ python-dateutil = ">=2.7"
[package.extras]
dev = ["meson-python (>=0.13.1,<0.17.0)", "numpy (>=1.25)", "pybind11 (>=2.6,!=2.13.3)", "setuptools (>=64)", "setuptools_scm (>=7)"]
+[[package]]
+name = "mdurl"
+version = "0.1.2"
+description = "Markdown URL utilities"
+optional = false
+python-versions = ">=3.7"
+groups = ["main"]
+files = [
+ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"},
+ {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
+]
+
[[package]]
name = "more-itertools"
-version = "10.7.0"
+version = "10.8.0"
description = "More routines for operating on iterables, beyond itertools"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
- {file = "more_itertools-10.7.0-py3-none-any.whl", hash = "sha256:d43980384673cb07d2f7d2d918c616b30c659c089ee23953f601d6609c67510e"},
- {file = "more_itertools-10.7.0.tar.gz", hash = "sha256:9fddd5403be01a94b204faadcff459ec3568cf110265d3c54323e1e866ad29d3"},
+ {file = "more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b"},
+ {file = "more_itertools-10.8.0.tar.gz", hash = "sha256:f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd"},
]
[[package]]
@@ -1037,14 +990,14 @@ test = ["pep440", "pre-commit", "pytest", "testpath"]
[[package]]
name = "nltk"
-version = "3.9.1"
+version = "3.9.2"
description = "Natural Language Toolkit"
optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
groups = ["main"]
files = [
- {file = "nltk-3.9.1-py3-none-any.whl", hash = "sha256:4fa26829c5b00715afe3061398a8989dc643b92ce7dd93fb4585a70930d168a1"},
- {file = "nltk-3.9.1.tar.gz", hash = "sha256:87d127bd3de4bd89a4f81265e5fa59cb1b199b27440175370f7417d2bc7ae868"},
+ {file = "nltk-3.9.2-py3-none-any.whl", hash = "sha256:1e209d2b3009110635ed9709a67a1a3e33a10f799490fa71cf4bec218c11c88a"},
+ {file = "nltk-3.9.2.tar.gz", hash = "sha256:0f409e9b069ca4177c1903c3e843eef90c7e92992fa4931ae607da6de49e1419"},
]
[package.dependencies]
@@ -1121,59 +1074,71 @@ files = [
[[package]]
name = "pandas"
-version = "2.3.0"
+version = "2.3.3"
description = "Powerful data structures for data analysis, time series, and statistics"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
- {file = "pandas-2.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:625466edd01d43b75b1883a64d859168e4556261a5035b32f9d743b67ef44634"},
- {file = "pandas-2.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a6872d695c896f00df46b71648eea332279ef4077a409e2fe94220208b6bb675"},
- {file = "pandas-2.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4dd97c19bd06bc557ad787a15b6489d2614ddaab5d104a0310eb314c724b2d2"},
- {file = "pandas-2.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:034abd6f3db8b9880aaee98f4f5d4dbec7c4829938463ec046517220b2f8574e"},
- {file = "pandas-2.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:23c2b2dc5213810208ca0b80b8666670eb4660bbfd9d45f58592cc4ddcfd62e1"},
- {file = "pandas-2.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:39ff73ec07be5e90330cc6ff5705c651ace83374189dcdcb46e6ff54b4a72cd6"},
- {file = "pandas-2.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:40cecc4ea5abd2921682b57532baea5588cc5f80f0231c624056b146887274d2"},
- {file = "pandas-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8adff9f138fc614347ff33812046787f7d43b3cef7c0f0171b3340cae333f6ca"},
- {file = "pandas-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e5f08eb9a445d07720776df6e641975665c9ea12c9d8a331e0f6890f2dcd76ef"},
- {file = "pandas-2.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa35c266c8cd1a67d75971a1912b185b492d257092bdd2709bbdebe574ed228d"},
- {file = "pandas-2.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14a0cc77b0f089d2d2ffe3007db58f170dae9b9f54e569b299db871a3ab5bf46"},
- {file = "pandas-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c06f6f144ad0a1bf84699aeea7eff6068ca5c63ceb404798198af7eb86082e33"},
- {file = "pandas-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ed16339bc354a73e0a609df36d256672c7d296f3f767ac07257801aa064ff73c"},
- {file = "pandas-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:fa07e138b3f6c04addfeaf56cc7fdb96c3b68a3fe5e5401251f231fce40a0d7a"},
- {file = "pandas-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2eb4728a18dcd2908c7fccf74a982e241b467d178724545a48d0caf534b38ebf"},
- {file = "pandas-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9d8c3187be7479ea5c3d30c32a5d73d62a621166675063b2edd21bc47614027"},
- {file = "pandas-2.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ff730713d4c4f2f1c860e36c005c7cefc1c7c80c21c0688fd605aa43c9fcf09"},
- {file = "pandas-2.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba24af48643b12ffe49b27065d3babd52702d95ab70f50e1b34f71ca703e2c0d"},
- {file = "pandas-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:404d681c698e3c8a40a61d0cd9412cc7364ab9a9cc6e144ae2992e11a2e77a20"},
- {file = "pandas-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6021910b086b3ca756755e86ddc64e0ddafd5e58e076c72cb1585162e5ad259b"},
- {file = "pandas-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:094e271a15b579650ebf4c5155c05dcd2a14fd4fdd72cf4854b2f7ad31ea30be"},
- {file = "pandas-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c7e2fc25f89a49a11599ec1e76821322439d90820108309bf42130d2f36c983"},
- {file = "pandas-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c6da97aeb6a6d233fb6b17986234cc723b396b50a3c6804776351994f2a658fd"},
- {file = "pandas-2.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb32dc743b52467d488e7a7c8039b821da2826a9ba4f85b89ea95274f863280f"},
- {file = "pandas-2.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:213cd63c43263dbb522c1f8a7c9d072e25900f6975596f883f4bebd77295d4f3"},
- {file = "pandas-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1d2b33e68d0ce64e26a4acc2e72d747292084f4e8db4c847c6f5f6cbe56ed6d8"},
- {file = "pandas-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:430a63bae10b5086995db1b02694996336e5a8ac9a96b4200572b413dfdfccb9"},
- {file = "pandas-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:4930255e28ff5545e2ca404637bcc56f031893142773b3468dc021c6c32a1390"},
- {file = "pandas-2.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f925f1ef673b4bd0271b1809b72b3270384f2b7d9d14a189b12b7fc02574d575"},
- {file = "pandas-2.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78ad363ddb873a631e92a3c063ade1ecfb34cae71e9a2be6ad100f875ac1042"},
- {file = "pandas-2.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:951805d146922aed8357e4cc5671b8b0b9be1027f0619cea132a9f3f65f2f09c"},
- {file = "pandas-2.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a881bc1309f3fce34696d07b00f13335c41f5f5a8770a33b09ebe23261cfc67"},
- {file = "pandas-2.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e1991bbb96f4050b09b5f811253c4f3cf05ee89a589379aa36cd623f21a31d6f"},
- {file = "pandas-2.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:bb3be958022198531eb7ec2008cfc78c5b1eed51af8600c6c5d9160d89d8d249"},
- {file = "pandas-2.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9efc0acbbffb5236fbdf0409c04edce96bec4bdaa649d49985427bd1ec73e085"},
- {file = "pandas-2.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:75651c14fde635e680496148a8526b328e09fe0572d9ae9b638648c46a544ba3"},
- {file = "pandas-2.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5be867a0541a9fb47a4be0c5790a4bccd5b77b92f0a59eeec9375fafc2aa14"},
- {file = "pandas-2.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84141f722d45d0c2a89544dd29d35b3abfc13d2250ed7e68394eda7564bd6324"},
- {file = "pandas-2.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f95a2aef32614ed86216d3c450ab12a4e82084e8102e355707a1d96e33d51c34"},
- {file = "pandas-2.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e0f51973ba93a9f97185049326d75b942b9aeb472bec616a129806facb129ebb"},
- {file = "pandas-2.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:b198687ca9c8529662213538a9bb1e60fa0bf0f6af89292eb68fea28743fcd5a"},
- {file = "pandas-2.3.0.tar.gz", hash = "sha256:34600ab34ebf1131a7613a260a61dbe8b62c188ec0ea4c296da7c9a06b004133"},
+ {file = "pandas-2.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:376c6446ae31770764215a6c937f72d917f214b43560603cd60da6408f183b6c"},
+ {file = "pandas-2.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e19d192383eab2f4ceb30b412b22ea30690c9e618f78870357ae1d682912015a"},
+ {file = "pandas-2.3.3-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5caf26f64126b6c7aec964f74266f435afef1c1b13da3b0636c7518a1fa3e2b1"},
+ {file = "pandas-2.3.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd7478f1463441ae4ca7308a70e90b33470fa593429f9d4c578dd00d1fa78838"},
+ {file = "pandas-2.3.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4793891684806ae50d1288c9bae9330293ab4e083ccd1c5e383c34549c6e4250"},
+ {file = "pandas-2.3.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:28083c648d9a99a5dd035ec125d42439c6c1c525098c58af0fc38dd1a7a1b3d4"},
+ {file = "pandas-2.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:503cf027cf9940d2ceaa1a93cfb5f8c8c7e6e90720a2850378f0b3f3b1e06826"},
+ {file = "pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523"},
+ {file = "pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45"},
+ {file = "pandas-2.3.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b468d3dad6ff947df92dcb32ede5b7bd41a9b3cceef0a30ed925f6d01fb8fa66"},
+ {file = "pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b"},
+ {file = "pandas-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37b5848ba49824e5c30bedb9c830ab9b7751fd049bc7914533e01c65f79791"},
+ {file = "pandas-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db4301b2d1f926ae677a751eb2bd0e8c5f5319c9cb3f88b0becbbb0b07b34151"},
+ {file = "pandas-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c"},
+ {file = "pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53"},
+ {file = "pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35"},
+ {file = "pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908"},
+ {file = "pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89"},
+ {file = "pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98"},
+ {file = "pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084"},
+ {file = "pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b"},
+ {file = "pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713"},
+ {file = "pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8"},
+ {file = "pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d"},
+ {file = "pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac"},
+ {file = "pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c"},
+ {file = "pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493"},
+ {file = "pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee"},
+ {file = "pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5"},
+ {file = "pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21"},
+ {file = "pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78"},
+ {file = "pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110"},
+ {file = "pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86"},
+ {file = "pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc"},
+ {file = "pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0"},
+ {file = "pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593"},
+ {file = "pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c"},
+ {file = "pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b"},
+ {file = "pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6"},
+ {file = "pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3"},
+ {file = "pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5"},
+ {file = "pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec"},
+ {file = "pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7"},
+ {file = "pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450"},
+ {file = "pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5"},
+ {file = "pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788"},
+ {file = "pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87"},
+ {file = "pandas-2.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c503ba5216814e295f40711470446bc3fd00f0faea8a086cbc688808e26f92a2"},
+ {file = "pandas-2.3.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a637c5cdfa04b6d6e2ecedcb81fc52ffb0fd78ce2ebccc9ea964df9f658de8c8"},
+ {file = "pandas-2.3.3-cp39-cp39-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:854d00d556406bffe66a4c0802f334c9ad5a96b4f1f868adf036a21b11ef13ff"},
+ {file = "pandas-2.3.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf1f8a81d04ca90e32a0aceb819d34dbd378a98bf923b6398b9a3ec0bf44de29"},
+ {file = "pandas-2.3.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:23ebd657a4d38268c7dfbdf089fbc31ea709d82e4923c5ffd4fbd5747133ce73"},
+ {file = "pandas-2.3.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5554c929ccc317d41a5e3d1234f3be588248e61f08a74dd17c9eabb535777dc9"},
+ {file = "pandas-2.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:d3e28b3e83862ccf4d85ff19cf8c20b2ae7e503881711ff2d534dc8f761131aa"},
+ {file = "pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b"},
]
[package.dependencies]
numpy = [
- {version = ">=1.22.4", markers = "python_version < \"3.11\""},
{version = ">=1.23.2", markers = "python_version == \"3.11\""},
{version = ">=1.26.0", markers = "python_version >= \"3.12\""},
]
@@ -1208,114 +1173,139 @@ xml = ["lxml (>=4.9.2)"]
[[package]]
name = "pillow"
-version = "11.2.1"
+version = "11.3.0"
description = "Python Imaging Library (Fork)"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
- {file = "pillow-11.2.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:d57a75d53922fc20c165016a20d9c44f73305e67c351bbc60d1adaf662e74047"},
- {file = "pillow-11.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:127bf6ac4a5b58b3d32fc8289656f77f80567d65660bc46f72c0d77e6600cc95"},
- {file = "pillow-11.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4ba4be812c7a40280629e55ae0b14a0aafa150dd6451297562e1764808bbe61"},
- {file = "pillow-11.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8bd62331e5032bc396a93609982a9ab6b411c05078a52f5fe3cc59234a3abd1"},
- {file = "pillow-11.2.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:562d11134c97a62fe3af29581f083033179f7ff435f78392565a1ad2d1c2c45c"},
- {file = "pillow-11.2.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:c97209e85b5be259994eb5b69ff50c5d20cca0f458ef9abd835e262d9d88b39d"},
- {file = "pillow-11.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0c3e6d0f59171dfa2e25d7116217543310908dfa2770aa64b8f87605f8cacc97"},
- {file = "pillow-11.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc1c3bc53befb6096b84165956e886b1729634a799e9d6329a0c512ab651e579"},
- {file = "pillow-11.2.1-cp310-cp310-win32.whl", hash = "sha256:312c77b7f07ab2139924d2639860e084ec2a13e72af54d4f08ac843a5fc9c79d"},
- {file = "pillow-11.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:9bc7ae48b8057a611e5fe9f853baa88093b9a76303937449397899385da06fad"},
- {file = "pillow-11.2.1-cp310-cp310-win_arm64.whl", hash = "sha256:2728567e249cdd939f6cc3d1f049595c66e4187f3c34078cbc0a7d21c47482d2"},
- {file = "pillow-11.2.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:35ca289f712ccfc699508c4658a1d14652e8033e9b69839edf83cbdd0ba39e70"},
- {file = "pillow-11.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0409af9f829f87a2dfb7e259f78f317a5351f2045158be321fd135973fff7bf"},
- {file = "pillow-11.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4e5c5edee874dce4f653dbe59db7c73a600119fbea8d31f53423586ee2aafd7"},
- {file = "pillow-11.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b93a07e76d13bff9444f1a029e0af2964e654bfc2e2c2d46bfd080df5ad5f3d8"},
- {file = "pillow-11.2.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:e6def7eed9e7fa90fde255afaf08060dc4b343bbe524a8f69bdd2a2f0018f600"},
- {file = "pillow-11.2.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:8f4f3724c068be008c08257207210c138d5f3731af6c155a81c2b09a9eb3a788"},
- {file = "pillow-11.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a0a6709b47019dff32e678bc12c63008311b82b9327613f534e496dacaefb71e"},
- {file = "pillow-11.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f6b0c664ccb879109ee3ca702a9272d877f4fcd21e5eb63c26422fd6e415365e"},
- {file = "pillow-11.2.1-cp311-cp311-win32.whl", hash = "sha256:cc5d875d56e49f112b6def6813c4e3d3036d269c008bf8aef72cd08d20ca6df6"},
- {file = "pillow-11.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:0f5c7eda47bf8e3c8a283762cab94e496ba977a420868cb819159980b6709193"},
- {file = "pillow-11.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:4d375eb838755f2528ac8cbc926c3e31cc49ca4ad0cf79cff48b20e30634a4a7"},
- {file = "pillow-11.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:78afba22027b4accef10dbd5eed84425930ba41b3ea0a86fa8d20baaf19d807f"},
- {file = "pillow-11.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78092232a4ab376a35d68c4e6d5e00dfd73454bd12b230420025fbe178ee3b0b"},
- {file = "pillow-11.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25a5f306095c6780c52e6bbb6109624b95c5b18e40aab1c3041da3e9e0cd3e2d"},
- {file = "pillow-11.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c7b29dbd4281923a2bfe562acb734cee96bbb129e96e6972d315ed9f232bef4"},
- {file = "pillow-11.2.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:3e645b020f3209a0181a418bffe7b4a93171eef6c4ef6cc20980b30bebf17b7d"},
- {file = "pillow-11.2.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b2dbea1012ccb784a65349f57bbc93730b96e85b42e9bf7b01ef40443db720b4"},
- {file = "pillow-11.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:da3104c57bbd72948d75f6a9389e6727d2ab6333c3617f0a89d72d4940aa0443"},
- {file = "pillow-11.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:598174aef4589af795f66f9caab87ba4ff860ce08cd5bb447c6fc553ffee603c"},
- {file = "pillow-11.2.1-cp312-cp312-win32.whl", hash = "sha256:1d535df14716e7f8776b9e7fee118576d65572b4aad3ed639be9e4fa88a1cad3"},
- {file = "pillow-11.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:14e33b28bf17c7a38eede290f77db7c664e4eb01f7869e37fa98a5aa95978941"},
- {file = "pillow-11.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:21e1470ac9e5739ff880c211fc3af01e3ae505859392bf65458c224d0bf283eb"},
- {file = "pillow-11.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fdec757fea0b793056419bca3e9932eb2b0ceec90ef4813ea4c1e072c389eb28"},
- {file = "pillow-11.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0e130705d568e2f43a17bcbe74d90958e8a16263868a12c3e0d9c8162690830"},
- {file = "pillow-11.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bdb5e09068332578214cadd9c05e3d64d99e0e87591be22a324bdbc18925be0"},
- {file = "pillow-11.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d189ba1bebfbc0c0e529159631ec72bb9e9bc041f01ec6d3233d6d82eb823bc1"},
- {file = "pillow-11.2.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:191955c55d8a712fab8934a42bfefbf99dd0b5875078240943f913bb66d46d9f"},
- {file = "pillow-11.2.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:ad275964d52e2243430472fc5d2c2334b4fc3ff9c16cb0a19254e25efa03a155"},
- {file = "pillow-11.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:750f96efe0597382660d8b53e90dd1dd44568a8edb51cb7f9d5d918b80d4de14"},
- {file = "pillow-11.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fe15238d3798788d00716637b3d4e7bb6bde18b26e5d08335a96e88564a36b6b"},
- {file = "pillow-11.2.1-cp313-cp313-win32.whl", hash = "sha256:3fe735ced9a607fee4f481423a9c36701a39719252a9bb251679635f99d0f7d2"},
- {file = "pillow-11.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:74ee3d7ecb3f3c05459ba95eed5efa28d6092d751ce9bf20e3e253a4e497e691"},
- {file = "pillow-11.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:5119225c622403afb4b44bad4c1ca6c1f98eed79db8d3bc6e4e160fc6339d66c"},
- {file = "pillow-11.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8ce2e8411c7aaef53e6bb29fe98f28cd4fbd9a1d9be2eeea434331aac0536b22"},
- {file = "pillow-11.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9ee66787e095127116d91dea2143db65c7bb1e232f617aa5957c0d9d2a3f23a7"},
- {file = "pillow-11.2.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9622e3b6c1d8b551b6e6f21873bdcc55762b4b2126633014cea1803368a9aa16"},
- {file = "pillow-11.2.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63b5dff3a68f371ea06025a1a6966c9a1e1ee452fc8020c2cd0ea41b83e9037b"},
- {file = "pillow-11.2.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:31df6e2d3d8fc99f993fd253e97fae451a8db2e7207acf97859732273e108406"},
- {file = "pillow-11.2.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:062b7a42d672c45a70fa1f8b43d1d38ff76b63421cbbe7f88146b39e8a558d91"},
- {file = "pillow-11.2.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4eb92eca2711ef8be42fd3f67533765d9fd043b8c80db204f16c8ea62ee1a751"},
- {file = "pillow-11.2.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f91ebf30830a48c825590aede79376cb40f110b387c17ee9bd59932c961044f9"},
- {file = "pillow-11.2.1-cp313-cp313t-win32.whl", hash = "sha256:e0b55f27f584ed623221cfe995c912c61606be8513bfa0e07d2c674b4516d9dd"},
- {file = "pillow-11.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:36d6b82164c39ce5482f649b437382c0fb2395eabc1e2b1702a6deb8ad647d6e"},
- {file = "pillow-11.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:225c832a13326e34f212d2072982bb1adb210e0cc0b153e688743018c94a2681"},
- {file = "pillow-11.2.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:7491cf8a79b8eb867d419648fff2f83cb0b3891c8b36da92cc7f1931d46108c8"},
- {file = "pillow-11.2.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8b02d8f9cb83c52578a0b4beadba92e37d83a4ef11570a8688bbf43f4ca50909"},
- {file = "pillow-11.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:014ca0050c85003620526b0ac1ac53f56fc93af128f7546623cc8e31875ab928"},
- {file = "pillow-11.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3692b68c87096ac6308296d96354eddd25f98740c9d2ab54e1549d6c8aea9d79"},
- {file = "pillow-11.2.1-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:f781dcb0bc9929adc77bad571b8621ecb1e4cdef86e940fe2e5b5ee24fd33b35"},
- {file = "pillow-11.2.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:2b490402c96f907a166615e9a5afacf2519e28295f157ec3a2bb9bd57de638cb"},
- {file = "pillow-11.2.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dd6b20b93b3ccc9c1b597999209e4bc5cf2853f9ee66e3fc9a400a78733ffc9a"},
- {file = "pillow-11.2.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:4b835d89c08a6c2ee7781b8dd0a30209a8012b5f09c0a665b65b0eb3560b6f36"},
- {file = "pillow-11.2.1-cp39-cp39-win32.whl", hash = "sha256:b10428b3416d4f9c61f94b494681280be7686bda15898a3a9e08eb66a6d92d67"},
- {file = "pillow-11.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:6ebce70c3f486acf7591a3d73431fa504a4e18a9b97ff27f5f47b7368e4b9dd1"},
- {file = "pillow-11.2.1-cp39-cp39-win_arm64.whl", hash = "sha256:c27476257b2fdcd7872d54cfd119b3a9ce4610fb85c8e32b70b42e3680a29a1e"},
- {file = "pillow-11.2.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:9b7b0d4fd2635f54ad82785d56bc0d94f147096493a79985d0ab57aedd563156"},
- {file = "pillow-11.2.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:aa442755e31c64037aa7c1cb186e0b369f8416c567381852c63444dd666fb772"},
- {file = "pillow-11.2.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0d3348c95b766f54b76116d53d4cb171b52992a1027e7ca50c81b43b9d9e363"},
- {file = "pillow-11.2.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85d27ea4c889342f7e35f6d56e7e1cb345632ad592e8c51b693d7b7556043ce0"},
- {file = "pillow-11.2.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:bf2c33d6791c598142f00c9c4c7d47f6476731c31081331664eb26d6ab583e01"},
- {file = "pillow-11.2.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e616e7154c37669fc1dfc14584f11e284e05d1c650e1c0f972f281c4ccc53193"},
- {file = "pillow-11.2.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:39ad2e0f424394e3aebc40168845fee52df1394a4673a6ee512d840d14ab3013"},
- {file = "pillow-11.2.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:80f1df8dbe9572b4b7abdfa17eb5d78dd620b1d55d9e25f834efdbee872d3aed"},
- {file = "pillow-11.2.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ea926cfbc3957090becbcbbb65ad177161a2ff2ad578b5a6ec9bb1e1cd78753c"},
- {file = "pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:738db0e0941ca0376804d4de6a782c005245264edaa253ffce24e5a15cbdc7bd"},
- {file = "pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9db98ab6565c69082ec9b0d4e40dd9f6181dab0dd236d26f7a50b8b9bfbd5076"},
- {file = "pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:036e53f4170e270ddb8797d4c590e6dd14d28e15c7da375c18978045f7e6c37b"},
- {file = "pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:14f73f7c291279bd65fda51ee87affd7c1e097709f7fdd0188957a16c264601f"},
- {file = "pillow-11.2.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:208653868d5c9ecc2b327f9b9ef34e0e42a4cdd172c2988fd81d62d2bc9bc044"},
- {file = "pillow-11.2.1.tar.gz", hash = "sha256:a64dd61998416367b7ef979b73d3a85853ba9bec4c2925f74e588879a58716b6"},
+ {file = "pillow-11.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b9c17fd4ace828b3003dfd1e30bff24863e0eb59b535e8f80194d9cc7ecf860"},
+ {file = "pillow-11.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:65dc69160114cdd0ca0f35cb434633c75e8e7fad4cf855177a05bf38678f73ad"},
+ {file = "pillow-11.3.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7107195ddc914f656c7fc8e4a5e1c25f32e9236ea3ea860f257b0436011fddd0"},
+ {file = "pillow-11.3.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc3e831b563b3114baac7ec2ee86819eb03caa1a2cef0b481a5675b59c4fe23b"},
+ {file = "pillow-11.3.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f1f182ebd2303acf8c380a54f615ec883322593320a9b00438eb842c1f37ae50"},
+ {file = "pillow-11.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4445fa62e15936a028672fd48c4c11a66d641d2c05726c7ec1f8ba6a572036ae"},
+ {file = "pillow-11.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:71f511f6b3b91dd543282477be45a033e4845a40278fa8dcdbfdb07109bf18f9"},
+ {file = "pillow-11.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:040a5b691b0713e1f6cbe222e0f4f74cd233421e105850ae3b3c0ceda520f42e"},
+ {file = "pillow-11.3.0-cp310-cp310-win32.whl", hash = "sha256:89bd777bc6624fe4115e9fac3352c79ed60f3bb18651420635f26e643e3dd1f6"},
+ {file = "pillow-11.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:19d2ff547c75b8e3ff46f4d9ef969a06c30ab2d4263a9e287733aa8b2429ce8f"},
+ {file = "pillow-11.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:819931d25e57b513242859ce1876c58c59dc31587847bf74cfe06b2e0cb22d2f"},
+ {file = "pillow-11.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1cd110edf822773368b396281a2293aeb91c90a2db00d78ea43e7e861631b722"},
+ {file = "pillow-11.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c412fddd1b77a75aa904615ebaa6001f169b26fd467b4be93aded278266b288"},
+ {file = "pillow-11.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1aa4de119a0ecac0a34a9c8bde33f34022e2e8f99104e47a3ca392fd60e37d"},
+ {file = "pillow-11.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:91da1d88226663594e3f6b4b8c3c8d85bd504117d043740a8e0ec449087cc494"},
+ {file = "pillow-11.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:643f189248837533073c405ec2f0bb250ba54598cf80e8c1e043381a60632f58"},
+ {file = "pillow-11.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:106064daa23a745510dabce1d84f29137a37224831d88eb4ce94bb187b1d7e5f"},
+ {file = "pillow-11.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd8ff254faf15591e724dc7c4ddb6bf4793efcbe13802a4ae3e863cd300b493e"},
+ {file = "pillow-11.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:932c754c2d51ad2b2271fd01c3d121daaa35e27efae2a616f77bf164bc0b3e94"},
+ {file = "pillow-11.3.0-cp311-cp311-win32.whl", hash = "sha256:b4b8f3efc8d530a1544e5962bd6b403d5f7fe8b9e08227c6b255f98ad82b4ba0"},
+ {file = "pillow-11.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:1a992e86b0dd7aeb1f053cd506508c0999d710a8f07b4c791c63843fc6a807ac"},
+ {file = "pillow-11.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:30807c931ff7c095620fe04448e2c2fc673fcbb1ffe2a7da3fb39613489b1ddd"},
+ {file = "pillow-11.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4"},
+ {file = "pillow-11.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:921bd305b10e82b4d1f5e802b6850677f965d8394203d182f078873851dada69"},
+ {file = "pillow-11.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb76541cba2f958032d79d143b98a3a6b3ea87f0959bbe256c0b5e416599fd5d"},
+ {file = "pillow-11.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67172f2944ebba3d4a7b54f2e95c786a3a50c21b88456329314caaa28cda70f6"},
+ {file = "pillow-11.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f07ed9f56a3b9b5f49d3661dc9607484e85c67e27f3e8be2c7d28ca032fec7"},
+ {file = "pillow-11.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:676b2815362456b5b3216b4fd5bd89d362100dc6f4945154ff172e206a22c024"},
+ {file = "pillow-11.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3e184b2f26ff146363dd07bde8b711833d7b0202e27d13540bfe2e35a323a809"},
+ {file = "pillow-11.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6be31e3fc9a621e071bc17bb7de63b85cbe0bfae91bb0363c893cbe67247780d"},
+ {file = "pillow-11.3.0-cp312-cp312-win32.whl", hash = "sha256:7b161756381f0918e05e7cb8a371fff367e807770f8fe92ecb20d905d0e1c149"},
+ {file = "pillow-11.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a6444696fce635783440b7f7a9fc24b3ad10a9ea3f0ab66c5905be1c19ccf17d"},
+ {file = "pillow-11.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:2aceea54f957dd4448264f9bf40875da0415c83eb85f55069d89c0ed436e3542"},
+ {file = "pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd"},
+ {file = "pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8"},
+ {file = "pillow-11.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f"},
+ {file = "pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c"},
+ {file = "pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd"},
+ {file = "pillow-11.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e"},
+ {file = "pillow-11.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1"},
+ {file = "pillow-11.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805"},
+ {file = "pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8"},
+ {file = "pillow-11.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2"},
+ {file = "pillow-11.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b"},
+ {file = "pillow-11.3.0-cp313-cp313-win32.whl", hash = "sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3"},
+ {file = "pillow-11.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51"},
+ {file = "pillow-11.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580"},
+ {file = "pillow-11.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e"},
+ {file = "pillow-11.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d"},
+ {file = "pillow-11.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced"},
+ {file = "pillow-11.3.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c"},
+ {file = "pillow-11.3.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8"},
+ {file = "pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59"},
+ {file = "pillow-11.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe"},
+ {file = "pillow-11.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c"},
+ {file = "pillow-11.3.0-cp313-cp313t-win32.whl", hash = "sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788"},
+ {file = "pillow-11.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31"},
+ {file = "pillow-11.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e"},
+ {file = "pillow-11.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d9da3df5f9ea2a89b81bb6087177fb1f4d1c7146d583a3fe5c672c0d94e55e12"},
+ {file = "pillow-11.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0b275ff9b04df7b640c59ec5a3cb113eefd3795a8df80bac69646ef699c6981a"},
+ {file = "pillow-11.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0743841cabd3dba6a83f38a92672cccbd69af56e3e91777b0ee7f4dba4385632"},
+ {file = "pillow-11.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2465a69cf967b8b49ee1b96d76718cd98c4e925414ead59fdf75cf0fd07df673"},
+ {file = "pillow-11.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41742638139424703b4d01665b807c6468e23e699e8e90cffefe291c5832b027"},
+ {file = "pillow-11.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77"},
+ {file = "pillow-11.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7966e38dcd0fa11ca390aed7c6f20454443581d758242023cf36fcb319b1a874"},
+ {file = "pillow-11.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:98a9afa7b9007c67ed84c57c9e0ad86a6000da96eaa638e4f8abe5b65ff83f0a"},
+ {file = "pillow-11.3.0-cp314-cp314-win32.whl", hash = "sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214"},
+ {file = "pillow-11.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:a418486160228f64dd9e9efcd132679b7a02a5f22c982c78b6fc7dab3fefb635"},
+ {file = "pillow-11.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:155658efb5e044669c08896c0c44231c5e9abcaadbc5cd3648df2f7c0b96b9a6"},
+ {file = "pillow-11.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:59a03cdf019efbfeeed910bf79c7c93255c3d54bc45898ac2a4140071b02b4ae"},
+ {file = "pillow-11.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f8a5827f84d973d8636e9dc5764af4f0cf2318d26744b3d902931701b0d46653"},
+ {file = "pillow-11.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ee92f2fd10f4adc4b43d07ec5e779932b4eb3dbfbc34790ada5a6669bc095aa6"},
+ {file = "pillow-11.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c96d333dcf42d01f47b37e0979b6bd73ec91eae18614864622d9b87bbd5bbf36"},
+ {file = "pillow-11.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c96f993ab8c98460cd0c001447bff6194403e8b1d7e149ade5f00594918128b"},
+ {file = "pillow-11.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477"},
+ {file = "pillow-11.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:068d9c39a2d1b358eb9f245ce7ab1b5c3246c7c8c7d9ba58cfa5b43146c06e50"},
+ {file = "pillow-11.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a1bc6ba083b145187f648b667e05a2534ecc4b9f2784c2cbe3089e44868f2b9b"},
+ {file = "pillow-11.3.0-cp314-cp314t-win32.whl", hash = "sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12"},
+ {file = "pillow-11.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db"},
+ {file = "pillow-11.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa"},
+ {file = "pillow-11.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:48d254f8a4c776de343051023eb61ffe818299eeac478da55227d96e241de53f"},
+ {file = "pillow-11.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7aee118e30a4cf54fdd873bd3a29de51e29105ab11f9aad8c32123f58c8f8081"},
+ {file = "pillow-11.3.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:23cff760a9049c502721bdb743a7cb3e03365fafcdfc2ef9784610714166e5a4"},
+ {file = "pillow-11.3.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6359a3bc43f57d5b375d1ad54a0074318a0844d11b76abccf478c37c986d3cfc"},
+ {file = "pillow-11.3.0-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:092c80c76635f5ecb10f3f83d76716165c96f5229addbd1ec2bdbbda7d496e06"},
+ {file = "pillow-11.3.0-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cadc9e0ea0a2431124cde7e1697106471fc4c1da01530e679b2391c37d3fbb3a"},
+ {file = "pillow-11.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6a418691000f2a418c9135a7cf0d797c1bb7d9a485e61fe8e7722845b95ef978"},
+ {file = "pillow-11.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:97afb3a00b65cc0804d1c7abddbf090a81eaac02768af58cbdcaaa0a931e0b6d"},
+ {file = "pillow-11.3.0-cp39-cp39-win32.whl", hash = "sha256:ea944117a7974ae78059fcc1800e5d3295172bb97035c0c1d9345fca1419da71"},
+ {file = "pillow-11.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:e5c5858ad8ec655450a7c7df532e9842cf8df7cc349df7225c60d5d348c8aada"},
+ {file = "pillow-11.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:6abdbfd3aea42be05702a8dd98832329c167ee84400a1d1f61ab11437f1717eb"},
+ {file = "pillow-11.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3cee80663f29e3843b68199b9d6f4f54bd1d4a6b59bdd91bceefc51238bcb967"},
+ {file = "pillow-11.3.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b5f56c3f344f2ccaf0dd875d3e180f631dc60a51b314295a3e681fe8cf851fbe"},
+ {file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e67d793d180c9df62f1f40aee3accca4829d3794c95098887edc18af4b8b780c"},
+ {file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d000f46e2917c705e9fb93a3606ee4a819d1e3aa7a9b442f6444f07e77cf5e25"},
+ {file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:527b37216b6ac3a12d7838dc3bd75208ec57c1c6d11ef01902266a5a0c14fc27"},
+ {file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:be5463ac478b623b9dd3937afd7fb7ab3d79dd290a28e2b6df292dc75063eb8a"},
+ {file = "pillow-11.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:8dc70ca24c110503e16918a658b869019126ecfe03109b754c402daff12b3d9f"},
+ {file = "pillow-11.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7c8ec7a017ad1bd562f93dbd8505763e688d388cde6e4a010ae1486916e713e6"},
+ {file = "pillow-11.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9ab6ae226de48019caa8074894544af5b53a117ccb9d3b3dcb2871464c829438"},
+ {file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe27fb049cdcca11f11a7bfda64043c37b30e6b91f10cb5bab275806c32f6ab3"},
+ {file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:465b9e8844e3c3519a983d58b80be3f668e2a7a5db97f2784e7079fbc9f9822c"},
+ {file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5418b53c0d59b3824d05e029669efa023bbef0f3e92e75ec8428f3799487f361"},
+ {file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:504b6f59505f08ae014f724b6207ff6222662aab5cc9542577fb084ed0676ac7"},
+ {file = "pillow-11.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c84d689db21a1c397d001aa08241044aa2069e7587b398c8cc63020390b1c1b8"},
+ {file = "pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523"},
]
[package.extras]
-docs = ["furo", "olefile", "sphinx (>=8.2)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"]
+docs = ["furo", "olefile", "sphinx (>=8.2)", "sphinx-autobuild", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"]
fpx = ["olefile"]
mic = ["olefile"]
test-arrow = ["pyarrow"]
-tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout", "trove-classifiers (>=2024.10.12)"]
+tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "trove-classifiers (>=2024.10.12)"]
typing = ["typing-extensions ; python_version < \"3.10\""]
xmp = ["defusedxml"]
[[package]]
name = "platformdirs"
-version = "4.3.8"
+version = "4.4.0"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
- {file = "platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4"},
- {file = "platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc"},
+ {file = "platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85"},
+ {file = "platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf"},
]
[package.extras]
@@ -1378,16 +1368,31 @@ files = [
[package.extras]
testing = ["pytest", "setuptools", "twine", "wheel"]
+[[package]]
+name = "pygments"
+version = "2.19.2"
+description = "Pygments is a syntax highlighting package written in Python."
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+files = [
+ {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"},
+ {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"},
+]
+
+[package.extras]
+windows-terminal = ["colorama (>=0.4.6)"]
+
[[package]]
name = "pyparsing"
-version = "3.2.3"
-description = "pyparsing module - Classes and methods to define and execute parsing grammars"
+version = "3.2.5"
+description = "pyparsing - Classes and methods to define and execute parsing grammars"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
- {file = "pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf"},
- {file = "pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be"},
+ {file = "pyparsing-3.2.5-py3-none-any.whl", hash = "sha256:e38a4f02064cf41fe6593d328d0512495ad1f3d8a91c4f73fc401b3079a59a5e"},
+ {file = "pyparsing-3.2.5.tar.gz", hash = "sha256:2df8d5b7b2802ef88e8d016a2eb9c7aeaa923529cd251ed0fe4608275d4105b6"},
]
[package.extras]
@@ -1395,26 +1400,25 @@ diagrams = ["jinja2", "railroad-diagrams"]
[[package]]
name = "pytest"
-version = "7.4.4"
+version = "8.4.2"
description = "pytest: simple powerful testing with Python"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.9"
groups = ["main"]
files = [
- {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"},
- {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"},
+ {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"},
+ {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"},
]
[package.dependencies]
-colorama = {version = "*", markers = "sys_platform == \"win32\""}
-exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
-iniconfig = "*"
-packaging = "*"
-pluggy = ">=0.12,<2.0"
-tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
+colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""}
+iniconfig = ">=1"
+packaging = ">=20"
+pluggy = ">=1.5,<2"
+pygments = ">=2.7.2"
[package.extras]
-testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
+dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"]
[[package]]
name = "python-dateutil"
@@ -1445,92 +1449,116 @@ files = [
[[package]]
name = "pywin32"
-version = "310"
+version = "311"
description = "Python for Window Extensions"
optional = false
python-versions = "*"
groups = ["main"]
markers = "sys_platform == \"win32\" and platform_python_implementation != \"PyPy\""
files = [
- {file = "pywin32-310-cp310-cp310-win32.whl", hash = "sha256:6dd97011efc8bf51d6793a82292419eba2c71cf8e7250cfac03bba284454abc1"},
- {file = "pywin32-310-cp310-cp310-win_amd64.whl", hash = "sha256:c3e78706e4229b915a0821941a84e7ef420bf2b77e08c9dae3c76fd03fd2ae3d"},
- {file = "pywin32-310-cp310-cp310-win_arm64.whl", hash = "sha256:33babed0cf0c92a6f94cc6cc13546ab24ee13e3e800e61ed87609ab91e4c8213"},
- {file = "pywin32-310-cp311-cp311-win32.whl", hash = "sha256:1e765f9564e83011a63321bb9d27ec456a0ed90d3732c4b2e312b855365ed8bd"},
- {file = "pywin32-310-cp311-cp311-win_amd64.whl", hash = "sha256:126298077a9d7c95c53823934f000599f66ec9296b09167810eb24875f32689c"},
- {file = "pywin32-310-cp311-cp311-win_arm64.whl", hash = "sha256:19ec5fc9b1d51c4350be7bb00760ffce46e6c95eaf2f0b2f1150657b1a43c582"},
- {file = "pywin32-310-cp312-cp312-win32.whl", hash = "sha256:8a75a5cc3893e83a108c05d82198880704c44bbaee4d06e442e471d3c9ea4f3d"},
- {file = "pywin32-310-cp312-cp312-win_amd64.whl", hash = "sha256:bf5c397c9a9a19a6f62f3fb821fbf36cac08f03770056711f765ec1503972060"},
- {file = "pywin32-310-cp312-cp312-win_arm64.whl", hash = "sha256:2349cc906eae872d0663d4d6290d13b90621eaf78964bb1578632ff20e152966"},
- {file = "pywin32-310-cp313-cp313-win32.whl", hash = "sha256:5d241a659c496ada3253cd01cfaa779b048e90ce4b2b38cd44168ad555ce74ab"},
- {file = "pywin32-310-cp313-cp313-win_amd64.whl", hash = "sha256:667827eb3a90208ddbdcc9e860c81bde63a135710e21e4cb3348968e4bd5249e"},
- {file = "pywin32-310-cp313-cp313-win_arm64.whl", hash = "sha256:e308f831de771482b7cf692a1f308f8fca701b2d8f9dde6cc440c7da17e47b33"},
- {file = "pywin32-310-cp38-cp38-win32.whl", hash = "sha256:0867beb8addefa2e3979d4084352e4ac6e991ca45373390775f7084cc0209b9c"},
- {file = "pywin32-310-cp38-cp38-win_amd64.whl", hash = "sha256:30f0a9b3138fb5e07eb4973b7077e1883f558e40c578c6925acc7a94c34eaa36"},
- {file = "pywin32-310-cp39-cp39-win32.whl", hash = "sha256:851c8d927af0d879221e616ae1f66145253537bbdd321a77e8ef701b443a9a1a"},
- {file = "pywin32-310-cp39-cp39-win_amd64.whl", hash = "sha256:96867217335559ac619f00ad70e513c0fcf84b8a3af9fc2bba3b59b97da70475"},
+ {file = "pywin32-311-cp310-cp310-win32.whl", hash = "sha256:d03ff496d2a0cd4a5893504789d4a15399133fe82517455e78bad62efbb7f0a3"},
+ {file = "pywin32-311-cp310-cp310-win_amd64.whl", hash = "sha256:797c2772017851984b97180b0bebe4b620bb86328e8a884bb626156295a63b3b"},
+ {file = "pywin32-311-cp310-cp310-win_arm64.whl", hash = "sha256:0502d1facf1fed4839a9a51ccbcc63d952cf318f78ffc00a7e78528ac27d7a2b"},
+ {file = "pywin32-311-cp311-cp311-win32.whl", hash = "sha256:184eb5e436dea364dcd3d2316d577d625c0351bf237c4e9a5fabbcfa5a58b151"},
+ {file = "pywin32-311-cp311-cp311-win_amd64.whl", hash = "sha256:3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503"},
+ {file = "pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2"},
+ {file = "pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31"},
+ {file = "pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067"},
+ {file = "pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852"},
+ {file = "pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d"},
+ {file = "pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d"},
+ {file = "pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a"},
+ {file = "pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee"},
+ {file = "pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87"},
+ {file = "pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42"},
+ {file = "pywin32-311-cp38-cp38-win32.whl", hash = "sha256:6c6f2969607b5023b0d9ce2541f8d2cbb01c4f46bc87456017cf63b73f1e2d8c"},
+ {file = "pywin32-311-cp38-cp38-win_amd64.whl", hash = "sha256:c8015b09fb9a5e188f83b7b04de91ddca4658cee2ae6f3bc483f0b21a77ef6cd"},
+ {file = "pywin32-311-cp39-cp39-win32.whl", hash = "sha256:aba8f82d551a942cb20d4a83413ccbac30790b50efb89a75e4f586ac0bb8056b"},
+ {file = "pywin32-311-cp39-cp39-win_amd64.whl", hash = "sha256:e0c4cfb0621281fe40387df582097fd796e80430597cb9944f0ae70447bacd91"},
+ {file = "pywin32-311-cp39-cp39-win_arm64.whl", hash = "sha256:62ea666235135fee79bb154e695f3ff67370afefd71bd7fea7512fc70ef31e3d"},
]
[[package]]
name = "pyyaml"
-version = "6.0.2"
+version = "6.0.3"
description = "YAML parser and emitter for Python"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
- {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"},
- {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"},
- {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"},
- {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"},
- {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"},
- {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"},
- {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"},
- {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"},
- {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"},
- {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"},
- {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"},
- {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"},
- {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"},
- {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"},
- {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"},
- {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"},
- {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"},
- {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"},
- {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"},
- {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"},
- {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"},
- {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"},
- {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"},
- {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"},
- {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"},
- {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"},
- {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"},
- {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"},
- {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"},
- {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"},
- {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"},
- {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"},
- {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"},
- {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"},
- {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"},
- {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"},
- {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"},
- {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"},
- {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"},
- {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"},
- {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"},
- {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"},
- {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"},
- {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"},
- {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"},
- {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"},
- {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"},
- {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"},
- {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"},
- {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"},
- {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"},
- {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"},
- {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"},
+ {file = "PyYAML-6.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f"},
+ {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4"},
+ {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efd7b85f94a6f21e4932043973a7ba2613b059c4a000551892ac9f1d11f5baf3"},
+ {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22ba7cfcad58ef3ecddc7ed1db3409af68d023b7f940da23c6c2a1890976eda6"},
+ {file = "PyYAML-6.0.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:6344df0d5755a2c9a276d4473ae6b90647e216ab4757f8426893b5dd2ac3f369"},
+ {file = "PyYAML-6.0.3-cp38-cp38-win32.whl", hash = "sha256:3ff07ec89bae51176c0549bc4c63aa6202991da2d9a6129d7aef7f1407d3f295"},
+ {file = "PyYAML-6.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:5cf4e27da7e3fbed4d6c3d8e797387aaad68102272f8f9752883bc32d61cb87b"},
+ {file = "pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b"},
+ {file = "pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956"},
+ {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8"},
+ {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198"},
+ {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b"},
+ {file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0"},
+ {file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69"},
+ {file = "pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e"},
+ {file = "pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c"},
+ {file = "pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e"},
+ {file = "pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824"},
+ {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c"},
+ {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00"},
+ {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d"},
+ {file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a"},
+ {file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4"},
+ {file = "pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b"},
+ {file = "pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf"},
+ {file = "pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196"},
+ {file = "pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0"},
+ {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28"},
+ {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c"},
+ {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc"},
+ {file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e"},
+ {file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea"},
+ {file = "pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5"},
+ {file = "pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b"},
+ {file = "pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd"},
+ {file = "pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8"},
+ {file = "pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1"},
+ {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c"},
+ {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5"},
+ {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6"},
+ {file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6"},
+ {file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be"},
+ {file = "pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26"},
+ {file = "pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c"},
+ {file = "pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb"},
+ {file = "pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac"},
+ {file = "pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310"},
+ {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7"},
+ {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788"},
+ {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5"},
+ {file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764"},
+ {file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35"},
+ {file = "pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac"},
+ {file = "pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3"},
+ {file = "pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3"},
+ {file = "pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba"},
+ {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c"},
+ {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702"},
+ {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c"},
+ {file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065"},
+ {file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65"},
+ {file = "pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9"},
+ {file = "pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b"},
+ {file = "pyyaml-6.0.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da"},
+ {file = "pyyaml-6.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917"},
+ {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9"},
+ {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5"},
+ {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a"},
+ {file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926"},
+ {file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7"},
+ {file = "pyyaml-6.0.3-cp39-cp39-win32.whl", hash = "sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0"},
+ {file = "pyyaml-6.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007"},
+ {file = "pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f"},
]
[[package]]
@@ -1552,118 +1580,139 @@ typing-extensions = {version = ">=4.4.0", markers = "python_version < \"3.13\""}
[[package]]
name = "regex"
-version = "2024.11.6"
+version = "2025.9.18"
description = "Alternative regular expression module, to replace re."
optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
groups = ["main"]
files = [
- {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91"},
- {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0"},
- {file = "regex-2024.11.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164d8b7b3b4bcb2068b97428060b2a53be050085ef94eca7f240e7947f1b080e"},
- {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3660c82f209655a06b587d55e723f0b813d3a7db2e32e5e7dc64ac2a9e86fde"},
- {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d22326fcdef5e08c154280b71163ced384b428343ae16a5ab2b3354aed12436e"},
- {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ac758ef6aebfc8943560194e9fd0fa18bcb34d89fd8bd2af18183afd8da3a2"},
- {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:997d6a487ff00807ba810e0f8332c18b4eb8d29463cfb7c820dc4b6e7562d0cf"},
- {file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02a02d2bb04fec86ad61f3ea7f49c015a0681bf76abb9857f945d26159d2968c"},
- {file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f02f93b92358ee3f78660e43b4b0091229260c5d5c408d17d60bf26b6c900e86"},
- {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06eb1be98df10e81ebaded73fcd51989dcf534e3c753466e4b60c4697a003b67"},
- {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:040df6fe1a5504eb0f04f048e6d09cd7c7110fef851d7c567a6b6e09942feb7d"},
- {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabbfc59f2c6edba2a6622c647b716e34e8e3867e0ab975412c5c2f79b82da2"},
- {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8447d2d39b5abe381419319f942de20b7ecd60ce86f16a23b0698f22e1b70008"},
- {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da8f5fc57d1933de22a9e23eec290a0d8a5927a5370d24bda9a6abe50683fe62"},
- {file = "regex-2024.11.6-cp310-cp310-win32.whl", hash = "sha256:b489578720afb782f6ccf2840920f3a32e31ba28a4b162e13900c3e6bd3f930e"},
- {file = "regex-2024.11.6-cp310-cp310-win_amd64.whl", hash = "sha256:5071b2093e793357c9d8b2929dfc13ac5f0a6c650559503bb81189d0a3814519"},
- {file = "regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638"},
- {file = "regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7"},
- {file = "regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20"},
- {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114"},
- {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3"},
- {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f"},
- {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0"},
- {file = "regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55"},
- {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89"},
- {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d"},
- {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34"},
- {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d"},
- {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45"},
- {file = "regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9"},
- {file = "regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60"},
- {file = "regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a"},
- {file = "regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9"},
- {file = "regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2"},
- {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4"},
- {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577"},
- {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3"},
- {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e"},
- {file = "regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe"},
- {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e"},
- {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29"},
- {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39"},
- {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51"},
- {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad"},
- {file = "regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54"},
- {file = "regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b"},
- {file = "regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84"},
- {file = "regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4"},
- {file = "regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0"},
- {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0"},
- {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7"},
- {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7"},
- {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c"},
- {file = "regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3"},
- {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07"},
- {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e"},
- {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6"},
- {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4"},
- {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d"},
- {file = "regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff"},
- {file = "regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a"},
- {file = "regex-2024.11.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3a51ccc315653ba012774efca4f23d1d2a8a8f278a6072e29c7147eee7da446b"},
- {file = "regex-2024.11.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ad182d02e40de7459b73155deb8996bbd8e96852267879396fb274e8700190e3"},
- {file = "regex-2024.11.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ba9b72e5643641b7d41fa1f6d5abda2c9a263ae835b917348fc3c928182ad467"},
- {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40291b1b89ca6ad8d3f2b82782cc33807f1406cf68c8d440861da6304d8ffbbd"},
- {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cdf58d0e516ee426a48f7b2c03a332a4114420716d55769ff7108c37a09951bf"},
- {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a36fdf2af13c2b14738f6e973aba563623cb77d753bbbd8d414d18bfaa3105dd"},
- {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1cee317bfc014c2419a76bcc87f071405e3966da434e03e13beb45f8aced1a6"},
- {file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50153825ee016b91549962f970d6a4442fa106832e14c918acd1c8e479916c4f"},
- {file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea1bfda2f7162605f6e8178223576856b3d791109f15ea99a9f95c16a7636fb5"},
- {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:df951c5f4a1b1910f1a99ff42c473ff60f8225baa1cdd3539fe2819d9543e9df"},
- {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:072623554418a9911446278f16ecb398fb3b540147a7828c06e2011fa531e773"},
- {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f654882311409afb1d780b940234208a252322c24a93b442ca714d119e68086c"},
- {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:89d75e7293d2b3e674db7d4d9b1bee7f8f3d1609428e293771d1a962617150cc"},
- {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:f65557897fc977a44ab205ea871b690adaef6b9da6afda4790a2484b04293a5f"},
- {file = "regex-2024.11.6-cp38-cp38-win32.whl", hash = "sha256:6f44ec28b1f858c98d3036ad5d7d0bfc568bdd7a74f9c24e25f41ef1ebfd81a4"},
- {file = "regex-2024.11.6-cp38-cp38-win_amd64.whl", hash = "sha256:bb8f74f2f10dbf13a0be8de623ba4f9491faf58c24064f32b65679b021ed0001"},
- {file = "regex-2024.11.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5704e174f8ccab2026bd2f1ab6c510345ae8eac818b613d7d73e785f1310f839"},
- {file = "regex-2024.11.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:220902c3c5cc6af55d4fe19ead504de80eb91f786dc102fbd74894b1551f095e"},
- {file = "regex-2024.11.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e7e351589da0850c125f1600a4c4ba3c722efefe16b297de54300f08d734fbf"},
- {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5056b185ca113c88e18223183aa1a50e66507769c9640a6ff75859619d73957b"},
- {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e34b51b650b23ed3354b5a07aab37034d9f923db2a40519139af34f485f77d0"},
- {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5670bce7b200273eee1840ef307bfa07cda90b38ae56e9a6ebcc9f50da9c469b"},
- {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08986dce1339bc932923e7d1232ce9881499a0e02925f7402fb7c982515419ef"},
- {file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93c0b12d3d3bc25af4ebbf38f9ee780a487e8bf6954c115b9f015822d3bb8e48"},
- {file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:764e71f22ab3b305e7f4c21f1a97e1526a25ebdd22513e251cf376760213da13"},
- {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f056bf21105c2515c32372bbc057f43eb02aae2fda61052e2f7622c801f0b4e2"},
- {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:69ab78f848845569401469da20df3e081e6b5a11cb086de3eed1d48f5ed57c95"},
- {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:86fddba590aad9208e2fa8b43b4c098bb0ec74f15718bb6a704e3c63e2cef3e9"},
- {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:684d7a212682996d21ca12ef3c17353c021fe9de6049e19ac8481ec35574a70f"},
- {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a03e02f48cd1abbd9f3b7e3586d97c8f7a9721c436f51a5245b3b9483044480b"},
- {file = "regex-2024.11.6-cp39-cp39-win32.whl", hash = "sha256:41758407fc32d5c3c5de163888068cfee69cb4c2be844e7ac517a52770f9af57"},
- {file = "regex-2024.11.6-cp39-cp39-win_amd64.whl", hash = "sha256:b2837718570f95dd41675328e111345f9b7095d821bac435aac173ac80b19983"},
- {file = "regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519"},
+ {file = "regex-2025.9.18-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:12296202480c201c98a84aecc4d210592b2f55e200a1d193235c4db92b9f6788"},
+ {file = "regex-2025.9.18-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:220381f1464a581f2ea988f2220cf2a67927adcef107d47d6897ba5a2f6d51a4"},
+ {file = "regex-2025.9.18-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:87f681bfca84ebd265278b5daa1dcb57f4db315da3b5d044add7c30c10442e61"},
+ {file = "regex-2025.9.18-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:34d674cbba70c9398074c8a1fcc1a79739d65d1105de2a3c695e2b05ea728251"},
+ {file = "regex-2025.9.18-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:385c9b769655cb65ea40b6eea6ff763cbb6d69b3ffef0b0db8208e1833d4e746"},
+ {file = "regex-2025.9.18-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8900b3208e022570ae34328712bef6696de0804c122933414014bae791437ab2"},
+ {file = "regex-2025.9.18-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c204e93bf32cd7a77151d44b05eb36f469d0898e3fba141c026a26b79d9914a0"},
+ {file = "regex-2025.9.18-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3acc471d1dd7e5ff82e6cacb3b286750decd949ecd4ae258696d04f019817ef8"},
+ {file = "regex-2025.9.18-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6479d5555122433728760e5f29edb4c2b79655a8deb681a141beb5c8a025baea"},
+ {file = "regex-2025.9.18-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:431bd2a8726b000eb6f12429c9b438a24062a535d06783a93d2bcbad3698f8a8"},
+ {file = "regex-2025.9.18-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0cc3521060162d02bd36927e20690129200e5ac9d2c6d32b70368870b122db25"},
+ {file = "regex-2025.9.18-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a021217b01be2d51632ce056d7a837d3fa37c543ede36e39d14063176a26ae29"},
+ {file = "regex-2025.9.18-cp310-cp310-win32.whl", hash = "sha256:4a12a06c268a629cb67cc1d009b7bb0be43e289d00d5111f86a2efd3b1949444"},
+ {file = "regex-2025.9.18-cp310-cp310-win_amd64.whl", hash = "sha256:47acd811589301298c49db2c56bde4f9308d6396da92daf99cba781fa74aa450"},
+ {file = "regex-2025.9.18-cp310-cp310-win_arm64.whl", hash = "sha256:16bd2944e77522275e5ee36f867e19995bcaa533dcb516753a26726ac7285442"},
+ {file = "regex-2025.9.18-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:51076980cd08cd13c88eb7365427ae27f0d94e7cebe9ceb2bb9ffdae8fc4d82a"},
+ {file = "regex-2025.9.18-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:828446870bd7dee4e0cbeed767f07961aa07f0ea3129f38b3ccecebc9742e0b8"},
+ {file = "regex-2025.9.18-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c28821d5637866479ec4cc23b8c990f5bc6dd24e5e4384ba4a11d38a526e1414"},
+ {file = "regex-2025.9.18-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:726177ade8e481db669e76bf99de0b278783be8acd11cef71165327abd1f170a"},
+ {file = "regex-2025.9.18-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f5cca697da89b9f8ea44115ce3130f6c54c22f541943ac8e9900461edc2b8bd4"},
+ {file = "regex-2025.9.18-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:dfbde38f38004703c35666a1e1c088b778e35d55348da2b7b278914491698d6a"},
+ {file = "regex-2025.9.18-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f2f422214a03fab16bfa495cfec72bee4aaa5731843b771860a471282f1bf74f"},
+ {file = "regex-2025.9.18-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a295916890f4df0902e4286bc7223ee7f9e925daa6dcdec4192364255b70561a"},
+ {file = "regex-2025.9.18-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:5db95ff632dbabc8c38c4e82bf545ab78d902e81160e6e455598014f0abe66b9"},
+ {file = "regex-2025.9.18-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fb967eb441b0f15ae610b7069bdb760b929f267efbf522e814bbbfffdf125ce2"},
+ {file = "regex-2025.9.18-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f04d2f20da4053d96c08f7fde6e1419b7ec9dbcee89c96e3d731fca77f411b95"},
+ {file = "regex-2025.9.18-cp311-cp311-win32.whl", hash = "sha256:895197241fccf18c0cea7550c80e75f185b8bd55b6924fcae269a1a92c614a07"},
+ {file = "regex-2025.9.18-cp311-cp311-win_amd64.whl", hash = "sha256:7e2b414deae99166e22c005e154a5513ac31493db178d8aec92b3269c9cce8c9"},
+ {file = "regex-2025.9.18-cp311-cp311-win_arm64.whl", hash = "sha256:fb137ec7c5c54f34a25ff9b31f6b7b0c2757be80176435bf367111e3f71d72df"},
+ {file = "regex-2025.9.18-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:436e1b31d7efd4dcd52091d076482031c611dde58bf9c46ca6d0a26e33053a7e"},
+ {file = "regex-2025.9.18-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c190af81e5576b9c5fdc708f781a52ff20f8b96386c6e2e0557a78402b029f4a"},
+ {file = "regex-2025.9.18-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e4121f1ce2b2b5eec4b397cc1b277686e577e658d8f5870b7eb2d726bd2300ab"},
+ {file = "regex-2025.9.18-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:300e25dbbf8299d87205e821a201057f2ef9aa3deb29caa01cd2cac669e508d5"},
+ {file = "regex-2025.9.18-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7b47fcf9f5316c0bdaf449e879407e1b9937a23c3b369135ca94ebc8d74b1742"},
+ {file = "regex-2025.9.18-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:57a161bd3acaa4b513220b49949b07e252165e6b6dc910ee7617a37ff4f5b425"},
+ {file = "regex-2025.9.18-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f130c3a7845ba42de42f380fff3c8aebe89a810747d91bcf56d40a069f15352"},
+ {file = "regex-2025.9.18-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5f96fa342b6f54dcba928dd452e8d8cb9f0d63e711d1721cd765bb9f73bb048d"},
+ {file = "regex-2025.9.18-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0f0d676522d68c207828dcd01fb6f214f63f238c283d9f01d85fc664c7c85b56"},
+ {file = "regex-2025.9.18-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:40532bff8a1a0621e7903ae57fce88feb2e8a9a9116d341701302c9302aef06e"},
+ {file = "regex-2025.9.18-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:039f11b618ce8d71a1c364fdee37da1012f5a3e79b1b2819a9f389cd82fd6282"},
+ {file = "regex-2025.9.18-cp312-cp312-win32.whl", hash = "sha256:e1dd06f981eb226edf87c55d523131ade7285137fbde837c34dc9d1bf309f459"},
+ {file = "regex-2025.9.18-cp312-cp312-win_amd64.whl", hash = "sha256:3d86b5247bf25fa3715e385aa9ff272c307e0636ce0c9595f64568b41f0a9c77"},
+ {file = "regex-2025.9.18-cp312-cp312-win_arm64.whl", hash = "sha256:032720248cbeeae6444c269b78cb15664458b7bb9ed02401d3da59fe4d68c3a5"},
+ {file = "regex-2025.9.18-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2a40f929cd907c7e8ac7566ac76225a77701a6221bca937bdb70d56cb61f57b2"},
+ {file = "regex-2025.9.18-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c90471671c2cdf914e58b6af62420ea9ecd06d1554d7474d50133ff26ae88feb"},
+ {file = "regex-2025.9.18-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a351aff9e07a2dabb5022ead6380cff17a4f10e4feb15f9100ee56c4d6d06af"},
+ {file = "regex-2025.9.18-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc4b8e9d16e20ddfe16430c23468a8707ccad3365b06d4536142e71823f3ca29"},
+ {file = "regex-2025.9.18-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4b8cdbddf2db1c5e80338ba2daa3cfa3dec73a46fff2a7dda087c8efbf12d62f"},
+ {file = "regex-2025.9.18-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a276937d9d75085b2c91fb48244349c6954f05ee97bba0963ce24a9d915b8b68"},
+ {file = "regex-2025.9.18-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92a8e375ccdc1256401c90e9dc02b8642894443d549ff5e25e36d7cf8a80c783"},
+ {file = "regex-2025.9.18-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0dc6893b1f502d73037cf807a321cdc9be29ef3d6219f7970f842475873712ac"},
+ {file = "regex-2025.9.18-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a61e85bfc63d232ac14b015af1261f826260c8deb19401c0597dbb87a864361e"},
+ {file = "regex-2025.9.18-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:1ef86a9ebc53f379d921fb9a7e42b92059ad3ee800fcd9e0fe6181090e9f6c23"},
+ {file = "regex-2025.9.18-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d3bc882119764ba3a119fbf2bd4f1b47bc56c1da5d42df4ed54ae1e8e66fdf8f"},
+ {file = "regex-2025.9.18-cp313-cp313-win32.whl", hash = "sha256:3810a65675845c3bdfa58c3c7d88624356dd6ee2fc186628295e0969005f928d"},
+ {file = "regex-2025.9.18-cp313-cp313-win_amd64.whl", hash = "sha256:16eaf74b3c4180ede88f620f299e474913ab6924d5c4b89b3833bc2345d83b3d"},
+ {file = "regex-2025.9.18-cp313-cp313-win_arm64.whl", hash = "sha256:4dc98ba7dd66bd1261927a9f49bd5ee2bcb3660f7962f1ec02617280fc00f5eb"},
+ {file = "regex-2025.9.18-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:fe5d50572bc885a0a799410a717c42b1a6b50e2f45872e2b40f4f288f9bce8a2"},
+ {file = "regex-2025.9.18-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b9d9a2d6cda6621551ca8cf7a06f103adf72831153f3c0d982386110870c4d3"},
+ {file = "regex-2025.9.18-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:13202e4c4ac0ef9a317fff817674b293c8f7e8c68d3190377d8d8b749f566e12"},
+ {file = "regex-2025.9.18-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:874ff523b0fecffb090f80ae53dc93538f8db954c8bb5505f05b7787ab3402a0"},
+ {file = "regex-2025.9.18-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d13ab0490128f2bb45d596f754148cd750411afc97e813e4b3a61cf278a23bb6"},
+ {file = "regex-2025.9.18-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:05440bc172bc4b4b37fb9667e796597419404dbba62e171e1f826d7d2a9ebcef"},
+ {file = "regex-2025.9.18-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5514b8e4031fdfaa3d27e92c75719cbe7f379e28cacd939807289bce76d0e35a"},
+ {file = "regex-2025.9.18-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:65d3c38c39efce73e0d9dc019697b39903ba25b1ad45ebbd730d2cf32741f40d"},
+ {file = "regex-2025.9.18-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ae77e447ebc144d5a26d50055c6ddba1d6ad4a865a560ec7200b8b06bc529368"},
+ {file = "regex-2025.9.18-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e3ef8cf53dc8df49d7e28a356cf824e3623764e9833348b655cfed4524ab8a90"},
+ {file = "regex-2025.9.18-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9feb29817df349c976da9a0debf775c5c33fc1c8ad7b9f025825da99374770b7"},
+ {file = "regex-2025.9.18-cp313-cp313t-win32.whl", hash = "sha256:168be0d2f9b9d13076940b1ed774f98595b4e3c7fc54584bba81b3cc4181742e"},
+ {file = "regex-2025.9.18-cp313-cp313t-win_amd64.whl", hash = "sha256:d59ecf3bb549e491c8104fea7313f3563c7b048e01287db0a90485734a70a730"},
+ {file = "regex-2025.9.18-cp313-cp313t-win_arm64.whl", hash = "sha256:dbef80defe9fb21310948a2595420b36c6d641d9bea4c991175829b2cc4bc06a"},
+ {file = "regex-2025.9.18-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:c6db75b51acf277997f3adcd0ad89045d856190d13359f15ab5dda21581d9129"},
+ {file = "regex-2025.9.18-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8f9698b6f6895d6db810e0bda5364f9ceb9e5b11328700a90cae573574f61eea"},
+ {file = "regex-2025.9.18-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:29cd86aa7cb13a37d0f0d7c21d8d949fe402ffa0ea697e635afedd97ab4b69f1"},
+ {file = "regex-2025.9.18-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7c9f285a071ee55cd9583ba24dde006e53e17780bb309baa8e4289cd472bcc47"},
+ {file = "regex-2025.9.18-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5adf266f730431e3be9021d3e5b8d5ee65e563fec2883ea8093944d21863b379"},
+ {file = "regex-2025.9.18-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1137cabc0f38807de79e28d3f6e3e3f2cc8cfb26bead754d02e6d1de5f679203"},
+ {file = "regex-2025.9.18-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7cc9e5525cada99699ca9223cce2d52e88c52a3d2a0e842bd53de5497c604164"},
+ {file = "regex-2025.9.18-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bbb9246568f72dce29bcd433517c2be22c7791784b223a810225af3b50d1aafb"},
+ {file = "regex-2025.9.18-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:6a52219a93dd3d92c675383efff6ae18c982e2d7651c792b1e6d121055808743"},
+ {file = "regex-2025.9.18-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:ae9b3840c5bd456780e3ddf2f737ab55a79b790f6409182012718a35c6d43282"},
+ {file = "regex-2025.9.18-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d488c236ac497c46a5ac2005a952c1a0e22a07be9f10c3e735bc7d1209a34773"},
+ {file = "regex-2025.9.18-cp314-cp314-win32.whl", hash = "sha256:0c3506682ea19beefe627a38872d8da65cc01ffa25ed3f2e422dffa1474f0788"},
+ {file = "regex-2025.9.18-cp314-cp314-win_amd64.whl", hash = "sha256:57929d0f92bebb2d1a83af372cd0ffba2263f13f376e19b1e4fa32aec4efddc3"},
+ {file = "regex-2025.9.18-cp314-cp314-win_arm64.whl", hash = "sha256:6a4b44df31d34fa51aa5c995d3aa3c999cec4d69b9bd414a8be51984d859f06d"},
+ {file = "regex-2025.9.18-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:b176326bcd544b5e9b17d6943f807697c0cb7351f6cfb45bf5637c95ff7e6306"},
+ {file = "regex-2025.9.18-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:0ffd9e230b826b15b369391bec167baed57c7ce39efc35835448618860995946"},
+ {file = "regex-2025.9.18-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ec46332c41add73f2b57e2f5b642f991f6b15e50e9f86285e08ffe3a512ac39f"},
+ {file = "regex-2025.9.18-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b80fa342ed1ea095168a3f116637bd1030d39c9ff38dc04e54ef7c521e01fc95"},
+ {file = "regex-2025.9.18-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f4d97071c0ba40f0cf2a93ed76e660654c399a0a04ab7d85472239460f3da84b"},
+ {file = "regex-2025.9.18-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0ac936537ad87cef9e0e66c5144484206c1354224ee811ab1519a32373e411f3"},
+ {file = "regex-2025.9.18-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dec57f96d4def58c422d212d414efe28218d58537b5445cf0c33afb1b4768571"},
+ {file = "regex-2025.9.18-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:48317233294648bf7cd068857f248e3a57222259a5304d32c7552e2284a1b2ad"},
+ {file = "regex-2025.9.18-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:274687e62ea3cf54846a9b25fc48a04459de50af30a7bd0b61a9e38015983494"},
+ {file = "regex-2025.9.18-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:a78722c86a3e7e6aadf9579e3b0ad78d955f2d1f1a8ca4f67d7ca258e8719d4b"},
+ {file = "regex-2025.9.18-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:06104cd203cdef3ade989a1c45b6215bf42f8b9dd705ecc220c173233f7cba41"},
+ {file = "regex-2025.9.18-cp314-cp314t-win32.whl", hash = "sha256:2e1eddc06eeaffd249c0adb6fafc19e2118e6308c60df9db27919e96b5656096"},
+ {file = "regex-2025.9.18-cp314-cp314t-win_amd64.whl", hash = "sha256:8620d247fb8c0683ade51217b459cb4a1081c0405a3072235ba43a40d355c09a"},
+ {file = "regex-2025.9.18-cp314-cp314t-win_arm64.whl", hash = "sha256:b7531a8ef61de2c647cdf68b3229b071e46ec326b3138b2180acb4275f470b01"},
+ {file = "regex-2025.9.18-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3dbcfcaa18e9480669030d07371713c10b4f1a41f791ffa5cb1a99f24e777f40"},
+ {file = "regex-2025.9.18-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1e85f73ef7095f0380208269055ae20524bfde3f27c5384126ddccf20382a638"},
+ {file = "regex-2025.9.18-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9098e29b3ea4ffffeade423f6779665e2a4f8db64e699c0ed737ef0db6ba7b12"},
+ {file = "regex-2025.9.18-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90b6b7a2d0f45b7ecaaee1aec6b362184d6596ba2092dd583ffba1b78dd0231c"},
+ {file = "regex-2025.9.18-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c81b892af4a38286101502eae7aec69f7cd749a893d9987a92776954f3943408"},
+ {file = "regex-2025.9.18-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3b524d010973f2e1929aeb635418d468d869a5f77b52084d9f74c272189c251d"},
+ {file = "regex-2025.9.18-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b498437c026a3d5d0be0020023ff76d70ae4d77118e92f6f26c9d0423452446"},
+ {file = "regex-2025.9.18-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0716e4d6e58853d83f6563f3cf25c281ff46cf7107e5f11879e32cb0b59797d9"},
+ {file = "regex-2025.9.18-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:065b6956749379d41db2625f880b637d4acc14c0a4de0d25d609a62850e96d36"},
+ {file = "regex-2025.9.18-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:d4a691494439287c08ddb9b5793da605ee80299dd31e95fa3f323fac3c33d9d4"},
+ {file = "regex-2025.9.18-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ef8d10cc0989565bcbe45fb4439f044594d5c2b8919d3d229ea2c4238f1d55b0"},
+ {file = "regex-2025.9.18-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:4baeb1b16735ac969a7eeecc216f1f8b7caf60431f38a2671ae601f716a32d25"},
+ {file = "regex-2025.9.18-cp39-cp39-win32.whl", hash = "sha256:8e5f41ad24a1e0b5dfcf4c4e5d9f5bd54c895feb5708dd0c1d0d35693b24d478"},
+ {file = "regex-2025.9.18-cp39-cp39-win_amd64.whl", hash = "sha256:50e8290707f2fb8e314ab3831e594da71e062f1d623b05266f8cfe4db4949afd"},
+ {file = "regex-2025.9.18-cp39-cp39-win_arm64.whl", hash = "sha256:039a9d7195fd88c943d7c777d4941e8ef736731947becce773c31a1009cb3c35"},
+ {file = "regex-2025.9.18.tar.gz", hash = "sha256:c5ba23274c61c6fef447ba6a39333297d0c247f53059dba0bca415cac511edc4"},
]
[[package]]
name = "requests"
-version = "2.32.4"
+version = "2.32.5"
description = "Python HTTP for Humans."
optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
groups = ["main"]
files = [
- {file = "requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c"},
- {file = "requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422"},
+ {file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"},
+ {file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"},
]
[package.dependencies]
@@ -1678,129 +1727,167 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
[[package]]
name = "rpds-py"
-version = "0.25.1"
+version = "0.27.1"
description = "Python bindings to Rust's persistent data structures (rpds)"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
- {file = "rpds_py-0.25.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:f4ad628b5174d5315761b67f212774a32f5bad5e61396d38108bd801c0a8f5d9"},
- {file = "rpds_py-0.25.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8c742af695f7525e559c16f1562cf2323db0e3f0fbdcabdf6865b095256b2d40"},
- {file = "rpds_py-0.25.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:605ffe7769e24b1800b4d024d24034405d9404f0bc2f55b6db3362cd34145a6f"},
- {file = "rpds_py-0.25.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ccc6f3ddef93243538be76f8e47045b4aad7a66a212cd3a0f23e34469473d36b"},
- {file = "rpds_py-0.25.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f70316f760174ca04492b5ab01be631a8ae30cadab1d1081035136ba12738cfa"},
- {file = "rpds_py-0.25.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1dafef8df605fdb46edcc0bf1573dea0d6d7b01ba87f85cd04dc855b2b4479e"},
- {file = "rpds_py-0.25.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0701942049095741a8aeb298a31b203e735d1c61f4423511d2b1a41dcd8a16da"},
- {file = "rpds_py-0.25.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e87798852ae0b37c88babb7f7bbbb3e3fecc562a1c340195b44c7e24d403e380"},
- {file = "rpds_py-0.25.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3bcce0edc1488906c2d4c75c94c70a0417e83920dd4c88fec1078c94843a6ce9"},
- {file = "rpds_py-0.25.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e2f6a2347d3440ae789505693a02836383426249d5293541cd712e07e7aecf54"},
- {file = "rpds_py-0.25.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4fd52d3455a0aa997734f3835cbc4c9f32571345143960e7d7ebfe7b5fbfa3b2"},
- {file = "rpds_py-0.25.1-cp310-cp310-win32.whl", hash = "sha256:3f0b1798cae2bbbc9b9db44ee068c556d4737911ad53a4e5093d09d04b3bbc24"},
- {file = "rpds_py-0.25.1-cp310-cp310-win_amd64.whl", hash = "sha256:3ebd879ab996537fc510a2be58c59915b5dd63bccb06d1ef514fee787e05984a"},
- {file = "rpds_py-0.25.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:5f048bbf18b1f9120685c6d6bb70cc1a52c8cc11bdd04e643d28d3be0baf666d"},
- {file = "rpds_py-0.25.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4fbb0dbba559959fcb5d0735a0f87cdbca9e95dac87982e9b95c0f8f7ad10255"},
- {file = "rpds_py-0.25.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4ca54b9cf9d80b4016a67a0193ebe0bcf29f6b0a96f09db942087e294d3d4c2"},
- {file = "rpds_py-0.25.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ee3e26eb83d39b886d2cb6e06ea701bba82ef30a0de044d34626ede51ec98b0"},
- {file = "rpds_py-0.25.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:89706d0683c73a26f76a5315d893c051324d771196ae8b13e6ffa1ffaf5e574f"},
- {file = "rpds_py-0.25.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2013ee878c76269c7b557a9a9c042335d732e89d482606990b70a839635feb7"},
- {file = "rpds_py-0.25.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45e484db65e5380804afbec784522de84fa95e6bb92ef1bd3325d33d13efaebd"},
- {file = "rpds_py-0.25.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:48d64155d02127c249695abb87d39f0faf410733428d499867606be138161d65"},
- {file = "rpds_py-0.25.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:048893e902132fd6548a2e661fb38bf4896a89eea95ac5816cf443524a85556f"},
- {file = "rpds_py-0.25.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0317177b1e8691ab5879f4f33f4b6dc55ad3b344399e23df2e499de7b10a548d"},
- {file = "rpds_py-0.25.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bffcf57826d77a4151962bf1701374e0fc87f536e56ec46f1abdd6a903354042"},
- {file = "rpds_py-0.25.1-cp311-cp311-win32.whl", hash = "sha256:cda776f1967cb304816173b30994faaf2fd5bcb37e73118a47964a02c348e1bc"},
- {file = "rpds_py-0.25.1-cp311-cp311-win_amd64.whl", hash = "sha256:dc3c1ff0abc91444cd20ec643d0f805df9a3661fcacf9c95000329f3ddf268a4"},
- {file = "rpds_py-0.25.1-cp311-cp311-win_arm64.whl", hash = "sha256:5a3ddb74b0985c4387719fc536faced33cadf2172769540c62e2a94b7b9be1c4"},
- {file = "rpds_py-0.25.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b5ffe453cde61f73fea9430223c81d29e2fbf412a6073951102146c84e19e34c"},
- {file = "rpds_py-0.25.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:115874ae5e2fdcfc16b2aedc95b5eef4aebe91b28e7e21951eda8a5dc0d3461b"},
- {file = "rpds_py-0.25.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a714bf6e5e81b0e570d01f56e0c89c6375101b8463999ead3a93a5d2a4af91fa"},
- {file = "rpds_py-0.25.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:35634369325906bcd01577da4c19e3b9541a15e99f31e91a02d010816b49bfda"},
- {file = "rpds_py-0.25.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d4cb2b3ddc16710548801c6fcc0cfcdeeff9dafbc983f77265877793f2660309"},
- {file = "rpds_py-0.25.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9ceca1cf097ed77e1a51f1dbc8d174d10cb5931c188a4505ff9f3e119dfe519b"},
- {file = "rpds_py-0.25.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c2cd1a4b0c2b8c5e31ffff50d09f39906fe351389ba143c195566056c13a7ea"},
- {file = "rpds_py-0.25.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1de336a4b164c9188cb23f3703adb74a7623ab32d20090d0e9bf499a2203ad65"},
- {file = "rpds_py-0.25.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9fca84a15333e925dd59ce01da0ffe2ffe0d6e5d29a9eeba2148916d1824948c"},
- {file = "rpds_py-0.25.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:88ec04afe0c59fa64e2f6ea0dd9657e04fc83e38de90f6de201954b4d4eb59bd"},
- {file = "rpds_py-0.25.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a8bd2f19e312ce3e1d2c635618e8a8d8132892bb746a7cf74780a489f0f6cdcb"},
- {file = "rpds_py-0.25.1-cp312-cp312-win32.whl", hash = "sha256:e5e2f7280d8d0d3ef06f3ec1b4fd598d386cc6f0721e54f09109a8132182fbfe"},
- {file = "rpds_py-0.25.1-cp312-cp312-win_amd64.whl", hash = "sha256:db58483f71c5db67d643857404da360dce3573031586034b7d59f245144cc192"},
- {file = "rpds_py-0.25.1-cp312-cp312-win_arm64.whl", hash = "sha256:6d50841c425d16faf3206ddbba44c21aa3310a0cebc3c1cdfc3e3f4f9f6f5728"},
- {file = "rpds_py-0.25.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:659d87430a8c8c704d52d094f5ba6fa72ef13b4d385b7e542a08fc240cb4a559"},
- {file = "rpds_py-0.25.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:68f6f060f0bbdfb0245267da014d3a6da9be127fe3e8cc4a68c6f833f8a23bb1"},
- {file = "rpds_py-0.25.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:083a9513a33e0b92cf6e7a6366036c6bb43ea595332c1ab5c8ae329e4bcc0a9c"},
- {file = "rpds_py-0.25.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:816568614ecb22b18a010c7a12559c19f6fe993526af88e95a76d5a60b8b75fb"},
- {file = "rpds_py-0.25.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c6564c0947a7f52e4792983f8e6cf9bac140438ebf81f527a21d944f2fd0a40"},
- {file = "rpds_py-0.25.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c4a128527fe415d73cf1f70a9a688d06130d5810be69f3b553bf7b45e8acf79"},
- {file = "rpds_py-0.25.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a49e1d7a4978ed554f095430b89ecc23f42014a50ac385eb0c4d163ce213c325"},
- {file = "rpds_py-0.25.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d74ec9bc0e2feb81d3f16946b005748119c0f52a153f6db6a29e8cd68636f295"},
- {file = "rpds_py-0.25.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3af5b4cc10fa41e5bc64e5c198a1b2d2864337f8fcbb9a67e747e34002ce812b"},
- {file = "rpds_py-0.25.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:79dc317a5f1c51fd9c6a0c4f48209c6b8526d0524a6904fc1076476e79b00f98"},
- {file = "rpds_py-0.25.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1521031351865e0181bc585147624d66b3b00a84109b57fcb7a779c3ec3772cd"},
- {file = "rpds_py-0.25.1-cp313-cp313-win32.whl", hash = "sha256:5d473be2b13600b93a5675d78f59e63b51b1ba2d0476893415dfbb5477e65b31"},
- {file = "rpds_py-0.25.1-cp313-cp313-win_amd64.whl", hash = "sha256:a7b74e92a3b212390bdce1d93da9f6488c3878c1d434c5e751cbc202c5e09500"},
- {file = "rpds_py-0.25.1-cp313-cp313-win_arm64.whl", hash = "sha256:dd326a81afe332ede08eb39ab75b301d5676802cdffd3a8f287a5f0b694dc3f5"},
- {file = "rpds_py-0.25.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:a58d1ed49a94d4183483a3ce0af22f20318d4a1434acee255d683ad90bf78129"},
- {file = "rpds_py-0.25.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f251bf23deb8332823aef1da169d5d89fa84c89f67bdfb566c49dea1fccfd50d"},
- {file = "rpds_py-0.25.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8dbd586bfa270c1103ece2109314dd423df1fa3d9719928b5d09e4840cec0d72"},
- {file = "rpds_py-0.25.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6d273f136e912aa101a9274c3145dcbddbe4bac560e77e6d5b3c9f6e0ed06d34"},
- {file = "rpds_py-0.25.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:666fa7b1bd0a3810a7f18f6d3a25ccd8866291fbbc3c9b912b917a6715874bb9"},
- {file = "rpds_py-0.25.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:921954d7fbf3fccc7de8f717799304b14b6d9a45bbeec5a8d7408ccbf531faf5"},
- {file = "rpds_py-0.25.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3d86373ff19ca0441ebeb696ef64cb58b8b5cbacffcda5a0ec2f3911732a194"},
- {file = "rpds_py-0.25.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c8980cde3bb8575e7c956a530f2c217c1d6aac453474bf3ea0f9c89868b531b6"},
- {file = "rpds_py-0.25.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8eb8c84ecea987a2523e057c0d950bcb3f789696c0499290b8d7b3107a719d78"},
- {file = "rpds_py-0.25.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:e43a005671a9ed5a650f3bc39e4dbccd6d4326b24fb5ea8be5f3a43a6f576c72"},
- {file = "rpds_py-0.25.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:58f77c60956501a4a627749a6dcb78dac522f249dd96b5c9f1c6af29bfacfb66"},
- {file = "rpds_py-0.25.1-cp313-cp313t-win32.whl", hash = "sha256:2cb9e5b5e26fc02c8a4345048cd9998c2aca7c2712bd1b36da0c72ee969a3523"},
- {file = "rpds_py-0.25.1-cp313-cp313t-win_amd64.whl", hash = "sha256:401ca1c4a20cc0510d3435d89c069fe0a9ae2ee6495135ac46bdd49ec0495763"},
- {file = "rpds_py-0.25.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:ce4c8e485a3c59593f1a6f683cf0ea5ab1c1dc94d11eea5619e4fb5228b40fbd"},
- {file = "rpds_py-0.25.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d8222acdb51a22929c3b2ddb236b69c59c72af4019d2cba961e2f9add9b6e634"},
- {file = "rpds_py-0.25.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4593c4eae9b27d22df41cde518b4b9e4464d139e4322e2127daa9b5b981b76be"},
- {file = "rpds_py-0.25.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd035756830c712b64725a76327ce80e82ed12ebab361d3a1cdc0f51ea21acb0"},
- {file = "rpds_py-0.25.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:114a07e85f32b125404f28f2ed0ba431685151c037a26032b213c882f26eb908"},
- {file = "rpds_py-0.25.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dec21e02e6cc932538b5203d3a8bd6aa1480c98c4914cb88eea064ecdbc6396a"},
- {file = "rpds_py-0.25.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:09eab132f41bf792c7a0ea1578e55df3f3e7f61888e340779b06050a9a3f16e9"},
- {file = "rpds_py-0.25.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c98f126c4fc697b84c423e387337d5b07e4a61e9feac494362a59fd7a2d9ed80"},
- {file = "rpds_py-0.25.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0e6a327af8ebf6baba1c10fadd04964c1965d375d318f4435d5f3f9651550f4a"},
- {file = "rpds_py-0.25.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:bc120d1132cff853ff617754196d0ac0ae63befe7c8498bd67731ba368abe451"},
- {file = "rpds_py-0.25.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:140f61d9bed7839446bdd44852e30195c8e520f81329b4201ceead4d64eb3a9f"},
- {file = "rpds_py-0.25.1-cp39-cp39-win32.whl", hash = "sha256:9c006f3aadeda131b438c3092124bd196b66312f0caa5823ef09585a669cf449"},
- {file = "rpds_py-0.25.1-cp39-cp39-win_amd64.whl", hash = "sha256:a61d0b2c7c9a0ae45732a77844917b427ff16ad5464b4d4f5e4adb955f582890"},
- {file = "rpds_py-0.25.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b24bf3cd93d5b6ecfbedec73b15f143596c88ee249fa98cefa9a9dc9d92c6f28"},
- {file = "rpds_py-0.25.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:0eb90e94f43e5085623932b68840b6f379f26db7b5c2e6bcef3179bd83c9330f"},
- {file = "rpds_py-0.25.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d50e4864498a9ab639d6d8854b25e80642bd362ff104312d9770b05d66e5fb13"},
- {file = "rpds_py-0.25.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7c9409b47ba0650544b0bb3c188243b83654dfe55dcc173a86832314e1a6a35d"},
- {file = "rpds_py-0.25.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:796ad874c89127c91970652a4ee8b00d56368b7e00d3477f4415fe78164c8000"},
- {file = "rpds_py-0.25.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:85608eb70a659bf4c1142b2781083d4b7c0c4e2c90eff11856a9754e965b2540"},
- {file = "rpds_py-0.25.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4feb9211d15d9160bc85fa72fed46432cdc143eb9cf6d5ca377335a921ac37b"},
- {file = "rpds_py-0.25.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ccfa689b9246c48947d31dd9d8b16d89a0ecc8e0e26ea5253068efb6c542b76e"},
- {file = "rpds_py-0.25.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:3c5b317ecbd8226887994852e85de562f7177add602514d4ac40f87de3ae45a8"},
- {file = "rpds_py-0.25.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:454601988aab2c6e8fd49e7634c65476b2b919647626208e376afcd22019eeb8"},
- {file = "rpds_py-0.25.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:1c0c434a53714358532d13539272db75a5ed9df75a4a090a753ac7173ec14e11"},
- {file = "rpds_py-0.25.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f73ce1512e04fbe2bc97836e89830d6b4314c171587a99688082d090f934d20a"},
- {file = "rpds_py-0.25.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ee86d81551ec68a5c25373c5643d343150cc54672b5e9a0cafc93c1870a53954"},
- {file = "rpds_py-0.25.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:89c24300cd4a8e4a51e55c31a8ff3918e6651b241ee8876a42cc2b2a078533ba"},
- {file = "rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:771c16060ff4e79584dc48902a91ba79fd93eade3aa3a12d6d2a4aadaf7d542b"},
- {file = "rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:785ffacd0ee61c3e60bdfde93baa6d7c10d86f15655bd706c89da08068dc5038"},
- {file = "rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2a40046a529cc15cef88ac5ab589f83f739e2d332cb4d7399072242400ed68c9"},
- {file = "rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:85fc223d9c76cabe5d0bff82214459189720dc135db45f9f66aa7cffbf9ff6c1"},
- {file = "rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0be9965f93c222fb9b4cc254235b3b2b215796c03ef5ee64f995b1b69af0762"},
- {file = "rpds_py-0.25.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8378fa4a940f3fb509c081e06cb7f7f2adae8cf46ef258b0e0ed7519facd573e"},
- {file = "rpds_py-0.25.1-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:33358883a4490287e67a2c391dfaea4d9359860281db3292b6886bf0be3d8692"},
- {file = "rpds_py-0.25.1-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1d1fadd539298e70cac2f2cb36f5b8a65f742b9b9f1014dd4ea1f7785e2470bf"},
- {file = "rpds_py-0.25.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:9a46c2fb2545e21181445515960006e85d22025bd2fe6db23e76daec6eb689fe"},
- {file = "rpds_py-0.25.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:50f2c501a89c9a5f4e454b126193c5495b9fb441a75b298c60591d8a2eb92e1b"},
- {file = "rpds_py-0.25.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7d779b325cc8238227c47fbc53964c8cc9a941d5dbae87aa007a1f08f2f77b23"},
- {file = "rpds_py-0.25.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:036ded36bedb727beeabc16dc1dad7cb154b3fa444e936a03b67a86dc6a5066e"},
- {file = "rpds_py-0.25.1-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:245550f5a1ac98504147cba96ffec8fabc22b610742e9150138e5d60774686d7"},
- {file = "rpds_py-0.25.1-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ff7c23ba0a88cb7b104281a99476cccadf29de2a0ef5ce864959a52675b1ca83"},
- {file = "rpds_py-0.25.1-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e37caa8cdb3b7cf24786451a0bdb853f6347b8b92005eeb64225ae1db54d1c2b"},
- {file = "rpds_py-0.25.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f2f48ab00181600ee266a095fe815134eb456163f7d6699f525dee471f312cf"},
- {file = "rpds_py-0.25.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9e5fc7484fa7dce57e25063b0ec9638ff02a908304f861d81ea49273e43838c1"},
- {file = "rpds_py-0.25.1-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:d3c10228d6cf6fe2b63d2e7985e94f6916fa46940df46b70449e9ff9297bd3d1"},
- {file = "rpds_py-0.25.1-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:5d9e40f32745db28c1ef7aad23f6fc458dc1e29945bd6781060f0d15628b8ddf"},
- {file = "rpds_py-0.25.1-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:35a8d1a24b5936b35c5003313bc177403d8bdef0f8b24f28b1c4a255f94ea992"},
- {file = "rpds_py-0.25.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:6099263f526efff9cf3883dfef505518730f7a7a93049b1d90d42e50a22b4793"},
- {file = "rpds_py-0.25.1.tar.gz", hash = "sha256:8960b6dac09b62dac26e75d7e2c4a22efb835d827a7278c34f72b2b84fa160e3"},
+ {file = "rpds_py-0.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:68afeec26d42ab3b47e541b272166a0b4400313946871cba3ed3a4fc0cab1cef"},
+ {file = "rpds_py-0.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74e5b2f7bb6fa38b1b10546d27acbacf2a022a8b5543efb06cfebc72a59c85be"},
+ {file = "rpds_py-0.27.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9024de74731df54546fab0bfbcdb49fae19159ecaecfc8f37c18d2c7e2c0bd61"},
+ {file = "rpds_py-0.27.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:31d3ebadefcd73b73928ed0b2fd696f7fefda8629229f81929ac9c1854d0cffb"},
+ {file = "rpds_py-0.27.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2e7f8f169d775dd9092a1743768d771f1d1300453ddfe6325ae3ab5332b4657"},
+ {file = "rpds_py-0.27.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d905d16f77eb6ab2e324e09bfa277b4c8e5e6b8a78a3e7ff8f3cdf773b4c013"},
+ {file = "rpds_py-0.27.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50c946f048209e6362e22576baea09193809f87687a95a8db24e5fbdb307b93a"},
+ {file = "rpds_py-0.27.1-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:3deab27804d65cd8289eb814c2c0e807c4b9d9916c9225e363cb0cf875eb67c1"},
+ {file = "rpds_py-0.27.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8b61097f7488de4be8244c89915da8ed212832ccf1e7c7753a25a394bf9b1f10"},
+ {file = "rpds_py-0.27.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8a3f29aba6e2d7d90528d3c792555a93497fe6538aa65eb675b44505be747808"},
+ {file = "rpds_py-0.27.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dd6cd0485b7d347304067153a6dc1d73f7d4fd995a396ef32a24d24b8ac63ac8"},
+ {file = "rpds_py-0.27.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6f4461bf931108c9fa226ffb0e257c1b18dc2d44cd72b125bec50ee0ab1248a9"},
+ {file = "rpds_py-0.27.1-cp310-cp310-win32.whl", hash = "sha256:ee5422d7fb21f6a00c1901bf6559c49fee13a5159d0288320737bbf6585bd3e4"},
+ {file = "rpds_py-0.27.1-cp310-cp310-win_amd64.whl", hash = "sha256:3e039aabf6d5f83c745d5f9a0a381d031e9ed871967c0a5c38d201aca41f3ba1"},
+ {file = "rpds_py-0.27.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:be898f271f851f68b318872ce6ebebbc62f303b654e43bf72683dbdc25b7c881"},
+ {file = "rpds_py-0.27.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:62ac3d4e3e07b58ee0ddecd71d6ce3b1637de2d373501412df395a0ec5f9beb5"},
+ {file = "rpds_py-0.27.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4708c5c0ceb2d034f9991623631d3d23cb16e65c83736ea020cdbe28d57c0a0e"},
+ {file = "rpds_py-0.27.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:abfa1171a9952d2e0002aba2ad3780820b00cc3d9c98c6630f2e93271501f66c"},
+ {file = "rpds_py-0.27.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b507d19f817ebaca79574b16eb2ae412e5c0835542c93fe9983f1e432aca195"},
+ {file = "rpds_py-0.27.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:168b025f8fd8d8d10957405f3fdcef3dc20f5982d398f90851f4abc58c566c52"},
+ {file = "rpds_py-0.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb56c6210ef77caa58e16e8c17d35c63fe3f5b60fd9ba9d424470c3400bcf9ed"},
+ {file = "rpds_py-0.27.1-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:d252f2d8ca0195faa707f8eb9368955760880b2b42a8ee16d382bf5dd807f89a"},
+ {file = "rpds_py-0.27.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6e5e54da1e74b91dbc7996b56640f79b195d5925c2b78efaa8c5d53e1d88edde"},
+ {file = "rpds_py-0.27.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ffce0481cc6e95e5b3f0a47ee17ffbd234399e6d532f394c8dce320c3b089c21"},
+ {file = "rpds_py-0.27.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a205fdfe55c90c2cd8e540ca9ceba65cbe6629b443bc05db1f590a3db8189ff9"},
+ {file = "rpds_py-0.27.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:689fb5200a749db0415b092972e8eba85847c23885c8543a8b0f5c009b1a5948"},
+ {file = "rpds_py-0.27.1-cp311-cp311-win32.whl", hash = "sha256:3182af66048c00a075010bc7f4860f33913528a4b6fc09094a6e7598e462fe39"},
+ {file = "rpds_py-0.27.1-cp311-cp311-win_amd64.whl", hash = "sha256:b4938466c6b257b2f5c4ff98acd8128ec36b5059e5c8f8372d79316b1c36bb15"},
+ {file = "rpds_py-0.27.1-cp311-cp311-win_arm64.whl", hash = "sha256:2f57af9b4d0793e53266ee4325535a31ba48e2f875da81a9177c9926dfa60746"},
+ {file = "rpds_py-0.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ae2775c1973e3c30316892737b91f9283f9908e3cc7625b9331271eaaed7dc90"},
+ {file = "rpds_py-0.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2643400120f55c8a96f7c9d858f7be0c88d383cd4653ae2cf0d0c88f668073e5"},
+ {file = "rpds_py-0.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16323f674c089b0360674a4abd28d5042947d54ba620f72514d69be4ff64845e"},
+ {file = "rpds_py-0.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9a1f4814b65eacac94a00fc9a526e3fdafd78e439469644032032d0d63de4881"},
+ {file = "rpds_py-0.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ba32c16b064267b22f1850a34051121d423b6f7338a12b9459550eb2096e7ec"},
+ {file = "rpds_py-0.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5c20f33fd10485b80f65e800bbe5f6785af510b9f4056c5a3c612ebc83ba6cb"},
+ {file = "rpds_py-0.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:466bfe65bd932da36ff279ddd92de56b042f2266d752719beb97b08526268ec5"},
+ {file = "rpds_py-0.27.1-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:41e532bbdcb57c92ba3be62c42e9f096431b4cf478da9bc3bc6ce5c38ab7ba7a"},
+ {file = "rpds_py-0.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f149826d742b406579466283769a8ea448eed82a789af0ed17b0cd5770433444"},
+ {file = "rpds_py-0.27.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:80c60cfb5310677bd67cb1e85a1e8eb52e12529545441b43e6f14d90b878775a"},
+ {file = "rpds_py-0.27.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7ee6521b9baf06085f62ba9c7a3e5becffbc32480d2f1b351559c001c38ce4c1"},
+ {file = "rpds_py-0.27.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a512c8263249a9d68cac08b05dd59d2b3f2061d99b322813cbcc14c3c7421998"},
+ {file = "rpds_py-0.27.1-cp312-cp312-win32.whl", hash = "sha256:819064fa048ba01b6dadc5116f3ac48610435ac9a0058bbde98e569f9e785c39"},
+ {file = "rpds_py-0.27.1-cp312-cp312-win_amd64.whl", hash = "sha256:d9199717881f13c32c4046a15f024971a3b78ad4ea029e8da6b86e5aa9cf4594"},
+ {file = "rpds_py-0.27.1-cp312-cp312-win_arm64.whl", hash = "sha256:33aa65b97826a0e885ef6e278fbd934e98cdcfed80b63946025f01e2f5b29502"},
+ {file = "rpds_py-0.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e4b9fcfbc021633863a37e92571d6f91851fa656f0180246e84cbd8b3f6b329b"},
+ {file = "rpds_py-0.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1441811a96eadca93c517d08df75de45e5ffe68aa3089924f963c782c4b898cf"},
+ {file = "rpds_py-0.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55266dafa22e672f5a4f65019015f90336ed31c6383bd53f5e7826d21a0e0b83"},
+ {file = "rpds_py-0.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d78827d7ac08627ea2c8e02c9e5b41180ea5ea1f747e9db0915e3adf36b62dcf"},
+ {file = "rpds_py-0.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae92443798a40a92dc5f0b01d8a7c93adde0c4dc965310a29ae7c64d72b9fad2"},
+ {file = "rpds_py-0.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c46c9dd2403b66a2a3b9720ec4b74d4ab49d4fabf9f03dfdce2d42af913fe8d0"},
+ {file = "rpds_py-0.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2efe4eb1d01b7f5f1939f4ef30ecea6c6b3521eec451fb93191bf84b2a522418"},
+ {file = "rpds_py-0.27.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:15d3b4d83582d10c601f481eca29c3f138d44c92187d197aff663a269197c02d"},
+ {file = "rpds_py-0.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4ed2e16abbc982a169d30d1a420274a709949e2cbdef119fe2ec9d870b42f274"},
+ {file = "rpds_py-0.27.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a75f305c9b013289121ec0f1181931975df78738cdf650093e6b86d74aa7d8dd"},
+ {file = "rpds_py-0.27.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:67ce7620704745881a3d4b0ada80ab4d99df390838839921f99e63c474f82cf2"},
+ {file = "rpds_py-0.27.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d992ac10eb86d9b6f369647b6a3f412fc0075cfd5d799530e84d335e440a002"},
+ {file = "rpds_py-0.27.1-cp313-cp313-win32.whl", hash = "sha256:4f75e4bd8ab8db624e02c8e2fc4063021b58becdbe6df793a8111d9343aec1e3"},
+ {file = "rpds_py-0.27.1-cp313-cp313-win_amd64.whl", hash = "sha256:f9025faafc62ed0b75a53e541895ca272815bec18abe2249ff6501c8f2e12b83"},
+ {file = "rpds_py-0.27.1-cp313-cp313-win_arm64.whl", hash = "sha256:ed10dc32829e7d222b7d3b93136d25a406ba9788f6a7ebf6809092da1f4d279d"},
+ {file = "rpds_py-0.27.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:92022bbbad0d4426e616815b16bc4127f83c9a74940e1ccf3cfe0b387aba0228"},
+ {file = "rpds_py-0.27.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:47162fdab9407ec3f160805ac3e154df042e577dd53341745fc7fb3f625e6d92"},
+ {file = "rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb89bec23fddc489e5d78b550a7b773557c9ab58b7946154a10a6f7a214a48b2"},
+ {file = "rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e48af21883ded2b3e9eb48cb7880ad8598b31ab752ff3be6457001d78f416723"},
+ {file = "rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f5b7bd8e219ed50299e58551a410b64daafb5017d54bbe822e003856f06a802"},
+ {file = "rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08f1e20bccf73b08d12d804d6e1c22ca5530e71659e6673bce31a6bb71c1e73f"},
+ {file = "rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dc5dceeaefcc96dc192e3a80bbe1d6c410c469e97bdd47494a7d930987f18b2"},
+ {file = "rpds_py-0.27.1-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:d76f9cc8665acdc0c9177043746775aa7babbf479b5520b78ae4002d889f5c21"},
+ {file = "rpds_py-0.27.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:134fae0e36022edad8290a6661edf40c023562964efea0cc0ec7f5d392d2aaef"},
+ {file = "rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb11a4f1b2b63337cfd3b4d110af778a59aae51c81d195768e353d8b52f88081"},
+ {file = "rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:13e608ac9f50a0ed4faec0e90ece76ae33b34c0e8656e3dceb9a7db994c692cd"},
+ {file = "rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dd2135527aa40f061350c3f8f89da2644de26cd73e4de458e79606384f4f68e7"},
+ {file = "rpds_py-0.27.1-cp313-cp313t-win32.whl", hash = "sha256:3020724ade63fe320a972e2ffd93b5623227e684315adce194941167fee02688"},
+ {file = "rpds_py-0.27.1-cp313-cp313t-win_amd64.whl", hash = "sha256:8ee50c3e41739886606388ba3ab3ee2aae9f35fb23f833091833255a31740797"},
+ {file = "rpds_py-0.27.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:acb9aafccaae278f449d9c713b64a9e68662e7799dbd5859e2c6b3c67b56d334"},
+ {file = "rpds_py-0.27.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b7fb801aa7f845ddf601c49630deeeccde7ce10065561d92729bfe81bd21fb33"},
+ {file = "rpds_py-0.27.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe0dd05afb46597b9a2e11c351e5e4283c741237e7f617ffb3252780cca9336a"},
+ {file = "rpds_py-0.27.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b6dfb0e058adb12d8b1d1b25f686e94ffa65d9995a5157afe99743bf7369d62b"},
+ {file = "rpds_py-0.27.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed090ccd235f6fa8bb5861684567f0a83e04f52dfc2e5c05f2e4b1309fcf85e7"},
+ {file = "rpds_py-0.27.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf876e79763eecf3e7356f157540d6a093cef395b65514f17a356f62af6cc136"},
+ {file = "rpds_py-0.27.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12ed005216a51b1d6e2b02a7bd31885fe317e45897de81d86dcce7d74618ffff"},
+ {file = "rpds_py-0.27.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:ee4308f409a40e50593c7e3bb8cbe0b4d4c66d1674a316324f0c2f5383b486f9"},
+ {file = "rpds_py-0.27.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b08d152555acf1f455154d498ca855618c1378ec810646fcd7c76416ac6dc60"},
+ {file = "rpds_py-0.27.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:dce51c828941973a5684d458214d3a36fcd28da3e1875d659388f4f9f12cc33e"},
+ {file = "rpds_py-0.27.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:c1476d6f29eb81aa4151c9a31219b03f1f798dc43d8af1250a870735516a1212"},
+ {file = "rpds_py-0.27.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3ce0cac322b0d69b63c9cdb895ee1b65805ec9ffad37639f291dd79467bee675"},
+ {file = "rpds_py-0.27.1-cp314-cp314-win32.whl", hash = "sha256:dfbfac137d2a3d0725758cd141f878bf4329ba25e34979797c89474a89a8a3a3"},
+ {file = "rpds_py-0.27.1-cp314-cp314-win_amd64.whl", hash = "sha256:a6e57b0abfe7cc513450fcf529eb486b6e4d3f8aee83e92eb5f1ef848218d456"},
+ {file = "rpds_py-0.27.1-cp314-cp314-win_arm64.whl", hash = "sha256:faf8d146f3d476abfee026c4ae3bdd9ca14236ae4e4c310cbd1cf75ba33d24a3"},
+ {file = "rpds_py-0.27.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:ba81d2b56b6d4911ce735aad0a1d4495e808b8ee4dc58715998741a26874e7c2"},
+ {file = "rpds_py-0.27.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:84f7d509870098de0e864cad0102711c1e24e9b1a50ee713b65928adb22269e4"},
+ {file = "rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e960fc78fecd1100539f14132425e1d5fe44ecb9239f8f27f079962021523e"},
+ {file = "rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:62f85b665cedab1a503747617393573995dac4600ff51869d69ad2f39eb5e817"},
+ {file = "rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fed467af29776f6556250c9ed85ea5a4dd121ab56a5f8b206e3e7a4c551e48ec"},
+ {file = "rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2729615f9d430af0ae6b36cf042cb55c0936408d543fb691e1a9e36648fd35a"},
+ {file = "rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b207d881a9aef7ba753d69c123a35d96ca7cb808056998f6b9e8747321f03b8"},
+ {file = "rpds_py-0.27.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:639fd5efec029f99b79ae47e5d7e00ad8a773da899b6309f6786ecaf22948c48"},
+ {file = "rpds_py-0.27.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fecc80cb2a90e28af8a9b366edacf33d7a91cbfe4c2c4544ea1246e949cfebeb"},
+ {file = "rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:42a89282d711711d0a62d6f57d81aa43a1368686c45bc1c46b7f079d55692734"},
+ {file = "rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:cf9931f14223de59551ab9d38ed18d92f14f055a5f78c1d8ad6493f735021bbb"},
+ {file = "rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f39f58a27cc6e59f432b568ed8429c7e1641324fbe38131de852cd77b2d534b0"},
+ {file = "rpds_py-0.27.1-cp314-cp314t-win32.whl", hash = "sha256:d5fa0ee122dc09e23607a28e6d7b150da16c662e66409bbe85230e4c85bb528a"},
+ {file = "rpds_py-0.27.1-cp314-cp314t-win_amd64.whl", hash = "sha256:6567d2bb951e21232c2f660c24cf3470bb96de56cdcb3f071a83feeaff8a2772"},
+ {file = "rpds_py-0.27.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c918c65ec2e42c2a78d19f18c553d77319119bf43aa9e2edf7fb78d624355527"},
+ {file = "rpds_py-0.27.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1fea2b1a922c47c51fd07d656324531adc787e415c8b116530a1d29c0516c62d"},
+ {file = "rpds_py-0.27.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbf94c58e8e0cd6b6f38d8de67acae41b3a515c26169366ab58bdca4a6883bb8"},
+ {file = "rpds_py-0.27.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c2a8fed130ce946d5c585eddc7c8eeef0051f58ac80a8ee43bd17835c144c2cc"},
+ {file = "rpds_py-0.27.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:037a2361db72ee98d829bc2c5b7cc55598ae0a5e0ec1823a56ea99374cfd73c1"},
+ {file = "rpds_py-0.27.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5281ed1cc1d49882f9997981c88df1a22e140ab41df19071222f7e5fc4e72125"},
+ {file = "rpds_py-0.27.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fd50659a069c15eef8aa3d64bbef0d69fd27bb4a50c9ab4f17f83a16cbf8905"},
+ {file = "rpds_py-0.27.1-cp39-cp39-manylinux_2_31_riscv64.whl", hash = "sha256:c4b676c4ae3921649a15d28ed10025548e9b561ded473aa413af749503c6737e"},
+ {file = "rpds_py-0.27.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:079bc583a26db831a985c5257797b2b5d3affb0386e7ff886256762f82113b5e"},
+ {file = "rpds_py-0.27.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4e44099bd522cba71a2c6b97f68e19f40e7d85399de899d66cdb67b32d7cb786"},
+ {file = "rpds_py-0.27.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e202e6d4188e53c6661af813b46c37ca2c45e497fc558bacc1a7630ec2695aec"},
+ {file = "rpds_py-0.27.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f41f814b8eaa48768d1bb551591f6ba45f87ac76899453e8ccd41dba1289b04b"},
+ {file = "rpds_py-0.27.1-cp39-cp39-win32.whl", hash = "sha256:9e71f5a087ead99563c11fdaceee83ee982fd39cf67601f4fd66cb386336ee52"},
+ {file = "rpds_py-0.27.1-cp39-cp39-win_amd64.whl", hash = "sha256:71108900c9c3c8590697244b9519017a400d9ba26a36c48381b3f64743a44aab"},
+ {file = "rpds_py-0.27.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7ba22cb9693df986033b91ae1d7a979bc399237d45fccf875b76f62bb9e52ddf"},
+ {file = "rpds_py-0.27.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5b640501be9288c77738b5492b3fd3abc4ba95c50c2e41273c8a1459f08298d3"},
+ {file = "rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb08b65b93e0c6dd70aac7f7890a9c0938d5ec71d5cb32d45cf844fb8ae47636"},
+ {file = "rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d7ff07d696a7a38152ebdb8212ca9e5baab56656749f3d6004b34ab726b550b8"},
+ {file = "rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fb7c72262deae25366e3b6c0c0ba46007967aea15d1eea746e44ddba8ec58dcc"},
+ {file = "rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b002cab05d6339716b03a4a3a2ce26737f6231d7b523f339fa061d53368c9d8"},
+ {file = "rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23f6b69d1c26c4704fec01311963a41d7de3ee0570a84ebde4d544e5a1859ffc"},
+ {file = "rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:530064db9146b247351f2a0250b8f00b289accea4596a033e94be2389977de71"},
+ {file = "rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7b90b0496570bd6b0321724a330d8b545827c4df2034b6ddfc5f5275f55da2ad"},
+ {file = "rpds_py-0.27.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:879b0e14a2da6a1102a3fc8af580fc1ead37e6d6692a781bd8c83da37429b5ab"},
+ {file = "rpds_py-0.27.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:0d807710df3b5faa66c731afa162ea29717ab3be17bdc15f90f2d9f183da4059"},
+ {file = "rpds_py-0.27.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:3adc388fc3afb6540aec081fa59e6e0d3908722771aa1e37ffe22b220a436f0b"},
+ {file = "rpds_py-0.27.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c796c0c1cc68cb08b0284db4229f5af76168172670c74908fdbd4b7d7f515819"},
+ {file = "rpds_py-0.27.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cdfe4bb2f9fe7458b7453ad3c33e726d6d1c7c0a72960bcc23800d77384e42df"},
+ {file = "rpds_py-0.27.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:8fabb8fd848a5f75a2324e4a84501ee3a5e3c78d8603f83475441866e60b94a3"},
+ {file = "rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eda8719d598f2f7f3e0f885cba8646644b55a187762bec091fa14a2b819746a9"},
+ {file = "rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c64d07e95606ec402a0a1c511fe003873fa6af630bda59bac77fac8b4318ebc"},
+ {file = "rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:93a2ed40de81bcff59aabebb626562d48332f3d028ca2036f1d23cbb52750be4"},
+ {file = "rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:387ce8c44ae94e0ec50532d9cb0edce17311024c9794eb196b90e1058aadeb66"},
+ {file = "rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aaf94f812c95b5e60ebaf8bfb1898a7d7cb9c1af5744d4a67fa47796e0465d4e"},
+ {file = "rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:4848ca84d6ded9b58e474dfdbad4b8bfb450344c0551ddc8d958bf4b36aa837c"},
+ {file = "rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2bde09cbcf2248b73c7c323be49b280180ff39fadcfe04e7b6f54a678d02a7cf"},
+ {file = "rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:94c44ee01fd21c9058f124d2d4f0c9dc7634bec93cd4b38eefc385dabe71acbf"},
+ {file = "rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:df8b74962e35c9249425d90144e721eed198e6555a0e22a563d29fe4486b51f6"},
+ {file = "rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:dc23e6820e3b40847e2f4a7726462ba0cf53089512abe9ee16318c366494c17a"},
+ {file = "rpds_py-0.27.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:aa8933159edc50be265ed22b401125c9eebff3171f570258854dbce3ecd55475"},
+ {file = "rpds_py-0.27.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a50431bf02583e21bf273c71b89d710e7a710ad5e39c725b14e685610555926f"},
+ {file = "rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78af06ddc7fe5cc0e967085a9115accee665fb912c22a3f54bad70cc65b05fe6"},
+ {file = "rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:70d0738ef8fee13c003b100c2fbd667ec4f133468109b3472d249231108283a3"},
+ {file = "rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2f6fd8a1cea5bbe599b6e78a6e5ee08db434fc8ffea51ff201c8765679698b3"},
+ {file = "rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8177002868d1426305bb5de1e138161c2ec9eb2d939be38291d7c431c4712df8"},
+ {file = "rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:008b839781d6c9bf3b6a8984d1d8e56f0ec46dc56df61fd669c49b58ae800400"},
+ {file = "rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:a55b9132bb1ade6c734ddd2759c8dc132aa63687d259e725221f106b83a0e485"},
+ {file = "rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a46fdec0083a26415f11d5f236b79fa1291c32aaa4a17684d82f7017a1f818b1"},
+ {file = "rpds_py-0.27.1-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:8a63b640a7845f2bdd232eb0d0a4a2dd939bcdd6c57e6bb134526487f3160ec5"},
+ {file = "rpds_py-0.27.1-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:7e32721e5d4922deaaf963469d795d5bde6093207c52fec719bd22e5d1bedbc4"},
+ {file = "rpds_py-0.27.1-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:2c426b99a068601b5f4623573df7a7c3d72e87533a2dd2253353a03e7502566c"},
+ {file = "rpds_py-0.27.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4fc9b7fe29478824361ead6e14e4f5aed570d477e06088826537e202d25fe859"},
+ {file = "rpds_py-0.27.1.tar.gz", hash = "sha256:26a1c73171d10b7acccbded82bf6a586ab8203601e565badc74bbbf8bc5a10f8"},
]
[[package]]
@@ -1860,14 +1947,14 @@ files = [
[[package]]
name = "soupsieve"
-version = "2.7"
+version = "2.8"
description = "A modern CSS selector implementation for Beautiful Soup."
optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
groups = ["main"]
files = [
- {file = "soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4"},
- {file = "soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a"},
+ {file = "soupsieve-2.8-py3-none-any.whl", hash = "sha256:0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c"},
+ {file = "soupsieve-2.8.tar.gz", hash = "sha256:e2dd4a40a628cb5f28f6d4b0db8800b8f581b65bb380b97de22ba5ca8d72572f"},
]
[[package]]
@@ -1901,48 +1988,6 @@ files = [
anyascii = "*"
pyahocorasick = "*"
-[[package]]
-name = "tomli"
-version = "2.2.1"
-description = "A lil' TOML parser"
-optional = false
-python-versions = ">=3.8"
-groups = ["main"]
-files = [
- {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"},
- {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"},
- {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"},
- {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"},
- {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"},
- {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"},
- {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"},
- {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"},
- {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"},
- {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"},
- {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"},
- {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"},
- {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"},
- {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"},
- {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"},
- {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"},
- {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"},
- {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"},
- {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"},
- {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"},
- {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"},
- {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"},
- {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"},
- {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"},
- {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"},
- {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"},
- {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"},
- {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"},
- {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"},
- {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"},
- {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"},
- {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"},
-]
-
[[package]]
name = "tqdm"
version = "4.67.1"
@@ -1994,19 +2039,18 @@ files = [
]
[package.dependencies]
-importlib_metadata = {version = ">=3.6", markers = "python_version < \"3.10\""}
typing_extensions = ">=4.14.0"
[[package]]
name = "typing-extensions"
-version = "4.14.0"
+version = "4.15.0"
description = "Backported and Experimental Type Hints for Python 3.9+"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
- {file = "typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af"},
- {file = "typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4"},
+ {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"},
+ {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"},
]
[[package]]
@@ -2023,21 +2067,21 @@ files = [
[[package]]
name = "urllib3"
-version = "2.5.0"
+version = "2.6.0"
description = "HTTP library with thread-safe connection pooling, file post, and more."
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
- {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"},
- {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"},
+ {file = "urllib3-2.6.0-py3-none-any.whl", hash = "sha256:c90f7a39f716c572c4e3e58509581ebd83f9b59cced005b7db7ad2d22b0db99f"},
+ {file = "urllib3-2.6.0.tar.gz", hash = "sha256:cb9bcef5a4b345d5da5d145dc3e30834f58e8018828cbc724d30b4cb7d4d49f1"},
]
[package.extras]
-brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""]
+brotli = ["brotli (>=1.2.0) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=1.2.0.0) ; platform_python_implementation != \"CPython\""]
h2 = ["h2 (>=4,<5)"]
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
-zstd = ["zstandard (>=0.18.0)"]
+zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""]
[[package]]
name = "validators"
@@ -2062,28 +2106,7 @@ testing = ["pytest (>=7.4.0)"]
tooling = ["black (>=23.7.0)", "pyright (>=1.1.325)", "ruff (>=0.0.287)"]
tooling-extras = ["pyaml (>=23.7.0)", "pypandoc-binary (>=1.11)", "pytest (>=7.4.0)"]
-[[package]]
-name = "zipp"
-version = "3.23.0"
-description = "Backport of pathlib-compatible object wrapper for zip files"
-optional = false
-python-versions = ">=3.9"
-groups = ["main"]
-markers = "python_version == \"3.9\""
-files = [
- {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"},
- {file = "zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166"},
-]
-
-[package.extras]
-check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""]
-cover = ["pytest-cov"]
-doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
-enabler = ["pytest-enabler (>=2.2)"]
-test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more_itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"]
-type = ["pytest-mypy"]
-
[metadata]
lock-version = "2.1"
-python-versions = ">=3.9,<=3.13"
-content-hash = "c5239f0416a42725c120463e60fb2e556d90812da2d11c5615027f5b4be512bb"
+python-versions = ">=3.11,<=3.13"
+content-hash = "838b86049ae7040bf96b8bee152cf278867dbb011a54c09daffea8b5c8da69f1"
diff --git a/pyproject.toml b/pyproject.toml
index bf4ea59..fecfd78 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,49 +1,48 @@
-[project]
+[tool.poetry]
name = "somef_core"
version = "0.0.1"
-description = "SOftware Metadata Extraction Framework (CORE): A tool for automatically extracting relevant software metadata from readme files and package files."
-authors = [
- { name = "Daniel Garijo", email = "daniel.garijo@upm.es" }
-]
+description = "SOftware Metadata Extraction Framework (CORE): A tool for automatically extracting relevant software metadata from a source code repository (README, package files, etc)."
+authors = ["Daniel Garijo "]
+readme = "README.md"
packages = [
{ include = "somef_core", from = "src" }
]
-requires-python = ">=3.9,<=3.13"
classifiers = [
"Programming Language :: Python :: 3.10",
"Operating System :: OS Independent",
"License :: OSI Approved :: MIT License",
"Topic :: Software Development :: Libraries :: Python Modules"
]
-urls = { homepage = "https://github.com/SciCodes/somef-core" }
-
-readme = "README.md"
-
-dependencies = [
- "bs4 >=0.0.1",
- "click >=8.1.7",
- "click-option-group >=0.5.6",
- "matplotlib >=3.8.2",
- "nltk >=3.9.0",
- "numpy >=1.26.3",
- "pandas >=2.1.4",
- "textblob >=0.17.1",
- "validators >=0.22.0",
- "scipy >=1.11.4",
- "inflect >=7.0.0",
- "contractions >=0.1.73",
- "chardet >=5.2.0",
- "pytest >=7.4.4",
- "bibtexparser >=1.4.1",
- "nbformat >=5.9.2",
- "markdown >=3.5.2",
- "requests >=2.31.0",
- "pyyaml >=6.0.2",
- "lxml >=5.1.0",
- "tomli >=2.0.1"
-]
+
+[tool.poetry.urls]
+homepage = "https://github.com/SciCodes/somef-core"
-[project.scripts]
+[tool.poetry.dependencies]
+ python = ">=3.11,<=3.13"
+ bs4 = "^0.0.1"
+ click = "^8.1.7"
+ click-option-group = "^0.5.6"
+ matplotlib = "^3.8.2"
+ nltk = "^3.9.0"
+ numpy = "^1.26.3"
+ pandas = "^2.1.4"
+ textblob = "^0.17.1"
+ validators = "^0.22.0"
+ scipy = "^1.11.4"
+ inflect = "^7.0.0"
+ contractions = "^0.1.73"
+ chardet = "^5.2.0"
+ pytest = "^8.0.0"
+ bibtexparser = "^1.4.1"
+ nbformat = "^5.9.2"
+ markdown = "^3.5.2"
+ markdown-it-py = "^3.0"
+ requests = "^2.31.0"
+ pyyaml = "^6.0.2"
+ lxml = "^5.1.0"
+
+
+[tool.poetry.scripts]
somef_core = "somef_core.__main__:cli"
[build-system]
diff --git a/pytest.ini b/pytest.ini
new file mode 100644
index 0000000..2f80c74
--- /dev/null
+++ b/pytest.ini
@@ -0,0 +1,7 @@
+[pytest]
+testpaths = somef/src/somef/test
+python_files = test_*.py
+python_classes = Test*
+python_functions = test_*
+
+norecursedirs = test_data .git .venv __pycache__ *.egg-info
diff --git a/src/somef_core/__init__.py b/src/somef_core/__init__.py
index 5f1d2cc..3f3e589 100644
--- a/src/somef_core/__init__.py
+++ b/src/somef_core/__init__.py
@@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
-__version__ = "0.0.1"
+__version__ = "0.9.13"
diff --git a/src/somef_core/__main__.py b/src/somef_core/__main__.py
index 7e20e9b..0b66683 100644
--- a/src/somef_core/__main__.py
+++ b/src/somef_core/__main__.py
@@ -33,10 +33,10 @@ def configure(auto, base_uri):
configuration.update_base_uri(base_uri)
else:
authorization = click.prompt("Authorization", default="")
- description = None
- invocation = None
- installation = None
- citation = None
+ description = click.prompt("Documentation classifier model file", default=configuration.default_description)
+ invocation = click.prompt("Invocation classifier model file", default=configuration.default_invocation)
+ installation = click.prompt("Installation classifier model file", default=configuration.default_installation)
+ citation = click.prompt("Citation classifier model file", default=configuration.default_citation)
base_uri = click.prompt("Base URI for RDF generation", default=base_uri)
# configuration.configure()
configuration.configure(authorization, description, invocation, installation, citation, base_uri)
@@ -114,6 +114,12 @@ def configure(auto, base_uri):
type=click.Path(),
help="Path to an output codemeta file"
)
+@optgroup.option(
+ "--google_codemeta_out",
+ "-gc",
+ type=click.Path(),
+ help="Path to an output Google-compliant codemeta file"
+)
@optgroup.option(
"--graph_out",
"-g",
@@ -157,8 +163,50 @@ def configure(auto, base_uri):
default=True,
help="""SOMEF will ignore the contents of all files within folders named test (True by default)"""
)
-def describe(**kwargs):
+@click.option(
+ "--requirements_all",
+ "-all",
+ is_flag=True,
+ default=False,
+ help="Export all detected requirements, including text and libraries (default)."
+)
+@click.option(
+ "--requirements_v",
+ "-v",
+ is_flag=True,
+ default=False,
+ help="Export only requirements from structured sources (pom.xml, requirements.txt, etc.)"
+)
+@click.option(
+ "--reconcile_authors",
+ "-ra",
+ is_flag=True,
+ default=False,
+ help="""SOMEF will extract additional information from certain files like CODEOWNERS, etc."""
+)
+@click.option(
+ "--branch",
+ "-b",
+ type=str,
+ default=None,
+ help="Branch of the repository to analyze. Overrides the default branch."
+)
+@click.option(
+ "--tag",
+ type=str,
+ default=None,
+ help="Tag of the repository to analyze. Incompatible with --branch"
+)
+def describe(requirements_v, requirements_all, **kwargs):
# import so missing packages get installed when appropriate
+ if requirements_v:
+ kwargs["requirements_mode"] = "v"
+ elif requirements_all:
+ kwargs["requirements_mode"] = "all"
+ else:
+ kwargs["requirements_mode"] = "all"
+
from . import somef_cli
somef_cli.run_cli(**kwargs)
- click.secho(f"Success", fg="green")
\ No newline at end of file
+ click.secho(f"Success", fg="green")
+
\ No newline at end of file
diff --git a/src/somef_core/configuration.py b/src/somef_core/configuration.py
index 177cf2e..d0d4004 100644
--- a/src/somef_core/configuration.py
+++ b/src/somef_core/configuration.py
@@ -3,8 +3,16 @@
import json
import sys
import logging
+
from .utils import constants
+path = Path(__file__).parent.absolute()
+default_description = os.path.join(str(path), "models", "description.p")
+default_invocation = os.path.join(str(path), "models", "invocation.p")
+default_installation = os.path.join(str(path), "models", "installation.p")
+default_citation = os.path.join(str(path), "models", "citation.p")
+
+
def get_configuration_file():
"""
Function that retrieves the configuration file
@@ -39,11 +47,12 @@ def update_base_uri(base_uri):
credentials_file.chmod(0o600)
json.dump(data, fh)
+
def configure(authorization="",
- description=None,
- invocation=None,
- installation=None,
- citation=None,
+ description=default_description,
+ invocation=default_invocation,
+ installation=default_installation,
+ citation=default_citation,
base_uri=constants.CONF_DEFAULT_BASE_URI):
""" Function to configure the main program"""
import nltk
diff --git a/src/somef_core/export/google_codemeta_export.py b/src/somef_core/export/google_codemeta_export.py
new file mode 100644
index 0000000..2cfcb80
--- /dev/null
+++ b/src/somef_core/export/google_codemeta_export.py
@@ -0,0 +1,171 @@
+import json
+import copy
+from . import json_export
+from ..utils import constants
+
+def save_google_codemeta_output(repo_data, output_path, pretty=False, requirements_mode="all"):
+ """
+ Generate a Google-compliant Codemeta JSON-LD file from repo_data.
+ """
+
+ # Generate base codemeta using SOMEF. It is basically a codemeta file with a few changes.
+ # This way we have the core in the codemeta export and changes about content go just in the first method.
+
+ json_export.save_codemeta_output(
+ repo_data,
+ output_path,
+ pretty=pretty,
+ requirements_mode=requirements_mode
+ )
+
+ # Load the generated codemeta
+ with open(output_path, "r") as f:
+ codemeta = json.load(f)
+
+ # Apply google-compliant transformations
+ codemeta = make_google_compliant(codemeta)
+
+ # Overwrite the same file (no tmp file)
+ with open(output_path, "w") as f:
+ if pretty:
+ json.dump(codemeta, f, indent=2, sort_keys=True)
+ else:
+ json.dump(codemeta, f)
+
+
+# SCHEMA_ORG_PROPERTIES = {
+# "@type",
+# "name",
+# "description",
+# "author",
+# "keywords",
+# "license",
+# "url",
+# "identifier",
+# "programmingLanguage",
+# "releaseNotes",
+# "releaseDate"
+# }
+
+def make_google_compliant(codemeta):
+ codemeta = copy.deepcopy(codemeta)
+
+ # context is different from codemeta
+ codemeta["@context"] = {
+ "@vocab": "https://schema.org/",
+ "codemeta": "https://w3id.org/codemeta/3.0/"
+ }
+
+ # Some categories must be in a @list even if only one value is present. Required order
+ for prop in ["author", "contributor", "editor"]:
+ if prop in codemeta:
+ codemeta[prop] = wrap_list(codemeta[prop])
+
+ # referencePublication.author is also a list
+ if "referencePublication" in codemeta:
+ for pub in codemeta["referencePublication"]:
+ if "author" in pub:
+ pub["author"] = wrap_list(pub["author"])
+
+ if isinstance(codemeta.get("softwareRequirements"), list):
+ codemeta["softwareRequirements"] = [
+ r for r in codemeta["softwareRequirements"] if isinstance(r, dict)
+ ]
+
+ if isinstance(codemeta.get("developmentStatus"), str):
+ codemeta["developmentStatus"] = codemeta["developmentStatus"].capitalize()
+
+ codemeta = prefix_all_codemeta_properties(codemeta)
+ for key, value in codemeta.items():
+ codemeta[key] = normalize_value(value, key)
+
+ # No mappings. No normalizations. No cleaning.
+ # The PR only requires @context and @list wrapping.
+
+ return codemeta
+
+
+# ------------------------------------------------------------
+# UTILITIES
+# ------------------------------------------------------------
+
+def wrap_list(value):
+ """
+ Always wrap lists in @list, even if only one element.
+ """
+ if isinstance(value, list):
+ return {"@list": value}
+ return value
+
+def prefix_all_codemeta_properties(codemeta):
+ """ Add codemeta: prefix to all properties that are NOT in Schema.org. """
+ new = {}
+
+ for key, value in codemeta.items():
+ if key in constants.SCHEMA_ORG_PROPERTIES or key == "@context":
+ new[key] = value
+ else:
+ new[f"codemeta:{key}"] = value
+
+ return new
+
+def normalize_value(value, key=None):
+ """
+ Minimal normalization:
+ - Only normalize keywords (CSV -> list)
+ - Only filter softwareRequirements (remove strings)
+ - Do NOT touch any other property
+ """
+
+ # keywords: convert CSV to list
+ if key == "keywords" and isinstance(value, str):
+ parts = [p.strip() for p in value.split(",") if p.strip()]
+ return parts
+
+ # softwareRequirements: keep only dicts
+ if key == "softwareRequirements" and isinstance(value, list):
+ return [v for v in value if isinstance(v, dict)]
+
+ return value
+
+
+# def apply_schemaorg_mappings(c):
+# """
+# Apply mappings from Codemeta to Schema.org for Google compliance.
+# """
+
+# # referencePublication -> citation
+# if "referencePublication" in c:
+# c["citation"] = c.pop("referencePublication")
+
+# # buildInstructions -> installUrl (if URL) or softwareHelp
+# if "buildInstructions" in c:
+# bi = c.pop("buildInstructions")
+# if isinstance(bi, list) and len(bi) == 1 and is_url(bi[0]):
+# c["installUrl"] = bi[0]
+# else:
+# c.setdefault("softwareHelp", [])
+# c["softwareHelp"].append({"@type": "CreativeWork", "text": bi})
+
+# # continuousIntegration -> softwareHelp
+# if "continuousIntegration" in c:
+# ci = c.pop("continuousIntegration")
+# c.setdefault("softwareHelp", [])
+# c["softwareHelp"].append({"@type": "CreativeWork", "url": ci})
+
+# # readme -> softwareHelp
+# if "readme" in c:
+# rd = c.pop("readme")
+# c.setdefault("softwareHelp", [])
+# c["softwareHelp"].append({"@type": "CreativeWork", "url": rd})
+
+# # developmentStatus normalization
+# if "developmentStatus" in c:
+# status = c["developmentStatus"]
+# if isinstance(status, str):
+# c["developmentStatus"] = status.capitalize()
+
+# return c
+
+
+
diff --git a/src/somef_core/export/json_export.py b/src/somef_core/export/json_export.py
index 7fed51d..b5bb205 100644
--- a/src/somef_core/export/json_export.py
+++ b/src/somef_core/export/json_export.py
@@ -5,6 +5,8 @@
from dateutil import parser as date_parser
from ..utils import constants
from ..regular_expressions import detect_license_spdx,extract_scholarly_article_natural, extract_scholarly_article_properties
+from typing import List, Dict
+from urllib.parse import urlparse, urlunparse
def save_json_output(repo_data, out_path, missing, pretty=False):
"""
@@ -31,7 +33,7 @@ def save_json_output(repo_data, out_path, missing, pretty=False):
json.dump(repo_data, output)
-def save_codemeta_output(repo_data, outfile, pretty=False):
+def save_codemeta_output(repo_data, outfile, pretty=False, requirements_mode='all'):
"""
Function that saves a Codemeta JSONLD file with a summary of the results
@@ -40,27 +42,12 @@ def save_codemeta_output(repo_data, outfile, pretty=False):
@param repo_data: JSON with the results to translate to Codemeta
@param outfile: path where to save the codemeta file
@param pretty: option to show the JSON results in a nice format
+ @param requriments_mode: option to show all requriments or just machine readable
"""
def format_date(date_string):
date_object = date_parser.parse(date_string)
return date_object.strftime("%Y-%m-%d")
- # latest_release = None
- # releases = data_path(["releases", "excerpt"])
- #
- # if releases is not None and len(releases) > 0:
- # latest_release = releases[0]
- # latest_pub_date = date_parser.parse(latest_release["datePublished"])
- # for index in range(1, len(releases)):
- # release = releases[index]
- # pub_date = date_parser.parse(release["datePublished"])
- #
- # if pub_date > latest_pub_date:
- # latest_release = release
- # latest_pub_date = pub_date
-
- # def release_path(path):
- # return DataGraph.resolve_path(latest_release, path)
code_repository = None
if constants.CAT_CODE_REPOSITORY in repo_data:
code_repository = repo_data[constants.CAT_CODE_REPOSITORY][0][constants.PROP_RESULT][constants.PROP_VALUE]
@@ -76,14 +63,45 @@ def format_date(date_string):
descriptions_text = []
if descriptions is not None:
- descriptions.sort(key=lambda x: (x[constants.PROP_CONFIDENCE] + (1 if x[constants.PROP_TECHNIQUE] == constants.GITHUB_API else 0)),
- reverse=True)
- descriptions_text = [x[constants.PROP_RESULT][constants.PROP_VALUE] for x in descriptions]
+
+ # priority descriptions: Codemeta, without codemeta but confidence 1, rest
+ codemeta_desc = [d for d in descriptions if d.get("technique") == "code_parser" and "codemeta.json" in d.get("source", "")]
+ github_desc = [d for d in descriptions if d.get("technique") == constants.GITHUB_API]
+ readme_desc = [d for d in descriptions if d.get("technique") not in ("code_parser", constants.GITHUB_API)]
+
+ if codemeta_desc:
+ # If codemeta just these
+ selected = codemeta_desc
+ elif github_desc:
+ # whitout codemeta, but we have descripciont with confidence 1
+ threshold_1 = [d for d in github_desc if d.get("confidence", 0) == 1]
+ selected = threshold_1 if threshold_1 else github_desc
+ else:
+ # Rest of descriptions
+ selected = sorted(readme_desc, key=lambda x: x.get("confidence", 0), reverse=True)[:1]
+
+ flat_descriptions = []
+ for d in selected:
+ value = d[constants.PROP_RESULT][constants.PROP_VALUE]
+ if isinstance(value, list):
+ for v in value:
+ if v not in flat_descriptions:
+ flat_descriptions.append(v)
+ else:
+ if value not in flat_descriptions:
+ flat_descriptions.append(value)
+
+ descriptions_text = flat_descriptions
+
+ # descriptions_text = [d[constants.PROP_RESULT][constants.PROP_VALUE] for d in selected]
+ # descriptions.sort(key=lambda x: (x[constants.PROP_CONFIDENCE] + (1 if x[constants.PROP_TECHNIQUE] == constants.GITHUB_API else 0)),
+ # reverse=True)
+ # descriptions_text = [x[constants.PROP_RESULT][constants.PROP_VALUE] for x in descriptions]
codemeta_output = {
"@context": "https://w3id.org/codemeta/3.0",
- "@type": "SoftwareSourceCode"
+ "@type": ["SoftwareSourceCode", "SoftwareApplication"]
}
if constants.CAT_LICENSE in repo_data:
# We mix the name of the license from github API with the URL of the file (if found)
@@ -121,27 +139,46 @@ def format_date(date_string):
l_result["url"] = l[constants.PROP_RESULT][constants.PROP_URL]
if constants.PROP_SPDX_ID in l[constants.PROP_RESULT].keys():
l_result["identifier"] = constants.SPDX_BASE + l[constants.PROP_RESULT][constants.PROP_SPDX_ID]
- l_result["spdx_id"] = l[constants.PROP_RESULT][constants.PROP_SPDX_ID]
+ # spdx_id does not exist in codemeta
+ # l_result["spdx_id"] = l[constants.PROP_RESULT][constants.PROP_SPDX_ID]
- codemeta_output["license"] = l_result
+ codemeta_output[constants.CAT_CODEMETA_LICENSE] = l_result
if code_repository is not None:
- codemeta_output["codeRepository"] = code_repository
- codemeta_output["issueTracker"] = code_repository + "/issues"
+ codemeta_output[constants.CAT_CODEMETA_CODEREPOSITORY] = code_repository
+ codemeta_output[constants.CAT_CODEMETA_ISSUETRACKER] = code_repository + "/issues"
if constants.CAT_DATE_CREATED in repo_data:
- codemeta_output["dateCreated"] = format_date(repo_data[constants.CAT_DATE_CREATED][0][constants.PROP_RESULT][constants.PROP_VALUE])
+ value = repo_data[constants.CAT_DATE_CREATED][0][constants.PROP_RESULT][constants.PROP_VALUE]
+ if value:
+ codemeta_output[constants.CAT_CODEMETA_DATECREATED] = format_date(value)
if constants.CAT_DATE_UPDATED in repo_data:
- codemeta_output["dateModified"] = format_date(repo_data[constants.CAT_DATE_UPDATED][0][constants.PROP_RESULT][constants.PROP_VALUE])
+ value = repo_data[constants.CAT_DATE_UPDATED][0][constants.PROP_RESULT][constants.PROP_VALUE]
+ if value:
+ codemeta_output[constants.CAT_CODEMETA_DATEMODIFIED] = format_date(value)
if constants.CAT_DOWNLOAD_URL in repo_data:
- codemeta_output["downloadUrl"] = repo_data[constants.CAT_DOWNLOAD_URL][0][constants.PROP_RESULT][constants.PROP_VALUE]
+ codemeta_output[constants.CAT_CODEMETA_DOWNLOADURL] = repo_data[constants.CAT_DOWNLOAD_URL][0][constants.PROP_RESULT][constants.PROP_VALUE]
if constants.CAT_NAME in repo_data:
- codemeta_output["name"] = repo_data[constants.CAT_NAME][0][constants.PROP_RESULT][constants.PROP_VALUE]
+ codemeta_output[constants.CAT_CODEMETA_NAME] = repo_data[constants.CAT_NAME][0][constants.PROP_RESULT][constants.PROP_VALUE]
if constants.CAT_LOGO in repo_data:
- codemeta_output["logo"] = repo_data[constants.CAT_LOGO][0][constants.PROP_RESULT][constants.PROP_VALUE]
+ codemeta_output[constants.CAT_CODEMETA_LOGO] = repo_data[constants.CAT_LOGO][0][constants.PROP_RESULT][constants.PROP_VALUE]
if constants.CAT_KEYWORDS in repo_data:
- codemeta_output["keywords"] = repo_data[constants.CAT_KEYWORDS][0][constants.PROP_RESULT][constants.PROP_VALUE]
+ # codemeta_output[constants.CAT_CODEMETA_KEYWORDS] = repo_data[constants.CAT_KEYWORDS][0][constants.PROP_RESULT][constants.PROP_VALUE]
+ codemeta_output[constants.CAT_CODEMETA_KEYWORDS] = []
+ for key in repo_data[constants.CAT_KEYWORDS]:
+ key_value = key[constants.PROP_RESULT][constants.PROP_VALUE]
+ if isinstance(key_value, str):
+ items = [s.strip() for s in key_value.split(",") if s.strip()]
+ elif isinstance(key_value, list):
+ items = key_value
+ else:
+ continue
+
+ for item in items:
+ if item not in codemeta_output[constants.CAT_CODEMETA_KEYWORDS]:
+ codemeta_output[constants.CAT_CODEMETA_KEYWORDS].append(item)
+
if constants.CAT_PROGRAMMING_LANGUAGES in repo_data:
# Calculate the total code size of all the programming languages
- codemeta_output["programmingLanguage"] = []
+ codemeta_output[constants.CAT_CODEMETA_PROGRAMMINGLANGUAGE] = []
language_data = repo_data.get(constants.CAT_PROGRAMMING_LANGUAGES, [])
total_size = 0
for item in language_data:
@@ -154,38 +191,71 @@ def format_date(date_string):
result = item.get(constants.PROP_RESULT, {})
size = result.get(constants.PROP_SIZE)
value = result.get(constants.PROP_VALUE)
- if value not in codemeta_output["programmingLanguage"]:
+ if value not in codemeta_output[constants.CAT_CODEMETA_PROGRAMMINGLANGUAGE]:
if size is not None and value is not None and total_size > 0:
percentage = (size / total_size) * 100
if percentage > constants.MINIMUM_PERCENTAGE_LANGUAGE_PROGRAMMING:
- codemeta_output["programmingLanguage"].append(value)
+ codemeta_output[constants.CAT_CODEMETA_PROGRAMMINGLANGUAGE].append(value)
elif size is None: # when size is not available, it comes from the parsers
- codemeta_output["programmingLanguage"].append(value)
+ codemeta_output[constants.CAT_CODEMETA_PROGRAMMINGLANGUAGE].append(value)
if constants.CAT_REQUIREMENTS in repo_data:
- # codemeta_output["softwareRequirements"] = [x[constants.PROP_RESULT][constants.PROP_VALUE] for x in repo_data[constants.CAT_REQUIREMENTS]]
- code_parser_requirements = [
- {
- "name": x[constants.PROP_RESULT].get(constants.PROP_NAME)
- if x[constants.PROP_RESULT].get(constants.PROP_NAME)
- else x[constants.PROP_RESULT].get(constants.PROP_VALUE),
- "version": x[constants.PROP_RESULT].get(constants.PROP_VERSION)
- }
- for x in repo_data[constants.CAT_REQUIREMENTS]
- if x.get(constants.PROP_TECHNIQUE) == constants.TECHNIQUE_CODE_CONFIG_PARSER
- ]
-
- other_requirements = [
- x[constants.PROP_RESULT][constants.PROP_VALUE]
- for x in repo_data[constants.CAT_REQUIREMENTS]
- if x.get(constants.PROP_TECHNIQUE) != constants.TECHNIQUE_CODE_CONFIG_PARSER
- ]
-
- codemeta_output["softwareRequirements"] = (
- code_parser_requirements if code_parser_requirements else other_requirements
- )
+ code_parser_requirements = []
+ seen_structured = set()
+ for x in repo_data[constants.CAT_REQUIREMENTS]:
+ if x.get(constants.PROP_TECHNIQUE) == constants.TECHNIQUE_CODE_CONFIG_PARSER:
+ source = x.get("source", "")
+ if any(src in source for src in constants.STRUCTURED_REQUIREMENTS_SOURCES):
+ name = x[constants.PROP_RESULT].get(constants.PROP_NAME) or x[constants.PROP_RESULT].get(constants.PROP_VALUE)
+ version = x[constants.PROP_RESULT].get(constants.PROP_VERSION)
+ # key = f"{name.strip()}|{version.strip() if version else ''}"
+
+ key = name.strip().lower()
+
+ if key not in seen_structured:
+ entry = {"name": name.strip()}
+ req_type = x[constants.PROP_RESULT].get("type")
+ if req_type:
+ entry["@type"] = map_requirement_type(req_type)
+
+ if version:
+ if isinstance(version, str):
+ entry["version"] = version.strip()
+ else:
+ entry["version"] = str(version)
+
+ code_parser_requirements.append(entry)
+ seen_structured.add(key)
+
+ other_requirements = []
+ seen_text = set()
+ for x in repo_data[constants.CAT_REQUIREMENTS]:
+ if not (
+ x.get(constants.PROP_TECHNIQUE) == constants.TECHNIQUE_CODE_CONFIG_PARSER
+ and x.get("source") is not None
+ and any(src in x["source"] for src in constants.STRUCTURED_REQUIREMENTS_SOURCES)
+ ):
+ result = x.get(constants.PROP_RESULT, {})
+ req_type = result.get("type", "")
+ if req_type in ("Url", "Text_excerpt"):
+ continue
+
+ value = result.get(constants.PROP_VALUE, "").strip().replace("\n", " ")
+ normalized = " ".join(value.split())
+ if normalized not in seen_text:
+ other_requirements.append(value)
+ seen_text.add(normalized)
+
+ if requirements_mode == "v":
+ codemeta_output[constants.CAT_CODEMETA_SOFTWAREREQUIREMENTS] = code_parser_requirements
+ else:
+ if code_parser_requirements:
+ codemeta_output[constants.CAT_CODEMETA_SOFTWAREREQUIREMENTS] = code_parser_requirements
+ else:
+ codemeta_output[constants.CAT_CODEMETA_SOFTWAREREQUIREMENTS] = other_requirements
+
if constants.CAT_CONTINUOUS_INTEGRATION in repo_data:
- codemeta_output["continuousIntegration"] = repo_data[constants.CAT_CONTINUOUS_INTEGRATION][0][constants.PROP_RESULT][constants.PROP_VALUE]
+ codemeta_output[constants.CAT_CODEMETA_CONTINUOUSINTEGRATION] = repo_data[constants.CAT_CONTINUOUS_INTEGRATION][0][constants.PROP_RESULT][constants.PROP_VALUE]
# if constants.CAT_WORKFLOWS in repo_data:
# codemeta_output["workflows"] = repo_data[constants.CAT_WORKFLOWS][0][constants.PROP_RESULT][constants.PROP_VALUE]
if constants.CAT_RELEASES in repo_data:
@@ -212,11 +282,11 @@ def format_date(date_string):
oldest_version = l[constants.PROP_RESULT].get(constants.PROP_TAG)
if latest_description is not None:
- codemeta_output["releaseNotes"] = latest_description
+ codemeta_output[constants.CAT_CODEMETA_RELEASENOTES] = latest_description
if latest_version is not None:
- codemeta_output["softwareVersion"] = latest_version
+ codemeta_output[constants.CAT_CODEMETA_SOFTWAREVERSION] = latest_version
if oldest_date is not None:
- codemeta_output["datePublished"] = format_date(oldest_date.isoformat())
+ codemeta_output[constants.CAT_CODEMETA_DATEPUBLISHED] = format_date(oldest_date.isoformat())
install_links = []
if constants.CAT_INSTALLATION in repo_data:
@@ -237,22 +307,89 @@ def format_date(date_string):
if len(install_links) > 0:
# remove duplicates and generate codemeta
install_links = list(set(install_links))
- codemeta_output["buildInstructions"] = install_links
+ codemeta_output[constants.CAT_CODEMETA_BUILDINSTRUCTIONS] = install_links
if constants.CAT_OWNER in repo_data:
# if user then person, otherwise organization
- type_aux = repo_data[constants.CAT_OWNER][0][constants.PROP_RESULT][constants.PROP_TYPE]
- if type_aux == "User":
- type_aux = "Person"
- codemeta_output["author"] = [
- {
- "@type": type_aux,
- "@id": "https://github.com/" + author_name
+ codemeta_authors = []
+ for owner in repo_data[constants.CAT_OWNER]:
+ result_owner = owner.get("result", {})
+ type_aux = repo_data[constants.CAT_OWNER][0][constants.PROP_RESULT][constants.PROP_TYPE]
+ if type_aux == "User":
+ type_aux = "Person"
+
+ author_obj = { "@type": type_aux }
+
+ if "name" in result_owner and result_owner[constants.PROP_AUTHOR_NAME]:
+ author_obj[constants.PROP_AUTHOR_NAME] = result_owner[constants.PROP_AUTHOR_NAME]
+ if "value" in result_owner and result_owner[constants.PROP_VALUE]:
+ author_obj[constants.PROP_IDENTIFIER] = result_owner[constants.PROP_VALUE]
+ author_obj["@id"] = "https://github.com/" + result_owner[constants.PROP_VALUE]
+ if "affiliation" in result_owner and result_owner[constants.PROP_AFFILIATION]:
+ author_obj[constants.PROP_AFFILIATION] = result_owner[constants.PROP_AFFILIATION]
+ if "email" in result_owner and result_owner[constants.PROP_EMAIL]:
+ author_obj[constants.PROP_EMAIL] = result_owner[constants.PROP_EMAIL]
+
+ codemeta_authors.append(author_obj)
+
+ if codemeta_authors:
+ codemeta_output[constants.CAT_CODEMETA_AUTHOR] = codemeta_authors
+
+ if constants.CAT_AUTHORS in repo_data:
+ if "author" not in codemeta_output:
+ codemeta_output[constants.CAT_CODEMETA_AUTHOR] = []
+
+ for author in repo_data[constants.CAT_AUTHORS]:
+ value_author = author[constants.PROP_RESULT].get(constants.PROP_VALUE)
+ name_author = author[constants.PROP_RESULT].get(constants.PROP_NAME)
+ if value_author and re.search(constants.REGEXP_LTD_INC, value_author, re.IGNORECASE):
+ type_author = "Organization"
+ else:
+ type_author = "Person"
+
+ author_l = {
+ "@type": type_author
}
- ]
+
+ if type_author == "Organization":
+ if name_author:
+ author_l['name'] = name_author
+ else:
+ family_name = None
+ given_name = None
+
+ if author[constants.PROP_RESULT].get('last_name'):
+ family_name = author[constants.PROP_RESULT].get('last_name')
+ author_l['familyName'] = family_name
+ if author[constants.PROP_RESULT].get('given_name'):
+ given_name = author[constants.PROP_RESULT].get('given_name')
+ author_l['givenName'] = given_name
+ if author[constants.PROP_RESULT].get('email'):
+ mail_author = author[constants.PROP_RESULT].get('email')
+ author_l['email'] = mail_author
+
+ if name_author:
+ author_l['name'] = name_author
+ else:
+ author_l['name'] = value_author
+
+ existing_authors = codemeta_output.get(constants.CAT_CODEMETA_AUTHOR, [])
+ existing = next((a for a in existing_authors if a.get("name") == author_l["name"]), None)
+
+ if existing:
+ for key, val in author_l.items():
+ if key not in existing or not existing[key]:
+ existing[key] = val
+ else:
+ codemeta_output[constants.CAT_CODEMETA_AUTHOR].append(author_l)
+ # if not any(a.get('name') == author_l['name'] for a in existing_authors):
+ # codemeta_output[constants.CAT_CODEMETA_AUTHOR].append(author_l)
+
+
if constants.CAT_CITATION in repo_data:
# url_cit = []
- codemeta_output["referencePublication"] = []
- scholarlyArticles = {}
+ codemeta_output[constants.CAT_CODEMETA_REFERENCEPUBLICATION] = []
+ all_reference_publications = []
+ # scholarlyArticles = {}
author_orcids = {}
for cit in repo_data[constants.CAT_CITATION]:
@@ -268,7 +405,6 @@ def format_date(date_string):
doi = yaml_content.get("doi") or preferred_citation.get("doi")
identifiers = yaml_content.get("identifiers", [])
url_citation = preferred_citation.get("url") or yaml_content.get("url")
-
identifier_url = next((id["value"] for id in identifiers if id["type"] == "url"), None)
identifier_doi = next((id["value"] for id in identifiers if id["type"] == "doi"), None)
@@ -329,14 +465,9 @@ def format_date(date_string):
if constants.PROP_DOI in cit[constants.PROP_RESULT].keys():
doi = cit[constants.PROP_RESULT][constants.PROP_DOI]
scholarlyArticle[constants.CAT_IDENTIFIER] = cit[constants.PROP_RESULT][constants.PROP_DOI]
- # elif constants.PROP_FORMAT in cit[constants.PROP_RESULT].keys() \
- # and cit[constants.PROP_RESULT][constants.PROP_FORMAT] == constants.FORMAT_CFF:
- # url_cit.append(cit[constants.PROP_SOURCE])
if constants.PROP_URL in cit[constants.PROP_RESULT].keys():
scholarlyArticle[constants.PROP_URL] = cit[constants.PROP_RESULT][constants.PROP_URL]
- # if constants.PROP_AUTHOR in cit[constants.PROP_RESULT].keys():
- # scholarlyArticle[constants.PROP_AUTHOR] = cit[constants.PROP_RESULT][constants.PROP_AUTHOR]
if constants.PROP_TITLE in cit[constants.PROP_RESULT].keys():
title = normalize_title(cit[constants.PROP_RESULT][constants.PROP_TITLE])
@@ -356,18 +487,12 @@ def format_date(date_string):
else:
scholarlyArticle = extract_scholarly_article_natural(cit[constants.PROP_RESULT][constants.PROP_VALUE], scholarlyArticle, 'CODEMETA')
- key = (doi, title)
+ all_reference_publications.append({
+ **scholarlyArticle,
+ "_source_format": "cff" if not is_bibtex else "bibtex"
+ })
- if key in scholarlyArticles:
- if is_bibtex:
- codemeta_output["referencePublication"].remove(scholarlyArticles[key])
- codemeta_output["referencePublication"].append(scholarlyArticle)
- scholarlyArticles[key] = scholarlyArticle
- else:
- codemeta_output["referencePublication"].append(scholarlyArticle)
- scholarlyArticles[key] = scholarlyArticle
-
- for article in codemeta_output["referencePublication"]:
+ for article in all_reference_publications:
if "author" in article:
for author in article["author"]:
family_name = author.get("familyName", "").strip()
@@ -376,21 +501,85 @@ def format_date(date_string):
if key and key in author_orcids:
author["@id"] = author_orcids[key]
+
+ codemeta_output[constants.CAT_CODEMETA_REFERENCEPUBLICATION] = deduplicate_publications(all_reference_publications)
if constants.CAT_STATUS in repo_data:
url_status = repo_data[constants.CAT_STATUS][0]['result'].get('value', '')
status = url_status.split('#')[-1] if '#' in url_status else None
if status:
- codemeta_output["developmentStatus"] = status
+ codemeta_output[constants.CAT_CODEMETA_DEVELOPMENTSTATUS] = status
+
if constants.CAT_IDENTIFIER in repo_data:
- codemeta_output["identifier"] = repo_data[constants.CAT_IDENTIFIER][0][constants.PROP_RESULT][constants.PROP_VALUE]
+ codemeta_output[constants.CAT_CODEMETA_IDENTIFIER] = []
+
+ for identifier in repo_data[constants.CAT_IDENTIFIER]:
+ value = identifier[constants.PROP_RESULT][constants.PROP_VALUE]
+ if value not in codemeta_output[constants.CAT_CODEMETA_IDENTIFIER]:
+ codemeta_output[constants.CAT_CODEMETA_IDENTIFIER].append(value)
+
+ if constants.CAT_HOMEPAGE in repo_data:
+
+ # example
+ # homepage_urls = {"http://foo.com", "https://foo.com", "http://bar.com"}
+ # Result--> filtered_urls = {"https://foo.com", "http://bar.com"}
+ # Prioritize https
+
+ homepage_urls = {hp[constants.PROP_RESULT][constants.PROP_VALUE].strip()
+ for hp in repo_data[constants.CAT_HOMEPAGE]}
+
+ filtered_urls = set()
+ roots_with_https = {url[len("https://"):] for url in homepage_urls if url.startswith("https://")}
+
+ for url in homepage_urls:
+ root = url.replace("http://", "").replace("https://", "")
+ if url.startswith("http://") and root in roots_with_https:
+ continue
+ filtered_urls.add(url)
+
+ codemeta_output[constants.CAT_CODEMETA_URL] = list(filtered_urls)
+
+ # codemeta_output["identifier"] = repo_data[constants.CAT_IDENTIFIER][0][constants.PROP_RESULT][constants.PROP_VALUE]
if constants.CAT_README_URL in repo_data:
- codemeta_output["readme"] = repo_data[constants.CAT_README_URL][0][constants.PROP_RESULT][constants.PROP_VALUE]
+ codemeta_output[constants.CAT_CODEMETA_README] = repo_data[constants.CAT_README_URL][0][constants.PROP_RESULT][constants.PROP_VALUE]
+
+ if constants.CAT_MAINTAINER in repo_data:
+ codemeta_maintainers = []
+ for maintainer in repo_data[constants.CAT_MAINTAINER]:
+ result_maint = maintainer.get("result", {})
+ maint_obj = { "@type": result_maint.get("type", "Person") }
+
+ if "name" in result_maint and result_maint[constants.PROP_AUTHOR_NAME]:
+ maint_obj[constants.PROP_AUTHOR_NAME] = result_maint[constants.PROP_AUTHOR_NAME]
+ if "username" in result_maint and result_maint[constants.PROP_USERNAME]:
+ maint_obj[constants.PROP_IDENTIFIER] = result_maint[constants.PROP_USERNAME]
+ if "email" in result_maint and result_maint[constants.PROP_EMAIL]:
+ maint_obj[constants.PROP_EMAIL] = result_maint[constants.PROP_EMAIL]
+
+ codemeta_maintainers.append(maint_obj)
+
+ if codemeta_maintainers:
+ codemeta_output[constants.CAT_CODEMETA_MAINTAINER] = codemeta_maintainers
+
+ if constants.CAT_RUNTIME_PLATFORM in repo_data:
+ runtimes = []
+
+ for runtime_entry in repo_data[constants.CAT_RUNTIME_PLATFORM]:
+ result = runtime_entry.get("result", {})
+ # name = result.get("name")
+ value = result.get("value")
+ if value:
+ # runtimes.append(f"{name} {version}")
+ runtimes.append(value)
+
+ if runtimes:
+ codemeta_output[constants.CAT_CODEMETA_RUNTIMEPLATFORM] = ", ".join(runtimes)
+
# if "contributors" in repo_data:
# codemeta_output["contributor"] = data_path(["contributors", "excerpt"])
# A person is expected, and we extract text at the moment
if descriptions_text:
- codemeta_output["description"] = descriptions_text
+ codemeta_output[constants.CAT_CODEMETA_DESCRIPTION] = descriptions_text
# if published_date != "":
# commenting this out because we cannot assume the last published date to be the published date
# codemeta_output["datePublished"] = published_date
@@ -408,10 +597,224 @@ def create_missing_fields(result):
The categories are added to the JSON results. This won't be added if you export TTL or Codemeta"""
missing = []
repo_data = result
- for c in constants.categories_files_header:
+ # for c in constants.categories_files_header:
+ for c in constants.all_categories:
if c not in repo_data:
missing.append(c)
return missing
-def normalize_title(title):
- return re.sub(r"\s+", " ", title.strip().lower()) if title else None
+# def normalize_title(title):
+# return re.sub(r"\s+", " ", title.strip().lower()) if title else None
+
+def normalize_title(title: str) -> str:
+ if not title:
+ return None
+ title = re.sub(r'[{}]', '', title)
+ title = re.sub(r'\s+', ' ', title)
+ return title.strip().lower()
+
+
+def deduplicate_publications(publications: List[Dict]) -> List[Dict]:
+ seen = {}
+ for pub in publications:
+ # doi = pub.get("identifier", "").lower().strip()
+ # doi = (pub.get("identifier") or "").lower().strip()
+ doi = extract_doi(pub.get("identifier") or "")
+ title = normalize_title(pub.get("name", ""))
+ key = (doi, title)
+
+ if key not in seen:
+ seen[key] = pub
+ else:
+ existing = seen[key]
+ existing_format = existing.get("_source_format", "")
+ new_format = pub.get("_source_format", "")
+ existing_url = (existing.get("url") or "").lower()
+ new_url = (pub.get("url") or "").lower()
+
+ # is_doi_url_existing = existing_url.startswith("https://doi.org/")
+ # is_doi_url_new = new_url.startswith("https://doi.org/")
+ doi_existing = extract_doi(existing_url)
+ # print(f'-----> DOI existing: {doi_existing}')
+ doi_new = extract_doi(new_url)
+ # print(f'-----> DOI existing: {doi_new}')
+ is_doi_url_existing = bool(doi_existing)
+ is_doi_url_new = bool(doi_new)
+
+ # Priority CFF
+ if existing_format != "cff" and new_format == "cff":
+ seen[key] = pub
+
+ # Priority DOI URL
+ elif existing_format == new_format:
+ if not is_doi_url_existing and is_doi_url_new:
+ seen[key] = pub
+
+ result = []
+ for pub in seen.values():
+ pub.pop("_source_format", None)
+ result.append(pub)
+
+ return result
+
+
+def extract_doi(url: str) -> str:
+ if not url:
+ return ""
+
+ match = re.search(constants.REGEXP_ALL_DOIS, url, re.IGNORECASE)
+ return match.group(0).lower() if match else ""
+
+
+def map_requirement_type(t):
+ """
+ Maps a free text requirement type to a Schema.org software (soft application, soft source....)
+ Keyword matching is used and unmatched values default to SoftwareApplication.
+ """
+ t = t.lower()
+
+ for key, mapped in constants.REQUIREMENT_ENTRIES_TYPE_MAP.items():
+ if key in t:
+ return mapped
+ # default
+ return constants.SCHEMA_SOFTWARE_APPLICATION
+
+
+"""
+This part of code implements the post processing and unification logic applied to the
+raw JSON extracted by SOMEF. Different extractors may produce duplicated or
+slightly divergent entries for the same underlying resource (e.g., documentation
+URLs, identifiers, authors). The functions below normalize values, detect
+equivalent items, and merge them while preserving all available information.
+
+Key ideas:
+- Canonicalize simple URL values to avoid redundant entries.
+- Never canonicalize structured objects (e.g., Release, Agent).
+- Merge complementary fields extracted by different techniques.
+- Combine techniques and sources without losing provenance.
+"""
+
+def canonicalize_value(value, value_type):
+ """Canonicalization for SOMEF:
+ - If URL points to a file (has extension), keep full path (no unification)
+ - Otherwise, unify to scheme://domain (documentation, badges, pages)
+ - Always remove query, fragment, trailing slash
+ """
+ if value_type == constants.RELEASE:
+ return value
+
+ if value_type == constants.URL:
+ parsed = urlparse(value)
+
+ # Remove query and fragment
+ path = parsed.path.rstrip('/')
+
+ # Detect if last segment has a file extension
+ last_segment = path.split('/')[-1]
+ if '.' in last_segment:
+ # It's a file → do NOT unify
+ clean_path = path
+ return urlunparse((parsed.scheme, parsed.netloc, clean_path, '', '', ''))
+
+ # It's a directory/page → unify to domain
+ return f"{parsed.scheme}://{parsed.netloc}"
+
+ if isinstance(value, str):
+ return value.strip()
+
+ return value
+
+
+def normalize_type(result):
+ value = result.get(constants.PROP_VALUE, "")
+ rtype = result.get(constants.PROP_TYPE, "")
+
+ # Only normalize if the object ONLY has type + value
+ # (i.e., it's a simple URL, not a structured object like Release)
+ if isinstance(value, str) and value.startswith("http"):
+ if set(result.keys()) <= {constants.PROP_TYPE, constants.PROP_VALUE}:
+ return constants.URL
+
+ return rtype
+
+
+def choose_more_general(a, b):
+ """
+ If both values are strings and one contains the other, return the shorter one.
+ Otherwise, return 'a'.
+ """
+ if isinstance(a, str) and isinstance(b, str):
+ if a in b:
+ return a
+ if b in a:
+ return b
+ return a
+
+
+def unify_results(repo_data: dict) -> dict:
+ """
+ Merge and normalize the raw extraction results produced by SOMEF.
+ Different extractors may return duplicated or
+ partially overlapping entries for the same underlying resource (urls, identifiers, authors...).
+ This function canonicalizes simple values, detects equivalent items
+ and merges them into a single unified entry while preserving all available information.
+ """
+ print("Unifying results...")
+ unified_data = {}
+
+ for category, items in repo_data.items():
+ if not isinstance(items, list):
+ unified_data[category] = items
+ continue
+
+ seen = {}
+
+ for item in items:
+ result = item.get(constants.PROP_RESULT, {})
+ normalized_type = normalize_type(result)
+ result[constants.PROP_TYPE] = normalized_type
+ value = result.get(constants.PROP_VALUE)
+ value_type = result.get(constants.PROP_TYPE)
+
+ canonical = canonicalize_value(value, value_type)
+
+ key = str(canonical)
+
+ if key in seen:
+ existing = seen[key]
+
+ # If types match, merge normally
+ existing[constants.PROP_RESULT][constants.PROP_VALUE] = choose_more_general(
+ existing[constants.PROP_RESULT][constants.PROP_VALUE], value
+ )
+
+ # merge other result fields because different techniques might have extracted different information
+ # (e.g., email in authors extracted by file exploration or code parser.
+ for field, new_val in result.items():
+ if field in (constants.PROP_VALUE, constants.PROP_TYPE):
+ continue
+ old_val = existing[constants.PROP_RESULT].get(field)
+ if old_val in (None, "", []):
+ existing[constants.PROP_RESULT][field] = new_val
+
+ # join techniques
+ t1 = existing.get("technique", [])
+ t2 = item.get("technique", [])
+ if not isinstance(t1, list): t1 = [t1]
+ if not isinstance(t2, list): t2 = [t2]
+ existing["technique"] = list(set(t1 + t2))
+
+ # join sources
+ s1 = existing.get("source", [])
+ s2 = item.get("source", [])
+ if s1 and not isinstance(s1, list): s1 = [s1]
+ if s2 and not isinstance(s2, list): s2 = [s2]
+ if s1 or s2:
+ existing["source"] = list(set(s1 + s2))
+
+ else:
+ seen[key] = item
+
+ unified_data[category] = list(seen.values())
+
+ return unified_data
diff --git a/src/somef_core/extract_software_type.py b/src/somef_core/extract_software_type.py
new file mode 100644
index 0000000..27aebea
--- /dev/null
+++ b/src/somef_core/extract_software_type.py
@@ -0,0 +1,329 @@
+import os
+from pathlib import Path
+import nbformat
+from nbformat.reader import NotJSONError
+from chardet import detect
+import re
+from .extract_workflows import is_file_workflow
+from .process_results import Result
+from .utils import constants
+# from .extract_ontologies import is_file_ontology
+
+import pdb
+
+
+def check_repository_type(path_repo, title, metadata_result: Result):
+ """ Function that adds the metadata result in the JSON
+ output depending on the software type or if the repository is not considered software"""
+
+ if check_static_websites(path_repo, metadata_result):
+ metadata_result.add_result(constants.CAT_TYPE,
+ {
+ constants.PROP_VALUE: 'static-website',
+ constants.PROP_TYPE: constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_HEURISTICS)
+ # elif check_ontologies(path_repo):
+ # metadata_result.add_result(constants.CAT_TYPE,
+ # {
+ # constants.PROP_VALUE: 'ontology',
+ # constants.PROP_TYPE: constants.STRING
+ # },
+ # 1,
+ # constants.TECHNIQUE_HEURISTICS)
+ elif check_notebooks(path_repo):
+ metadata_result.add_result(constants.CAT_TYPE,
+ {
+ constants.PROP_VALUE: 'notebook-application',
+ constants.PROP_TYPE: constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_HEURISTICS)
+ # commenting out this heuristic, as if a repository contains other files apart from workflows, it may not be a wf
+ # elif check_workflow(path_repo, title):
+ # metadata_result.add_result(constants.CAT_TYPE,
+ # {
+ # constants.PROP_VALUE: 'workflow',
+ # constants.PROP_TYPE: constants.STRING
+ # },
+ # 1,
+ # constants.TECHNIQUE_HEURISTICS)
+ elif check_command_line(path_repo):
+ """The 0.82 confidence result is from running the analysis on 300 repos and showing the precision
+ of the heuristic"""
+ metadata_result.add_result(constants.CAT_TYPE,
+ {
+ constants.PROP_VALUE: 'commandline-application',
+ constants.PROP_TYPE: constants.STRING
+ },
+ 0.82,
+ constants.TECHNIQUE_HEURISTICS)
+
+ elif check_extras(path_repo):
+ metadata_result.add_result(constants.CAT_TYPE,
+ {
+ constants.PROP_VALUE: 'non-software',
+ constants.PROP_TYPE: constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_HEURISTICS)
+ return metadata_result
+
+
+def check_notebooks(path_repo):
+ """Function which checks if the specified repository is a Notebook Application
+ depending on the extensions present and number of notebooks which contain code
+
+ The function checks for presence of more than one code_notebook inside the repo and checks against code
+ extensions present returning true if both conditions are fulfilled ."""
+ code_notebooks = 0
+ total_files = 0
+
+ bad_extensions = False
+ for root, dirs, files in os.walk(path_repo):
+ for file in files:
+ if file.endswith((".ipynb", ".rmd", '.Rmd')):
+ notebook_path = os.path.join(root, file)
+ try:
+ if file.endswith(".ipynb"):
+ if is_notebook_code(notebook_path):
+ code_notebooks += 1
+ elif file.endswith(".rmd") or file.endswith('.Rmd'):
+ if has_code_in_rmd(notebook_path):
+ code_notebooks += 1
+ except Exception as e:
+ print(f"Error reading notebook file {notebook_path}: {str(e)}")
+ pass
+ if file.endswith(constants.code_extensions):
+ bad_extensions = True
+ if code_notebooks > 1:
+ return not bad_extensions
+ return False
+
+
+# def check_ontologies(path_repo):
+# """Function which detects if repository is an Ontology based on files present
+# and the non-existence of code files"""
+# ontology = False
+# for root, dirs, files in os.walk(path_repo):
+# repo_relative_path = os.path.relpath(root, path_repo)
+# for file in files:
+# file_path = os.path.join(repo_relative_path, file)
+# # print(os.path.join(repo_relative_path,file_path))
+# if file.endswith(constants.code_extensions):
+# return False
+# elif file.endswith(constants.ontology_extensions):
+# if not ontology:
+# ontology = is_file_ontology(os.path.join(path_repo, file_path))
+# return ontology
+
+
+def check_command_line(path_repo):
+ """Function which detects if repository is a Commandline Application
+ based on README analysis of commandline arguments and implementations"""
+ pattern_commandline = r"(?i)command[-\s]?line"
+ pattern_cmd_arg = r"(?i)(explanation\s+of\s+)?arguments\b"
+ pattern_cmd_arg2 = r"(?i)-\w+:"
+ for dir_path, dir_names, filenames in os.walk(path_repo):
+ repo_relative_path = os.path.relpath(dir_path, path_repo)
+ for filename in filenames:
+ file_path = os.path.join(repo_relative_path, filename)
+ filename_no_ext = os.path.splitext(filename)[0]
+ if "README" == filename_no_ext.upper():
+ if repo_relative_path == ".":
+ try:
+ # print(os.path.join(dir_path, filename))
+ with open(os.path.join(dir_path, filename), "r", encoding="utf-8") as data_file:
+ data_file_text = data_file.read()
+ try:
+ cmd_match2 = re.search(pattern_commandline, data_file_text)
+ cmd_match3 = re.search(pattern_cmd_arg, data_file_text)
+ cmd_match4 = re.search(pattern_cmd_arg2, data_file_text)
+ if cmd_match2 or (cmd_match3 and cmd_match4):
+ return True
+ except:
+ return False
+ except:
+ pass
+
+ return False
+
+
+def check_extras(path_repo):
+ """Function which detects if a repository is non-software by checking against
+ software related files"""
+ for root, dirs, files in os.walk(path_repo):
+ for file in files:
+ notebook_path = os.path.join(root, file)
+ if file.endswith(constants.code_extensions) or file.endswith(constants.ontology_extensions):
+ return False
+ elif file.endswith(".ipynb"):
+ if is_notebook_code(notebook_path):
+ return False
+ elif file.endswith(".rmd") or file.endswith('.Rmd'):
+ if has_code_in_rmd(notebook_path):
+ return False
+ return True
+
+
+def check_static_websites(path_repo, repo_metadata: Result):
+ """Function that analyzes byte size of js,css,html languages and checks if
+ repository contains files not associated with static websites
+
+ The evaluation numbers were extracted from the analysis of 997 css repositories representing libraries nad 998 js repos containing libraries
+ with control repositories .
+
+ """
+ # print(path_repo)
+ nr_files = 0
+ web_files = 0
+ total_size = 0
+ js_size = 0
+ css_size = 0
+ html_file = 0
+ for root, dirs, files in os.walk(path_repo):
+ for file in files:
+ file_path = os.path.join(root, file)
+ if file.endswith(constants.code_extensions) or file.endswith(
+ constants.ontology_extensions) or file.lower() in (('bower.json', 'package.json')):
+ return False
+ elif file.endswith((".js", ".css", ".scss")):
+ web_files += 1
+ elif file.endswith(".html"):
+ html_file += 1
+ elif file.endswith(".ipynb"):
+ if is_notebook_code(file_path):
+ return False
+ elif file.endswith(".rmd") or file.endswith('.Rmd'):
+ if has_code_in_rmd(file_path):
+ return False
+ try:
+ languages = repo_metadata[constants.CAT_PROGRAMMING_LANGUAGES]
+ print(languages)
+ for language in languages:
+ language_name = language[constants.PROP_RESULT][constants.PROP_NAME]
+ print(language_name)
+ if language_name.lower() == "javascript":
+ js_size += language[constants.PROP_RESULT][constants.PROP_SIZE]
+ print(js_size)
+ elif language_name.lower() == "scss" or language_name.lower() == "css":
+ css_size += language[constants.PROP_RESULT][constants.PROP_SIZE]
+ total_size += language[constants.PROP_RESULT][constants.PROP_SIZE]
+ except Exception as e:
+ print(e)
+ if html_file > 0:
+ if js_size > 0 and css_size == 0:
+ if js_size / total_size < 0.91:
+ return True
+ elif js_size == 0 and css_size > 0:
+ if css_size / total_size < 0.798:
+ return True
+ return True
+
+ return False
+
+
+def check_workflow(repo_path, title):
+ """Function which checks inside text for presence of repository being a workflow and analysis of the
+ files inside to check if they are correct workflow files. Also checks for repositories with no information
+ the name of the files which might point to it being a workflow.
+ PARAMETERS:
+ @repo_path(path to the repository directory)
+ @title(title of the repository)
+ """
+ list = []
+ total_workflows = 0
+ good_workflows = 0
+ for root, dirs, files in os.walk(repo_path):
+ repo_relative_path = os.path.relpath(root, repo_path)
+ for file in files:
+ filename_no_ext = os.path.splitext(file)[0]
+ if "README" == filename_no_ext.upper():
+ if repo_relative_path == ".":
+ try:
+ with open(os.path.join(root, file), "r") as readme_file:
+ readme_contents = readme_file.read()
+
+ title_words = title.split()
+
+ pattern = r'([^.?!]*(?:\b|\W){}(?:\b|\W)[^.?!]*[.?!])'.format(
+ '|'.join(map(re.escape, title_words)))
+ sentences = re.findall(pattern, readme_contents, flags=re.IGNORECASE)
+ for sentence in sentences:
+ if re.search(rf'\b{title}\b', sentence, flags=re.IGNORECASE) and re.search(
+ r'\b(pipeline|workflow)\b', sentence, flags=re.IGNORECASE):
+ return True
+
+ pattern_md = r'##.*\b(workflow|pipeline)\b'
+ pattern_rst = r'^([^=\n]+(?:\n(?![-=]).*)*\b(workflow|pipeline)\b(?:\n(?![-=]).*)*)\n=+'
+
+ match_md = re.findall(pattern_md, readme_contents, re.IGNORECASE)
+ match_rst = re.findall(pattern_rst, readme_contents, re.MULTILINE | re.IGNORECASE)
+
+ if match_md or match_rst:
+ return True
+
+ except:
+ continue
+
+ if file.endswith(constants.workflow_extensions) or file == "Snakefile":
+ total_workflows += 1
+ file_path = os.path.join(root, file)
+ if is_file_workflow(file_path):
+ list.append(file_path)
+ good_workflows += 1
+ else:
+ continue
+ if check_name(file):
+ return True
+ if list != []:
+ return True
+ else:
+ return False
+
+
+def check_name(filename):
+ """Assisting function for check_workflow to look for specific named files"""
+ pattern1 = re.compile(r"pipeline", re.IGNORECASE)
+ pattern2 = re.compile(r"(model|example)", re.IGNORECASE)
+ programming_language_extensions = (
+ ".py", ".java", ".cpp", ".c", ".php", ".rb", ".js", ".html", ".css", ".go", ".swift", ".scala", ".pl", ".ipynb")
+ if pattern1.search(filename) and pattern2.search(filename):
+ if filename.endswith(programming_language_extensions):
+ return True
+ elif pattern1.search(filename) and not pattern2.search(filename):
+ return False
+ elif (not pattern1.search(filename) and not pattern2.search(filename)) or (
+ not pattern1.search(filename) and pattern2.search(filename)):
+ return False
+
+
+def is_notebook_code(file_path):
+ has_code = False
+ num_code_cells = 0
+ num_total_cells = 0
+ try:
+ nb = nbformat.read(file_path, as_version=4)
+
+ for cell in nb['cells']:
+ if cell['cell_type'] == 'code':
+ num_total_cells += 1
+ if cell['source'].strip():
+ num_code_cells += 1
+ has_code = True
+ return has_code
+ except NotJSONError:
+ return False
+ except Exception:
+ return False
+
+
+def has_code_in_rmd(file_path):
+ try:
+ with open(file_path, 'r') as file:
+ content = file.read()
+ if "```{r" in content or "```{python" in content or "```{bash" in content:
+ return True
+ except:
+ return False
diff --git a/src/somef_core/extract_workflows.py b/src/somef_core/extract_workflows.py
index a1426e9..f07037d 100644
--- a/src/somef_core/extract_workflows.py
+++ b/src/somef_core/extract_workflows.py
@@ -1,4 +1,5 @@
import re
+import logging
Galaxy_pattern = r"(?i)a[_\s-]?galaxy[_\s-]?workflow"
CWL_pattern = r"\bclass:\s*[Ww]orkflow\b"
@@ -9,6 +10,32 @@
GitLab_ContinuosIntegration_keywords = [r"script:", r"rules:", r"variables:", r"before_script:", r"after_script:", r"image:"]
+def is_file_workflow(file_path):
+ try:
+ with open(file_path, "rb") as file:
+ raw_data = file.read()
+
+ try:
+ content = raw_data.decode("utf-8")
+ except UnicodeDecodeError:
+ logging.warning(f"File {file_path} is not UTF-8 decodable. Skipping.")
+ return False
+
+ Galaxy_match = re.search(Galaxy_pattern, content)
+ CWL_match = re.search(CWL_pattern, content)
+ Workflow_match = re.search(Workflow_content_pattern, content)
+ Workflow_match_2 = re.search(workflow_pattern, content, re.IGNORECASE)
+ Nextflow_match = re.search(Nextflow_pattern, content)
+
+ if Galaxy_match or CWL_match or Workflow_match or Workflow_match_2 or Nextflow_match:
+ return True
+ else:
+ return False
+
+ except Exception as e:
+ logging.warning(f"Error reading file {file_path}: {e}")
+ return False
+
def is_file_continuous_integration_gitlab(file_path):
"""Detects if a file is a GitLab CI/CD pipeline."""
with open(file_path, 'r') as file:
diff --git a/src/somef_core/header_analysis.py b/src/somef_core/header_analysis.py
index 1545fdb..7c29ff6 100644
--- a/src/somef_core/header_analysis.py
+++ b/src/somef_core/header_analysis.py
@@ -9,9 +9,14 @@
from .process_results import Result
from .parser import mardown_parser
from .utils import constants
-
+from typing import Dict, Iterable, List, Tuple
+from functools import lru_cache
pd.options.mode.chained_assignment = None # default='warn'
+
+SIMILARITY_THRESHOLD = 0.8
+
+
# Define wordnet groups
group = dict()
@@ -81,36 +86,40 @@
Word("report").synsets[6]]
group.update({constants.CAT_SUPPORT: support})
+@lru_cache(maxsize=4096)
+def get_synsets(word: str):
+ """Cached access to WordNet synsets."""
+ return Word(word).synsets
-def extract_bash_code(text):
- """Function to detect code blocks"""
- split = text.split("```")
- output = []
- if len(split) >= 3:
- for index, value in enumerate(split):
- if index % 2 == 1:
- output.append(split[index])
- return output
+@lru_cache(maxsize=16384)
+def path_similarity_cached(sense1, sense2) -> float:
+ """Cached path similarity lookup."""
+ sim = sense1.path_similarity(sense2)
+ return sim if sim is not None else 0.0
-def extract_header_content(text):
- """Function designed to extract headers and contents of text and place it in a dataframe"""
- header = []
+def extract_bash_code(text: str) -> List[str]:
+ blocks = text.split("```")
+ return [blocks[i] for i in range(1, len(blocks), 2)]
+
+
+def extract_header_content(text: str) -> Tuple[pd.DataFrame, str | None]:
headers = mardown_parser.extract_headers(text)
- for key in headers.keys():
- if headers[key]:
- header.append(key)
+ header_list = [h for h, v in headers.items() if v]
+
content, none_header_content = mardown_parser.extract_content_per_header(text, headers)
- parent_headers = mardown_parser.extract_headers_parents(text)
- # into dataframe
- df = pd.DataFrame(columns=['Header', 'Content', 'ParentHeader'])
- dfs = [pd.DataFrame({'Header': [i], 'Content': [j], 'ParentHeader': [parent_headers.get(i, None)]}) for i, j in
- zip(header, content)]
- df = pd.concat(dfs, ignore_index=True)
- # for i, j in zip(header, content):
- # df = df.append({'Header': i, 'Content': j, 'ParentHeader': parent_headers[i]}, ignore_index=True)
- df['Content'].replace('', np.nan, inplace=True)
+ parents = mardown_parser.extract_headers_parents(text)
+
+ df = pd.DataFrame({
+ 'Header': header_list,
+ 'Content': content,
+ 'ParentHeader': [parents.get(h) for h in header_list],
+ })
+
+ # df['Content'].replace('', np.nan, inplace=True)
+ df['Content'] = df['Content'].replace('', np.nan)
df.dropna(subset=['Content'], inplace=True)
+
return df, none_header_content
@@ -136,23 +145,6 @@ def find_sim(wordlist, wd):
return 0
-def match_group(word_syn, group, threshold):
- """Function designed to match a word with a subgroup"""
- currmax = 0
- maxgroup = ""
- simvalues = dict()
- for sense in word_syn: # for a given sense of a word
- similarities = []
- for key, value in group.items(): # value has all the similar words
- path_sim = find_sim(value, sense)
- # print("Similarity is:",path_sim)
- if path_sim > threshold: # then append to the list
- if path_sim > currmax:
- maxgroup = key
- currmax = path_sim
- return maxgroup
-
-
def label_header(header):
"""Function designed to label a header with a subgroup"""
# remove punctuation
@@ -160,11 +152,12 @@ def label_header(header):
sentence = header_clean.strip().split(" ")
label = []
for s in sentence:
- synn = Word(s).synsets
+ synn = Word(s).synsets
if len(synn) > 0:
- bestgroup = match_group(synn, group, 0.8)
+ # bestgroup = match_group(synn, group, 0.8)
+ bestgroup = match_group(synn)
if bestgroup != "" and bestgroup not in label:
- label.append(bestgroup)
+ label.append(bestgroup)
return label
@@ -180,7 +173,8 @@ def label_parent_headers(parentHeaders):
for s in sentence:
synn = Word(s).synsets
if len(synn) > 0:
- bestgroup = match_group(synn, group, 0.8)
+ # bestgroup = match_group(synn, group, 0.8)
+ bestgroup = match_group(synn)
if bestgroup != "" and bestgroup not in label:
label.append(bestgroup)
return label
@@ -193,75 +187,251 @@ def clean_html(text):
return cleantext
-def extract_categories(repo_data, repository_metadata: Result):
+def max_similarity(word_synsets, group_synsets) -> float:
+ return max(
+ (path_similarity_cached(ws, gs) for ws in word_synsets for gs in group_synsets),
+ default=0.0,
+ )
+
+
+WORDNET_GROUPS: Dict[str, List] | None = None
+
+
+def get_groups() -> Dict[str, List]:
+ global WORDNET_GROUPS
+ if WORDNET_GROUPS is None:
+ WORDNET_GROUPS = build_wordnet_groups()
+ return WORDNET_GROUPS
+
+def match_group(word_synsets) -> str:
+ best_group = ""
+ best_score = 0.0
+
+ for key, synsets in get_groups().items():
+ score = max_similarity(word_synsets, synsets)
+ if score > SIMILARITY_THRESHOLD and score > best_score:
+ best_score = score
+ best_group = key
+
+ return best_group
+
+
+# def tokenize_header(text: str) -> Iterable[str]:
+# clean = text.translate(str.maketrans('', '', string.punctuation))
+# return clean.strip().split()
+def tokenize_header(text) -> Iterable[str]:
+ # if list transform to string
+ if isinstance(text, list):
+ text = " ".join(map(str, text))
+ elif text is None:
+ text = ""
+ clean = text.translate(str.maketrans('', '', string.punctuation))
+ return clean.strip().split()
+
+def label_text(text: str) -> List[str]:
+ labels: List[str] = []
+
+ if isinstance(text, list):
+ text = " ".join(text)
+ else:
+ text = str(text)
+
+ for token in tokenize_header(text):
+ synsets = get_synsets(token)
+ if synsets:
+ grp = match_group(synsets)
+ # Skip if the header matches a known false positive for this group
+
+ # if isinstance(text, list):
+ # text = " ".join(text)
+
+ if is_false_positive_header(text, grp):
+ # print(f"Skipping false positive header '{text}' for category '{grp}'")
+ continue
+ if grp and grp not in labels:
+ labels.append(grp)
+ return labels
+
+
+def is_false_positive_header(text: str, category: str) -> bool:
"""
- Function that adds category information extracted using header information
- Parameters
- ----------
- @param repo_data: data to use the header analysis
- @param repository_metadata: Result object with the results found so far in the repo
+ Checks if a header is a known false positive for a given category.
- Returns
- -------
- @return Result with the information added.
+ Prevents headers like 'Reference implementation' from being classified as
+ bibliographic citations (CAT_CITATION) while allowing legitimate ones like 'References'.
+
+ Args:
+ text (str): The header text to check.
+ category (str): The category being considered (e.g., CAT_CITATION).
+
+ Returns:
+ bool: True if the header is a known false positive for the category.
"""
+
+ text_lower = text.lower()
+
+ # false positives for bibliographic citations
+ if category == constants.CAT_CITATION:
+ for pattern in constants.NEGATIVE_PATTERNS_CITATION_HEADERS:
+ if pattern in text_lower:
+ return True
+ return False
+
+
+def extract_categories(repo_data: str, repository_metadata: Result) -> Tuple[Result, List[str]]:
logging.info("Extracting information using headers")
- if repo_data is None or repo_data == "" or len(repo_data) == 0:
+
+ if not repo_data:
return repository_metadata, []
+
try:
- data, none_header_content = extract_header_content(repo_data)
- logging.info('Labeling headers.')
- if data.empty:
+ df, none_header_content = extract_header_content(repo_data)
+
+ if df.empty:
logging.warning("File to analyze has no headers")
return repository_metadata, [repo_data]
- data['Group'] = data['Header'].apply(lambda row: label_header(row))
- data['GroupParent'] = data['ParentHeader'].apply(lambda row: label_parent_headers(row))
- for i in data.index:
- if len(data['Group'][i]) == 0 and len(data['GroupParent'][i]) > 0:
- data.at[i, 'Group'] = data['GroupParent'][i]
- data = data.drop(columns=['GroupParent'])
- if len(data['Group'].iloc[0]) == 0:
- data['Group'].iloc[0] = ['unknown']
- groups = data.apply(lambda x: pd.Series(x['Group']), axis=1).stack().reset_index(level=1, drop=True)
-
- groups.name = 'Group'
- data = data.drop('Group', axis=1).join(groups)
- if data['Group'].iloc[0] == 'unknown':
- data['Group'].iloc[0] = np.NaN
-
- # to json
- group = data.loc[(data['Group'] != 'None') & pd.notna(data['Group'])]
- group.rename(columns={'Content': constants.PROP_VALUE}, inplace=True)
- group.rename(columns={'Header': constants.PROP_ORIGINAL_HEADER}, inplace=True)
- group.rename(columns={'ParentHeader': constants.PROP_PARENT_HEADER}, inplace=True)
- for index, row in group.iterrows():
- source = ""
- if constants.CAT_README_URL in repository_metadata.results.keys():
- source = repository_metadata.results[constants.CAT_README_URL][0]
- source = source[constants.PROP_RESULT][constants.PROP_VALUE]
- parent_header = ""
- if row[constants.PROP_PARENT_HEADER] != "":
- parent_header = row.loc[constants.PROP_PARENT_HEADER]
+
+ df['Group'] = df['Header'].map(label_text)
+ df['ParentGroup'] = df['ParentHeader'].fillna('').map(label_text)
+
+ df.loc[df['Group'].str.len() == 0, 'Group'] = df['ParentGroup']
+ df.drop(columns=['ParentGroup'], inplace=True)
+
+ if not df.iloc[0]['Group']:
+ df.loc[df.index[0], 'Group'] = ['unknown']
+
+ df = df.explode('Group')
+ df.loc[df['Group'] == 'unknown', 'Group'] = np.nan
+
+ valid = df[df['Group'].notna()].copy()
+ valid = valid.rename(columns={
+ 'Content': constants.PROP_VALUE,
+ 'Header': constants.PROP_ORIGINAL_HEADER,
+ 'ParentHeader': constants.PROP_PARENT_HEADER,
+ })
+
+ source = None
+ if constants.CAT_README_URL in repository_metadata.results:
+ source = repository_metadata.results[constants.CAT_README_URL][0]
+ source = source[constants.PROP_RESULT][constants.PROP_VALUE]
+
+ logging.info("Extracting information using headers - iterating over valid entries")
+ logging.info("Valid rows: %s", len(valid))
+
+ for _, row in valid.iterrows():
result = {
- constants.PROP_VALUE: row.loc[constants.PROP_VALUE],
+ constants.PROP_VALUE: row[constants.PROP_VALUE],
constants.PROP_TYPE: constants.TEXT_EXCERPT,
- constants.PROP_ORIGINAL_HEADER: row.loc[constants.PROP_ORIGINAL_HEADER]
+ constants.PROP_ORIGINAL_HEADER: row[constants.PROP_ORIGINAL_HEADER],
}
- if parent_header != "" and len(parent_header) > 0:
- result[constants.PROP_PARENT_HEADER] = parent_header
- if source != "":
- repository_metadata.add_result(row.Group, result, 1, constants.TECHNIQUE_HEADER_ANALYSIS, source)
- else:
- repository_metadata.add_result(row.Group, result, 1, constants.TECHNIQUE_HEADER_ANALYSIS)
-
- # strings without tag (they will be classified)
- string_list = data.loc[data['Group'].isna(), ['Content']].values.squeeze().tolist()
- if type(string_list) != list:
- string_list = [string_list]
- if none_header_content is not None and none_header_content != "":
- string_list.append(none_header_content.strip())
- logging.info("Header information extracted.")
- return repository_metadata, string_list
- except Exception as e:
- logging.error("Error while extracting headers: ", str(e))
+
+ if row[constants.PROP_PARENT_HEADER]:
+ result[constants.PROP_PARENT_HEADER] = row[constants.PROP_PARENT_HEADER]
+
+ repository_metadata.add_result(
+ row['Group'],
+ result,
+ 1,
+ constants.TECHNIQUE_HEADER_ANALYSIS,
+ source,
+ )
+
+ leftovers = df[df['Group'].isna()]['Content'].tolist()
+ if none_header_content:
+ leftovers.append(none_header_content.strip())
+
+ return repository_metadata, leftovers
+
+ except Exception:
+ logging.exception("Error while extracting headers")
return repository_metadata, [repo_data]
+
+
+def build_wordnet_groups() -> Dict[str, List]:
+ """Build WordNet groups lazily."""
+ g: Dict[str, List] = {}
+
+ g[constants.CAT_CITATION] = [
+ Word("citation").synsets[3],
+ Word("reference").synsets[1],
+ Word("cite").synsets[3],
+ ]
+
+ g[constants.CAT_ACKNOWLEDGEMENT] = [Word("acknowledgement").synsets[0]]
+
+ g[constants.CAT_RUN] = [
+ Word("run").synsets[9],
+ Word("run").synsets[34],
+ Word("execute").synsets[4],
+ ]
+
+ g[constants.CAT_INSTALLATION] = [
+ Word("installation").synsets[0],
+ Word("install").synsets[0],
+ Word("setup").synsets[1],
+ Word("prepare").synsets[0],
+ Word("preparation").synsets[0],
+ Word("manual").synsets[0],
+ Word("guide").synsets[2],
+ Word("guide").synsets[9],
+ ]
+
+ g[constants.CAT_DOWNLOAD] = [Word("download").synsets[0]]
+
+ g[constants.CAT_REQUIREMENTS] = [
+ Word("requirement").synsets[2],
+ Word("prerequisite").synsets[0],
+ Word("prerequisite").synsets[1],
+ Word("dependency").synsets[0],
+ Word("dependent").synsets[0],
+ ]
+
+ g[constants.CAT_CONTACT] = [Word("contact").synsets[9]]
+
+ g[constants.CAT_DESCRIPTION] = [
+ Word("description").synsets[0],
+ Word("description").synsets[1],
+ Word("introduction").synsets[3],
+ Word("introduction").synsets[6],
+ Word("basics").synsets[0],
+ Word("initiation").synsets[1],
+ Word("summary").synsets[0],
+ Word("summary").synsets[2],
+ ]
+
+ g[constants.CAT_CONTRIBUTORS] = [Word("contributor").synsets[0]]
+ g[constants.CAT_CONTRIBUTING_GUIDELINES] = [Word("contributing").synsets[1]]
+ g[constants.CAT_DOCUMENTATION] = [Word("documentation").synsets[1]]
+ g[constants.CAT_LICENSE] = [Word("license").synsets[3], Word("license").synsets[0]]
+
+ g[constants.CAT_USAGE] = [
+ Word("usage").synsets[0],
+ Word("example").synsets[0],
+ Word("example").synsets[5],
+ Word("demo").synsets[1],
+ Word("tutorial").synsets[0],
+ Word("tutorial").synsets[1],
+ Word("start").synsets[0],
+ Word("start").synsets[4],
+ Word("started").synsets[0],
+ Word("started").synsets[1],
+ Word("started").synsets[7],
+ Word("started").synsets[8],
+ ]
+
+ g[constants.CAT_FAQ] = [
+ Word("errors").synsets[5],
+ Word("problems").synsets[0],
+ Word("problems").synsets[2],
+ Word("faq").synsets[0],
+ ]
+
+ g[constants.CAT_SUPPORT] = [
+ Word("support").synsets[7],
+ Word("help").synsets[0],
+ Word("help").synsets[9],
+ Word("report").synsets[0],
+ Word("report").synsets[6],
+ ]
+
+ return g
diff --git a/src/somef_core/parser/authors_parser.py b/src/somef_core/parser/authors_parser.py
new file mode 100644
index 0000000..0a8a2f1
--- /dev/null
+++ b/src/somef_core/parser/authors_parser.py
@@ -0,0 +1,81 @@
+import re
+from ..utils import constants
+
+def parse_author_file(author_str):
+ """
+ Process a text with possible authors from markdown-style AUTHORS files.
+ Filters and extracts only lines that match expected person or organization patterns.
+ """
+ if not author_str:
+ return []
+
+ author_str = re.sub(r'\{[^}]*\}', '', author_str, flags=re.DOTALL)
+
+ authors = []
+ lines = author_str.splitlines()
+
+ for i, line in enumerate(lines):
+ line = line.replace("\t", " ").strip()
+ next_line = lines[i + 1].strip() if i + 1 < len(lines) else ""
+ email_match = re.search(r'<[^<>@ ]+@[^<>@ ]+\.[^<>@ ]+>', line)
+
+ if (
+ not line or
+ line.startswith("#") or
+ line.startswith("*") or
+ line.startswith("=") or
+ (line[0].islower() and not email_match) or
+ re.match(r'^\s*\{.*\}\s*$', line) or
+ re.match(r'^https?://', line) or
+ len(re.findall(r'[A-Za-z]', line)) < 2 or
+ (
+ line.endswith(".")
+ and not re.match(r'^([A-Z][a-zA-Z0-9&\-\.]+(?:\s+[A-Z][a-zA-Z0-9&\-\.]+){0,3})\.$', line)
+ and not re.search(constants.REGEXP_LTD_INC, line, re.IGNORECASE)
+ ) or
+ line.endswith(",") or
+ line.endswith(":") or
+ re.match(r'^[A-Z][a-z]+ *:$', line) or
+ re.match(r"^[=\-*]{2,}$", next_line)
+ ):
+ continue
+
+ email_match = re.search(r'<(.*?)>', line)
+ email = email_match.group(1) if email_match else None
+ name_part = line[:email_match.start()].strip() if email_match else line
+
+ name_part = re.sub(r'\(.*?\)', '', name_part)
+
+ parts = name_part.split('-')
+ if len(parts) > 1 and parts[0].strip() == "":
+ name_part = parts[1].strip()
+ else:
+ name_part = parts[0].strip()
+
+ name_words = name_part.split()
+
+ if re.search(constants.REGEXP_LTD_INC, name_part, re.IGNORECASE):
+ raw_author = {
+ "type": "Organization",
+ "name": name_part,
+ "value": name_part
+ }
+ if email:
+ raw_author["email"] = email
+ authors.append(raw_author)
+ continue
+
+ if email or len(name_words) >= 2 and len(name_words) <= 4:
+ raw_author = {
+ "type": "Person",
+ "name": name_part,
+ "value": name_part,
+ "given_name": name_words[0]
+ }
+ if len(name_words) > 1:
+ raw_author["last_name"] = " ".join(name_words[1:])
+ if email:
+ raw_author["email"] = email
+ authors.append(raw_author)
+
+ return authors
\ No newline at end of file
diff --git a/src/somef_core/parser/bower_parser.py b/src/somef_core/parser/bower_parser.py
new file mode 100644
index 0000000..039592e
--- /dev/null
+++ b/src/somef_core/parser/bower_parser.py
@@ -0,0 +1,184 @@
+import json
+import logging
+import os
+from pathlib import Path
+from ..process_results import Result
+from ..utils import constants
+from ..regular_expressions import detect_license_spdx
+
+def parse_bower_json_file(file_path, metadata_result: Result, source):
+ """
+
+ Parameters
+ ----------
+ file_path: path of the bower file being analysed
+ metadata_result: metadata object where the metadata dictionary is kept
+ source: source of the package file (URL)
+
+ Returns
+ -------
+
+ """
+ try:
+ if Path(file_path).name.lower() in ["bower.json"]:
+ metadata_result.add_result(
+ constants.CAT_HAS_PACKAGE_FILE,
+ {
+ # "value": "bower.json",
+ "value": source,
+ "type": constants.URL,
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ with open(file_path, 'r', encoding='utf-8') as file:
+ data = json.load(file)
+
+ if "name" in data:
+ metadata_result.add_result(
+ constants.CAT_NAME,
+ {
+ "value": data["name"],
+ "type": constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ if "description" in data:
+ metadata_result.add_result(
+ constants.CAT_DESCRIPTION,
+ {
+ "value": data["description"],
+ "type": constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ if "homepage" in data:
+ home = data["homepage"]
+ if home and home.strip():
+ metadata_result.add_result(
+ constants.CAT_HOMEPAGE,
+ {
+ "value": data["homepage"],
+ "type": constants.URL
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ if "version" in data:
+ metadata_result.add_result(
+ constants.CAT_VERSION,
+ {
+ "value": data["version"],
+ "type": constants.RELEASE,
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ if "authors" in data:
+ authors_data = data["authors"]
+ if isinstance(authors_data, str):
+ authors_list = [a.strip() for a in authors_data.split(",") if a.strip()]
+ elif isinstance(authors_data, list):
+ authors_list = []
+ for a in authors_data:
+ if isinstance(a, str):
+ authors_list.extend([s.strip() for s in a.split(",") if s.strip()])
+ elif isinstance(a, dict):
+ name = a.get("name", "")
+ email = f" <{a['email']}>" if "email" in a else ""
+ authors_list.append(f"{name}{email}".strip())
+ else:
+ authors_list = []
+
+ for aut in authors_list:
+ # "value": data["authors"],
+ metadata_result.add_result(
+ constants.CAT_AUTHORS,
+ {
+ "value": aut,
+ "type": constants.AGENT
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ if "license" in data:
+ metadata_result.add_result(
+ constants.CAT_LICENSE,
+ {
+ "value": data["license"],
+ "type": constants.LICENSE
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+
+ if "dependencies" in data and isinstance(data["dependencies"], dict):
+ for name, version in data["dependencies"].items():
+ req = f"{name}: {version}"
+ metadata_result.add_result(
+ constants.CAT_REQUIREMENTS,
+ {
+ "value": req,
+ "name": name,
+ "version": version,
+ "type": constants.SOFTWARE_APPLICATION,
+ "dependency_type": constants.DEPENDENCY_TYPE_RUNTIME,
+ "dependency_resolver": "bower"
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ if "devDependencies" in data and isinstance(data["devDependencies"], dict):
+ for name, version in data["devDependencies"].items():
+ req = f"{name}: {version}"
+
+ metadata_result.add_result(
+ constants.CAT_REQUIREMENTS,
+ {
+ "value": req,
+ "name": name,
+ "version": version,
+ "type": constants.SOFTWARE_APPLICATION,
+ "dependency_type": constants.DEPENDENCY_TYPE_RUNTIME,
+ "dependency_resolver": "bower"
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ if "keywords" in data:
+ for keyword in data["keywords"]:
+ metadata_result.add_result(
+ constants.CAT_KEYWORDS,
+ {
+ "value": keyword,
+ "type": constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ except Exception as e:
+ logging.error(f"Error parsing bower.json from {file_path}: {str(e)}")
+
+ return metadata_result
\ No newline at end of file
diff --git a/src/somef_core/parser/cabal_parser.py b/src/somef_core/parser/cabal_parser.py
new file mode 100644
index 0000000..5941a59
--- /dev/null
+++ b/src/somef_core/parser/cabal_parser.py
@@ -0,0 +1,222 @@
+import os
+import re
+import logging
+from pathlib import Path
+from ..process_results import Result
+from ..regular_expressions import detect_license_spdx
+from ..utils import constants
+
+def parse_cabal_file(file_path, metadata_result: Result, source):
+ """
+ Parse a .cabal file and extract relevant metadata.
+
+ Parameters
+ ----------
+ file_path: path of the cabal file being analysed
+ metadata_result: metadata object where the metadata dictionary is kept
+ source: source of the package file (URL)
+
+ Returns
+ """
+
+ try:
+ if file_path.endswith('.cabal'):
+ metadata_result.add_result(
+ constants.CAT_HAS_PACKAGE_FILE,
+ {
+ # "value": Path(file_path).name,
+ "value": source,
+ "type": constants.URL,
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ with open(file_path, 'r', encoding='utf-8') as file:
+ content = file.read()
+
+ name_match = re.search(r'name:\s*(.*)', content, re.IGNORECASE)
+ if name_match:
+ metadata_result.add_result(
+ constants.CAT_PACKAGE_ID,
+ {
+ "value": name_match.group(1),
+ "type": constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ version_match = re.search(r'version:\s*(.*)', content, re.IGNORECASE)
+ if version_match:
+ metadata_result.add_result(
+ constants.CAT_VERSION,
+ {
+ "value": version_match.group(1),
+ "type": constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+
+ # description_match = re.search(r'description:\s*(.*)', content, re.IGNORECASE)
+ cleaned_content = content.replace('\r\n', '\n').replace('\r', '\n')
+ description_regex = re.compile(
+ r'description:\s*(.*?)(?=\n\S|\Z)',
+ re.IGNORECASE | re.DOTALL
+ )
+
+ description_match = description_regex.search(cleaned_content)
+ synopsis_match = re.search(r'synopsis:\s*(.*)', content, re.IGNORECASE)
+ if description_match:
+
+ metadata_result.add_result(
+ constants.CAT_DESCRIPTION,
+ {
+ "value": description_match.group(1),
+ "type": constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+ if synopsis_match:
+ metadata_result.add_result(
+ constants.CAT_DESCRIPTION,
+ {
+ "value": synopsis_match.group(1),
+ "type": constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ homepage_match = re.search(r'homepage:\s*(.*)', content, re.IGNORECASE)
+ if homepage_match:
+ metadata_result.add_result(
+ constants.CAT_HOMEPAGE,
+ {
+ "value": homepage_match.group(1),
+ "type": constants.URL
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ stability_match = re.search(r'stability:\s*(.*)', content, re.IGNORECASE)
+ if stability_match:
+ metadata_result.add_result(
+ constants.CAT_DEV_STATUS,
+ {
+ "value": stability_match.group(1),
+ "type": constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ bug_reports_match = re.search(r'bug-reports:\s*(.*)', content, re.IGNORECASE)
+ if bug_reports_match:
+ metadata_result.add_result(
+ constants.CAT_ISSUE_TRACKER,
+ {
+ "value": bug_reports_match.group(1),
+ "type": constants.URL
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ license_match = re.search(r'license:\s*(.*)', content, re.IGNORECASE)
+ if license_match:
+ license_value = license_match.group(1)
+ license_text = ""
+
+ dir_path = os.path.dirname(file_path)
+ license_paths = [
+ os.path.join(dir_path, "LICENSE"),
+ os.path.join(dir_path, "LICENSE.txt"),
+ os.path.join(dir_path, "LICENSE.md")
+ ]
+
+ for license_path in license_paths:
+ if os.path.exists(license_path):
+ with open(license_path, "r", encoding="utf-8") as lf:
+ license_text = lf.read()
+ break
+
+ license_info_spdx = detect_license_spdx(license_text, 'Ruby')
+
+ if license_info_spdx:
+ license_data = {
+ "value": license_value,
+ "spdx_id": license_info_spdx.get('spdx_id'),
+ "name": license_info_spdx.get('name'),
+ "type": constants.LICENSE
+ }
+ else:
+ license_data = {
+ "value": license_value,
+ "type": constants.LICENSE
+ }
+
+ metadata_result.add_result(
+ constants.CAT_LICENSE,
+ license_data,
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ library_section_match = re.search(r'library\s*\n(.*?)(?=\n\S|\Z)', content, re.DOTALL | re.IGNORECASE)
+ if library_section_match:
+ library_content = library_section_match.group(1)
+
+ build_depends_match = re.search(
+ r'build-depends:\s*(.*?)(?=\n\s*(?:[A-Za-z0-9_-]+\s*:|if\s|\Z))',
+ library_content,
+ re.DOTALL
+ )
+ # build_depends_match = re.search(r'build-depends:\s*(.*?)(?=\n\s*\w+:|\Z)', library_content, re.DOTALL)
+ if build_depends_match:
+ build_depends_content = build_depends_match.group(1)
+
+ dependencies = re.split(r'[,\n]', build_depends_content)
+
+ for dep_line in dependencies:
+ dep_line = dep_line.strip()
+ if dep_line and not dep_line.startswith(','):
+
+ dep_match = re.match(r'^([a-zA-Z0-9-_]+)\s*(.*?)$', dep_line)
+ if dep_match:
+ name = dep_match.group(1)
+ version_constraint = dep_match.group(2).strip() if dep_match.group(2) else "any"
+ req = f"{name}: {version_constraint}" if version_constraint != "any" else name
+
+ metadata_result.add_result(
+ constants.CAT_REQUIREMENTS,
+ {
+ "value": req,
+ "name": name,
+ "version": version_constraint,
+ "type": constants.SOFTWARE_APPLICATION,
+ "dependency_type": constants.DEPENDENCY_TYPE_RUNTIME,
+ "dependency_resolver": "cabal"
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ except Exception as e:
+ logging.error(f"Error parsing gemspec file from {file_path}: {str(e)}")
+
+ return metadata_result
\ No newline at end of file
diff --git a/src/somef_core/parser/codemeta_parser.py b/src/somef_core/parser/codemeta_parser.py
new file mode 100644
index 0000000..6c6098a
--- /dev/null
+++ b/src/somef_core/parser/codemeta_parser.py
@@ -0,0 +1,695 @@
+import json
+import logging
+from ..process_results import Result
+from ..utils import constants
+import re
+
+
+def parse_keywords(keywords_data):
+ """
+ Parse keywords from codemeta.json file
+
+ Parameters
+ ----------
+ keywords_data: str or list
+ The keywords data from codemeta.json, can be a comma-separated string or a list
+
+ Returns
+ -------
+ list
+ List of processed keywords
+ """
+ processed_keywords = []
+ if isinstance(keywords_data, str):
+ for k in keywords_data.split(","):
+ k = k.strip()
+ if k:
+ processed_keywords.append(k)
+ return processed_keywords
+ elif isinstance(keywords_data, list):
+ for item in keywords_data:
+ if isinstance(item, str):
+ sub_parts = item.split(",")
+ for sub_part in sub_parts:
+ processed_keywords.append(sub_part.strip())
+ else:
+ processed_keywords.append(str(item).strip())
+ return processed_keywords
+ return processed_keywords
+
+
+def parse_license(license_data):
+ """
+ Parse license information from codemeta.json
+
+ Parameters
+ ----------
+ license_data: dict or str
+ The license data from codemeta.json
+
+ Returns
+ -------
+ dict or None
+ Processed license information with keys: name, url, identifier, spdx_id
+ """
+ license_info = {}
+ if isinstance(license_data, dict):
+ license_info["name"] = license_data.get("name")
+ license_info["url"] = license_data.get("url")
+ license_info["identifier"] = license_data.get("identifier")
+
+ identifier = license_info.get("identifier", "")
+ if "spdx.org/licenses/" in identifier:
+ spdx_id = identifier.split("spdx.org/licenses/")[-1].split("/")[0]
+ license_info["spdx_id"] = spdx_id
+ elif isinstance(license_data, str):
+ license_info["name"] = license_data
+ license_info["identifier"] = f"https://spdx.org/licenses/{license_data}"
+ license_info["spdx_id"] = license_data
+ else:
+ return None
+ return license_info
+
+
+def parse_software_requirements(requirements_data):
+ """
+ Parse software requirements information from codemeta.json
+
+ Parameters
+ ----------
+ requirements_data: list or str
+ The software requirements data from codemeta.json
+
+ Returns
+ -------
+ list
+ """
+ requirements = []
+
+ if isinstance(requirements_data, str):
+ requirements_data = [requirements_data]
+
+ if isinstance(requirements_data, list):
+ for req in requirements_data:
+ if isinstance(req, str):
+ original = req.strip()
+
+ match = re.match(r'^([a-zA-Z0-9_.\-]+)([<>=!~].*)?$', original)
+ if match:
+ name = match.group(1)
+ version = match.group(2).strip() if match.group(2) else None
+ requirements.append({
+ "value": original,
+ # "name": name,
+ # "version": version
+ **({"name": name} if name else {}),
+ **({"version": version} if version else {})
+ })
+ else:
+ requirements.append({
+ "value": original
+ })
+ elif isinstance(req, dict):
+ # Handle structured object format
+ name = req.get("name") or req.get("identifier")
+ version = req.get("version")
+ identifier = req.get("identifier")
+
+ if name:
+ requirement_dict = {
+ "value": name,
+ "name": name
+ }
+
+ if version:
+ requirement_dict["version"] = version
+ requirement_dict["value"] = f"{name}=={version}" if version else name
+
+ if identifier and identifier != name:
+ requirement_dict["identifier"] = identifier
+
+ requirements.append(requirement_dict)
+
+ return requirements
+
+
+def parse_referenced_publication(reference_data):
+ """
+ Parse referenced publication information from codemeta.json
+
+ Parameters
+ ----------
+ reference_data: dict
+ The reference publication data from codemeta.json
+
+ Returns
+ -------
+ dict or None
+ """
+ if isinstance(reference_data, dict):
+ return {
+ "title": reference_data.get("name") or reference_data.get("title"),
+ "author": reference_data.get("author"),
+ "url": reference_data.get("url"),
+ "date_published": reference_data.get("datePublished"),
+ "identifier": reference_data.get("identifier")
+ }
+ return None
+
+
+def parse_funding(funding_data):
+ """
+ Parse funding information from codemeta.json
+
+ Parameters
+ ----------
+ funding_data: dict, str
+ The funding data from codemeta.json
+
+ Returns
+ -------
+ dict or None
+ """
+ if isinstance(funding_data, dict):
+ funder_name = None
+ if "funder" in funding_data:
+ funder = funding_data["funder"]
+ if isinstance(funder, dict):
+ funder_name = funder.get("name")
+
+ return {
+ "funder": funder_name,
+ "funding": funding_data.get("fundingIdentifier")
+ }
+ elif isinstance(funding_data, str):
+ return {
+ "funder": None,
+ "funding": funding_data
+ }
+
+ return None
+
+def parse_programming_language(language_data):
+ """
+ Parse programming language information from codemeta.json
+
+ Parameters
+ ----------
+ language_data: dict or str
+ The programming language data from codemeta.json
+
+ Returns
+ -------
+ dict or None
+ Processed programming language information
+ """
+ if isinstance(language_data, dict):
+ name = language_data.get("name")
+ version = language_data.get("version")
+ url = language_data.get("url")
+
+ if name:
+ lang_info = {
+ "name": name,
+ "value": name,
+ "type": constants.LANGUAGE
+ # "version": version if version is not None else None
+ }
+ if version:
+ lang_info["version"] = version
+
+ if url:
+ lang_info["url"] = url
+
+ return lang_info
+
+ elif isinstance(language_data, str):
+ return {
+ "name": language_data,
+ "value": language_data,
+ "type": constants.LANGUAGE
+ # ,
+ # "version": None
+ }
+
+ return None
+
+def parse_codemeta_json_file(file_path, metadata_result: Result, source):
+ """
+
+ Parameters
+ ----------
+ file_path: path of the package file being analysed
+ metadata_result: metadata object where the metadata dictionary is kept
+ source: source of the codemeta file (URL)
+
+ Returns
+ -------
+
+ """
+ print(f"CODEMETA PARSER - Processing file: {file_path}")
+ print(f"CODEMETA PARSER - Source: {source}")
+
+ try:
+ with open(file_path, 'r', encoding='utf-8') as file:
+ data = json.load(file)
+
+ if "applicationCategory" in data:
+ metadata_result.add_result(
+ constants.CAT_APPLICATION_DOMAIN,
+ {
+ "value": data["applicationCategory"],
+ "type": constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ if "softwareVersion" in data:
+ metadata_result.add_result(
+ constants.CAT_VERSION,
+ {
+ "value": data["softwareVersion"],
+ "type": constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ if "codeRepository" in data:
+ metadata_result.add_result(
+ constants.CAT_CODE_REPOSITORY,
+ {
+ "value": data["codeRepository"],
+ "type": constants.URL
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ if "issueTracker" in data:
+ metadata_result.add_result(
+ constants.CAT_ISSUE_TRACKER,
+ {
+ "value": data["issueTracker"],
+ "type": constants.URL
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ if "dateCreated" in data:
+ date_created = data["dateCreated"]
+ if date_created:
+ metadata_result.add_result(
+ constants.CAT_DATE_CREATED,
+ {
+ "value": date_created,
+ "type": constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ if "dateModified" in data:
+ date_modified = data["dateModified"]
+ if date_modified:
+ metadata_result.add_result(
+ constants.CAT_DATE_UPDATED,
+ {
+ "value": date_modified,
+ "type": constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ if "datePublished" in data:
+ date_published = data["datePublished"]
+ if date_published:
+ metadata_result.add_result(
+ constants.CAT_DATE_PUBLISHED,
+ {
+ "value": date_published,
+ "type": constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ if "downloadUrl" in data:
+ metadata_result.add_result(
+ constants.CAT_DOWNLOAD_URL,
+ {
+ "value": data["downloadUrl"],
+ "type": constants.URL
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ if "name" in data:
+ metadata_result.add_result(
+ constants.CAT_NAME,
+ {
+ "value": data["name"],
+ "type": constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ if "contIntegration" in data:
+ metadata_result.add_result(
+ constants.CAT_CONTINUOUS_INTEGRATION,
+ {
+ "value": data["contIntegration"],
+ "type": constants.URL
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ if "keywords" in data:
+ keywords = parse_keywords(data['keywords'])
+ metadata_result.add_result(
+ constants.CAT_KEYWORDS,
+ {
+ "value": keywords,
+ "type": constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ if "referencePublication" in data:
+ ref_publications = data["referencePublication"]
+ if isinstance(ref_publications, list):
+
+ for pub in ref_publications:
+ pub_data = parse_referenced_publication(pub)
+ if pub_data:
+
+ result_dict = {
+ "value": pub_data.get("title", ""),
+ "title": pub_data.get("title", ""),
+ "type": constants.SCHOLARLY_ARTICLE
+ }
+
+ if pub_data.get("url"):
+ result_dict["url"] = pub_data.get("url")
+
+ if pub_data.get("date_published"):
+ result_dict["date_published"] = pub_data.get("date_published")
+
+ if pub_data.get("identifier"):
+ result_dict["doi"] = pub_data.get("identifier")
+
+ metadata_result.add_result(
+ # constants.CAT_REF_PUBLICATION,
+ constants.CAT_CITATION,
+ result_dict,
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+ elif isinstance(ref_publications, dict):
+
+ pub_data = parse_referenced_publication(ref_publications)
+ if pub_data:
+ result_dict = {
+ "value": pub_data.get("title", ""),
+ "title": pub_data.get("title", ""),
+ "type": constants.SCHOLARLY_ARTICLE
+ }
+
+ if pub_data.get("url"):
+ result_dict["url"] = pub_data.get("url")
+
+ if pub_data.get("date_published"):
+ result_dict["date_published"] = pub_data.get("date_published")
+
+ if pub_data.get("identifier"):
+ result_dict["doi"] = pub_data.get("identifier")
+
+ metadata_result.add_result(
+ # constants.CAT_REF_PUBLICATION,
+ constants.CAT_CITATION,
+ result_dict,
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+ else:
+ metadata_result.add_result(
+ # constants.CAT_REF_PUBLICATION,
+ constants.CAT_CITATION,
+ {
+ "value": data["referencePublication"],
+ "type": constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ if "funding" in data:
+ funding_data = data["funding"]
+ if isinstance(funding_data, list):
+ for fund in funding_data:
+ fund_info = parse_funding(fund)
+ if fund_info:
+ metadata_result.add_result(
+ constants.CAT_FUNDING,
+ {
+ "funder": fund_info.get("funder", ""),
+ "funding": fund_info.get("funding", ""),
+ "type": constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+ elif isinstance(funding_data, dict):
+ fund_info = parse_funding(funding_data)
+ if fund_info:
+ metadata_result.add_result(
+ constants.CAT_FUNDING,
+ {
+ "funder": fund_info.get("funder", ""),
+ "funding": fund_info.get("funding", ""),
+ "type": constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ if "developmentStatus" in data:
+ metadata_result.add_result(
+ constants.CAT_DEV_STATUS,
+ {
+ "value": data["developmentStatus"],
+ "type": constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ if "identifier" in data:
+ metadata_result.add_result(
+ constants.CAT_IDENTIFIER,
+ {
+ "value": data["identifier"],
+ "type": constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ if "readme" in data:
+ metadata_result.add_result(
+ constants.CAT_README_URL,
+ {
+ "value": data["readme"],
+ "type": constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ if "description" in data:
+ metadata_result.add_result(
+ constants.CAT_DESCRIPTION,
+ {
+ "value": data["description"],
+ "type": constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ if "version" in data:
+ metadata_result.add_result(
+ constants.CAT_VERSION,
+ {
+ "value": data["version"],
+ "type": constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ if "author" in data:
+ authors = data["author"]
+ if isinstance(authors, list):
+ for author in authors:
+ if isinstance(author, dict):
+ author_name = ""
+ if "givenName" in author and "familyName" in author:
+ author_name = f"{author['givenName']} {author['familyName']}"
+ elif "name" in author:
+ author_name = author["name"]
+
+ if author_name:
+ author_info = {
+ "value": author_name,
+ "type": constants.STRING
+ }
+
+ if "email" in author:
+ author_info["email"] = author["email"]
+
+ if "affiliation" in author:
+ if isinstance(author["affiliation"], dict) and "name" in author["affiliation"]:
+ author_info["affiliation"] = author["affiliation"]["name"]
+ elif isinstance(author["affiliation"], str):
+ author_info["affiliation"] = author["affiliation"]
+
+ identifier = author.get("identifier") or author.get("@id")
+ if identifier:
+ author_info["identifier"] = identifier
+
+ metadata_result.add_result(
+ constants.CAT_AUTHORS,
+ author_info,
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+ elif isinstance(authors, dict):
+ author = authors
+ author_name = ""
+ if "givenName" in author and "familyName" in author:
+ author_name = f"{author['givenName']} {author['familyName']}"
+ elif "name" in author:
+ author_name = author["name"]
+
+ if author_name:
+ author_info = {
+ "value": author_name,
+ "type": constants.STRING
+ }
+
+ if "email" in author:
+ author_info["email"] = author["email"]
+
+ if "affiliation" in author and "name" in author["affiliation"]:
+ author_info["affiliation"] = author["affiliation"]["name"]
+
+ if "identifier" in author:
+ author_info["identifier"] = author["identifier"]
+
+ metadata_result.add_result(
+ constants.CAT_AUTHORS,
+ author_info,
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ if "license" in data:
+ license_info = parse_license(data["license"])
+ if license_info:
+ result_dict = {
+ "value": license_info.get("name", ""),
+ "type": constants.LICENSE
+ }
+
+ if license_info.get("url"):
+ result_dict["url"] = license_info["url"]
+ if license_info.get("identifier"):
+ result_dict["identifier"] = license_info["identifier"]
+ if license_info.get("spdx_id"):
+ result_dict["spdx_id"] = license_info["spdx_id"]
+
+ metadata_result.add_result(
+ constants.CAT_LICENSE,
+ result_dict,
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ if "softwareRequirements" in data:
+ requirements = parse_software_requirements(data["softwareRequirements"])
+ for requirement in requirements:
+ metadata_result.add_result(
+ constants.CAT_REQUIREMENTS,
+ {
+ "value": requirement.get("value"),
+ # "name": requirement.get("name"),
+ # "version": requirement.get("version"),
+ **({"name": requirement["name"]} if "name" in requirement else {}),
+ **({"version": requirement["version"]} if "version" in requirement else {}),
+ "type": constants.SOFTWARE_APPLICATION
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+ if "programmingLanguage" in data:
+ programming_language = data["programmingLanguage"]
+ if isinstance(programming_language, list):
+ for lang in programming_language:
+ lang_info = parse_programming_language(lang)
+ if lang_info:
+ metadata_result.add_result(
+ constants.CAT_PROGRAMMING_LANGUAGES,
+ lang_info,
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+ else:
+ lang_info = parse_programming_language(programming_language)
+ if lang_info:
+ metadata_result.add_result(
+ constants.CAT_PROGRAMMING_LANGUAGES,
+ lang_info,
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ except Exception as e:
+ logging.error(f"Error parsing codemeta JSON file {file_path}: {str(e)}")
+
+ return metadata_result
+
diff --git a/src/somef_core/parser/codeowners_parser.py b/src/somef_core/parser/codeowners_parser.py
new file mode 100644
index 0000000..1c7c92a
--- /dev/null
+++ b/src/somef_core/parser/codeowners_parser.py
@@ -0,0 +1,169 @@
+import json
+import logging
+import os
+import requests
+from pathlib import Path
+from ..process_results import Result
+from ..utils import constants
+
+def parse_codeowners_structured(dir_path, filename):
+ codeowners = []
+
+ with open(os.path.join(dir_path, filename), "r", encoding="utf-8") as file:
+ for line in file:
+ line = line.strip()
+ if line and not line.startswith("#"):
+ parts = line.split()
+ path = parts[0]
+ owners = [o.lstrip("@") for o in parts[1:]]
+ codeowners.append({"path": path, "owners": owners})
+
+ return {"codeowners": codeowners}
+
+
+def parse_codeowners_file(file_path, metadata_result: Result, source, reconcile_authors=None, repo_type=None, server_url=None) -> Result:
+ try:
+ logging.info(f"Reconcile authors flag: {reconcile_authors}")
+ if Path(file_path).name.upper() == constants.CODEOWNERS_FILE:
+ owners = parse_codeowners_structured(
+ os.path.dirname(file_path),
+ Path(file_path).name
+ )
+
+ metadata_result.add_result(
+ constants.CAT_HAS_PACKAGE_FILE,
+ {
+ "value": source,
+ "type": constants.URL,
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ added_maintainers = set()
+ for entry in owners["codeowners"]:
+ for owner in entry["owners"]:
+
+ if owner in added_maintainers:
+ continue
+
+ added_maintainers.add(owner)
+
+ maintainer_data = {
+ constants.PROP_VALUE: owner,
+ constants.PROP_USERNAME: owner,
+ constants.PROP_ROLE: "Maintainer",
+ constants.PROP_TYPE: "Person"
+ }
+
+ if reconcile_authors:
+ user_info = enrich_user(owner, repo_type, server_url)
+ if user_info:
+ if user_info.get(constants.PROP_CODEOWNERS_NAME):
+ maintainer_data[constants.PROP_NAME] = user_info.get(constants.PROP_CODEOWNERS_NAME)
+ if user_info.get(constants.PROP_CODEOWNERS_COMPANY):
+ maintainer_data[constants.PROP_AFFILIATION] = user_info.get(constants.PROP_CODEOWNERS_COMPANY)
+ if user_info.get(constants.PROP_CODEOWNERS_EMAIL):
+ maintainer_data[constants.PROP_EMAIL] = user_info.get(constants.PROP_CODEOWNERS_EMAIL)
+
+ metadata_result.add_result(
+ constants.CAT_MAINTAINER,
+ maintainer_data,
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+
+ except Exception as e:
+ logging.error(f"Error parsing CODEOWNERS: {e}")
+
+ return metadata_result
+
+# def enrich_github_user(username):
+# """ Enrich user metadata using the appropriate platform API.
+# Currently only GitHub is supported.
+# """
+# try:
+# url = f"https://api.github.com/users/{username}"
+# response = requests.get(url, timeout=5)
+
+# if response.status_code != 200:
+# return None
+
+# data = response.json()
+
+# return {
+# constants.PROP_CODEOWNERS_NAME: data.get("name"),
+# constants.PROP_CODEOWNERS_COMPANY: data.get("company"),
+# constants.PROP_CODEOWNERS_EMAIL: data.get("email"),
+# }
+
+
+# except Exception:
+# return None
+
+
+def enrich_user(username, repo_type, server_url=None):
+ """
+ Enrich user metadata using the appropriate platform API.
+
+ Parameters
+ ----------
+ username : str Username to enrich.
+ repo_type : str "GITHUB" or "GITLAB"
+ server_url : str, optional
+ Base URL of GitLab instance if repo_type is "GITLAB"
+
+ Returns
+ -------
+ dict or None
+ Dictionary with available user info (name, company, email), or None if not found.
+ """
+
+ if repo_type == constants.RepositoryType.GITHUB:
+ # logging.info(f"Enriching GitHub user {username}")
+ try:
+ url = f"https://api.github.com/users/{username}"
+ response = requests.get(url, timeout=5)
+ if response.status_code != 200:
+ return None
+ data = response.json()
+ return {
+ constants.PROP_CODEOWNERS_NAME: data.get("name"),
+ constants.PROP_CODEOWNERS_COMPANY: data.get("company"),
+ constants.PROP_CODEOWNERS_EMAIL: data.get("email"),
+ }
+ except Exception as e:
+ logging.warning(f"Error enriching GitHub user {username}: {e}")
+ return None
+
+ elif repo_type == constants.RepositoryType.GITLAB:
+ try:
+ if server_url is None:
+ server_url = "https://gitlab.com"
+ if not server_url.startswith("http"):
+ server_url = "https://" + server_url
+ api_url = f"{server_url.rstrip('/')}/api/v4/users?username={username}"
+ response = requests.get(api_url, timeout=5)
+ if response.status_code != 200:
+ logging.warning(f"GitLab API request failed for {username}: {response.status_code}")
+ return None
+ data = response.json()
+ if not data:
+ return None
+ user_info = data[0]
+ return {
+ constants.PROP_CODEOWNERS_NAME: user_info.get("name"),
+ constants.PROP_CODEOWNERS_COMPANY: user_info.get("organization"),
+ constants.PROP_CODEOWNERS_EMAIL: user_info.get("public_email"),
+ }
+ except Exception as e:
+ logging.error(f"Error enriching GitLab user {username}: {e}")
+ return None
+
+ else:
+ logging.warning(f"Unsupported repo_type {repo_type}")
+ return None
+
diff --git a/src/somef_core/parser/composer_parser.py b/src/somef_core/parser/composer_parser.py
new file mode 100644
index 0000000..9908abc
--- /dev/null
+++ b/src/somef_core/parser/composer_parser.py
@@ -0,0 +1,218 @@
+import json
+import logging
+import os
+from pathlib import Path
+from ..process_results import Result
+from ..utils import constants
+from ..regular_expressions import detect_license_spdx
+
+def parse_composer_json(file_path, metadata_result: Result, source):
+ """
+ Parse a composer.json file to extract metadata.
+
+ Parameters
+ ----------
+ file_path: path to the composer.json file being analyzed
+ metadata_result: Metadata object dictionary
+ source: source of the package file (URL)
+
+ Returns
+ -------
+ """
+ try:
+
+ if Path(file_path).name.lower() in ["composer.json"]:
+ metadata_result.add_result(
+ constants.CAT_HAS_PACKAGE_FILE,
+ {
+ # "value": "composer.json",
+ "value": source,
+ "type": constants.URL,
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ with open(file_path, "r", encoding="utf-8") as f:
+ data = json.load(f)
+
+ if "name" in data:
+ metadata_result.add_result(
+ constants.CAT_PACKAGE_ID,
+ {
+ "value": data["name"],
+ "type": constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ if "description" in data:
+ metadata_result.add_result(
+ constants.CAT_DESCRIPTION,
+ {
+ "value": data["description"],
+ "type": constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ if "homepage" in data:
+ metadata_result.add_result(
+ constants.CAT_HOMEPAGE,
+ {
+ "value": data["homepage"],
+ "type": constants.URL
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ if "version" in data:
+ metadata_result.add_result(
+ constants.CAT_VERSION,
+ {
+ "value": data["version"],
+ "type": constants.RELEASE,
+ "tag": data["version"]
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ if "authors" in data:
+ for author in data["authors"]:
+ author_data = {
+ "type": constants.AGENT,
+ "value": author.get("name", "")
+ }
+
+ if "name" in author:
+ author_data["name"] = author["name"]
+
+ if "email" in author:
+ author_data["email"] = author["email"]
+
+ if "homepage" in author:
+ author_data["url"] = author["homepage"]
+
+ if "role" in author:
+ author_data["role"] = author["role"]
+
+ metadata_result.add_result(
+ constants.CAT_AUTHORS,
+ author_data,
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ if "license" in data:
+ license_value = data["license"]
+ license_text = ""
+
+ dir_path = os.path.dirname(file_path)
+ license_paths = [
+ os.path.join(dir_path, "LICENSE"),
+ os.path.join(dir_path, "LICENSE.txt"),
+ os.path.join(dir_path, "LICENSE.md")
+ ]
+
+ for license_path in license_paths:
+ if os.path.exists(license_path):
+ with open(license_path, "r", encoding="utf-8") as lf:
+ license_text = lf.read()
+ break
+
+ license_info_spdx = detect_license_spdx(license_text, 'JSON')
+
+ if license_info_spdx:
+ license_data = {
+ "value": license_value,
+ "spdx_id": license_info_spdx.get('spdx_id'),
+ "name": license_info_spdx.get('name'),
+ "type": constants.LICENSE
+ }
+ else:
+ license_data = {
+ "value": license_value,
+ "type": constants.LICENSE
+ }
+
+ metadata_result.add_result(
+ constants.CAT_LICENSE,
+ license_data,
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ dependency_sections = ["require", "require-dev"]
+ for section in dependency_sections:
+ if section in data:
+ for name, version in data[section].items():
+ req = f"{name}: {version}"
+ if section == "require-dev":
+ dep_type = constants.DEPENDENCY_TYPE_DEVELOPMENT
+ else:
+ dep_type = constants.DEPENDENCY_TYPE_RUNTIME
+
+ metadata_result.add_result(
+ constants.CAT_REQUIREMENTS,
+ {
+ "value": req,
+ "name": name,
+ "version": version,
+ "type": constants.SOFTWARE_APPLICATION,
+ "dependency_type": dep_type,
+ "dependency_resolver": "composer"
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ if "repository" in data:
+ repo_data = data["repository"]
+ if isinstance(repo_data, str):
+ repo_url = repo_data
+ elif isinstance(repo_data, dict) and "url" in repo_data:
+ repo_url = repo_data["url"]
+ else:
+ repo_url = None
+
+ if repo_url:
+ metadata_result.add_result(
+ constants.CAT_CODE_REPOSITORY,
+ {
+ "value": repo_url,
+ "type": constants.URL
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ if "keywords" in data:
+ for keyword in data["keywords"]:
+ metadata_result.add_result(
+ constants.CAT_KEYWORDS,
+ {
+ "value": keyword,
+ "type": constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ except Exception as e:
+ logging.error(f"Error parsing composer.json from {file_path}: {str(e)}")
+
+ return metadata_result
\ No newline at end of file
diff --git a/src/somef_core/parser/conda_environment_parser.py b/src/somef_core/parser/conda_environment_parser.py
new file mode 100644
index 0000000..b1c25e3
--- /dev/null
+++ b/src/somef_core/parser/conda_environment_parser.py
@@ -0,0 +1,91 @@
+import json
+import yaml
+import logging
+from pathlib import Path
+from ..process_results import Result
+from ..utils import constants
+import re
+
+def parse_conda_environment_file(file_path, metadata_result: Result, source):
+
+ try:
+ with open(file_path, "r", encoding="utf-8") as f:
+ data = yaml.safe_load(f)
+ except Exception as e:
+ logging.warning(f"Could not parse environment.yml {file_path}: {e}")
+ return metadata_result
+
+ if not isinstance(data, dict) or "dependencies" not in data:
+ return metadata_result
+
+ if Path(file_path).name.lower() in {"environment.yml", "environment.yaml"}:
+ metadata_result.add_result(
+ constants.CAT_HAS_PACKAGE_FILE,
+ {
+ constants.PROP_VALUE: source,
+ constants.PROP_TYPE: constants.URL,
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ name = data.get("name")
+ # not sure about this channels. I doubt they are relevant for the metadata in somef.
+ channels = data.get("channels", [])
+ dependencies = data.get("dependencies", [])
+
+ conda_deps = []
+ pip_deps = []
+
+ for dep in dependencies:
+ if isinstance(dep, str):
+ conda_deps.append(dep)
+ elif isinstance(dep, dict) and "pip" in dep:
+ pip_deps.extend(dep["pip"])
+
+ # conda dependencies
+ for dep in conda_deps:
+ dep_dict = {
+ constants.PROP_VALUE: dep,
+ constants.PROP_NAME: re.split(r"[=<>!]", dep)[0],
+ constants.PROP_TYPE: constants.SOFTWARE_APPLICATION,
+ constants.PROP_DEPENDENCY_TYPE: constants.DEPENDENCY_TYPE_RUNTIME,
+ constants.PROP_DEPENDENCY_RESOLVER: "conda"
+ }
+
+ match = re.search(r"[=<>!]+(.+)", dep)
+ if match:
+ dep_dict[constants.PROP_VERSION] = match.group(1)
+
+ metadata_result.add_result(
+ constants.CAT_REQUIREMENTS,
+ dep_dict,
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+ # pip dependdencies
+ for dep in pip_deps:
+
+ dep_dict = {
+ constants.PROP_VALUE: dep,
+ constants.PROP_NAME: re.split(r"[=<>!~]", dep)[0],
+ constants.PROP_TYPE: constants.SOFTWARE_APPLICATION,
+ constants.PROP_DEPENDENCY_TYPE: constants.DEPENDENCY_TYPE_RUNTIME,
+ constants.PROP_DEPENDENCY_RESOLVER: "pip"
+ }
+
+ match = re.search(r"[=<>!~]+(.+)", dep)
+ if match:
+ dep_dict[constants.PROP_VERSION] = match.group(1)
+
+ metadata_result.add_result(
+ constants.CAT_REQUIREMENTS,
+ dep_dict,
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ return metadata_result
diff --git a/src/somef_core/parser/create_excerpts.py b/src/somef_core/parser/create_excerpts.py
index c4ef97b..bf0f4a2 100644
--- a/src/somef_core/parser/create_excerpts.py
+++ b/src/somef_core/parser/create_excerpts.py
@@ -28,43 +28,44 @@ def unmark_element(element, stream=None):
__md.stripTopLevelTags = False
-def split_into_excerpts(string_list):
- """
- ## Function takes readme text as input and divides it into excerpts
- Parameters
- ----------
- string_list: input text
+# def split_into_excerpts(string_list):
+# """
+# ## Function takes readme text as input and divides it into excerpts
+# Parameters
+# ----------
+# string_list: input text
- Returns
- -------
- Returns the extracted excerpts
- """
- divisions = []
- for text in string_list:
- if text:
- divisions = divisions + markdown_utils.unmark(text).splitlines()
- divisions = [i for i in divisions if i]
- return divisions
+# Returns
+# -------
+# Returns the extracted excerpts
+# """
+# divisions = []
+# for text in string_list:
+# if text:
+# divisions = divisions + markdown_utils.unmark(text).splitlines()
+# divisions = [i for i in divisions if i]
+# return divisions
-def create_excerpts(string_list):
- """
- Function takes readme text as input and divides it into excerpts
- Parameters
- ----------
- string_list: Markdown text passed as input
- Returns
- -------
- Extracted excerpts
- """
- logging.info("Splitting text into valid excerpts for classification")
- string_list = markdown_utils.remove_bibtex(string_list)
- divisions = mardown_parser.extract_blocks_excerpts(string_list)
- logging.info("Text Successfully split.")
- output = {}
- for division in divisions:
- original = division
- division = regular_expressions.remove_links_images(division)
- if len(division) > 0:
- output[division] = original
- return output
+# def create_excerpts(string_list):
+# """
+# Function takes readme text as input and divides it into excerpts
+# Parameters
+# ----------
+# string_list: Markdown text passed as input
+# Returns
+# -------
+# Extracted excerpts
+# """
+# logging.info("Splitting text into valid excerpts for classification")
+# string_list = markdown_utils.remove_bibtex(string_list)
+# logging.info("Extracting excerpts for classification")
+# divisions = mardown_parser.extract_blocks_excerpts(string_list)
+# logging.info("Text Successfully split.")
+# output = {}
+# for division in divisions:
+# original = division
+# division = regular_expressions.remove_links_images(division)
+# if len(division) > 0:
+# output[division] = original
+# return output
diff --git a/src/somef_core/parser/description_parser.py b/src/somef_core/parser/description_parser.py
new file mode 100644
index 0000000..6ec783b
--- /dev/null
+++ b/src/somef_core/parser/description_parser.py
@@ -0,0 +1,228 @@
+import re
+import logging
+from pathlib import Path
+from ..process_results import Result
+from ..utils import constants
+
+def parse_description_file(file_path, metadata_result: Result, source):
+
+ """
+ Parse a DESCRIPTION file to extract metadata.
+
+ Parameters
+ ----------
+ file_path: path of the DESCRIPTION file being analysed
+ metadata_result: metadata object where the metadata dictionary is kept
+ source: source of the package file (URL)
+
+ Returns
+ -------
+ """
+
+ try:
+ if Path(file_path).name.upper() == "DESCRIPTION":
+ metadata_result.add_result(
+ constants.CAT_HAS_PACKAGE_FILE,
+ {
+ # "value": "DESCRIPTION",
+ "value": source,
+ "type": constants.URL,
+ "source": source
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ with open(file_path, "r", encoding="utf-8") as f:
+ content = f.read()
+
+ package_match = re.search(r'Package:\s*([^\n]+)', content)
+ if package_match:
+ print("FOUND PACKAGE NAME")
+ package_name = package_match.group(1).strip()
+ metadata_result.add_result(
+ constants.CAT_PACKAGE_ID,
+ {
+ "value": package_name,
+ "type": constants.STRING,
+ "source": source
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+ else:
+ print("NO PACKAGE NAME FOUND")
+
+ desc_match = re.search(r'Description:\s*([^\n]+(?:\n\s+[^\n]+)*)', content)
+ if desc_match:
+ description = desc_match.group(1).strip()
+ metadata_result.add_result(
+ constants.CAT_DESCRIPTION,
+ {
+ "value": description,
+ "type": constants.STRING,
+ "source": source
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ version_match = re.search(r'Version:\s*([^\n]+)', content)
+ if version_match:
+ version = version_match.group(1).strip()
+ metadata_result.add_result(
+ constants.CAT_VERSION,
+ {
+ "value": version,
+ "type": constants.STRING,
+ "source": source
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+
+ authors_section = re.search(r'Authors@R:\s*c\(([\s\S]*?)\)\s*$', content, re.MULTILINE)
+ # authors_section = re.search(r'Authors@R:\s*c\(([^)]+)\)', content, re.DOTALL)
+ if authors_section:
+ authors_text = authors_section.group(1)
+
+ pattern = re.compile(
+ r'''person\(
+ \s*"([^"]+)"
+ (?:\s*,\s*"([^"]*)")?
+ (?:\s*,\s*)*
+ (?:
+ "(?P[^"]+@[^"]+)"
+ |
+ .*?email\s*=\s*"(?P[^"]+)"
+ )?
+ [^)]*
+ \)''',
+ re.VERBOSE
+ )
+ # person_entries = re.findall(r'person\(\s*"([^"]+)"\s*,\s*"([^"]+)"(?:\s*,\s*)?(?:"[^"]*")?(?:\s*,\s*)?(?:"([^"]+)")?', authors_text)
+ person_entries = []
+ for match in pattern.finditer(authors_text):
+ given = match.group(1)
+ family = match.group(2) or ""
+ email = match.group("email1") or match.group("email2") or ""
+ person_entries.append((given, family, email))
+
+ for given, family, email in person_entries:
+ name = given.strip()
+ if family:
+ name += " " + family.strip()
+ author = {
+ "value": name,
+ "type": constants.AGENT,
+ }
+
+ if email:
+ author["email"] = email.strip()
+
+ metadata_result.add_result(
+ constants.CAT_AUTHORS,
+ author,
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ license_match = re.search(r'License:\s*([^\n]+)', content)
+ if license_match:
+ license_text = license_match.group(1).strip()
+ metadata_result.add_result(
+ constants.CAT_LICENSE,
+ {
+ "value": license_text,
+ "type": constants.STRING,
+ "source": source
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ url_section = re.search(r'URL:\s*([^\n]+(?:\n\s+[^\n]+)*)', content)
+ if url_section:
+ url_text = url_section.group(1).strip()
+ urls = []
+ for url in re.split(r',\s*', url_text):
+ stripped = url.strip()
+ if stripped:
+ urls.append(stripped)
+
+ for url in urls:
+ if 'github.com' in url.lower() or 'gitlab.com' in url.lower():
+ metadata_result.add_result(
+ constants.CAT_CODE_REPOSITORY,
+ {
+ "value": url,
+ "type": constants.URL,
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+ else:
+ metadata_result.add_result(
+ constants.CAT_HOMEPAGE,
+ {
+ "value": url,
+ "type": constants.URL,
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ issue_tracker_match = re.search(r'BugReports:\s*([^\n]+)', content)
+ if issue_tracker_match:
+ issue_tracker_url = issue_tracker_match.group(1).strip()
+ metadata_result.add_result(
+ constants.CAT_ISSUE_TRACKER,
+ {
+ "value": issue_tracker_url,
+ "type": constants.URL,
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ for field in ('Depends', 'Imports'):
+ section_match = re.search(r'^' + field + r':\s*(.*(?:\n[ \t]+.*)*)', content, re.MULTILINE)
+ if section_match:
+ section_text = section_match.group(1)
+ dependencies = re.findall(r'([A-Za-z][A-Za-z0-9.]*)\s*(?:\(([^)]*)\))?', section_text)
+ for dependency in dependencies:
+ name = dependency[0]
+ version = dependency[1] if len(dependency) > 1 and dependency[1] else ""
+ req = f"{name} ({version})" if version else f"{name}"
+
+ metadata_result.add_result(
+ constants.CAT_REQUIREMENTS,
+ {
+ "value": req,
+ "name": name,
+ "version": version,
+ "type": constants.SOFTWARE_APPLICATION
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+
+
+ except Exception as e:
+ logging.error(f"Error parsing gemspec file from {file_path}: {str(e)}")
+
+ return metadata_result
+
+
diff --git a/src/somef_core/parser/dockerfile_parser.py b/src/somef_core/parser/dockerfile_parser.py
new file mode 100644
index 0000000..d47cd44
--- /dev/null
+++ b/src/somef_core/parser/dockerfile_parser.py
@@ -0,0 +1,227 @@
+import logging
+import os
+import re
+from ..utils import constants
+from ..process_results import Result
+
+def parse_dockerfile(file_path, metadata_result: Result, source):
+
+ print(f"Extracting properties from Dockerfile: {file_path}")
+
+ try:
+ with open(file_path, "rb") as file:
+ raw_data = file.read()
+
+ content = raw_data.decode("utf-8")
+ except (OSError, UnicodeDecodeError) as e:
+ logging.warning(f"Could not process Dockerfile {file_path}: {e}")
+ return None
+
+ # print(content)
+ title_match = re.search(
+ constants.REGEXP_DOCKER_TITLE,
+ content,
+ re.IGNORECASE
+ )
+
+ if title_match:
+ title = title_match.group(1).strip()
+ if title:
+ metadata_result.add_result(
+ constants.CAT_NAME,
+ {
+ "value": title,
+ "type": constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ description_match = re.search(
+ constants.REGEXP_DOCKER_DESCRIPTION,
+ content,
+ re.IGNORECASE
+ )
+
+ if description_match:
+ description = description_match.group(1).strip()
+ if description:
+ metadata_result.add_result(
+ constants.CAT_DESCRIPTION,
+ {
+ "value": description,
+ "type": constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ licenses_match = re.search(constants.REGEXP_DOCKER_LICENSES, content, re.IGNORECASE)
+ if licenses_match:
+ license_info_spdx = detect_license_spdx(licenses_match.group(1).strip())
+
+ if license_info_spdx:
+ license_data = {
+ "value": licenses_match.group(1).strip(),
+ "spdx_id": license_info_spdx.get('spdx_id'),
+ "name": license_info_spdx.get('name'),
+ "type": constants.LICENSE
+ }
+ else:
+ license_data = {
+ "value": licenses_match.group(1).strip(),
+ "type": constants.LICENSE
+ }
+ metadata_result.add_result(
+ constants.CAT_LICENSE,
+ license_data,
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+
+ # source_match = re.search(constants.REGEXP_DOCKER_SOURCE, content, re.IGNORECASE)
+ # if source_match:
+ # properties[constants.PROP_SOURCE] = source_match.group(1).strip()
+
+ url_match = re.search(constants.REGEXP_DOCKER_URL, content, re.IGNORECASE)
+ if url_match:
+ metadata_result.add_result(
+ constants.CAT_CODE_REPOSITORY,
+ {
+ "value": url_match.group(1).strip(),
+ "type": constants.URL
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ version_match = re.search(constants.REGEXP_DOCKER_VERSION, content, re.IGNORECASE)
+ if version_match:
+ metadata_result.add_result(
+ constants.CAT_VERSION,
+ {
+ "value": version_match.group(1).strip(),
+ "type": constants.RELEASE,
+ "tag": version_match.group(1).strip()
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ documentation_match = re.search(constants.REGEXP_DOCKER_DOCUMENTATION, content, re.IGNORECASE)
+ if documentation_match:
+ metadata_result.add_result(
+ constants.CAT_DOCUMENTATION,
+ {
+ "value": documentation_match.group(1).strip(),
+ "type": constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+
+ vendor_match = re.search(
+ constants.REGEXP_DOCKER_VENDOR,
+ content,
+ re.IGNORECASE
+ )
+
+ if vendor_match:
+ vendor = vendor_match.group(1).strip()
+ if vendor:
+ if vendor and re.search(constants.REGEXP_LTD_INC, vendor, re.IGNORECASE):
+ type_vendor = "Organization"
+ else:
+ type_vendor = "Person"
+
+ metadata_result.add_result(
+ constants.CAT_OWNER,
+ {
+ "value": vendor,
+ "type": type_vendor
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ # Extract maintainers
+ maintainers = []
+ unique_maintainers = []
+
+ maintainer_oci_match = re.findall(
+ constants.REGEXP_MAINTAINER_LABEL_OCI,
+ content,
+ re.IGNORECASE | re.MULTILINE
+ )
+ # LABEL maintainer free
+ maintanainer_label_match = re.findall(
+ constants.REGEXP_MAINTAINER_LABEL_FREE,
+ content,
+ re.IGNORECASE | re.MULTILINE
+ )
+ # Deprecated maintainer
+ maintainer_match = re.findall(
+ constants.REGEXP_MAINTAINER,
+ content,
+ re.IGNORECASE | re.MULTILINE
+ )
+ maintainers.extend(maintainer_oci_match)
+ maintainers.extend(maintanainer_label_match)
+ maintainers.extend(maintainer_match)
+
+ unique_maintainers = list({m.strip() for m in maintainers if m.strip()})
+
+ for maintainer in unique_maintainers:
+ metadata_result.add_result(
+ constants.CAT_AUTHORS,
+ {
+ "type": constants.AGENT,
+ "value": maintainer
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ return metadata_result
+
+def detect_license_spdx(license_text):
+ """
+ Function that given a license text, infers the name and spdx id in a dockerfile
+ Parameters
+ ----------
+ license_text
+
+ Returns
+ -------
+ A JSON dictionary with name and spdx id
+ """
+ print("Detecting license from text:", license_text)
+ for license_name, license_info in constants.LICENSES_DICT.items():
+ if re.search(license_info["regex"], license_text, re.IGNORECASE):
+ return {
+ "name": license_name,
+ "spdx_id": f"{license_info['spdx_id']}",
+ "@id": f"https://spdx.org/licenses/{license_info['spdx_id']}"
+ }
+
+ for license_name, license_info in constants.LICENSES_DICT.items():
+ spdx_id = license_info["spdx_id"]
+ if re.search(rf'\b{re.escape(spdx_id)}\b', license_text, re.IGNORECASE):
+ return {
+ "name": license_name,
+ "spdx_id": spdx_id,
+ "@id": f"https://spdx.org/licenses/{spdx_id}"
+ }
+ return None
+
+
\ No newline at end of file
diff --git a/src/somef_core/parser/gemspec_parser.py b/src/somef_core/parser/gemspec_parser.py
new file mode 100644
index 0000000..fa213bb
--- /dev/null
+++ b/src/somef_core/parser/gemspec_parser.py
@@ -0,0 +1,224 @@
+import os
+import re
+import logging
+from pathlib import Path
+from ..process_results import Result
+from ..utils import constants
+from ..regular_expressions import detect_license_spdx
+
+def parse_gemspec_file(file_path, metadata_result: Result, source):
+ # To do Version
+ # To do Email
+ """
+ Parse a Ruby gemspec file to extract metadata.
+
+ Parameters
+ ----------
+ file_path: path of the gemspec file being analysed
+ metadata_result: metadata object where the metadata dictionary is kept
+ source: source of the package file (URL)
+
+ Returns
+ -------
+ metadata_result: Updated metadata result object
+ """
+ try:
+ if file_path.endswith('.gemspec'):
+ metadata_result.add_result(
+ constants.CAT_HAS_PACKAGE_FILE,
+ {
+ # "value": Path(file_path).name,
+ "value": source,
+ "type": constants.URL,
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ with open(file_path, 'r', encoding='utf-8') as file:
+ content = file.read()
+
+ name_match = re.search(r'gem\.name\s*=\s*["\']([^"\']+)["\']', content)
+ if name_match:
+ metadata_result.add_result(
+ constants.CAT_PACKAGE_ID,
+ {
+ "value": name_match.group(1),
+ "type": constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ desc_match = re.search(r'gem\.description\s*=\s*%q{([^}]+)}|gem\.description\s*=\s*["\']([^"\']+)["\']', content)
+ if desc_match:
+ description = desc_match.group(1) if desc_match.group(1) else desc_match.group(2)
+ metadata_result.add_result(
+ constants.CAT_DESCRIPTION,
+ {
+ "value": description,
+ "type": constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ homepage_match = re.search(r'gem\.homepage\s*=\s*["\']([^"\']+)["\']', content)
+ if homepage_match:
+ metadata_result.add_result(
+ constants.CAT_HOMEPAGE,
+ {
+ "value": homepage_match.group(1),
+ "type": constants.URL
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ authors_match = re.findall(r'gem\.author[s]?\s*=\s*(?P"[^"]*"|\[[^\]]*\])', content)
+
+ for match in authors_match:
+ if match.startswith('['):
+ author_list = re.findall(r'["\']([^"\']+)["\']', match)
+ else:
+ author_list = [re.sub(r'["\']', '', match).strip()]
+
+ for author in author_list:
+ metadata_result.add_result(
+ constants.CAT_AUTHORS,
+ {
+ "type": constants.AGENT,
+ "value": author
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ # authors_str = authors_match.group(1)
+ # author_list = re.findall(r'["\']([^"\']+)["\']', authors_str)
+
+ # for author in author_list:
+ # metadata_result.add_result(
+ # constants.CAT_AUTHORS,
+ # {
+ # "type": constants.AGENT,
+ # "value": author
+ # },
+ # 1,
+ # constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ # source
+ # )
+
+ # license_match = re.search(r'gem\.license\s*=\s*["\']([^"\']+)["\']', content)
+ license_match = re.search(r'gem\.license[s]?\s*=\s*["\']([^"\']+)["\']', content)
+ if license_match:
+ license_value = license_match.group(1)
+ license_text = ""
+
+ dir_path = os.path.dirname(file_path)
+ license_paths = [
+ os.path.join(dir_path, "LICENSE"),
+ os.path.join(dir_path, "LICENSE.txt"),
+ os.path.join(dir_path, "LICENSE.md")
+ ]
+
+ for license_path in license_paths:
+ if os.path.exists(license_path):
+ with open(license_path, "r", encoding="utf-8") as lf:
+ license_text = lf.read()
+ break
+
+ license_info_spdx = detect_license_spdx(license_text, 'Ruby')
+
+ if license_info_spdx:
+ license_data = {
+ "value": license_value,
+ "spdx_id": license_info_spdx.get('spdx_id'),
+ "name": license_info_spdx.get('name'),
+ "type": constants.LICENSE
+ }
+ else:
+ license_data = {
+ "value": license_value,
+ "type": constants.LICENSE
+ }
+
+ metadata_result.add_result(
+ constants.CAT_LICENSE,
+ license_data,
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+
+ dependency_matches = re.search(r'gem\.requirements\s*=\s*(\[.*?\])', content, re.DOTALL)
+
+ if dependency_matches:
+ array_requirements = dependency_matches.group(1)
+ dependencies = re.findall(r'["\']([^"\']+)["\']', array_requirements)
+
+ if dependencies:
+ metadata_result.add_result(
+ constants.CAT_REQUIREMENTS,
+ {
+ "value": dependencies,
+ "type": constants.SOFTWARE_APPLICATION,
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+
+ dependency_matches = re.findall(r'gem\.add_dependency\s*["\']([^"\']+)["\'](?:\s*,\s*["\']([^"\']+)["\'])?', content)
+ for dep in dependency_matches:
+ name = dep[0]
+ version = dep[1] if len(dep) > 1 and dep[1] else "any"
+ req = f"{name}: {version}"
+
+ metadata_result.add_result(
+ constants.CAT_REQUIREMENTS,
+ {
+ "value": req,
+ "name": name,
+ "version": version,
+ "type": constants.SOFTWARE_APPLICATION,
+ "dependency_type": constants.DEPENDENCY_TYPE_RUNTIME,
+ "dependency_resolver": "bundler"
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ dev_dependency_matches = re.findall(r'gem\.add_development_dependency\s*["\']([^"\']+)["\'](?:\s*,\s*["\']([^"\']+)["\'])?', content)
+ for dep in dev_dependency_matches:
+ name = dep[0]
+ version = dep[1] if len(dep) > 1 and dep[1] else "any"
+ req = f"{name}: {version}"
+
+ metadata_result.add_result(
+ constants.CAT_REQUIREMENTS,
+ {
+ "value": req,
+ "name": name,
+ "version": version,
+ "type": constants.SOFTWARE_APPLICATION,
+ "dependency_type": constants.DEPENDENCY_TYPE_DEVELOPMENT,
+ "dependency_resolver": "bundler"
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ except Exception as e:
+ logging.error(f"Error parsing gemspec file from {file_path}: {str(e)}")
+
+ return metadata_result
\ No newline at end of file
diff --git a/src/somef_core/parser/mardown_parser.py b/src/somef_core/parser/mardown_parser.py
index 1c0fb83..d1139e2 100644
--- a/src/somef_core/parser/mardown_parser.py
+++ b/src/somef_core/parser/mardown_parser.py
@@ -51,9 +51,12 @@ def extract_content_per_header(original_text, headers):
output = {}
limit = len(keys)
index = 0
- top = keys[0]
bottom = None
none_header_content = None
+ if not keys:
+ return [], original_text
+
+ top = keys[0]
text_tokenized = original_text.split('\n')
if len(text_tokenized) == 1:
return text_tokenized[0]
diff --git a/src/somef_core/parser/package_json_parser.py b/src/somef_core/parser/package_json_parser.py
index 83ba1eb..2ad7e70 100644
--- a/src/somef_core/parser/package_json_parser.py
+++ b/src/somef_core/parser/package_json_parser.py
@@ -46,7 +46,18 @@ def parse_package_json_file(file_path, metadata_result: Result, source):
constants.TECHNIQUE_CODE_CONFIG_PARSER,
source
)
-
+
+ if "homepage" in data:
+ metadata_result.add_result(
+ constants.CAT_HOMEPAGE,
+ {
+ "value": data["homepage"],
+ "type": constants.URL},
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
if "version" in data:
metadata_result.add_result(
constants.CAT_VERSION,
@@ -137,30 +148,51 @@ def parse_package_json_file(file_path, metadata_result: Result, source):
constants.TECHNIQUE_CODE_CONFIG_PARSER,
source
)
+
+ runtimes = parse_runtime_platform_from_package_json(data)
+ if runtimes:
+ for runtime in runtimes:
+ metadata_result.add_result(
+ constants.CAT_RUNTIME_PLATFORM,
+ runtime,
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ # deps = {}
+ # deps.update(data.get("dependencies", {}))
+ # deps.update(data.get("devDependencies", {}))
- deps = {}
- deps.update(data.get("dependencies", {}))
- deps.update(data.get("devDependencies", {}))
-
- for name, version in deps.items():
- req = f"{name}@{version}"
- metadata_result.add_result(
- constants.CAT_REQUIREMENTS,
- {
- "value": req,
- "name": name,
- "version": version,
- "type": constants.SOFTWARE_APPLICATION
- },
- 1,
- constants.TECHNIQUE_CODE_CONFIG_PARSER,
- source
- )
+ # for name, version in deps.items():
+ sections = {
+ "dependencies": constants.DEPENDENCY_TYPE_RUNTIME,
+ "devDependencies": constants.DEPENDENCY_TYPE_DEVELOPMENT
+ }
+
+ for section, dep_type in sections.items():
+ for name, version in data.get(section, {}).items():
+ req = f"{name}@{version}"
+ metadata_result.add_result(
+ constants.CAT_REQUIREMENTS,
+ {
+ "value": req,
+ "name": name,
+ "version": version,
+ "type": constants.SOFTWARE_APPLICATION,
+ "dependency_type": dep_type,
+ "dependency_resolver": "npm"
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
metadata_result.add_result(
constants.CAT_HAS_PACKAGE_FILE,
{
- "value": "package.json",
+ # "value": "package.json",
+ "value": source,
"type": constants.URL,
},
1,
@@ -199,4 +231,33 @@ def parse_bugs(bugs_data):
return bugs_data["url"]
if isinstance(bugs_data, str):
return bugs_data
- return None
\ No newline at end of file
+ return None
+
+def parse_runtime_platform_from_package_json(data):
+ """
+ Extract runtime information from a package.json dict.
+ Returns a list of dicts with 'name' and 'version', e.g.:
+ [{'name': 'Node.js', 'version': '18.x'}]
+ """
+ runtimes = []
+
+ engines = data.get("engines", {})
+ if isinstance(engines, dict):
+ for runtime_name, version_value in engines.items():
+
+ if version_value:
+ value_str = f"{runtime_name}: {version_value}".strip()
+ else:
+ value_str = runtime_name
+
+ run = {
+ "value": value_str,
+ "name": runtime_name.capitalize(),
+ "type": constants.STRING
+ }
+ if version_value:
+ run["version"] = version_value.strip()
+
+ runtimes.append(run)
+
+ return runtimes
\ No newline at end of file
diff --git a/src/somef_core/parser/pom_xml_parser.py b/src/somef_core/parser/pom_xml_parser.py
index b9a799e..4addc26 100644
--- a/src/somef_core/parser/pom_xml_parser.py
+++ b/src/somef_core/parser/pom_xml_parser.py
@@ -40,6 +40,7 @@ def parse_pom_file(file_path, metadata_result: Result, source):
data = lxml.etree.parse(file)
root = data.getroot()
+
if root.tag != "{" + POM_NAMESPACE + "}project":
logging.warning(f"Expected root tag 'project' in {POM_NAMESPACE} namespace, got {root.tag} instead")
return metadata_result
@@ -59,7 +60,9 @@ def parse_pom_file(file_path, metadata_result: Result, source):
"dependencies": [],
"developers": [],
"issue_tracker": None,
- "scm_url": None
+ "scm_url": None,
+ "homepage": [],
+ "runtime_platform": []
}
for key, node in parse_node(root):
@@ -81,9 +84,17 @@ def parse_pom_file(file_path, metadata_result: Result, source):
project_data["scm_url"] = parse_scm(node)
elif key == "developers":
project_data["developers"] = parse_developers(node)
+ # elif key == "dependencies" or key == "dependencyManagement":
+ # project_data["dependencies"] = parse_dependencies(node)
elif key == "dependencies":
- project_data["dependencies"] = parse_dependencies(node)
-
+ project_data["dependencies"].extend(parse_dependencies(node))
+ elif key == "dependencyManagement":
+ for k, n in parse_node(node):
+ if k == "dependencies":
+ project_data["dependencies"].extend(parse_dependencies(n))
+ elif key == "properties":
+ project_data["runtime_platform"] = parse_runtime_platform(node)
+
if group_id or artifact_id:
identifier_value = f"{group_id or ''}.{artifact_id or ''}".strip(':')
metadata_result.add_result(
@@ -120,7 +131,18 @@ def parse_pom_file(file_path, metadata_result: Result, source):
constants.TECHNIQUE_CODE_CONFIG_PARSER,
source
)
-
+
+ if project_data["homepage"]:
+ metadata_result.add_result(
+ constants.CAT_HOMEPAGE,
+ {
+ "value": project_data["homepage"],
+ "type": constants.URL},
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
if project_data["scm_url"]:
metadata_result.add_result(
constants.CAT_PACKAGE_DISTRIBUTION,
@@ -132,45 +154,89 @@ def parse_pom_file(file_path, metadata_result: Result, source):
constants.TECHNIQUE_CODE_CONFIG_PARSER,
source
)
-
+
if project_data["dependencies"]:
for dependency in project_data["dependencies"]:
+ name_d = dependency.get("artifactId", "")
+ version_d = dependency.get("version", "")
+ scope = dependency.get("scope", None)
+
+ if scope == "test":
+ dep_type = constants.DEPENDENCY_TYPE_DEVELOPMENT
+ elif scope == "import":
+ continue
+ else:
+ dep_type = constants.DEPENDENCY_TYPE_RUNTIME
+
metadata_result.add_result(
constants.CAT_REQUIREMENTS,
{
"value": f'{dependency.get("groupId", "")}.{dependency.get("artifactId", "")}'.strip("."),
- "name": dependency.get("artifactId", ""),
- "version": dependency.get("version", ""),
- "type": constants.SOFTWARE_APPLICATION
+ "name": name_d,
+ "version": version_d,
+ "type": constants.SOFTWARE_APPLICATION,
+ "dependency_type": dep_type,
+ "dependency_resolver": "maven"
},
1,
constants.TECHNIQUE_CODE_CONFIG_PARSER,
source
)
- if project_data["developers"] :
- metadata_result.add_result(
- constants.CAT_AUTHORS ,
- {
- "value": project_data["developers"],
- "type": constants.URL
- },
- 1,
- constants.TECHNIQUE_CODE_CONFIG_PARSER,
- source
- )
+
+ if project_data["developers"]:
+ for author in project_data["developers"]:
+
+ if "type" not in author:
+ author["type"] = constants.AGENT
+
+ metadata_result.add_result(
+ constants.CAT_AUTHORS,
+ author,
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
if repositories:
- metadata_result.add_result(
- constants.CAT_PACKAGE_DISTRIBUTION,
- {
- "value": repositories,
- "type": constants.URL
- },
- 1,
- constants.TECHNIQUE_CODE_CONFIG_PARSER,
- source
- )
+
+ for rep in repositories:
+ rep["type"] = constants.URL
+
+ metadata_result.add_result(
+ constants.CAT_PACKAGE_DISTRIBUTION,
+ rep,
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ # constants.CAT_PACKAGE_DISTRIBUTION,
+ # {
+ # # "value": repositories,
+ # rep,
+ # "type": constants.URL
+ # },
+ # 1,
+ # constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ # source
+ # )
+
+ if project_data["runtime_platform"]:
+
+ for runtime in project_data["runtime_platform"]:
+ metadata_result.add_result(
+ constants.CAT_RUNTIME_PLATFORM,
+ {
+ "value": runtime["value"],
+ "version": runtime["version"],
+ "name": runtime["name"],
+ "type": constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
metadata = {}
for k, v in project_data.items():
@@ -180,7 +246,8 @@ def parse_pom_file(file_path, metadata_result: Result, source):
metadata_result.add_result(
constants.CAT_HAS_PACKAGE_FILE,
{
- "value": "pom.xml",
+ # "value": "pom.xml",
+ "value": source,
"type": constants.URL
},
1,
@@ -189,7 +256,6 @@ def parse_pom_file(file_path, metadata_result: Result, source):
)
processed_pom = True
-
return metadata_result
def parse_licenses(licenses_node):
@@ -204,16 +270,27 @@ def parse_licenses(licenses_node):
licenses.append(license_data)
return licenses
-def parse_dependencies(dependencies_node):
+def parse_dependencies(node):
dependencies = []
- for key, node in parse_node(dependencies_node):
- if key == "dependency":
+
+ for child in node:
+ if not isinstance(child.tag, str):
+ continue
+
+ tag = child.tag.split("}")[-1]
+ if tag == "dependencies":
+ dependencies.extend(parse_dependencies(child))
+ elif tag == "dependency":
dep_data = {}
- for key2, node2 in parse_node(node):
- if key2 in ["groupId", "artifactId", "version"] and node2.text:
- dep_data[key2] = node2.text
+ for sub in child:
+ if not isinstance(sub.tag, str):
+ continue
+ sub_tag = sub.tag.split("}")[-1]
+ if sub_tag in ["groupId", "artifactId", "version"] and sub.text:
+ dep_data[sub_tag] = sub.text.strip()
if dep_data:
dependencies.append(dep_data)
+
return dependencies
def parse_developers(developers_node):
@@ -222,10 +299,10 @@ def parse_developers(developers_node):
if key == "developer":
dev_data = {}
author_data = {
- "name": None,
- "email": None,
- "url": None,
- "organization": None,
+ # "name": None,
+ # "email": None,
+ # "url": None,
+ # "affiliation": None,
"type": constants.AGENT
}
for key2, node2 in parse_node(node):
@@ -237,10 +314,10 @@ def parse_developers(developers_node):
elif key2 == "url" and node2.text:
author_data["url"] = node2.text
elif key2 == "organization" and node2.text:
- author_data["organization"] = node2.text
+ author_data["affiliation"] = node2.text
- if not author_data["value"]:
- author_data["value"] = author_data["email"] or author_data["organization"]
+ if "value" not in author_data:
+ author_data["value"] = author_data["email"] or author_data["affiliation"]
dev_data.update({k: v for k, v in author_data.items() if k != "type"})
developers.append(dev_data)
return developers
@@ -264,7 +341,61 @@ def parse_repositories(repo_node):
repo_data = {}
for key2, node2 in parse_node(node):
if key2 in ["id", "name", "url"] and node2.text:
- repo_data[key2] = node2.text
+ if key2 == "id":
+ repo_data["value"] = node2.text
+ else:
+ repo_data[key2] = node2.text
if repo_data:
repos.append(repo_data)
- return repos
\ No newline at end of file
+ return repos
+
+def parse_runtime_platform(properties_node):
+ """
+ Extracts runtime platform information from a node in a pom.xml.
+ Returns a list of dictionaries with runtime names and versions, or an empty list if none are found.
+
+ Parameters
+ ----------
+ properties_node : lxml.etree.Element
+ The node from the POM file.
+
+ Returns
+ -------
+ list of dict
+ Each dict has the keys 'name' and 'version', e.g. [{'name': 'Java', 'version': '1.8'}, {'name': 'Python', 'version': '3.11'}].
+ Returns an empty list if no runtime information is present.
+ """
+ runtimes = []
+
+ if properties_node is None:
+ return runtimes
+
+ for child in properties_node:
+ if not isinstance(child.tag, str):
+ continue
+ tag = child.tag.split("}")[-1].lower()
+ text = (child.text or "").strip()
+
+ if not text:
+ continue
+
+ if any(x in tag for x in ["java.version", "javaversion", "java_version"]):
+ runtimes.append({
+ "name": "Java",
+ "version": text,
+ "value": f"Java: {text}"
+ })
+ elif any(x in tag for x in ["kotlin.version", "kotlinversion", "kotlin_version"]):
+ runtimes.append({
+ "name": "Kotlin",
+ "version": text,
+ "value": f"Kotlin: {text}"
+ })
+ elif any(x in tag for x in ["scala.version", "scalaversion", "scala_version"]):
+ runtimes.append({
+ "name": "Scala",
+ "version": text,
+ "value": f"Scala: {text}"
+ })
+
+ return runtimes
\ No newline at end of file
diff --git a/src/somef_core/parser/publiccode_parser.py b/src/somef_core/parser/publiccode_parser.py
new file mode 100644
index 0000000..dc028cf
--- /dev/null
+++ b/src/somef_core/parser/publiccode_parser.py
@@ -0,0 +1,385 @@
+
+import json
+import yaml
+import logging
+from pathlib import Path
+from ..process_results import Result
+from ..utils import constants
+import re
+
+def parse_publiccode_file(file_path, metadata_result: Result, source):
+ """
+ Parse a publiccode.yml file and extract relevant metadata.
+
+ Parameters
+ ----------
+ file_path: path of the publiccode.yml file being analysed
+ metadata_result: metadata object where the metadata dictionary is kept
+ source: source of the package file (URL)
+
+ Returns
+ """
+ print("file_path publiccode:", file_path)
+ try:
+ with open(file_path, 'r', encoding='utf-8') as file:
+ content = yaml.safe_load(file)
+
+ except Exception as e:
+ logging.warning(f"Could not parse publiccode.yml {file_path}: {e}")
+ return metadata_result
+
+ if not isinstance(content, dict):
+ return metadata_result
+
+ if Path(file_path).name.lower() in {"publiccode.yml", "publiccode.yaml"}:
+ print("Detected publiccode.yml file.")
+ metadata_result.add_result(
+ constants.CAT_HAS_PACKAGE_FILE,
+ {
+ "value": source,
+ "type": constants.URL,
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ if "url" in content:
+ metadata_result.add_result(
+ constants.CAT_CODE_REPOSITORY,
+ {
+ "value": content["url"],
+ "type": constants.URL
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+ #application domain: Categories and description.[lang].genericName
+ if "categories" in content:
+ for category in content["categories"]:
+ metadata_result.add_result(
+ constants.CAT_APPLICATION_DOMAIN,
+ {
+ "value": category,
+ "type": constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ if "name" in content:
+ metadata_result.add_result(
+ constants.CAT_NAME,
+ {
+ "value": content["name"],
+ "type": constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ if "description" in content:
+ # this is the structure. But we don't need the language
+ # description:
+ # en:
+ # genericName: Registry
+ # es:
+ # genericName: Registro
+ description = content.get("description", {})
+
+ generic_names = {
+ lang_block["genericName"]
+ for lang_block in description.values()
+ if isinstance(lang_block, dict) and "genericName" in lang_block
+ }
+ for generic_name in generic_names:
+ metadata_result.add_result(
+ constants.CAT_APPLICATION_DOMAIN,
+ {
+ "value": generic_name,
+ "type": constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ short_descriptions = [
+ lang_block.get("shortDescription")
+ for lang_block in description.values()
+ if isinstance(lang_block, dict)
+ ]
+ for short_desc in filter(None, short_descriptions):
+ metadata_result.add_result(
+ constants.CAT_DESCRIPTION,
+ {
+ "value": short_desc.strip(),
+ "type": constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ long_descriptions = [
+ lang_block.get("longDescription")
+ for lang_block in description.values()
+ if isinstance(lang_block, dict)
+ ]
+ for long_desc in filter(None, long_descriptions):
+ metadata_result.add_result(
+ constants.CAT_DESCRIPTION,
+ {
+ "value": long_desc.strip(),
+ "type": constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ names = {
+ lang_block.get("localisedName")
+ for lang_block in description.values()
+ if isinstance(lang_block, dict)
+ }
+ for name in filter(None, names):
+ metadata_result.add_result(
+ constants.CAT_NAME,
+ {
+ "value": name.strip(),
+ "type": constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ keywords = []
+ for lang_block in description.values():
+ if not isinstance(lang_block, dict):
+ continue
+
+ features = lang_block.get("features", [])
+ if isinstance(features, list):
+ for feature in features:
+ if isinstance(feature, str):
+ keywords.append(feature)
+
+ if keywords:
+ metadata_result.add_result(
+ constants.CAT_KEYWORDS,
+ {
+ "value": keywords,
+ "type": constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ if "platforms" in content:
+ for platform in content["platforms"]:
+ metadata_result.add_result(
+ constants.CAT_RUNTIME_PLATFORM,
+ {
+ "value": platform,
+ "type": constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ depends_on = content.get("dependsOn", {})
+# dependsOn:
+# open: or propietary or hardware
+# - name: MySQL
+# versionMin: "1.1"
+# versionMax: "1.3"
+# optional: true
+ for dep_type, dep_list in depends_on.items(): # dep_type = "open", "proprietary", "hardware"
+ for dep in dep_list:
+ name = dep.get("name")
+ version = None
+ version_str = None
+
+ # complex version handling because sometimes is version and sometimes versionMin/versionMax
+ if "version" in dep:
+ version = dep["version"]
+ version_str = f"=={version}"
+ else:
+ version_min = dep.get("versionMin")
+ version_max = dep.get("versionMax")
+ if version_min and version_max:
+ version = f">={version_min},<{version_max}"
+ version_str = version
+ elif version_min:
+ version = f">={version_min}"
+ version_str = version
+ elif version_max:
+ version = f"<{version_max}"
+ version_str = version
+
+
+ metadata_result.add_result(
+ constants.CAT_REQUIREMENTS,
+ {
+ "value": f"{name}{version_str}" if version_str else name,
+ "name": name,
+ "version": version,
+ "type": constants.SOFTWARE_APPLICATION,
+ "dependency_type": constants.DEPENDENCY_TYPE_RUNTIME,
+ "dependency_resolver": "publiccode"
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ if "softwareVersion" in content:
+ metadata_result.add_result(
+ constants.CAT_VERSION,
+ {
+ "value": content["softwareVersion"],
+ "type": constants.RELEASE,
+ "tag": content["softwareVersion"]
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ if "developmentStatus" in content:
+ metadata_result.add_result(
+ constants.CAT_DEV_STATUS,
+ {
+ "value": content["developmentStatus"],
+ "type": constants.RELEASE,
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ if "legal" in content:
+ legal = content["legal"]
+ if "license" in legal:
+ license_info_spdx = detect_license_spdx(legal["license"])
+
+ if license_info_spdx:
+ license_data = {
+ "value": legal["license"],
+ "spdx_id": license_info_spdx.get('spdx_id'),
+ "name": license_info_spdx.get('name'),
+ "type": constants.LICENSE
+ }
+ else:
+ license_data = {
+ "value": legal["license"],
+ "type": constants.LICENSE
+ }
+ metadata_result.add_result(
+ constants.CAT_LICENSE,
+ license_data,
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ if "releaseDate" in content:
+ date_published = content["releaseDate"]
+ # Add both date published and date updated with the same value
+ if date_published:
+ metadata_result.add_result(
+ constants.CAT_DATE_PUBLISHED,
+ {
+ "value": date_published,
+ "type": constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+ metadata_result.add_result(
+ constants.CAT_DATE_UPDATED,
+ {
+ "value": date_published,
+ "type": constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ if "maintenance" in content:
+ maintenance = content.get("maintenance", {})
+ # maintenance_type = maintenance.get("type")
+
+ # if maintenance_type == "contract":
+ role_map = [
+ ("contractors", "maintenance_contractor"),
+ ("contacts", "maintenance_contact"),
+ ]
+
+ for key, role in role_map:
+ for person in maintenance.get(key, []):
+ author_data = {
+ "type": constants.AGENT,
+ "value": person.get("name", ""),
+ "name": person.get("name", ""),
+ "role": role
+ }
+
+ if "email" in person:
+ author_data["email"] = person["email"]
+
+ if "website" in person:
+ author_data["url"] = person["website"]
+
+ if "affiliation" in person:
+ author_data["affiliation"] = person["affiliation"]
+
+ metadata_result.add_result(
+ constants.CAT_OWNER,
+ author_data,
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ return metadata_result
+
+
+def detect_license_spdx(license_text):
+ """
+ Function that given a license text, infers the name and spdx id in a dockerfile
+ Parameters
+ ----------
+ license_text
+
+ Returns
+ -------
+ A JSON dictionary with name and spdx id
+ """
+
+ for license_name, license_info in constants.LICENSES_DICT.items():
+ if re.search(license_info["regex"], license_text, re.IGNORECASE):
+ return {
+ "name": license_name,
+ "spdx_id": f"{license_info['spdx_id']}",
+ "@id": f"https://spdx.org/licenses/{license_info['spdx_id']}"
+ }
+
+ for license_name, license_info in constants.LICENSES_DICT.items():
+ spdx_id = license_info["spdx_id"]
+ if re.search(rf'\b{re.escape(spdx_id)}\b', license_text, re.IGNORECASE):
+ return {
+ "name": license_name,
+ "spdx_id": spdx_id,
+ "@id": f"https://spdx.org/licenses/{spdx_id}"
+ }
+ return None
diff --git a/src/somef_core/parser/python_parser.py b/src/somef_core/parser/python_parser.py
index f0f02e1..7c062da 100644
--- a/src/somef_core/parser/python_parser.py
+++ b/src/somef_core/parser/python_parser.py
@@ -1,6 +1,11 @@
import ast
import os
-import tomli
+# for compatibility in python 3.11 projects
+try:
+ import tomllib as tomli
+except ModuleNotFoundError:
+ import tomli
+
import logging
import re
from pathlib import Path
@@ -13,17 +18,20 @@
"""
def parse_dependency(dependency_str):
+
if not dependency_str:
return None, None
- parts = re.split(r'(>=|<=|==|!=|>|<|~=|\[)', dependency_str, 1)
+ # parts = re.split(r'(>=|<=|==|!=|>|<|~=|\[)', dependency_str, 1)
+ parts = re.split(r'(>=|<=|==|!=|>|<|~=)', dependency_str, 1)
name = parts[0].strip()
if len(parts) > 1:
version = ''.join(parts[1:])
else:
version = ""
- version = version.strip("[]() -.,:")
-
+ # version = version.strip("[]() -.,:")
+ version = re.sub(r'[\[\]\(\)]', '', version)
+
return name, version
def parse_url_type(url_label):
@@ -39,6 +47,8 @@ def parse_url_type(url_label):
return constants.CAT_README_URL
elif "download" in label:
return constants.CAT_DOWNLOAD_URL
+ elif "homepage" in label:
+ return constants.CAT_HOMEPAGE
else:
return constants.CAT_RELATED_DOCUMENTATION
@@ -60,246 +70,6 @@ def get_project_data(data):
project = data["tool"]["poetry"]
return project
-def parse_pyproject_toml(file_path, metadata_result: Result, source):
- """
-
- Parameters
- ----------
- file_path: path to the package file being analysed
- metadata_result: Metadata object dictionary
- source: source of the package file (URL)
-
- Returns
- -------
-
- """
- try:
- if Path(file_path).name == "pyproject.toml":
- metadata_result.add_result(
- constants.CAT_HAS_PACKAGE_FILE,
- {
- "value": "pyproject.toml",
- "type": constants.URL,
- },
- 1,
- constants.TECHNIQUE_CODE_CONFIG_PARSER,
- source
- )
- with open(file_path, "rb") as f:
- data = tomli.load(f)
-
- project = get_project_data(data)
-
- if "name" in project:
- metadata_result.add_result(
- constants.CAT_PACKAGE_ID,
- {
- "value": project["name"],
- "type": constants.STRING
- },
- 1,
- constants.TECHNIQUE_CODE_CONFIG_PARSER,
- source
- )
-
- if "description" in project:
- metadata_result.add_result(
- constants.CAT_DESCRIPTION,
- {
- "value": project["description"],
- "type": constants.STRING
- },
- 1,
- constants.TECHNIQUE_CODE_CONFIG_PARSER,
- source
- )
-
- if "version" in project:
- metadata_result.add_result(
- constants.CAT_VERSION,
- {
- "value": project["version"],
- "type": constants.RELEASE,
- "tag": project["version"]
- },
- 1,
- constants.TECHNIQUE_CODE_CONFIG_PARSER,
- source
- )
-
- dependencies = project.get("dependencies", [])
- if isinstance(dependencies, list):
- for req in dependencies:
- name, version = parse_dependency(req)
- if name:
- metadata_result.add_result(
- constants.CAT_REQUIREMENTS,
- {
- "value": req,
- "name": name,
- "version": version,
- "type": constants.SOFTWARE_APPLICATION
- },
- 1,
- constants.TECHNIQUE_CODE_CONFIG_PARSER,
- source
- )
- elif isinstance(dependencies, dict):
- for name, version in dependencies.items():
- req = f"{name}{version}" if version else name
- metadata_result.add_result(
- constants.CAT_REQUIREMENTS,
- {
- "value": req,
- "name": name,
- "version": version,
- "type": constants.SOFTWARE_APPLICATION
- },
- 1,
- constants.TECHNIQUE_CODE_CONFIG_PARSER,
- source
- )
-
- # This is for detecting the "requires" section in a pyrpoject.toml file
- if "build-system" in data and "requires" in data["build-system"]:
- build_requires = data["build-system"]["requires"]
- if isinstance(build_requires, list):
- for req in build_requires:
- name, version = parse_dependency(req)
- if name:
- metadata_result.add_result(
- constants.CAT_REQUIREMENTS,
- {
- "value": req,
- "name": name,
- "version": version,
- "type": constants.SOFTWARE_APPLICATION
- },
- 1,
- constants.TECHNIQUE_CODE_CONFIG_PARSER,
- source
- )
-
- urls = project.get("urls", {})
- if not urls and "tool" in data and "poetry" in data["tool"]:
-
- poetry = data["tool"]["poetry"]
- if "repository" in poetry:
- urls["repository"] = poetry["repository"]
- if "homepage" in poetry:
- urls["homepage"] = poetry["homepage"]
- if "documentation" in poetry:
- urls["documentation"] = poetry["documentation"]
-
- for url_type, url in urls.items():
- category = parse_url_type(url_type)
- metadata_result.add_result(
- category,
- {
- "value": url,
- "type": constants.URL
- },
- 1,
- constants.TECHNIQUE_CODE_CONFIG_PARSER,
- source
- )
-
- authors = project.get("authors", [])
- if isinstance(authors, list):
- for author in authors:
- if isinstance(author, dict):
- author_data = {
- "name": author.get("name"),
- "email": author.get("email"),
- "type": constants.AGENT,
- "value": author.get("name")
- }
- if author.get("url") is not None:
- author_data["url"] = author.get("url")
- else:
-
- parsed = parse_author_string(author)
- author_data = {
- "name": parsed["name"],
- "email": parsed["email"],
- "type": constants.AGENT,
- "value": parsed["name"]
- }
-
- metadata_result.add_result(
- constants.CAT_AUTHORS,
- author_data,
- 1,
- constants.TECHNIQUE_CODE_CONFIG_PARSER,
- source
- )
-
- if "license" in project:
- license_info = project["license"]
- license_text = ""
-
- if isinstance(license_info, dict):
- # Check if license is specified as a file
- license_path_file = license_info["file"]
- dir_path_license = os.path.dirname(file_path)
- license_path = os.path.join(dir_path_license, license_path_file)
-
- if os.path.exists(license_path):
- with open(license_path, "r", encoding="utf-8") as f:
- license_text = f.read()
-
- if "file" in license_info:
- license_value = f"License file: {license_info['file']}"
- else:
- license_text = license_info.get("text", "")
- license_value = license_info.get("type", "")
- else:
- license_text = ""
- license_value = license_info
-
- license_info_spdx = detect_license_spdx(license_text, 'JSON')
- spdx_id = ""
- spdx_name = ""
-
- if license_info_spdx:
- license_data = {
- "value": license_value,
- "spdx_id": license_info_spdx.get('spdx_id'),
- "name": license_info_spdx.get('name'),
- "type": constants.LICENSE
- }
- else:
- license_data = {
- "value": license_value,
- "type": constants.LICENSE
- }
-
- metadata_result.add_result(
- constants.CAT_LICENSE,
- license_data,
- 1,
- constants.TECHNIQUE_CODE_CONFIG_PARSER,
- source
- )
-
- if "keywords" in project:
- for keyword in project["keywords"]:
- metadata_result.add_result(
- constants.CAT_KEYWORDS,
- {
- "value": keyword,
- "type": constants.STRING
- },
- 1,
- constants.TECHNIQUE_CODE_CONFIG_PARSER,
- source
- )
-
- except Exception as e:
- logging.error(f"Error parsing pyproject.toml from {file_path}: {str(e)}")
-
- return metadata_result
-
def parse_requirements_txt(file_path, metadata_result: Result, source):
"""
Parameters
@@ -314,22 +84,45 @@ def parse_requirements_txt(file_path, metadata_result: Result, source):
"""
try:
if Path(file_path).name.lower() in ["requirements.txt", "requirement.txt"]:
-
-
+
with open(file_path, "r", encoding="utf-8") as f:
- for line in f:
+ lines = f.readlines()
+
+ for line in lines:
line = line.strip()
if not line or line.startswith('#'):
continue
name, version = parse_dependency(line)
if name:
- metadata_result.add_result(
- constants.CAT_REQUIREMENTS,
- {
+ req = {
"value": line,
"name": name,
- "version": version,
"type": constants.SOFTWARE_APPLICATION
+ }
+ if version:
+ req['version'] = version
+
+ metadata_result.add_result(
+ constants.CAT_REQUIREMENTS,
+ req,
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+
+ runtimes = parse_runtime_platform_from_requirements(lines)
+ if runtimes:
+ for runtime in runtimes:
+ metadata_result.add_result(
+ constants.CAT_RUNTIME_PLATFORM,
+ {
+ "value": runtime["value"],
+ "version": runtime["version"],
+ "name": runtime["name"],
+ "type": constants.STRING,
+ "dependency_type": constants.DEPENDENCY_TYPE_RUNTIME,
+ "dependency_resolver": "pip"
},
1,
constants.TECHNIQUE_CODE_CONFIG_PARSER,
@@ -404,7 +197,7 @@ def parse_setup_py(file_path, metadata_result: Result, source):
constants.CAT_AUTHORS,
{
"name": value,
- "email": None,
+ # "email": None,
"type": constants.AGENT,
},
1,
@@ -420,8 +213,12 @@ def parse_setup_py(file_path, metadata_result: Result, source):
value = module_vars[keyword.value.id]
else:
value = ast.literal_eval(keyword.value)
-
- author_results = metadata_result.results.get(constants.CAT_AUTHORS, [])
+
+ # author_results = metadata_result.results.get(constants.CAT_AUTHORS, [])
+ author_results = [
+ a for a in metadata_result.results.get(constants.CAT_AUTHORS, [])
+ if a.get("technique") == constants.TECHNIQUE_CODE_CONFIG_PARSER
+ ]
if author_results:
for result in author_results:
result["result"]["email"] = value
@@ -429,7 +226,7 @@ def parse_setup_py(file_path, metadata_result: Result, source):
metadata_result.add_result(
constants.CAT_AUTHORS,
{
- "name": None,
+ # "name": None,
"email": value,
"type": constants.AGENT,
"value": value
@@ -564,4 +361,72 @@ def parse_setup_py(file_path, metadata_result: Result, source):
except Exception as e:
logging.error(f"Error parsing setup.py from {file_path}: {str(e)}")
- return metadata_result
\ No newline at end of file
+ return metadata_result
+
+def parse_runtime_platform_from_pyproject(project_section):
+ """
+ Given the `[tool.poetry]` section from pyproject.toml,
+ this method extracts the runtime platform(s) declared (e.g. Python version). Return a list of dicts.
+
+ """
+ runtimes = []
+
+ deps = project_section.get("dependencies", {})
+ if isinstance(deps, dict):
+ python_spec = deps.get("python")
+ if python_spec:
+ runtimes.append({"name": "Python", "version": python_spec, "value": f'Python {python_spec}'})
+
+ req_python = project_section.get("requires-python")
+ if req_python:
+ runtimes.append({"name": "Python", "version": req_python, "value": f'Python {python_spec}'})
+
+ return runtimes
+
+def parse_runtime_platform_from_requirements(requirements_lines):
+ """
+ Extracts runtime information (e.g., Python) from a requirements.txt file.
+ Only returns an entry if the runtime is explicitly mentioned with a version.
+
+ Parameters
+ ----------
+ requirements_lines :
+ Lines from the requirements.txt file.
+
+ Returns
+ -------
+ list of dict
+ Each dictionary has the keys 'name' and 'version', e.g.:
+ [{'name': 'Python', 'version': '3.11'}]
+ Returns an empty list if no explicit runtime information is found.
+ """
+ runtimes = []
+
+ if not requirements_lines:
+ return runtimes
+
+ for line in requirements_lines:
+ clean_line = line.strip()
+ if not clean_line or clean_line.startswith('#'):
+ continue
+
+ if clean_line.lower().startswith('python'):
+ version = None
+
+ for sep in ['==', '>=', '<=', '~=', '>', '<']:
+ if sep in clean_line:
+ version = clean_line.split(sep)[1].strip()
+ break
+
+ if version is None:
+ match = re.search(r'python\s*([0-9.]+)', clean_line, re.IGNORECASE)
+ if not match:
+ match = re.search(r'python([0-9.]+)', clean_line, re.IGNORECASE)
+ if match:
+ version = match.group(1)
+
+ if version:
+ runtimes.append({'name': 'Python', 'version': version, 'value': f'Python: {version}'})
+ break
+
+ return runtimes
\ No newline at end of file
diff --git a/src/somef_core/parser/toml_parser.py b/src/somef_core/parser/toml_parser.py
new file mode 100644
index 0000000..04f3e5e
--- /dev/null
+++ b/src/somef_core/parser/toml_parser.py
@@ -0,0 +1,715 @@
+# -*- coding: utf-8 -*-
+
+# for compatibility in python 3.11 projects
+try:
+ import tomllib as tomli
+except ModuleNotFoundError:
+ import tomli
+import re
+import os
+import logging
+from pathlib import Path
+from ..process_results import Result
+from ..utils import constants
+from ..regular_expressions import detect_license_spdx, detect_spdx_from_declared
+
+
+def parse_toml_file(file_path, metadata_result: Result, source):
+ """
+ Unified TOML parser that handles Cargo.toml, pyproject.toml, and Project.toml files.
+
+ Parameters
+ ----------
+ file_path: path to the TOML file being analyzed
+ metadata_result: Metadata object dictionary
+ source: source of the package file (URL)
+
+ Returns
+ -------
+ metadata_result: Updated metadata result object
+ """
+ try:
+ filename = Path(file_path).name.lower()
+
+ if filename == "cargo.toml":
+ file_type = "cargo"
+ display_name = "Cargo.toml"
+
+ elif filename == "pyproject.toml":
+ file_type = "pyproject"
+ display_name = "pyproject.toml"
+
+ elif filename == "project.toml":
+ file_type = "julia_project"
+ display_name = "Project.toml"
+
+ else:
+ logging.warning(f"Unknown TOML file type: {filename}")
+ return metadata_result
+
+ metadata_result.add_result(
+ constants.CAT_HAS_PACKAGE_FILE,
+ {
+ # "value": display_name,
+ "value": source,
+ "type": constants.URL,
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ with open(file_path, "rb") as f:
+ data = tomli.load(f)
+
+ if file_type == "cargo":
+ parse_cargo_metadata(data, metadata_result, source, file_path)
+ elif file_type == "pyproject":
+ parse_pyproject_metadata(data, metadata_result, source, file_path)
+ elif file_type == "julia_project":
+ parse_julia_project_metadata(data, metadata_result, source)
+
+ except Exception as e:
+ logging.error(f"Error parsing TOML file {file_path}: {str(e)}")
+
+ return metadata_result
+
+
+def extract_common_name_field(data, metadata_result, source, file_type):
+ """
+ Extract 'name' field that's common across Cargo.toml, pyproject.toml, and Project.toml.
+
+ For Cargo.toml: data["package"]["name"]
+ For pyproject.toml: data["project"]["name"] or data["tool"]["poetry"]["name"]
+ For Project.toml: data["name"]
+ """
+ name_value = None
+
+ if file_type == "cargo" and "package" in data and "name" in data["package"]:
+ name_value = data["package"]["name"]
+
+ elif file_type == "pyproject":
+ project = get_project_data(data)
+ if "name" in project:
+ name_value = project["name"]
+
+ elif file_type == "julia_project" and "name" in data:
+ name_value = data["name"]
+
+ if name_value:
+ metadata_result.add_result(
+ constants.CAT_PACKAGE_ID,
+ {
+ "value": name_value,
+ "type": constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+
+def extract_common_version_field(data, metadata_result, source, file_type):
+ """
+ Extract 'version' field that's common across all three TOML types.
+
+ For Cargo.toml: data["package"]["version"]
+ For pyproject.toml: data["project"]["version"] or data["tool"]["poetry"]["version"]
+ For Project.toml: data["version"]
+ """
+ version_value = None
+
+ if file_type == "cargo" and "package" in data and "version" in data["package"]:
+ version_value = data["package"]["version"]
+ version_type = constants.RELEASE
+
+ elif file_type == "pyproject":
+ project = get_project_data(data)
+ if "version" in project:
+ version_value = project["version"]
+ version_type = constants.RELEASE
+
+ elif file_type == "julia_project" and "version" in data:
+ version_value = data["version"]
+ version_type = constants.STRING
+
+ if version_value:
+ result_dict = {
+ "value": version_value,
+ "type": version_type
+ }
+ if file_type in ["cargo", "pyproject"]:
+ result_dict["tag"] = version_value
+
+ metadata_result.add_result(
+ constants.CAT_VERSION,
+ result_dict,
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+
+def extract_common_description_field(data, metadata_result, source, file_type):
+ """
+ Extract 'description' field common to Cargo.toml and pyproject.toml.
+
+ For Cargo.toml: data["package"]["description"]
+ For pyproject.toml: data["project"]["description"] or data["tool"]["poetry"]["description"]
+ """
+ description_value = None
+
+ if file_type == "cargo" and "package" in data and "description" in data["package"]:
+ description_value = data["package"]["description"]
+
+ elif file_type == "pyproject":
+ project = get_project_data(data)
+ if "description" in project:
+ description_value = project["description"]
+
+ if description_value:
+ metadata_result.add_result(
+ constants.CAT_DESCRIPTION,
+ {
+ "value": description_value,
+ "type": constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+
+def extract_common_authors_field(data, metadata_result, source, file_type):
+ """
+ Extract 'authors' field common across all three TOML types.
+
+ All three formats can have authors with name and email.
+ """
+ authors = None
+
+ if file_type == "cargo" and "package" in data and "authors" in data["package"]:
+ authors = data["package"]["authors"]
+
+ elif file_type == "pyproject":
+ project = get_project_data(data)
+ if "authors" in project:
+ authors = project["authors"]
+
+ elif file_type == "julia_project" and "authors" in data:
+ authors = data["authors"]
+
+ if authors:
+ for author in authors:
+ if isinstance(author, dict):
+ # pyproject.toml format: {name: "...", email: "..."}
+ author_data = {
+ "name": author.get("name"),
+ "email": author.get("email"),
+ "type": constants.AGENT,
+ "value": author.get("name")
+ }
+ if author.get("url"):
+ author_data["url"] = author.get("url")
+ else:
+ # String format "Name " or just "Name"
+ match = re.match(r'^(.+?)\s*<(.+?)>$', str(author).strip())
+ if match:
+ author_name = match.group(1).strip()
+ author_email = match.group(2).strip()
+ author_data = {
+ "name": author_name,
+ "email": author_email,
+ "type": constants.AGENT,
+ "value": author_name
+ }
+ else:
+ author_data = {
+ "name": str(author).strip(),
+ "email": None,
+ "type": constants.AGENT,
+ "value": str(author).strip()
+ }
+
+ metadata_result.add_result(
+ constants.CAT_AUTHORS,
+ author_data,
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+
+def extract_common_keywords_field(data, metadata_result, source, file_type):
+ """
+ Extract 'keywords' field common to Cargo.toml and pyproject.toml.
+ """
+ keywords = None
+
+ if file_type == "cargo" and "package" in data and "keywords" in data["package"]:
+ keywords = data["package"]["keywords"]
+ elif file_type == "pyproject":
+ project = get_project_data(data)
+ if "keywords" in project:
+ keywords = project["keywords"]
+
+ if keywords:
+ for keyword in keywords:
+ metadata_result.add_result(
+ constants.CAT_KEYWORDS,
+ {
+ "value": keyword,
+ "type": constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+
+def parse_cargo_metadata(data, metadata_result, source, file_path):
+ """Parse Cargo.toml specific metadata."""
+
+ extract_common_name_field(data, metadata_result, source, "cargo")
+ extract_common_version_field(data, metadata_result, source, "cargo")
+ extract_common_description_field(data, metadata_result, source, "cargo")
+ extract_common_authors_field(data, metadata_result, source, "cargo")
+ extract_common_keywords_field(data, metadata_result, source, "cargo")
+
+ if "package" in data and "repository" in data["package"]:
+ metadata_result.add_result(
+ constants.CAT_CODE_REPOSITORY,
+ {
+ "value": data["package"]["repository"],
+ "type": constants.URL
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ if "package" in data and "license" in data["package"]:
+ license_value = data["package"]["license"]
+ license_text = ""
+
+ dir_path = os.path.dirname(file_path)
+ license_paths = [
+ os.path.join(dir_path, "LICENSE"),
+ os.path.join(dir_path, "LICENSE.txt"),
+ os.path.join(dir_path, "LICENSE.md")
+ ]
+
+ for license_path in license_paths:
+ if os.path.exists(license_path):
+ with open(license_path, "r", encoding="utf-8") as lf:
+ license_text = lf.read()
+ break
+
+ license_info_spdx = detect_license_spdx(license_text, 'JSON')
+
+ if license_info_spdx:
+ license_data = {
+ "value": license_value,
+ "spdx_id": license_info_spdx.get('spdx_id'),
+ "name": license_info_spdx.get('name'),
+ "type": constants.LICENSE
+ }
+ else:
+ license_data = {
+ "value": license_value,
+ "type": constants.LICENSE
+ }
+
+ metadata_result.add_result(
+ constants.CAT_LICENSE,
+ license_data,
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ if "dependencies" in data:
+ dependencies = data["dependencies"]
+ for name, info in dependencies.items():
+ version, dep_type, req_details = determine_dependency_type(info)
+ req = f"{name} = {{ {req_details} }}"
+
+ metadata_result.add_result(
+ constants.CAT_REQUIREMENTS,
+ {
+ "value": req,
+ "name": name,
+ "version": version,
+ "type": constants.SOFTWARE_APPLICATION,
+ "dependency_type": dep_type,
+ "dependency_resolver": "cargo" },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ # target-specific dependencies
+ for key in data:
+ if key.startswith("target.") and isinstance(data[key], dict) and "dependencies" in data[key]:
+ target_deps = data[key]["dependencies"]
+ for name, info in target_deps.items():
+ version, dep_type, req_details = determine_dependency_type(info)
+ req = f"{name} = {{ {req_details} }} (target-specific)"
+
+ metadata_result.add_result(
+ constants.CAT_REQUIREMENTS,
+ {
+ "value": req,
+ "name": name,
+ "version": version,
+ "type": constants.SOFTWARE_APPLICATION,
+ "dependency_type": dep_type,
+ "dependency_resolver": "cargo"
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+
+def parse_pyproject_metadata(data, metadata_result, source, file_path):
+ """Parse pyproject.toml specific metadata."""
+ project = get_project_data(data)
+
+ extract_common_name_field(data, metadata_result, source, "pyproject")
+ extract_common_version_field(data, metadata_result, source, "pyproject")
+ extract_common_description_field(data, metadata_result, source, "pyproject")
+ extract_common_authors_field(data, metadata_result, source, "pyproject")
+ extract_common_keywords_field(data, metadata_result, source, "pyproject")
+
+ if "homepage" in project:
+ metadata_result.add_result(
+ constants.CAT_HOMEPAGE,
+ {
+ "value": project["homepage"],
+ "type": constants.URL
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ dependencies = project.get("dependencies", [])
+ if isinstance(dependencies, list):
+ for req in dependencies:
+ name, version = parse_dependency(req)
+ if name:
+ metadata_result.add_result(
+ constants.CAT_REQUIREMENTS,
+ {
+ "value": req,
+ "name": name,
+ "version": version,
+ "type": constants.SOFTWARE_APPLICATION,
+ "dependency_type":constants.DEPENDENCY_TYPE_RUNTIME,
+ "dependency_resolver": "python"
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+ elif isinstance(dependencies, dict):
+ for name, version in dependencies.items():
+ req = f"{name}{version}" if version else name
+ metadata_result.add_result(
+ constants.CAT_REQUIREMENTS,
+ {
+ "value": req,
+ "name": name,
+ "version": version,
+ "type": constants.SOFTWARE_APPLICATION,
+ "dependency_type": constants.DEPENDENCY_TYPE_RUNTIME,
+ "dependency_resolver": "python"
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ # build-system requires
+ if "build-system" in data and "requires" in data["build-system"]:
+ build_requires = data["build-system"]["requires"]
+ if isinstance(build_requires, list):
+ for req in build_requires:
+ name, version = parse_dependency(req)
+ if name:
+ metadata_result.add_result(
+ constants.CAT_REQUIREMENTS,
+ {
+ "value": req,
+ "name": name,
+ "version": version,
+ "type": constants.SOFTWARE_APPLICATION,
+ "dependency_type": constants.DEPENDENCY_TYPE_RUNTIME,
+ "dependency_resolver": "python"
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ urls = project.get("urls", {})
+
+ if not urls and "tool" in data and "poetry" in data["tool"]:
+ poetry = data["tool"]["poetry"]
+ if "repository" in poetry:
+ urls["repository"] = poetry["repository"]
+ if "homepage" in poetry:
+ urls["homepage"] = poetry["homepage"]
+ if "documentation" in poetry:
+ urls["documentation"] = poetry["documentation"]
+
+ for url_type, url in urls.items():
+ category = parse_url_type(url_type)
+ metadata_result.add_result(
+ category,
+ {
+ "value": url,
+ "type": constants.URL
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ if "license" in project:
+ license_info = project["license"]
+ license_text = ""
+
+ if isinstance(license_info, dict):
+ license_path_file = license_info.get("file")
+ if license_path_file:
+ dir_path_license = os.path.dirname(file_path)
+ license_path = os.path.join(dir_path_license, license_path_file)
+
+ if os.path.exists(license_path):
+ with open(license_path, "r", encoding="utf-8") as f:
+ license_text = f.read()
+
+ license_value = f"License file: {license_path_file}"
+ else:
+ license_text = license_info.get("text", "")
+ license_value = license_info.get("type", "")
+ else:
+ license_text = ""
+ license_value = license_info
+
+
+ licence_info_spdx = None
+
+ if license_text:
+ license_info_spdx = detect_license_spdx(license_text, 'JSON')
+ else:
+ # there is no text in licence and we cant detect license in the text as usual,
+ # so we check if the declared license matches an SPDX ID from our dictionary of licences
+ license_info_spdx = detect_spdx_from_declared(license_value)
+
+ if license_info_spdx:
+ license_data = {
+ "value": license_value,
+ "spdx_id": license_info_spdx.get('spdx_id'),
+ "name": license_info_spdx.get('name'),
+ "type": constants.LICENSE
+ }
+ else:
+ license_data = {
+ "value": license_value,
+ "type": constants.LICENSE
+ }
+
+ metadata_result.add_result(
+ constants.CAT_LICENSE,
+ license_data,
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ runtimes = parse_runtime_platform_from_pyproject(project)
+ if runtimes:
+ for runtime in runtimes:
+ metadata_result.add_result(
+ constants.CAT_RUNTIME_PLATFORM,
+ {
+ "value": f'{runtime["name"]}{runtime["version"]}',
+ "name": runtime["name"],
+ "version": runtime["version"],
+ "type": constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+
+def parse_julia_project_metadata(data, metadata_result, source):
+ """Parse Project.toml (Julia) specific metadata."""
+ extract_common_name_field(data, metadata_result, source, "julia_project")
+ extract_common_version_field(data, metadata_result, source, "julia_project")
+ extract_common_authors_field(data, metadata_result, source, "julia_project")
+
+ if "uuid" in data:
+ metadata_result.add_result(
+ constants.CAT_IDENTIFIER,
+ {
+ "value": data["uuid"],
+ "type": constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ if "deps" in data:
+ deps = data["deps"]
+ for req in deps.keys():
+ metadata_result.add_result(
+ constants.CAT_REQUIREMENTS,
+ {
+ "value": req,
+ "name": req,
+ "type": constants.SOFTWARE_APPLICATION,
+ "dependency_type": constants.DEPENDENCY_TYPE_RUNTIME,
+ "dependency_resolver": "julia"
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ # devs requirements
+ dev_deps = set()
+
+ if "extras" in data:
+ dev_deps.update(data["extras"].keys())
+
+ if "targets" in data and "test" in data["targets"]:
+ dev_deps.update(data["targets"]["test"])
+
+ # Emit dev deps
+ for req in dev_deps:
+ metadata_result.add_result(
+ constants.CAT_REQUIREMENTS,
+ {
+ "value": req,
+ "name": req,
+ "type": constants.SOFTWARE_APPLICATION,
+ "dependency_type": constants.DEPENDENCY_TYPE_DEVELOPMENT,
+ "dependency_resolver": "julia"
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+ if "compat" in data:
+ compat = data["compat"]
+ for package_name, version in compat.items():
+ metadata_result.add_result(
+ constants.CAT_RUNTIME_PLATFORM,
+ {
+ "value": f"{package_name}",
+ "package": package_name,
+ "version": version,
+ "type": constants.STRING
+ },
+ 1,
+ constants.TECHNIQUE_CODE_CONFIG_PARSER,
+ source
+ )
+
+
+# Helper functions
+
+def get_project_data(data):
+ """Get project section from pyproject.toml (handles both standard and Poetry formats)."""
+ project = data.get("project", {})
+ if not project and "tool" in data and "poetry" in data["tool"]:
+ project = data["tool"]["poetry"]
+ return project
+
+
+def parse_dependency(dependency_str):
+ """Parse a dependency string to extract name and version."""
+ if not dependency_str:
+ return None, None
+
+ parts = re.split(r'(>=|<=|==|!=|>|<|~=)', dependency_str, 1)
+ name = parts[0].strip()
+ if len(parts) > 1:
+ version = ''.join(parts[1:])
+ else:
+ version = ""
+
+ version = re.sub(r'[\[\]]', '', version)
+
+ return name, version
+
+
+def parse_url_type(url_label):
+ """Determine the category for a URL based on its label."""
+ label = url_label.lower()
+
+ if "repository" in label or label in ("git", "github", "code", "sourcecode", "source"):
+ return constants.CAT_CODE_REPOSITORY
+ elif "issue" in label or label in ("bug tracker", "tracker", "issues"):
+ return constants.CAT_ISSUE_TRACKER
+ elif "doc" in label or label in ("documentation", "api reference", "reference"):
+ return constants.CAT_DOCUMENTATION
+ elif label == "readme":
+ return constants.CAT_README_URL
+ elif "download" in label:
+ return constants.CAT_DOWNLOAD_URL
+ elif "homepage" in label:
+ return constants.CAT_HOMEPAGE
+ else:
+ return constants.CAT_RELATED_DOCUMENTATION
+
+
+def determine_dependency_type(info):
+ """Determine the type of dependency for Cargo.toml."""
+ if isinstance(info, dict):
+ if "version" in info:
+ version = info["version"]
+ req = f"version = {version}"
+ dep_type = "version"
+ elif "git" in info:
+ version = info["git"]
+ req = f"git = \"{version}\""
+ dep_type = "url"
+ elif "path" in info:
+ version = info["path"]
+ req = f"path = \"{version}\""
+ dep_type = "path"
+ else:
+ version = str(info)
+ req = f"other = {version}"
+ dep_type = "other"
+ else:
+ version = info
+ req = f"version = {version}"
+ dep_type = "version"
+
+ return version, dep_type, req
+
+
+def parse_runtime_platform_from_pyproject(project_section):
+ """Extract runtime platform from pyproject.toml."""
+ runtimes = []
+
+ deps = project_section.get("dependencies", {})
+ if isinstance(deps, dict):
+ python_spec = deps.get("python")
+ if python_spec:
+ runtimes.append({"name": "Python", "version": python_spec})
+
+ req_python = project_section.get("requires-python")
+ if req_python:
+ runtimes.append({"name": "Python", "version": req_python})
+
+ return runtimes
diff --git a/src/somef_core/process_files.py b/src/somef_core/process_files.py
index 548bb39..af27c3d 100644
--- a/src/somef_core/process_files.py
+++ b/src/somef_core/process_files.py
@@ -3,22 +3,36 @@
import re
import urllib
import yaml
+import string
from urllib.parse import urlparse
-from .utils import constants, markdown_utils
+from .utils import constants
+# from . import extract_workflows, extract_ontologies
from . import extract_workflows
from .process_results import Result
from .regular_expressions import detect_license_spdx, extract_scholarly_article_natural, extract_scholarly_article_properties
from .parser.pom_xml_parser import parse_pom_file
from .parser.package_json_parser import parse_package_json_file
-from .parser.python_parser import parse_pyproject_toml
from .parser.python_parser import parse_setup_py
-from.parser.python_parser import parse_requirements_txt
+from .parser.codemeta_parser import parse_codemeta_json_file
+from .parser.composer_parser import parse_composer_json
+from .parser.python_parser import parse_requirements_txt
+from .parser.authors_parser import parse_author_file
+from .parser.bower_parser import parse_bower_json_file
+from .parser.gemspec_parser import parse_gemspec_file
+from .parser.description_parser import parse_description_file
+from .parser.toml_parser import parse_toml_file
+from .parser.cabal_parser import parse_cabal_file
+from .parser.dockerfile_parser import parse_dockerfile
+from .parser.publiccode_parser import parse_publiccode_file
+from .parser.codeowners_parser import parse_codeowners_file
+from .parser.conda_environment_parser import parse_conda_environment_file
from chardet import detect
+
domain_gitlab = ''
def process_repository_files(repo_dir, metadata_result: Result, repo_type, owner="", repo_name="",
- repo_default_branch="", ignore_test_folder=True):
+ repo_default_branch="", ignore_test_folder=True, reconcile_authors=False):
"""
Method that given a folder, it recognizes whether there are notebooks, dockerfiles, docs, script files or
ontologies.
@@ -37,16 +51,30 @@ def process_repository_files(repo_dir, metadata_result: Result, repo_type, owner
@return: text of the main readme and a JSON dictionary (filtered_resp) with the findings in files
"""
+ global domain_gitlab
if repo_type == constants.RepositoryType.GITLAB:
domain_gitlab = extract_gitlab_domain(metadata_result, repo_type)
text = ""
+ readmeMD_proccesed = False
+
+ is_local_repo = (owner == "" and repo_name == "")
+
try:
+ parsed_build_files = set()
+
for dir_path, dir_names, filenames in os.walk(repo_dir):
+
+ dir_names[:] = [d for d in dir_names if d.lower() not in constants.IGNORED_DIRS]
+ if is_local_repo:
+ dir_names[:] = [d for d in dir_names if d.lower() != "lib"]
+
repo_relative_path = os.path.relpath(dir_path, repo_dir)
+ current_dir = os.path.basename(repo_relative_path).lower()
# if this is a test folder, we ignore it (except for the root repo)
- if ignore_test_folder and repo_relative_path != "." and "test" in repo_relative_path.lower():
- # skip this file if it's in a test folder, or inside one
+ # if ignore_test_folder and repo_relative_path != "." and "test" in repo_relative_path.lower():
+ if ignore_test_folder and repo_relative_path != "." and current_dir in constants.IGNORED_DIRS:
+ # skip this file if it's in a test folder, ignored dire, or inside one
continue
for filename in filenames:
file_path = os.path.join(repo_relative_path, filename)
@@ -58,18 +86,31 @@ def process_repository_files(repo_dir, metadata_result: Result, repo_type, owner
if filename == "Dockerfile" or filename.lower() == "docker-compose.yml":
docker_url = get_file_link(repo_type, file_path, owner, repo_name, repo_default_branch, repo_dir,
repo_relative_path, filename)
+
+ # full_path = os.path.join(repo_dir, file_path)
+
+ result_value = {
+ constants.PROP_VALUE: docker_url,
+ constants.PROP_TYPE: constants.URL,
+ }
+
if filename == "Dockerfile":
format_file = constants.FORMAT_DOCKERFILE
+ result_value[constants.PROP_FORMAT] = format_file
+ metadata_result = parse_dockerfile(os.path.join(dir_path, filename), metadata_result, docker_url)
else:
format_file = constants.FORMAT_DOCKER_COMPOSE
- metadata_result.add_result(constants.CAT_HAS_BUILD_FILE,
- {
- constants.PROP_VALUE: docker_url,
- constants.PROP_TYPE: constants.URL,
- constants.PROP_FORMAT: format_file
- },
- 1,
- constants.TECHNIQUE_FILE_EXPLORATION, docker_url)
+
+ result_value[constants.PROP_FORMAT] = format_file
+
+ metadata_result.add_result(
+ constants.CAT_HAS_BUILD_FILE,
+ result_value,
+ 1,
+ constants.TECHNIQUE_FILE_EXPLORATION,
+ docker_url
+ )
+
if filename.lower().endswith(".ipynb"):
notebook_url = get_file_link(repo_type, file_path, owner, repo_name, repo_default_branch, repo_dir,
repo_relative_path, filename)
@@ -84,29 +125,51 @@ def process_repository_files(repo_dir, metadata_result: Result, repo_type, owner
filename_no_ext = os.path.splitext(filename)[0]
# this will take into account README, README.MD, README.TXT, README.RST
if "README" == filename_no_ext.upper():
- if repo_relative_path == ".":
- try:
- with open(os.path.join(dir_path, filename), "rb") as data_file:
- data_file_text = data_file.read()
- try:
- text = data_file_text.decode("utf-8")
- except UnicodeError as err:
- logging.error(f"{type(err).__name__} was raised: {err} Trying other encodings...")
- text = data_file_text.decode(detect(data_file_text)["encoding"])
- if repo_type == constants.RepositoryType.GITHUB:
- readme_url = convert_to_raw_user_content_github(filename, owner,
- repo_name,
- repo_default_branch)
- metadata_result.add_result(constants.CAT_README_URL,
- {
- constants.PROP_VALUE: readme_url,
- constants.PROP_TYPE: constants.URL
- },
- 1,
- constants.TECHNIQUE_FILE_EXPLORATION)
- except ValueError:
- logging.error("README Error: error while reading file content")
- logging.error(f"{type(err).__name__} was raised: {err}")
+ # There is unexpected behavior when the README is available in multiple formats.
+ # We prioritize the .md format as it is more readable than pdf and others
+ if not readmeMD_proccesed:
+ if repo_relative_path == ".":
+ try:
+
+ with open(os.path.join(dir_path, filename), "rb") as data_file:
+ data_file_text = data_file.read()
+
+ try:
+ text = data_file_text.decode("utf-8")
+ except UnicodeError as err:
+ logging.error(f"{type(err).__name__} was raised: {err} Trying other encodings...")
+ # text = data_file_text.decode(detect(data_file_text)["encoding"])
+ result_detect = detect(data_file_text)
+ encoding = result_detect.get("encoding")
+ if encoding:
+ try:
+ text = data_file_text.decode(encoding)
+ except UnicodeError as err:
+ logging.warning(f"Detected encoding '{encoding}' failed: {err}. Using utf-8 with replacement.")
+ text = data_file_text.decode("utf-8", errors="replace")
+ else:
+ logging.warning("Could not detect encoding. Using utf-8 with replacement.")
+ text = data_file_text.decode("utf-8", errors="replace")
+ text = clean_text(text)
+
+ if repo_type == constants.RepositoryType.GITHUB:
+ readme_url = convert_to_raw_user_content_github(filename, owner,
+ repo_name,
+ repo_default_branch)
+ metadata_result.add_result(constants.CAT_README_URL,
+ {
+ constants.PROP_VALUE: readme_url,
+ constants.PROP_TYPE: constants.URL
+ },
+ 1,
+ constants.TECHNIQUE_FILE_EXPLORATION)
+
+ if filename.upper() == "README.MD":
+ readmeMD_proccesed = True
+
+ except ValueError:
+ logging.error("README Error: error while reading file content")
+ logging.error(f"{type(err).__name__} was raised: {err}")
if ("LICENCE" == filename.upper() or "LICENSE" == filename.upper() or "LICENSE.MD"== filename.upper()
or "LICENSE.RST"== filename.upper()):
metadata_result = get_file_content_or_link(repo_type, file_path, owner, repo_name,
@@ -133,7 +196,13 @@ def process_repository_files(repo_dir, metadata_result: Result, repo_type, owner
metadata_result = get_file_content_or_link(repo_type, file_path, owner, repo_name,
repo_default_branch,
repo_dir, repo_relative_path, filename, dir_path,
- metadata_result, constants.CAT_CONTRIBUTORS)
+ metadata_result, constants.CAT_CONTRIBUTORS)
+ if "AUTHORS" == filename.upper() or "AUTHORS.MD" == filename.upper() or "AUTHORS.TXT" == filename.upper():
+ metadata_result = get_file_content_or_link(repo_type, file_path, owner, repo_name,
+ repo_default_branch,
+ repo_dir, repo_relative_path, filename, dir_path,
+ metadata_result, constants.CAT_AUTHORS)
+
if "INSTALL" in filename.upper() and filename.upper().endswith("MD"):
metadata_result = get_file_content_or_link(repo_type, file_path, owner, repo_name,
repo_default_branch,
@@ -162,15 +231,40 @@ def process_repository_files(repo_dir, metadata_result: Result, repo_type, owner
constants.PROP_TYPE: constants.URL
}, 1, constants.TECHNIQUE_FILE_EXPLORATION
)
+
if filename.upper() == constants.CODEOWNERS_FILE:
- codeowners_json = parse_codeowners_structured(dir_path,filename)
+ # codeowners_json = parse_codeowners_structured(dir_path,filename)
+ logging.info("Processing CODEOWNERS file...")
+ codeowner_file_url = get_file_link(repo_type, file_path, owner, repo_name, repo_default_branch,
+ repo_dir,
+ repo_relative_path, filename)
+
+ metadata_result = parse_codeowners_file(os.path.join(dir_path, filename), metadata_result, codeowner_file_url, reconcile_authors, repo_type, server_url=domain_gitlab)
+ parsed_build_files.add(filename.lower())
+
+ if filename.lower() == "codemeta.json":
+ if filename.lower() in parsed_build_files and repo_relative_path != ".":
+ logging.info(f"Ignoring secondary {filename} in {dir_path}")
+ continue
+
+ codemeta_file_url = get_file_link(repo_type, file_path, owner, repo_name, repo_default_branch, repo_dir, repo_relative_path, filename)
+ metadata_result = parse_codemeta_json_file(os.path.join(dir_path, filename), metadata_result, codemeta_file_url)
+ parsed_build_files.add(filename.lower())
- # TO DO: Code owners not fully implemented yet
if filename.lower() == "pom.xml" or filename.lower() == "package.json" or \
- filename.lower() == "pyproject.toml" or filename.lower() == "setup.py" or filename.lower() == "requirements.txt":
+ filename.lower() == "pyproject.toml" or filename.lower() == "setup.py" or filename.endswith(".gemspec") or \
+ filename.lower() == "requirements.txt" or filename.lower() == "bower.json" or filename == "DESCRIPTION" or \
+ (filename.lower() == "environment.yml" or filename.lower() == "environment.yaml") or \
+ (filename.lower() == "cargo.toml" and repo_relative_path == ".") or (filename.lower() == "composer.json" and repo_relative_path == ".") or \
+ (filename == "Project.toml" or (filename.lower()== "publiccode.yml" or filename.lower()== "publiccode.yaml") and repo_relative_path == "."):
+ if filename.lower() in parsed_build_files and repo_relative_path != ".":
+ logging.info(f"Ignoring secondary {filename} in {dir_path}")
+ continue
+
build_file_url = get_file_link(repo_type, file_path, owner, repo_name, repo_default_branch,
repo_dir,
repo_relative_path, filename)
+
metadata_result.add_result(constants.CAT_HAS_BUILD_FILE,
{
constants.PROP_VALUE: build_file_url,
@@ -179,18 +273,35 @@ def process_repository_files(repo_dir, metadata_result: Result, repo_type, owner
},
1,
constants.TECHNIQUE_FILE_EXPLORATION, build_file_url)
- logging.info(f"############### Processing package file: {filename} ############### ")
+ logging.info(f"############### (NEW UPDATE) Processing package file: {filename} ############### ")
if filename.lower() == "pom.xml":
metadata_result = parse_pom_file(os.path.join(dir_path, filename), metadata_result, build_file_url)
if filename.lower() == "package.json":
metadata_result = parse_package_json_file(os.path.join(dir_path, filename), metadata_result, build_file_url)
- if filename.lower() == "pyproject.toml":
- metadata_result = parse_pyproject_toml(os.path.join(dir_path, filename), metadata_result, build_file_url)
if filename.lower() == "setup.py":
metadata_result = parse_setup_py(os.path.join(dir_path, filename), metadata_result, build_file_url)
if filename.lower() == "requirements.txt":
metadata_result = parse_requirements_txt(os.path.join(dir_path, filename), metadata_result, build_file_url)
-
+ if filename.lower() == "bower.json":
+ metadata_result = parse_bower_json_file(os.path.join(dir_path, filename), metadata_result, build_file_url)
+ if filename.lower() == "composer.json":
+ metadata_result = parse_composer_json(os.path.join(dir_path, filename), metadata_result, build_file_url)
+ if filename.endswith(".gemspec"):
+ metadata_result = parse_gemspec_file(os.path.join(dir_path, filename), metadata_result, build_file_url)
+ if filename == "DESCRIPTION":
+ metadata_result = parse_description_file(os.path.join(dir_path, filename), metadata_result, build_file_url)
+ if filename.lower() == "pyproject.toml" or filename.lower() == "cargo.toml" or filename == "Project.toml":
+ metadata_result = parse_toml_file(os.path.join(dir_path, filename), metadata_result, build_file_url)
+ if filename.endswith == ".cabal":
+ metadata_result = parse_cabal_file(os.path.join(dir_path, filename), metadata_result, build_file_url)
+ if filename.lower() == "publiccode.yml" or filename.lower() == "publiccode.yaml":
+ metadata_result = parse_publiccode_file(os.path.join(dir_path, filename), metadata_result, build_file_url)
+ if filename.lower() == "environment.yml" or filename.lower() == "environment.yaml":
+ print("Processing conda environment file...")
+ metadata_result = parse_conda_environment_file(os.path.join(dir_path, filename), metadata_result, build_file_url)
+
+ parsed_build_files.add(filename.lower())
+
# if repo_type == constants.RepositoryType.GITLAB:
if filename.endswith(".yml"):
if repo_type == constants.RepositoryType.GITLAB:
@@ -202,14 +313,16 @@ def process_repository_files(repo_dir, metadata_result: Result, repo_type, owner
{
constants.PROP_VALUE: workflow_url_gitlab,
constants.PROP_TYPE: constants.URL
- }, 1, constants.TECHNIQUE_FILE_EXPLORATION)
+ }, 1, constants.TECHNIQUE_FILE_EXPLORATION)
+ elif extract_workflows.is_file_workflow(os.path.join(repo_dir, file_path)):
+ workflow_url = get_file_link(repo_type, file_path, owner, repo_name, repo_default_branch,
+ repo_dir, repo_relative_path, filename)
+ metadata_result.add_result(constants.CAT_WORKFLOWS,
+ {
+ constants.PROP_VALUE: workflow_url,
+ constants.PROP_TYPE: constants.URL
+ }, 1, constants.TECHNIQUE_FILE_EXPLORATION)
elif repo_type == constants.RepositoryType.GITHUB:
- # if file_path.startswith(".github/workflows/"):
- # category = constants.CAT_WORKFLOWS
- # elif filename in [".travis.yml", "azure-pipelines.yml", "jenkinsfile"] or file_path.startswith(".circleci/"):
- # category = constants.CAT_CONTINUOUS_INTEGRATION
- # else:
- # category = None
if file_path.startswith(".github/workflows/"):
category = constants.CAT_CONTINUOUS_INTEGRATION
else:
@@ -220,7 +333,23 @@ def process_repository_files(repo_dir, metadata_result: Result, repo_type, owner
repo_dir, repo_relative_path, filename)
metadata_result.add_result(category,
{constants.PROP_VALUE: workflow_url, constants.PROP_TYPE: constants.URL},
- 1, constants.TECHNIQUE_FILE_EXPLORATION)
+ 1, constants.TECHNIQUE_FILE_EXPLORATION)
+
+ if filename.endswith(".ga") or filename.endswith(".cwl") or filename.endswith(".nf") or (
+ filename.endswith(".snake") or filename.endswith(
+ ".smk") or "Snakefile" == filename_no_ext) or filename.endswith(".knwf") or filename.endswith(
+ ".t2flow") or filename.endswith(".dag") or filename.endswith(".kar") or filename.endswith(
+ ".wdl"):
+ analysis = extract_workflows.is_file_workflow(os.path.join(repo_dir, file_path))
+ if analysis:
+ workflow_url = get_file_link(repo_type, file_path, owner, repo_name, repo_default_branch,
+ repo_dir, repo_relative_path, filename)
+ metadata_result.add_result(constants.CAT_WORKFLOWS,
+ {
+ constants.PROP_VALUE: workflow_url,
+ constants.PROP_TYPE: constants.URL
+ }, 1, constants.TECHNIQUE_FILE_EXPLORATION)
+
if 'citation' in metadata_result.results:
for cit in metadata_result.results['citation']:
scholarly_article = {}
@@ -356,6 +485,33 @@ def get_file_content_or_link(repo_type, file_path, owner, repo_name, repo_defaul
if license_info:
result[constants.PROP_NAME] = license_info['name']
result[constants.PROP_SPDX_ID] = license_info['spdx_id']
+
+ if category is constants.CAT_AUTHORS:
+ result = {}
+ authors_list = parse_author_file(file_text)
+ for author_l in authors_list:
+
+ author_data = {
+ "name": author_l.get("name"),
+ "type": constants.AGENT,
+ "value": author_l.get("name")
+ }
+
+ if author_l.get("url") is not None:
+ author_data["url"] = author_l.get("url")
+ if author_l.get("email") is not None:
+ author_data["email"] = author_l.get("email")
+ if author_l["type"] == "Person":
+ if author_l.get("last_name") is not None:
+ author_data["last_name"] = author_l.get("last_name")
+ if author_l.get("given_name") is not None:
+ author_data["given_name"] = author_l.get("given_name")
+ metadata_result.add_result(
+ constants.CAT_AUTHORS,
+ author_data,
+ 1,
+ constants.TECHNIQUE_FILE_EXPLORATION, url
+ )
# Properties extraction from cff
if format_result == 'cff':
yaml_content = yaml.safe_load(file_text)
@@ -364,13 +520,13 @@ def get_file_content_or_link(repo_type, file_path, owner, repo_name, repo_defaul
identifiers = yaml_content.get("identifiers", [])
url_citation = preferred_citation.get("url") or yaml_content.get("url")
+ if identifiers:
+ result[constants.CAT_IDENTIFIER] = identifiers
+
identifier_url = next((id["value"] for id in identifiers if id["type"] == "url"), None)
identifier_doi = next((id["value"] for id in identifiers if id["type"] == "doi"), None)
-
+
title = yaml_content.get("title") or preferred_citation.get("title", None)
- # doi = preferred_citation.get("doi", None)
- # url_citation = preferred_citation.get("url", None)
- # authors = preferred_citation.get("authors", [])
authors = yaml_content.get("authors", [])
if identifier_doi:
@@ -415,16 +571,8 @@ def get_file_content_or_link(repo_type, file_path, owner, repo_name, repo_defaul
if author_list:
result[constants.PROP_AUTHOR] = author_list
-
-
- # author_names = ", ".join(
- # f"{a.get('given-names', '').strip()} {a.get('family-names', '').strip()}".strip()
- # for a in authors if a.get('given-names') and a.get('family-names')
- # ) or None
if title:
result[constants.PROP_TITLE] = title
- # if author_names:
- # result[constants.PROP_AUTHOR] = author_names
if final_url:
result[constants.PROP_URL] = final_url
if doi:
@@ -502,17 +650,11 @@ def extract_gitlab_domain(metadata_result, repo_type):
return domain
return None
-def parse_codeowners_structured(dir_path, filename):
- codeowners = []
-
- with open(os.path.join(dir_path, filename), "r", encoding="utf-8") as file:
- for line in file:
- line = line.strip()
- if line and not line.startswith("#"):
- parts = line.split()
- path = parts[0] # Primera parte es el path o patrón
- owners = parts[1:] # Lo demás son los propietarios
- codeowners.append({"path": path, "owners": owners})
-
- return {"codeowners": codeowners}
+def clean_text(text):
+ cleaned_lines = []
+ for line in text.splitlines():
+ printable_chars = sum(1 for c in line if c in string.printable)
+ if len(line) == 0 or (printable_chars / len(line)) > 0.9:
+ cleaned_lines.append(line)
+ return "\n".join(cleaned_lines)
diff --git a/src/somef_core/process_repository.py b/src/somef_core/process_repository.py
index 280fc5e..9836dae 100644
--- a/src/somef_core/process_repository.py
+++ b/src/somef_core/process_repository.py
@@ -11,6 +11,7 @@
from . import configuration
from .process_results import Result
from .regular_expressions import detect_license_spdx
+from .parser.codeowners_parser import enrich_user
# Constructs a template HTTP header, which:
# - has a key for the authorization token if passed via the authorization argument, otherwise
@@ -30,7 +31,6 @@ def is_gitlab(gitlab_server):
api_url = f"https://{gitlab_server}/api/v4/projects"
try:
response = requests.get(api_url, timeout=5)
- print(response.status_code)
if response.status_code in [200, 401, 403]:
return True
except requests.RequestException:
@@ -38,14 +38,54 @@ def is_gitlab(gitlab_server):
return False
# the same as requests.get(args).json(), but protects against rate limiting
-def rate_limit_get(*args, backoff_rate=2, initial_backoff=1, **kwargs):
+def rate_limit_get(*args, backoff_rate=2, initial_backoff=1, size_limit_mb=constants.SIZE_DOWNLOAD_LIMIT_MB, **kwargs):
+# def rate_limit_get(*args, backoff_rate=2, initial_backoff=1, **kwargs):
"""Function to obtain how many requests we have pending with the GitHub API"""
+
+ """GET request that handles rate limiting and prevents downloading excessively large files"""
+ size_limit_bytes = size_limit_mb * 1024 * 1024
+ url = args[0] if args else kwargs.get("url")
+ if not url:
+ raise ValueError("Missing URL in rate_limit_get")
+
+ parsed = urlparse(url)
+ is_api_request = "api.github.com" in parsed.netloc
+ content_length = None
+ # just verify size if NOT is a request to api.github.com
+ if not is_api_request:
+ try:
+ head_response = requests.get(url, stream=True, allow_redirects=True, **kwargs)
+ content_length = head_response.headers.get("Content-Length")
+ if content_length is not None:
+ size_bytes = int(content_length)
+ if size_bytes > size_limit_bytes:
+ logging.warning(
+ f"Download size {size_bytes} bytes exceeds limit of {size_limit_bytes} bytes. Skipping download."
+ )
+ return None, None
+ else:
+ logging.warning(f"No Content-Length header for {url}. Proceeding with download anyway (unable to estimate size).")
+ except Exception as e:
+ logging.warning(f"HEAD/stream request failed: {e}. Continuing with GET...")
+
rate_limited = True
- response = {}
date = ""
+ response = {}
while rate_limited:
- response = requests.get(*args, **kwargs)
- date = response.headers["Date"]
+ use_stream = not is_api_request and (content_length is None or int(content_length) > 10 * 1024 * 1024)
+ response = requests.get(
+ *args,
+ timeout=(constants.DOWNLOAD_TIMEOUT_SECONDS, constants.DOWNLOAD_TIMEOUT_SECONDS),
+ stream=use_stream,
+ **kwargs
+ )
+ # Detect invalid or insufficient GitHub token
+ if response.status_code == 401:
+ raise Exception("Invalid GitHub token. Run `somef configure` to set a valid token.")
+ if response.status_code == 403:
+ raise Exception("GitHub token lacks required permissions or scopes.")
+
+ date = response.headers.get("Date", "")
# Show rate limit information if available
if "X-RateLimit-Remaining" in response.headers:
rate_limit_remaining = response.headers["X-RateLimit-Remaining"]
@@ -54,14 +94,54 @@ def rate_limit_get(*args, backoff_rate=2, initial_backoff=1, **kwargs):
logging.info(
"Remaining GitHub API requests: " + rate_limit_remaining + " ### Next rate limit reset at: " + str(
date_reset))
- if 'message' in response and 'API rate limit exceeded' in response['message']:
- rate_limited = True
- logging.warning(f"rate limited. Backing off for {initial_backoff} seconds")
- time.sleep(initial_backoff)
- # increase the backoff for next time
- initial_backoff *= backoff_rate
- else:
- rate_limited = False
+
+ if not use_stream:
+ try:
+ json_data = response.json()
+ msg = json_data.get("message", "")
+ if "API rate limit exceeded" in msg:
+ rate_limited = True
+ logging.warning(f"Rate limited. Backing off for {initial_backoff} seconds")
+ time.sleep(initial_backoff)
+ initial_backoff *= backoff_rate
+ continue
+ except Exception:
+ pass
+
+ rate_limited = False
+
+ if not is_api_request and use_stream:
+ content = bytearray()
+ total_read = 0
+ chunk_size = 1024 * 1024 # 1 MB
+ start_time = time.monotonic()
+
+ try:
+ for i, chunk in enumerate(response.iter_content(chunk_size=chunk_size)):
+ if not chunk:
+ # logging.debug(f"Chunk {i} empty, continue...")
+ continue
+
+ # logging.debug(f"Chunk read {i}: {len(chunk)} bytes")
+ content.extend(chunk)
+ total_read += len(chunk)
+
+ # Comprobar límite de tamaño
+ if total_read > size_limit_bytes:
+ logging.warning(f"Downloaded content exceeded {size_limit_bytes} bytes. Aborting.")
+ return None, None
+
+ # Comprobar límite de tiempo
+ elapsed = time.monotonic() - start_time
+ if elapsed > constants.DOWNLOAD_TIMEOUT_SECONDS:
+ logging.warning(f"Download exceeded time limit ({elapsed:.2f}s). Aborting.")
+ return None, None
+
+ except Exception as e:
+ logging.warning(f"Error while streaming: {e}")
+ return None, None
+
+ response._content = bytes(content)
return response, date
@@ -91,14 +171,41 @@ def load_gitlab_repository_metadata(repo_metadata: Result, repository_url):
path_components = url.path.split('/')
- if len(path_components) < 3:
- logging.error("Gitlab link is not correct.")
+ # if len(path_components) < 3:
+ # logging.error("Gitlab link is not correct.")
+ # return " ", {}
+
+ # owner = path_components[1]
+ # repo_name = path_components[2]
+ # if len(path_components) == 4:
+ # repo_name = repo_name + '/' + path_components[3]
+
+ # new code to support complex gitlab urls. Before this we just accepted urls in the format https://gitlab.com/{owner}/{repo_name}
+ # clean path components
+ path_components = [p for p in url.path.split('/') if p]
+
+ # GitLab requires at least owner + repo
+ if len(path_components) < 2:
+ logging.error("GitLab link is not correct. Expected https://gitlab.com//")
return " ", {}
- owner = path_components[1]
- repo_name = path_components[2]
- if len(path_components) == 4:
- repo_name = repo_name + '/' + path_components[3]
+ # the owner is alwyas the first
+ owner = path_components[0]
+
+ # and repo name is the last
+ routing_markers = {"-", "tree", "blob", "issues", "merge_requests"}
+
+ # If the last component is a routing marker, the repo is the previous one
+ if path_components[-1] in routing_markers:
+ repo_name = path_components[-2]
+ else:
+ repo_name = path_components[-1]
+
+ default_branch = None
+ if "tree" in path_components:
+ idx = path_components.index("tree")
+ if idx + 1 < len(path_components):
+ default_branch = "/".join(path_components[idx+1:])
# could be gitlab.com or some gitlab self-hosted GitLab servers like gitlab.in2p3.fr
if repository_url.rfind("gitlab.com") > 0:
@@ -148,6 +255,26 @@ def load_gitlab_repository_metadata(repo_metadata: Result, repository_url):
elif zip_gz_entry:
value = zip_gz_entry["url"]
+ if category == constants.CAT_ASSETS:
+ assets_release_list_filtered = []
+ for source in release[constants.CAT_ASSETS]['sources']:
+ source_asset = {
+ constants.PROP_ENCODING_FORMAT: source[constants.PROP_FORMAT],
+ constants.PROP_CONTENT_URL: source[constants.PROP_URL]
+ }
+ assets_release_list_filtered.append(source_asset)
+
+ for link in release[constants.CAT_ASSETS]['links']:
+ link_asset = {
+ constants.PROP_NAME: link[constants.PROP_NAME],
+ constants.PROP_ENCODING_FORMAT: link["link_type"],
+ constants.PROP_URL: constants.PROP_URL,
+ constants.PROP_CONTENT_URL: link["direct_asset_url"],
+ }
+ assets_release_list_filtered.append(link_asset)
+
+ value = assets_release_list_filtered
+
if value:
release_obj[category] = value
else:
@@ -155,20 +282,6 @@ def load_gitlab_repository_metadata(repo_metadata: Result, repository_url):
repo_metadata.add_result(constants.CAT_RELEASES, release_obj, 1, constants.TECHNIQUE_GITLAB_API)
-
- default_branch = None
-
- if len(path_components) >= 5:
- if not path_components[4] == "tree":
- logging.error(
- "GitLab link is not correct. \nThe correct format is https://gitlab.com/{owner}/{repo_name}.")
-
- return " ", {}
-
- # we must join all after 4, as sometimes tags have "/" in them.
- default_branch = "/".join(path_components[5:])
- ref_param = {"ref": default_branch}
-
if 'defaultBranch' in project_details.keys():
general_resp = {'defaultBranch': project_details['defaultBranch']}
elif 'default_branch' in project_details.keys():
@@ -189,17 +302,19 @@ def load_gitlab_repository_metadata(repo_metadata: Result, repository_url):
if default_branch is None:
default_branch = general_resp['defaultBranch']
+ project_path = "/".join(path_components)
+
+ # {constants.PROP_VALUE: f"https://{url.netloc}/{owner}/{repo_name}/",
repo_metadata.add_result(constants.CAT_CODE_REPOSITORY,
- {constants.PROP_VALUE: f"https://{url.netloc}/{owner}/{repo_name}/",
- constants.PROP_TYPE: constants.URL
- }, 1, constants.TECHNIQUE_GITLAB_API)
+ {constants.PROP_VALUE: f"https://{url.netloc}/{project_path}/",
+ constants.PROP_TYPE: constants.URL
+ }, 1, constants.TECHNIQUE_GITLAB_API)
- # filtered_resp = do_crosswalk(general_resp, github_crosswalk_table)
- # filtered_resp = {"downloadUrl": f"https://gitlab.com/{owner}/{repo_name}/-/branches"}
+ # {constants.PROP_VALUE: f"https://{url.netloc}/{owner}/{repo_name}/-/branches",
repo_metadata.add_result(constants.CAT_DOWNLOAD_URL,
- {constants.PROP_VALUE: f"https://{url.netloc}/{owner}/{repo_name}/-/branches",
- constants.PROP_TYPE: constants.URL
- }, 1, constants.TECHNIQUE_GITLAB_API)
+ {constants.PROP_VALUE: f"https://{url.netloc}/{project_path}/-/branches",
+ constants.PROP_TYPE: constants.URL
+ }, 1, constants.TECHNIQUE_GITLAB_API)
# condense license information
license_result = {constants.PROP_TYPE: constants.URL}
@@ -230,11 +345,6 @@ def load_gitlab_repository_metadata(repo_metadata: Result, repository_url):
if constants.PROP_VALUE in license_result:
repo_metadata.add_result(constants.CAT_LICENSE, license_result, 1, constants.TECHNIQUE_GITLAB_API)
- # get keywords / topics
- # topics_headers = header
- # topics_headers['accept'] = 'application/vnd.github.mercy-preview+json'
- # topics_resp, date = rate_limit_get(repo_api_base_url + "/topics",
- # headers=topics_headers)
topics_resp = {}
keywords = []
@@ -288,7 +398,9 @@ def load_gitlab_repository_metadata(repo_metadata: Result, repository_url):
}, 1, constants.TECHNIQUE_GITLAB_API)
logging.info("Repository information successfully loaded. \n")
- return repo_metadata, owner, repo_name, default_branch
+ # return repo_metadata, owner, repo_name, default_branch
+ return repo_metadata, owner, repo_name, default_branch, project_path
+
def download_gitlab_files(directory, owner, repo_name, repo_branch, repo_ref):
@@ -309,9 +421,15 @@ def download_gitlab_files(directory, owner, repo_name, repo_branch, repo_ref):
url = urlparse(repo_ref)
path_components = url.path.split('/')
- repo_archive_url = f"https://{url.netloc}/{owner}/{repo_name}/-/archive/{repo_branch}/{repo_name}-{repo_branch}.zip"
- if len(path_components) == 4:
- repo_archive_url = f"https://{url.netloc}/{owner}/{repo_name}/-/archive/{repo_branch}/{path_components[3]}.zip"
+ path_components = [p for p in path_components if p]
+ project_path = "/".join(path_components)
+
+ # repo_archive_url = f"https://{url.netloc}/{owner}/{repo_name}/-/archive/{repo_branch}/{repo_name}-{repo_branch}.zip"
+ # if len(path_components) == 4:
+ # repo_archive_url = f"https://{url.netloc}/{owner}/{repo_name}/-/archive/{repo_branch}/{path_components[3]}.zip"
+ repo_archive_url = (
+ f"https://{url.netloc}/{project_path}/-/archive/{repo_branch}/{repo_name}-{repo_branch}.zip"
+ )
logging.info(f"Downloading {repo_archive_url}")
repo_download = requests.get(repo_archive_url)
@@ -335,8 +453,8 @@ def download_gitlab_files(directory, owner, repo_name, repo_branch, repo_ref):
return None
-def download_readme(owner, repo_name, default_branch, repo_type, authorization):
- """
+def download_readme(owner, repo_name, default_branch, repo_type, authorization, project_path = None):
+ """ss
Method that given a repository owner, name and default branch, it downloads the readme content only.
The readme is assumed to be README.md
Parameters
@@ -351,8 +469,9 @@ def download_readme(owner, repo_name, default_branch, repo_type, authorization):
@return: text with the contents of the readme file
"""
if repo_type is constants.RepositoryType.GITLAB:
- primary_url = f"https://gitlab.com/{owner}/{repo_name}/-/raw/{default_branch}/README.md"
- secondary_url = f"https://gitlab.com/{owner}/{repo_name}/-/raw/master/README.md"
+ base = f"https://gitlab.com/{project_path}" if project_path else f"https://gitlab.com/{owner}/{repo_name}"
+ primary_url = f"{base}/-/raw/{default_branch}/README.md"
+ secondary_url = f"{base}/-/raw/master/README.md"
elif repo_type is constants.RepositoryType.GITHUB:
primary_url = f"https://raw.githubusercontent.com/{owner}/{repo_name}/{default_branch}/README.md"
secondary_url = f"https://raw.githubusercontent.com/{owner}/{repo_name}/master/README.md"
@@ -362,10 +481,17 @@ def download_readme(owner, repo_name, default_branch, repo_type, authorization):
logging.info(f"Downloading {primary_url}")
repo_download, date = rate_limit_get(primary_url, headers=header_template(authorization))
+
+ if repo_download is None:
+ logging.warning(f"Repository archive skipped due to size limit: {constants.SIZE_DOWNLOAD_LIMIT_MB} MB or content-lenght none")
+ return None
if repo_download.status_code == 404:
logging.error(f"Error: Archive request failed with HTTP {repo_download.status_code}")
logging.info(f"Trying to download {secondary_url}")
repo_download, date = rate_limit_get(secondary_url, headers=header_template(authorization))
+ if repo_download is None:
+ logging.warning(f"Repository archive skipped due to size limit: {constants.SIZE_DOWNLOAD_LIMIT_MB} MB or content-lenght none")
+ return None
if repo_download.status_code != 200:
logging.error(f"Error: Archive request failed with HTTP {repo_download.status_code}")
return None
@@ -375,7 +501,8 @@ def download_readme(owner, repo_name, default_branch, repo_type, authorization):
def load_online_repository_metadata(repository_metadata: Result, repository_url, ignore_api_metadata=False,
- repo_type=constants.RepositoryType.GITHUB, authorization=None):
+ repo_type=constants.RepositoryType.GITHUB, authorization=None, reconcile_authors=False,
+ branch=None,tag=None):
"""
Function uses the repository_url provided to load required information from GitHub or Gitlab.
Information kept from the repository is written in keep_keys.
@@ -386,6 +513,9 @@ def load_online_repository_metadata(repository_metadata: Result, repository_url,
@param ignore_api_metadata: true if you do not want to do an additional request to the target API
@param repository_url: target repository URL.
@param authorization: GitHub authorization token
+ @param reconcile_authors: flag to indicate if additional should be extracted from certain files as codeowners. More request.
+ @param branch: branch of the repository to analyze. Overrides the default branch detected from the repository metadata.
+ @param tag: tag of the repository to analyze. Cannot be used together with the branch parameter.
Returns
-------
@@ -440,7 +570,12 @@ def load_online_repository_metadata(repository_metadata: Result, repository_url,
date = ""
if not ignore_api_metadata:
general_resp_raw, date = rate_limit_get(repo_api_base_url, headers=header)
+ if general_resp_raw is None:
+ logging.warning(f"Repository archive skipped due to size limit: {constants.SIZE_DOWNLOAD_LIMIT_MB} MB or content-lenght none")
+ return repository_metadata, "", "", ""
+
general_resp = general_resp_raw.json()
+
if 'message' in general_resp:
if general_resp['message'] == "Not Found":
logging.error("Error: Repository name is private or incorrect")
@@ -455,6 +590,11 @@ def load_online_repository_metadata(repository_metadata: Result, repository_url,
elif default_branch is None:
default_branch = general_resp['default_branch']
+ if branch:
+ default_branch = branch
+ if tag:
+ default_branch = tag
+
# filter the general response with only the fields we are interested in, mapping them to our keys
filtered_resp = {}
if not ignore_api_metadata:
@@ -464,17 +604,29 @@ def load_online_repository_metadata(repository_metadata: Result, repository_url,
for category, value in filtered_resp.items():
value_type = constants.STRING
+ maintainer_data = {}
if category in constants.all_categories:
if category == constants.CAT_ISSUE_TRACKER:
value = value.replace("{/number}", "")
if category == constants.CAT_OWNER:
+ if reconcile_authors:
+ logging.info("Enriching owner information from codeowners...")
+ user_info = enrich_user(owner,repo_type)
+ if user_info:
+ if user_info.get(constants.PROP_CODEOWNERS_NAME):
+ maintainer_data[constants.PROP_NAME] = user_info.get(constants.PROP_CODEOWNERS_NAME)
+ if user_info.get(constants.PROP_CODEOWNERS_COMPANY):
+ maintainer_data[constants.PROP_AFFILIATION] = user_info.get(constants.PROP_CODEOWNERS_COMPANY)
+ if user_info.get(constants.PROP_CODEOWNERS_EMAIL):
+ maintainer_data[constants.PROP_EMAIL] = user_info.get(constants.PROP_CODEOWNERS_EMAIL)
+
value_type = filtered_resp[constants.AGENT_TYPE]
if category == constants.CAT_KEYWORDS:
# we concatenate all keywords in a list, as the return value is always a single object
value = '%s,' % (', '.join(value))
value = value.rstrip(',')
if category in [constants.CAT_CODE_REPOSITORY, constants.CAT_ISSUE_TRACKER,
- constants.CAT_DOWNLOAD_URL, constants.CAT_FORKS_URLS]:
+ constants.CAT_DOWNLOAD_URL, constants.CAT_FORKS_URLS, constants.CAT_HOMEPAGE]:
value_type = constants.URL
if category in [constants.CAT_DATE_CREATED, constants.CAT_DATE_UPDATED]:
value_type = constants.DATE
@@ -489,18 +641,35 @@ def load_online_repository_metadata(repository_metadata: Result, repository_url,
}
if "spdx_id" in value.keys():
result[constants.PROP_SPDX_ID] = value["spdx_id"]
+ elif category == constants.CAT_OWNER:
+ result = {
+ constants.PROP_VALUE: value,
+ constants.PROP_TYPE: value_type
+ }
+ if maintainer_data.get("name"):
+ result[constants.PROP_NAME] = maintainer_data["name"]
+ if maintainer_data.get("affiliation"):
+ result[constants.PROP_AFFILIATION] = maintainer_data["affiliation"]
+ if maintainer_data.get("email"):
+ result[constants.PROP_EMAIL] = maintainer_data["email"]
else:
result = {
constants.PROP_VALUE: value,
constants.PROP_TYPE: value_type
}
-
+
if (result['value']):
repository_metadata.add_result(category, result, 1, constants.TECHNIQUE_GITHUB_API)
# get languages
if not ignore_api_metadata:
languages_raw, date = rate_limit_get(filtered_resp['languages_url'], headers=header)
- languages = languages_raw.json()
+
+ if languages_raw is None:
+ logging.warning("Skipping languages: rate_limit_get returned None (size limit or network error)")
+ languages = {}
+ else:
+ languages = languages_raw.json()
+
if "message" in languages:
logging.error("Error while retrieving languages: " + languages["message"])
else:
@@ -537,7 +706,6 @@ def load_online_repository_metadata(repository_metadata: Result, repository_url,
value = {
constants.PROP_NAME: value,
constants.PROP_TYPE: release[constants.AGENT_TYPE]
-
}
if value != "":
release_obj[category] = value
@@ -564,7 +732,7 @@ def load_online_repository_metadata(repository_metadata: Result, repository_url,
repository_metadata.add_result(constants.CAT_RELEASES, release_obj, 1,
constants.TECHNIQUE_GITHUB_API)
logging.info("Repository information successfully loaded.\n")
- return repository_metadata, owner, repo_name, default_branch
+ return repository_metadata, owner, repo_name, default_branch, None
def get_path(obj, path):
@@ -582,8 +750,7 @@ def get_path(obj, path):
def do_crosswalk(data, crosswalk_table):
output = {}
- # print('------------data realeases')
- # print(data)
+
for somef_key, path in crosswalk_table.items():
value = get_path(data, path)
if value is not None:
@@ -640,15 +807,41 @@ def download_github_files(directory, owner, repo_name, repo_ref, authorization):
# download the repo at the selected branch with the link
repo_archive_url = f"https://github.com/{owner}/{repo_name}/archive/{repo_ref}.zip"
logging.info(f"Downloading {repo_archive_url}")
+
repo_download, date = rate_limit_get(repo_archive_url, headers=header_template(authorization))
+
+ if repo_download is None:
+ logging.warning(f"Repository archive skipped due to size limit: {constants.SIZE_DOWNLOAD_LIMIT_MB} MB or not content lenght.")
+ return None
+
+ if repo_download.status_code == 300:
+ logging.warning(f"Ambiguous ref detected for {repo_ref}, trying tags/heads resolution")
+
+ for ref_type in ["tags", "heads"]:
+ repo_archive_url = f"https://github.com/{owner}/{repo_name}/archive/refs/{ref_type}/{repo_ref}.zip"
+ logging.info(f"Trying to download {repo_archive_url}")
+
+ repo_download, date = rate_limit_get(repo_archive_url, headers=header_template(authorization))
+
+ if repo_download is None:
+ logging.warning(f"Repository archive skipped due to size limit: {constants.SIZE_DOWNLOAD_LIMIT_MB} MB or not content length.")
+ return None
+
+ if repo_download.status_code == 200:
+ break
+
if repo_download.status_code == 404:
logging.error(f"Error: Archive request failed with HTTP {repo_download.status_code}")
repo_archive_url = f"https://github.com/{owner}/{repo_name}/archive/main.zip"
logging.info(f"Trying to download {repo_archive_url}")
repo_download, date = rate_limit_get(repo_archive_url, headers=header_template(authorization))
-
+ if repo_download is None:
+ logging.warning(f"Repository archive skipped due to size limit: {constants.SIZE_DOWNLOAD_LIMIT_MB} MB or not content lenght.")
+ return None
+
if repo_download.status_code != 200:
- sys.exit(f"Error: Archive request failed with HTTP {repo_download.status_code}")
+ logging.error(f"Error: Archive request failed with HTTP {repo_download.status_code}")
+ return None
repo_zip = repo_download.content
@@ -659,13 +852,19 @@ def download_github_files(directory, owner, repo_name, repo_ref, authorization):
with open(repo_zip_file, "wb") as f:
f.write(repo_zip)
- with zipfile.ZipFile(repo_zip_file, "r") as zip_ref:
- zip_ref.extractall(repo_extract_dir)
-
+ try:
+ with zipfile.ZipFile(repo_zip_file, "r") as zip_ref:
+ zip_ref.extractall(repo_extract_dir)
+ except zipfile.BadZipFile:
+ logging.error("Downloaded archive is not a valid zip (repo may be empty)")
+ return None
+
repo_folders = os.listdir(repo_extract_dir)
+ if not repo_folders:
+ logging.warning("Repository archive is empty")
+ return None
repo_dir = os.path.join(repo_extract_dir, repo_folders[0])
-
return repo_dir
@@ -715,34 +914,34 @@ def get_all_gitlab_releases(repo_api_base_url):
while True:
url = f"{repo_api_base_url}/releases?page={page}&per_page=100"
- logging.info(f"Obteniendo releases desde: {url}")
+ logging.info(f"Getting releases from: {url}")
response = requests.get(url)
- logging.info(f"Respuesta: {response.status_code}")
+ logging.info(f"Response: {response.status_code}")
content_type = response.headers.get("Content-Type", "")
if response.status_code != 200 or "application/json" not in content_type:
- logging.error(f"Error en la respuesta o no es JSON: {response.text}")
- break # Termina el bucle si la respuesta no es válida
+ logging.error(f"Error in response or not JSON: {response.text}")
+ break
try:
releases = response.json()
except requests.exceptions.JSONDecodeError as e:
- logging.error(f"Error al decodificar JSON: {e}. Respuesta: {response.text}")
+ logging.error(f"Error decoding JSON: {e}. Response: {response.text}")
break
if not releases:
- break # Si ya no hay releases, termina el bucle
+ break
all_releases.extend(releases)
- # Verifica si hay más páginas
+ # check if more pages
next_page = response.headers.get("X-Next-Page")
if not next_page:
- break # Si no hay más páginas, termina el bucle
+ break # No more pages
page = int(next_page)
if not all_releases:
- logging.warning("No se encontraron releases.")
+ logging.warning("Not releseases found.")
return []
@@ -760,25 +959,24 @@ def get_all_gitlab_releases(repo_api_base_url):
def get_gitlab_releases(project_id, base_url):
"""
- Obtiene las releases de un repositorio en GitLab sin autenticación.
-
- :param project_id: ID del proyecto en GitLab
- :param base_url: URL base del GitLab donde está alojado el proyecto (ej: https://gitlab.in2p3.fr)
- :return: Lista de releases con información relevante
+ Retrieves the releases of a GitLab repository without authentication.
+
+ :param project_id: ID of the project in GitLab
+ :param base_url: Base URL of the GitLab where the project is hosted (e.g., https://gitlab.in2p3.fr)
+ :return: List of releases with relevant information
"""
releases_url = f"{base_url}/api/v4/projects/{project_id}/releases"
- logging.info(f"Obteniendo releases desde: {releases_url}")
+ logging.info(f"Getting releases from: {releases_url}")
response = requests.get(releases_url)
if response.status_code != 200:
- logging.error(f"Error obteniendo releases: {response.text}")
+ logging.error(f"Error getting releases: {response.text}")
return []
releases_list = response.json()
- # Filtrar información relevante
release_list_filtered = [
{
"url": release.get("description"),
@@ -793,7 +991,6 @@ def get_gitlab_releases(project_id, base_url):
# error when github url is wrong
class GithubUrlError(Exception):
- # print("The URL provided seems to be incorrect")
pass
@@ -821,9 +1018,12 @@ def get_all_paginated_results(base_url, headers, per_page=100):
while True:
url = f"{base_url}?per_page={per_page}&page={page}"
- print(url)
response, _ = rate_limit_get(url, headers=headers)
+ if response is None:
+ logging.warning(f"Skipping page {page}: rate_limit_get returned None (size limit or network error)")
+ break
+
if response.status_code != 200:
logging.warning(f"GitHub API error on page {page}: {response.status_code}")
break
@@ -835,4 +1035,4 @@ def get_all_paginated_results(base_url, headers, per_page=100):
all_results.extend(page_data)
page += 1
- return all_results
\ No newline at end of file
+ return all_results
diff --git a/src/somef_core/regular_expressions.py b/src/somef_core/regular_expressions.py
index 27da864..eec7963 100644
--- a/src/somef_core/regular_expressions.py
+++ b/src/somef_core/regular_expressions.py
@@ -3,40 +3,49 @@
import re
import markdown
import requests
+import json
import validators
from .utils import constants
from .process_results import Result
from urllib.parse import urlparse
+
import bibtexparser
def extract_title(unfiltered_text, repository_metadata: Result, readme_source) -> Result:
+ from .header_analysis import label_header
"""
- Regexp to extract title (first header) from a repository
- Parameters
- ----------
- @param unfiltered_text: repo text
- @param repository_metadata: Result with the extractions so far
- @param readme_source: url to the file used (for provenance)
-
- Returns
- -------
- @returns a Result including the title (if found)
-
+ Regexp to extract title (first header) from a repository
+ Parameters
+ ----------
+ @param unfiltered_text: repo text
+ @param repository_metadata: Result with the extractions so far
+ @param readme_source: url to the file used (for provenance)
+
+ Returns
+ -------
+ @returns a Result including the title (if found)
"""
+
html_text = markdown.markdown(unfiltered_text)
splitted = html_text.split("\n")
index = 0
limit = len(splitted)
output = ""
regex = r'<[^<>]+>'
+
while index < limit:
line = splitted[index]
if line.startswith(""):
- output = re.sub(regex, '', line)
+ title = re.sub(regex, '', line).strip()
+ header_labels = label_header(title.lower())
+ if not header_labels and title.lower() != "overview":
+ output = title
break
index += 1
+
+
# If the output is empty or none, the category doesn't make sense and shouldn't be displayed in the final result
if has_valid_output(output):
repository_metadata.add_result(constants.CAT_FULL_TITLE,
@@ -63,7 +72,7 @@ def extract_title_old(unfiltered_text):
# header declared with ====
title = ""
if len(underline_header) != 0:
- title = re.split('.+[=]+[\n]+', unfiltered_text)[0].strip()
+ title = re.split(r'.+[=]+[\n]+', unfiltered_text)[0].strip()
else:
# The first occurrence is assumed to be the title.
title = re.findall(r'# .+', unfiltered_text)[0]
@@ -72,7 +81,7 @@ def extract_title_old(unfiltered_text):
title = title[1:].strip()
# Remove other markup (links, etc.)
if "[!" in title:
- title = re.split('\[\!', title)[0].strip()
+ title = re.split(r'\[\!', title)[0].strip()
return title
@@ -111,7 +120,8 @@ def extract_readthedocs(readme_text, repository_metadata: Result, readme_source)
}
try:
# if name of the repo is known then compare against the readthedocs one. Only add it if it's similar/same
- name_in_link = re.findall('https://([^.]+)\.readthedocs\.io', link)
+ # name_in_link = re.findall('https://([^.]+)\.readthedocs\.io', link)
+ name_in_link = re.findall(r'https://([^.]+)\.readthedocs\.io', link)
name_in_link = name_in_link[0]
if name == "" or name_in_link.lower() == name.lower():
repository_metadata.add_result(constants.CAT_DOCUMENTATION, result, 1,
@@ -318,7 +328,8 @@ def extract_images(unfiltered_text, repo_url, local_repo, repository_metadata: R
-------
A Result object with the logos and images from the given text
"""
- logo = ""
+ # logo = ""
+ logos = []
images = []
repo_name = ""
if repo_url is not None and repo_url != "":
@@ -331,20 +342,38 @@ def extract_images(unfiltered_text, repo_url, local_repo, repository_metadata: R
img_html = [_.start() for _ in re.finditer(" 0 or img.find("/badge") >= 0 or img.find("/travis-ci.") >= 0 \
or img.find("img.shields.io") >= 0:
- pass
- elif logo == "" and repo_url is not None:
- start = img.rindex("/")
+ # pass
+ continue
+ elif repo_url is not None:
+
+ start = img.rfind("/")
+ if start == -1:
+ start = 0
+
if img.find(repo_name, start) > 0:
- logo = rename_github_image(img, repo_url, local_repo, def_branch)
+ logos.append(rename_github_image(img, repo_url, local_repo, def_branch))
elif get_alt_text_md(html_text, img) == repo_name or get_alt_text_md(html_text, img).upper() == "LOGO":
- logo = rename_github_image(img, repo_url, local_repo, def_branch)
+ logos.append(rename_github_image(img, repo_url, local_repo, def_branch))
else:
- start = img.rindex("/")
- if img.upper().find("LOGO", start) > 0:
- logo = rename_github_image(img, repo_url, local_repo, def_branch)
+ start = img.rfind("/")
+ if start == -1:
+ start = 0
+ # if "/" in img:
+ # start = img.rindex("/")
+ # else:
+ # start = 0
+ # start = img.rindex("/")
+ # if img.upper().find("LOGO", start) >= 0:
+ if filename_upper.startswith("LOGO"):
+ logos.append(rename_github_image(img, repo_url, local_repo, def_branch))
else:
images.append(rename_github_image(img, repo_url, local_repo, def_branch))
else:
@@ -354,40 +383,108 @@ def extract_images(unfiltered_text, repo_url, local_repo, repository_metadata: R
init = html_text.find("src=\"", index_img)
end = html_text.find("\"", init + 5)
img = html_text[init + 5:end]
+ if not img:
+ continue
+ filename = img.split("/")[-1]
+ filename_upper = filename.upper()
+
# if the image contains jitpack.io, the element is not processed
if img.find("jitpack.io") > 0 or img.find("/badge") >= 0 or img.find("/travis-ci.") >= 0 \
or img.find("img.shields.io") >= 0:
- pass
- elif logo == "" and repo_url is not None:
- start = 0
- if img.find("/") > 0:
- start = img.rindex("/")
+ # pass
+ continue
+ elif repo_url is not None:
+ start = img.rfind("/")
+ if start == -1:
+ start = 0
+ # start = 0
+ # if img.find("/") > 0:
+ # start = img.rindex("/")
image_name = img[start:]
- if image_name.find(repo_name) > 0 or image_name.upper().find("LOGO") > 0:
- logo = rename_github_image(img, repo_url, local_repo, def_branch)
+ # if image_name.find(repo_name) > 0 or image_name.upper().find("LOGO") >= 0:
+ # if image_name.find(repo_name) > 0 or filename_upper.startswith("LOGO"):
+ if image_name.find(repo_name) > 0 or "LOGO" in filename_upper:
+ logos.append(rename_github_image(img, repo_url, local_repo, def_branch))
elif get_alt_text_img(html_text, index_img) == repo_name or get_alt_text_img(html_text,
index_img).upper() == "LOGO":
- logo = rename_github_image(img, repo_url, local_repo, def_branch)
+ logos.append(rename_github_image(img, repo_url, local_repo, def_branch))
else:
images.append(rename_github_image(img, repo_url, local_repo, def_branch))
else:
- start = img.rindex("/")
- if img.upper().find("LOGO", start) > 0:
- logo = rename_github_image(img, repo_url, local_repo, def_branch)
+ start = img.rfind("/")
+ if start == -1:
+ start = 0
+ # if "/" in img:
+ # start = img.rindex("/")
+ # else:
+ # start = 0
+ # start = img.rindex("/")
+ # if img.upper().find("LOGO", start) >= 0:
+ # if filename_upper.startswith("LOGO"):
+ if "LOGO" in filename_upper:
+ logos.append(rename_github_image(img, repo_url, local_repo, def_branch))
else:
images.append(rename_github_image(img, repo_url, local_repo, def_branch))
- if logo != "":
+
+ # final decission. Choose better logo following some priorities
+ # Priorities
+ logo_plus_name = []
+ logo_only = []
+ name_only = []
+
+ # If repo_name is empty, disable repo-name-based matching
+ if not repo_name:
+ repo_name = None
+
+ for logo in logos:
+ fname = logo.lower()
+
+ # Priority 1: logo + repo name
+ if repo_name and "logo" in fname and repo_name.lower() in fname:
+ logo_plus_name.append(logo)
+
+ # Priority 3: only repo name (but not logo)
+ elif repo_name and repo_name.lower() in fname:
+ name_only.append(logo)
+
+ # Priority 2: only "logo" (but not repo name)
+ elif "logo" in fname:
+ logo_only.append(logo)
+
+ # Apply priorities
+ if logo_plus_name:
+ final_logos = [logo_plus_name[0]]
+ discarded = [l for l in logos if l not in final_logos]
+
+ elif name_only:
+ final_logos = [name_only[0]]
+ discarded = [l for l in logos if l not in final_logos]
+
+ elif logo_only:
+ final_logos = logo_only
+ discarded = [l for l in logos if l not in final_logos]
+
+ else:
+ final_logos = []
+ discarded = logos
+
+ images.extend(discarded)
+
+
+ for logo in final_logos:
+
repository_metadata.add_result(constants.CAT_LOGO,
{
constants.PROP_TYPE: constants.URL,
constants.PROP_VALUE: logo
}, 1, constants.TECHNIQUE_REGULAR_EXPRESSION, readme_source)
for image in images:
- repository_metadata.add_result(constants.CAT_IMAGE,
- {
- constants.PROP_TYPE: constants.URL,
- constants.PROP_VALUE: image
- }, 1, constants.TECHNIQUE_REGULAR_EXPRESSION, readme_source)
+ if image not in final_logos:
+ repository_metadata.add_result(constants.CAT_IMAGE,
+ {
+ constants.PROP_TYPE: constants.URL,
+ constants.PROP_VALUE: image
+ }, 1, constants.TECHNIQUE_REGULAR_EXPRESSION, readme_source)
return repository_metadata
@@ -424,6 +521,30 @@ def extract_package_distributions(unfiltered_text, repository_metadata: Result,
constants.PROP_TYPE: constants.URL,
constants.PROP_VALUE: output
}, 1, constants.TECHNIQUE_REGULAR_EXPRESSION, readme_source)
+
+ matches = re.findall(constants.REGEXP_PACKAGE_MANAGER, unfiltered_text, re.VERBOSE)
+ found_urls = set()
+
+ for match in matches:
+ try:
+ response = requests.get(match, timeout=5)
+ final_url = response.url
+ except requests.RequestException:
+ final_url = match
+
+ if final_url not in found_urls and has_valid_output(final_url):
+ found_urls.add(final_url)
+ repository_metadata.add_result(
+ constants.CAT_PACKAGE_DISTRIBUTION,
+ {
+ constants.PROP_TYPE: constants.URL,
+ constants.PROP_VALUE: final_url
+ },
+ 1,
+ constants.TECHNIQUE_REGULAR_EXPRESSION,
+ readme_source
+ )
+
return repository_metadata
@@ -541,17 +662,211 @@ def extract_doi_badges(readme_text, repository_metadata: Result, source) -> Resu
"""
# regex = r'\[\!\[DOI\]([^\]]+)\]\(([^)]+)\)'
# regex = r'\[\!\[DOI\]\(.+\)\]\(([^)]+)\)'
+
doi_badges = re.findall(constants.REGEXP_DOI, readme_text)
# The identifier is in position 1. Position 0 is the badge id, which we don't want to export
- for doi in doi_badges:
- repository_metadata.add_result(constants.CAT_IDENTIFIER,
+ if not doi_badges:
+ match = re.search(constants.REGEXP_ZENODO_LATEST_DOI, readme_text)
+ badge_url = None
+ if match:
+ badge_url = match.group(1)
+ else:
+ match = re.search(constants.REGEXP_ZENODO_DOI, readme_text)
+ if match:
+ badge_url = match.group(0)
+ try:
+ if badge_url is not None:
+ response = requests.get(badge_url, allow_redirects=True, timeout=10)
+ match = re.search(constants.REGEXP_ZENODO_JSON_LD,
+ response.text,
+ re.DOTALL | re.IGNORECASE
+ )
+ if match:
+ json_ld_text = match.group(1).strip()
+ try:
+ json_ld_data = json.loads(json_ld_text)
+ identifier = json_ld_data.get('identifier')
+ repository_metadata.add_result(constants.CAT_IDENTIFIER,
+ {
+ constants.PROP_TYPE: constants.URL,
+ constants.PROP_VALUE: identifier,
+ }, 1, constants.TECHNIQUE_REGULAR_EXPRESSION, source)
+ except json.JSONDecodeError:
+ logging.warning("Error parsing Zenodo JSON-LD")
+ except requests.RequestException as e:
+ logging.warning(f"Error fetching DOI from Zenodo badge: {e}")
+ else:
+
+ for doi in doi_badges:
+ repository_metadata.add_result(constants.CAT_IDENTIFIER,
+ {
+ constants.PROP_TYPE: constants.URL,
+ constants.PROP_VALUE: doi[1]
+ }, 1, constants.TECHNIQUE_REGULAR_EXPRESSION, source)
+
+ return repository_metadata
+
+def extract_project_homepage_badges(readme_text, repository_metadata: Result, source) -> Result:
+ """
+ Function that takes the text of a readme file and searches if there are any project homepages.
+ Parameters
+ ----------
+ @param readme_text: Text of the readme
+ @param repository_metadata: Result with all the findings in the repo
+ @param source: source file on top of which the extraction is performed (provenance)
+ Returns
+ -------
+ @returns Result with the Sofware heritage badges found
+ """
+ homepage_badges = re.findall(constants.REGEXP_PROJECT_HOMEPAGE, readme_text)
+ # The identifier is in position 1. Position 0 is the badge id, which we don't want to export
+
+ for homepage in homepage_badges:
+ repository_metadata.add_result(constants.CAT_HOMEPAGE,
{
constants.PROP_TYPE: constants.URL,
- constants.PROP_VALUE: doi[1]
+ constants.PROP_VALUE: homepage[1]
}, 1, constants.TECHNIQUE_REGULAR_EXPRESSION, source)
+
return repository_metadata
+def extract_readthedocs_badgeds(readme_text, repository_metadata: Result, source) -> Result:
+ """
+ Function that takes the text of a readme file and searches if there are readthedocs badges.
+ Parameters
+ ----------
+ @param readme_text: Text of the readme
+ @param repository_metadata: Result with all the findings in the repo
+ @param source: source file on top of which the extraction is performed (provenance)
+ Returns
+ -------
+ @returns Result with the readthedocs badges found
+ """
+
+ urls = set()
+
+ # RST
+ for match in re.findall(constants.REGEXP_READTHEDOCS_RST, readme_text):
+ if isinstance(match, tuple):
+ urls.update([u for u in match if u])
+ elif match:
+ urls.add(match)
+
+ # Markdown
+ for match in re.findall(constants.REGEXP_READTHEDOCS_MD, readme_text):
+ if isinstance(match, tuple):
+ urls.update([u for u in match if u])
+ elif match:
+ urls.add(match)
+
+ # HTML
+ # pattern_html = re.compile(constants.REGEXP_READTHEDOCS_HTML, flags=re.VERBOSE |re.DOTALL | re.IGNORECASE)
+ # for match in pattern_html.findall(readme_text):
+ # if isinstance(match, tuple):
+ # urls.update([u for u in match if u])
+ # elif match:
+ # urls.add(match)
+ pattern_html = re.compile(
+ constants.REGEXP_READTHEDOCS_HTML,
+ flags=re.VERBOSE | re.IGNORECASE
+ )
+ for match in pattern_html.finditer(readme_text):
+ url = match.group(1)
+ if url:
+ urls.add(url)
+
+ for url in urls:
+ if "pypi.org/project" in url:
+ category = constants.CAT_PACKAGE_DISTRIBUTION
+ else:
+ category = constants.CAT_DOCUMENTATION
+
+ repository_metadata.add_result(
+ category,
+ {
+ constants.PROP_TYPE: constants.URL,
+ constants.PROP_VALUE: url
+ },
+ 1,
+ constants.TECHNIQUE_REGULAR_EXPRESSION,
+ source
+ )
+
+ return repository_metadata
+
+# def extract_package_manager_badgeds(readme_text, repository_metadata: Result, source) -> Result:
+# """
+# Function that takes the text of a readme file and searches if there are package manager badges.
+# Parameters
+# ----------
+# @param readme_text: Text of the readme
+# @param repository_metadata: Result with all the findings in the repo
+# @param source: source file on top of which the extraction is performed (provenance)
+# Returns
+# -------
+# @returns Result with the package badges found
+# """
+# package_manager_badges = re.findall(constants.REGEXP_READTHEDOCS_BADGES, readme_text, re.DOTALL)
+# for package in package_manager_badges:
+# repository_metadata.add_result(constants.CAT_DOCUMENTATION,
+# {
+# constants.PROP_TYPE: constants.URL,
+# constants.PROP_VALUE: package
+# }, 1, constants.TECHNIQUE_REGULAR_EXPRESSION, source)
+
+
+# return repository_metadata
+
+
+def extract_swh_badges(readme_text, repository_metadata: Result, source) -> Result:
+ """
+ Function that takes the text of a readme file and searches if there are any Software Heritage (swh) badges.
+ Parameters
+ ----------
+ @param readme_text: Text of the readme
+ @param repository_metadata: Result with all the findings in the repo
+ @param source: source file on top of which the extraction is performed (provenance)
+ Returns
+ -------
+ @returns Result with the Sofware heritage badges found
+ """
+ swh_badges = re.findall(constants.REGEXP_SWH, readme_text)
+ # The identifier is in position 1. Position 0 is the badge id, which we don't want to export
+
+ for swh in swh_badges:
+
+ identifier = extract_swh_identifier_from_url(swh[1])
+ if identifier:
+ url_identifier = constants.SWH_ROOT + identifier
+ repository_metadata.add_result(constants.CAT_IDENTIFIER,
+ {
+ constants.PROP_TYPE: constants.URL,
+ constants.PROP_VALUE: url_identifier
+ }, 1, constants.TECHNIQUE_REGULAR_EXPRESSION, source)
+
+ return repository_metadata
+
+def extract_swh_identifier_from_url(url: str) -> str | None:
+ """
+ Function that look for a correct identifier en the swh url
+ """
+
+ # If anchor look for identifier
+ anchor_match = re.search(constants.REGEXP_SWH_ANCHOR, url)
+ if anchor_match:
+ return anchor_match.group(1)
+
+ # If no anchor look for all identifiers
+ all_matches = re.findall(constants.REGEXP_SWH_ALL_IDENTIFIERS, url)
+ if all_matches:
+ for preferred_type in ['rev', 'snp', 'dir', 'cnt']:
+ for match in all_matches:
+ if f"swh:1:{preferred_type}:" in match:
+ return match
+
+ return None
+
def extract_binder_links(readme_text, repository_metadata: Result, source) -> Result:
"""
Function that does a regex to extract binder and colab links used as reference in the readme.
@@ -612,13 +927,26 @@ def get_alt_text_md(text, image):
def get_alt_text_img(html_text, index):
"""Processing alt names for images"""
end = html_text.find(">", index)
- output = ""
- if html_text.find("alt=", index, end) > 0:
- texto = html_text[index:end]
- init = texto.find("alt=\"") + 5
- end = texto.index("\"", init)
- output = texto[init:end]
- return output
+
+ if end == -1:
+ return ""
+
+ # output = ""
+ # if html_text.find("alt=", index, end) > 0:
+ # texto = html_text[index:end]
+ # init = texto.find("alt=\"") + 5
+ # end = texto.index("\"", init)
+ # output = texto[init:end]
+ # return output
+ fragment = html_text[index:end]
+ pos = fragment.find('alt="')
+ if pos == -1:
+ return ""
+ pos += 5
+ end_quote = fragment.find('"', pos)
+ if end_quote == -1:
+ return ""
+ return fragment[pos:end_quote]
def get_alt_text_html(text, image):
@@ -654,6 +982,7 @@ def detect_license_spdx(license_text, type):
-------
A JSON dictionary with name and spdx id
"""
+
for license_name, license_info in constants.LICENSES_DICT.items():
if re.search(license_info["regex"], license_text, re.IGNORECASE):
if type == 'JSON':
@@ -667,16 +996,28 @@ def detect_license_spdx(license_text, type):
"name": license_name,
"identifier": f"https://spdx.org/licenses/{license_info['spdx_id']}"
}
+ for license_name, license_info in constants.LICENSES_DICT.items():
+ spdx_id = license_info["spdx_id"]
+ if re.search(rf'\b{re.escape(spdx_id)}\b', license_text, re.IGNORECASE):
+ return {
+ "name": license_name,
+ "spdx_id": spdx_id,
+ "@id": f"https://spdx.org/licenses/{spdx_id}"
+ }
return None
-# def detect_license(license_text):
-# for license_name, license_info in constants.LICENSES_DICT.items():
-# if re.search(license_info["regex"], license_text, re.IGNORECASE):
-# return {
-# "name": license_name,
-# "identifier": f"https://spdx.org/licenses/{license_info['spdx_id']}"
-# }
-# return None
+def detect_spdx_from_declared(value: str):
+ """
+ Check if declared license matches an SPDX ID in LICENSES_DICT.
+ """
+ for name, info in constants.LICENSES_DICT.items():
+ if value == info["spdx_id"]:
+ return {
+ "name": name,
+ "spdx_id": info["spdx_id"],
+ "@id": f"https://spdx.org/licenses/{info['spdx_id']}"
+ }
+ return None
def extract_scholarly_article_properties(bibtex_entry, scholarlyArticle, type):
"""
diff --git a/src/somef_core/somef_cli.py b/src/somef_core/somef_cli.py
index 0188d3b..f302a58 100644
--- a/src/somef_core/somef_cli.py
+++ b/src/somef_core/somef_cli.py
@@ -1,6 +1,6 @@
import sys
-from uu import encode
-
+# from uu import encode
+import warnings
import validators
import logging
import os
@@ -11,13 +11,14 @@
from . import header_analysis, regular_expressions, process_repository, configuration, process_files
from .process_results import Result
from .utils import constants, markdown_utils
-from .parser import mardown_parser, create_excerpts
from .export import json_export
+from .export import google_codemeta_export
+from .extract_software_type import check_repository_type
from urllib.parse import urlparse, quote
def cli_get_data(threshold, ignore_classifiers, repo_url=None, doc_src=None, local_repo=None,
ignore_github_metadata=False, readme_only=False, keep_tmp=None, authorization=None,
- ignore_test_folder=True) -> Result:
+ ignore_test_folder=True,requirements_mode='all', reconcile_authors=False, branch=None, tag=None) -> Result:
"""
Main function to get the data through the command line
Parameters
@@ -32,34 +33,43 @@ def cli_get_data(threshold, ignore_classifiers, repo_url=None, doc_src=None, loc
@param keep_tmp: path where to store TMP files in case SOMEF is instructed to keep them
@param authorization: GitHub authorization token
@param ignore_test_folder: Ignore contents of test folders
+ @param requiriments_mode: flag to indicate what requirements show in codemeta
+ @param reconcile_authors: flag to indicate if additional should be extracted from certain files as codeowners. Bear in mind that using this flags consumes more requests to the GitHub API.
+ @param branch: branch of the repository to analyze. Overrides the default branch detected from the repository metadata.
+ @param tag: tag of the repository to analyze. Cannot be used together with the branch parameter.
Returns
-------
@return: Dictionary with the results found by SOMEF, formatted as a Result object.
"""
# Set up logging
+ warnings.filterwarnings("ignore", category=UserWarning, module="pyparsing")
+ warnings.filterwarnings("ignore", category=DeprecationWarning, module="rdflib")
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s-%(levelname)s-%(message)s',
datefmt='%d-%b-%y %H:%M:%S', force=True)
logging.getLogger("bibtexparser").setLevel(logging.ERROR)
+ logging.getLogger("urllib3").setLevel(logging.WARNING)
file_paths = configuration.get_configuration_file()
repo_type = constants.RepositoryType.GITHUB
repository_metadata = Result()
def_branch = "main"
+
+ if branch and tag:
+ logging.error("You cannot use --branch and --tag at the same time. Mutually exclusive")
+ sys.exit()
+
if repo_url is not None:
try:
- """
- It is necessary to make changes to all methods related to GitLab because, until now,
- they only worked with repositories on GitLab.com but not with self-hosted GitLab servers like gitlab.in2p3.fr, for example.
- We are going to split the process so that it also takes these servers into account.
- """
+ # It is necessary to make changes to all methods related to GitLab because, until now,
+ # they only worked with repositories on GitLab.com but not with self-hosted GitLab servers
+ # like gitlab.in2p3.fr, for example. We are going to split the process so that it also
+ # takes these servers into account.
- """
- The only sure way to know if a server is from GitLab is by checking its API.
- GitLab servers are usually of the type gitlab.com, gitlab.in2p3.fr, or even salsa.debian.org,
- so you cannot discriminate solely with the string 'gitlab'.
- """
+ # The only sure way to know if a server is from GitLab is by checking its API.
+ # GitLab servers are usually of the type gitlab.com, gitlab.in2p3.fr, or even
+ # salsa.debian.org, so you cannot discriminate solely with the string 'gitlab'.
url = urlparse(repo_url)
servidor = url.netloc
bGitLab = False
@@ -67,41 +77,61 @@ def cli_get_data(threshold, ignore_classifiers, repo_url=None, doc_src=None, loc
logging.info(f"{servidor} is GitLab.")
bGitLab = True
+ logging.info(f"DEBUG: {servidor} is_gitlab = {bGitLab}")
if bGitLab:
repo_type = constants.RepositoryType.GITLAB
- repository_metadata, owner, repo_name, def_branch = process_repository.load_online_repository_metadata(
+
+ repository_metadata, owner, repo_name, def_branch, project_path = process_repository.load_online_repository_metadata(
repository_metadata,
repo_url,
ignore_github_metadata,
repo_type,
- authorization
+ authorization,
+ reconcile_authors,
+ branch=branch,
+ tag=tag
)
# download files and obtain path to download folder
if readme_only:
# download readme only with the information above
- readme_text = process_repository.download_readme(owner, repo_name, def_branch, repo_type, authorization)
+ readme_text = process_repository.download_readme(owner, repo_name, def_branch, repo_type, authorization, project_path)
elif keep_tmp is not None: # save downloaded files locally
os.makedirs(keep_tmp, exist_ok=True)
local_folder = process_repository.download_repository_files(owner, repo_name, def_branch, repo_type,
keep_tmp, repo_url, authorization)
- readme_text, full_repository_metadata = process_files.process_repository_files(local_folder,
+ if local_folder is not None:
+ readme_text, full_repository_metadata = process_files.process_repository_files(local_folder,
repository_metadata,
repo_type, owner,
repo_name,
def_branch,
- ignore_test_folder)
+ ignore_test_folder,
+ reconcile_authors)
+ repository_metadata = check_repository_type(local_folder, repo_name, full_repository_metadata)
+ else:
+ logging.error("Error processing the target repository")
+ return repository_metadata
else: # Use a temp directory
+
with tempfile.TemporaryDirectory() as temp_dir:
local_folder = process_repository.download_repository_files(owner, repo_name, def_branch, repo_type,
temp_dir, repo_url, authorization)
- readme_text, full_repository_metadata = process_files.process_repository_files(local_folder,
- repository_metadata,
- repo_type, owner,
- repo_name,
- def_branch,
- ignore_test_folder)
+ if local_folder is not None:
+ readme_text, full_repository_metadata = process_files.process_repository_files(local_folder,
+ repository_metadata,
+ repo_type, owner,
+ repo_name,
+ def_branch,
+ ignore_test_folder,
+ reconcile_authors)
+
+ repository_metadata = check_repository_type(local_folder, repo_name, full_repository_metadata)
+ else:
+ logging.error("Error processing the target repository")
+ return repository_metadata
+
if readme_text == "":
logging.warning("README document does not exist in the target repository")
except process_repository.GithubUrlError:
@@ -112,7 +142,8 @@ def cli_get_data(threshold, ignore_classifiers, repo_url=None, doc_src=None, loc
readme_text, full_repository_metadata = process_files.process_repository_files(local_repo,
repository_metadata,
repo_type,
- ignore_test_folder)
+ ignore_test_folder,
+ reconcile_authors = reconcile_authors)
if readme_text == "":
logging.warning("Warning: README document does not exist in the local repository")
except process_repository.GithubUrlError:
@@ -130,7 +161,11 @@ def cli_get_data(threshold, ignore_classifiers, repo_url=None, doc_src=None, loc
readme_unfiltered_text = markdown_utils.remove_comments(readme_unfiltered_text)
repository_metadata, string_list = header_analysis.extract_categories(readme_unfiltered_text,
repository_metadata)
+
+ logging.info("Extracted categories from headers successfully.")
readme_text_unmarked = markdown_utils.unmark(readme_text)
+ logging.info("readme text unmarked successfully.")
+
if readme_text_unmarked != "":
try:
readme_source = repository_metadata.results[constants.CAT_README_URL][0]
@@ -141,12 +176,18 @@ def cli_get_data(threshold, ignore_classifiers, repo_url=None, doc_src=None, loc
readme_source)
repository_metadata = regular_expressions.extract_doi_badges(readme_unfiltered_text, repository_metadata,
readme_source)
+ repository_metadata = regular_expressions.extract_swh_badges(readme_unfiltered_text, repository_metadata,
+ readme_source)
+ repository_metadata = regular_expressions.extract_project_homepage_badges(readme_unfiltered_text, repository_metadata,
+ readme_source)
repository_metadata = regular_expressions.extract_title(readme_unfiltered_text, repository_metadata,
readme_source)
repository_metadata = regular_expressions.extract_binder_links(readme_unfiltered_text, repository_metadata,
readme_source)
repository_metadata = regular_expressions.extract_readthedocs(readme_unfiltered_text, repository_metadata,
readme_source)
+ repository_metadata = regular_expressions.extract_readthedocs_badgeds(readme_unfiltered_text, repository_metadata,
+ readme_source)
repository_metadata = regular_expressions.extract_repo_status(readme_unfiltered_text, repository_metadata,
readme_source)
repository_metadata = regular_expressions.extract_wiki_links(readme_unfiltered_text, repo_url,
@@ -188,10 +229,15 @@ def run_cli(*,
graph_out=None,
graph_format="turtle",
codemeta_out=None,
+ google_codemeta_out=None,
pretty=False,
missing=False,
keep_tmp=None,
- ignore_test_folder=True
+ ignore_test_folder=True,
+ requirements_mode="all",
+ reconcile_authors=False,
+ branch=None,
+ tag=None
):
"""Function to run all the required components of the cli for a repository"""
# check if it is a valid url
@@ -225,16 +271,31 @@ def run_cli(*,
encoded_url = encoded_url.replace(".","") #removing dots just in case
repo_data = cli_get_data(threshold=threshold, ignore_classifiers=ignore_classifiers, repo_url=repo_url,
ignore_github_metadata=ignore_github_metadata, readme_only=readme_only,
- keep_tmp=keep_tmp, ignore_test_folder=ignore_test_folder)
+ keep_tmp=keep_tmp, ignore_test_folder=ignore_test_folder, requirements_mode=requirements_mode,
+ reconcile_authors=reconcile_authors, branch=branch, tag=tag)
+
+ if hasattr(repo_data, "get_json"):
+ repo_data = repo_data.get_json()
+
+ repo_data = json_export.unify_results(repo_data.results)
if output is not None:
output = output.replace(".json","")
output = output + "_" + encoded_url + ".json"
- json_export.save_json_output(repo_data.results, output, missing, pretty=pretty)
+ json_export.save_json_output(repo_data, output, missing, pretty=pretty)
if codemeta_out is not None:
codemeta_out = codemeta_out.replace(".json", "")
codemeta_out = codemeta_out + "_" + encoded_url + ".json"
- json_export.save_codemeta_output(repo_data.results, codemeta_out, pretty=pretty)
+ json_export.save_codemeta_output(repo_data, codemeta_out, pretty=pretty, requirements_mode= requirements_mode)
+ if google_codemeta_out is not None:
+ gc_out = google_codemeta_out.replace(".json", "")
+ gc_out = gc_out + "_" + encoded_url + ".json"
+ google_codemeta_export.save_google_codemeta_output(
+ repo_data,
+ gc_out,
+ pretty=pretty,
+ requirements_mode=requirements_mode
+ )
except:
logging.error("Error when processing repo: " + repo_url)
else:
@@ -244,18 +305,28 @@ def run_cli(*,
if repo_url:
repo_data = cli_get_data(threshold=threshold, ignore_classifiers=ignore_classifiers, repo_url=repo_url,
ignore_github_metadata=ignore_github_metadata, readme_only=readme_only,
- keep_tmp=keep_tmp, ignore_test_folder=ignore_test_folder)
+ keep_tmp=keep_tmp, ignore_test_folder=ignore_test_folder,
+ reconcile_authors=reconcile_authors,branch=branch, tag=tag)
elif local_repo:
repo_data = cli_get_data(threshold=threshold, ignore_classifiers=ignore_classifiers,
- local_repo=local_repo, keep_tmp=keep_tmp, ignore_test_folder=ignore_test_folder)
+ local_repo=local_repo, keep_tmp=keep_tmp, ignore_test_folder=ignore_test_folder,
+ reconcile_authors=reconcile_authors,branch=branch, tag=tag)
else:
repo_data = cli_get_data(threshold=threshold, ignore_classifiers=ignore_classifiers,
- doc_src=doc_src, keep_tmp=keep_tmp, ignore_test_folder=ignore_test_folder)
+ doc_src=doc_src, keep_tmp=keep_tmp, ignore_test_folder=ignore_test_folder,
+ reconcile_authors=reconcile_authors,branch=branch, tag=tag)
+
+ if hasattr(repo_data, "get_json"):
+ repo_data = repo_data.get_json()
+ repo_data = json_export.unify_results(repo_data.results)
+
if output is not None:
- json_export.save_json_output(repo_data.results, output, missing, pretty=pretty)
+ json_export.save_json_output(repo_data, output, missing, pretty=pretty)
if codemeta_out is not None:
- json_export.save_codemeta_output(repo_data.results, codemeta_out, pretty=pretty)
+ json_export.save_codemeta_output(repo_data, codemeta_out, pretty=pretty, requirements_mode=requirements_mode)
+ if google_codemeta_out is not None:
+ google_codemeta_export.save_google_codemeta_output(repo_data, google_codemeta_out, pretty=pretty, requirements_mode=requirements_mode)
diff --git a/src/somef_core/test/test_JSON_export.py b/src/somef_core/test/test_JSON_export.py
index 27dc464..9c58112 100644
--- a/src/somef_core/test/test_JSON_export.py
+++ b/src/somef_core/test/test_JSON_export.py
@@ -7,65 +7,70 @@
test_data_path = str(Path(__file__).parent / "test_data") + os.path.sep
test_data_repositories = str(Path(__file__).parent / "test_data" / "repositories") + os.path.sep
-
+test_data_api_json = str(Path(__file__).parent / "test_data" / "api_responses") + os.path.sep
class TestJSONExport(unittest.TestCase):
@classmethod
def setUpClass(cls):
"""Runs somef_cli once and saves the JSON"""
- cls.json_file = test_data_path + "test_json_widoco_export.json"
-
- somef_cli.run_cli(threshold=0.8,
- ignore_classifiers=False,
- repo_url="https://github.com/dgarijo/Widoco",
- local_repo=None,
- doc_src=None,
- in_file=None,
- output=cls.json_file,
- graph_out=None,
- graph_format="turtle",
- codemeta_out=None,
- pretty=True,
- missing=False,
- readme_only=False)
+ # cls.json_file = test_data_path + "test_json_widoco_export.json"
+ cls.api_results_file = test_data_api_json + "widoco_api_response.json"
+ with open(cls.api_results_file, "r", encoding="utf-8") as f:
+ cls.json_content= json.load(f)
- with open(cls.json_file, "r") as f:
- cls.json_content = json.load(f)
+ # somef_cli.run_cli(threshold=0.8,
+ # ignore_classifiers=False,
+ # repo_url="https://github.com/dgarijo/Widoco",
+ # local_repo=None,
+ # doc_src=None,
+ # in_file=None,
+ # output=cls.json_file,
+ # graph_out=None,
+ # graph_format="turtle",
+ # codemeta_out=None,
+ # pretty=True,
+ # missing=False,
+ # readme_only=False)
- def test_issue_417(self):
- """Checks whether a repository correctly extracts to Codemeta"""
- somef_cli.run_cli(threshold=0.8,
- ignore_classifiers=False,
- repo_url="https://github.com/dgarijo/Widoco",
- local_repo=None,
- doc_src=None,
- in_file=None,
- output=None,
- graph_out=None,
- graph_format="turtle",
- codemeta_out=test_data_path + "test-417.json-ld",
- pretty=True,
- missing=False,
- readme_only=False)
-
- text_file = open(test_data_path + "test-417.json-ld", "r")
- data = text_file.read()
- text_file.close()
- json_content = json.loads(data)
- issue_tracker = json_content["issueTracker"] # JSON is in Codemeta format
+ # with open(cls.json_file, "r") as f:
+ # cls.json_content = json.load(f)
+
+ # def test_issue_417(self):
+ # """Checks whether a repository correctly extracts to Codemeta"""
+
+
+ # somef_cli.run_cli(threshold=0.8,
+ # ignore_classifiers=False,
+ # repo_url="https://github.com/dgarijo/Widoco",
+ # local_repo=None,
+ # doc_src=None,
+ # in_file=None,
+ # output=None,
+ # graph_out=None,
+ # graph_format="turtle",
+ # codemeta_out=test_data_path + "test-417.json-ld",
+ # pretty=True,
+ # missing=False,
+ # readme_only=False)
- #len(json_content["citation"])
- #codemeta category citation is now referencePublication
- assert issue_tracker == 'https://github.com/dgarijo/Widoco/issues' and len(json_content["referencePublication"]) > 0 and \
- len(json_content["name"]) > 0 and len(json_content["identifier"]) > 0 and \
- len(json_content["description"]) > 0 and len(json_content["readme"]) > 0 and \
- len(json_content["author"]) > 0 and len(json_content["buildInstructions"]) > 0 and \
- len(json_content["softwareRequirements"]) > 0 and len(json_content["programmingLanguage"]) > 0 and \
- len(json_content["keywords"]) > 0 and len(json_content["logo"]) > 0 and \
- len(json_content["license"]) > 0 and len(json_content["dateCreated"]) > 0
+ # text_file = open(test_data_path + "test-417.json-ld", "r")
+ # data = text_file.read()
+ # text_file.close()
+ # json_content = json.loads(data)
+ # issue_tracker = json_content["issueTracker"] # JSON is in Codemeta format
+
+ # #len(json_content["citation"])
+ # #codemeta category citation is now referencePublication
+ # assert issue_tracker == 'https://github.com/dgarijo/Widoco/issues' and len(json_content["referencePublication"]) > 0 and \
+ # len(json_content["name"]) > 0 and len(json_content["identifier"]) > 0 and \
+ # len(json_content["description"]) > 0 and len(json_content["readme"]) > 0 and \
+ # len(json_content["author"]) > 0 and len(json_content["buildInstructions"]) > 0 and \
+ # len(json_content["softwareRequirements"]) > 0 and len(json_content["programmingLanguage"]) > 0 and \
+ # len(json_content["keywords"]) > 0 and len(json_content["logo"]) > 0 and \
+ # len(json_content["license"]) > 0 and len(json_content["dateCreated"]) > 0
- os.remove(test_data_path + "test-417.json-ld")
+ # os.remove(test_data_path + "test-417.json-ld")
def test_issue_311(self):
"""Checks if Codemeta export has labels defined outside Codemeta"""
@@ -155,6 +160,8 @@ def test_issue_629(self):
# os.remove(test_data_path + "test_issue_629.json")
+
+
def test_issue_651(self):
"""Checks if keywords is in the missing categories because is empty"""
somef_cli.run_cli(threshold=0.8,
@@ -179,8 +186,9 @@ def test_issue_745(self):
"""Checks whether all the items in license has a spdx_id"""
somef_cli.run_cli(threshold=0.8,
ignore_classifiers=False,
- repo_url="https://github.com/sunpy/sunpy",
- local_repo=None,
+ # repo_url="https://github.com/sunpy/sunpy",
+ repo_url=None,
+ local_repo=test_data_repositories + "sunpy",
doc_src=None,
in_file=None,
output=test_data_path + "test_issue_745.json",
@@ -195,6 +203,7 @@ def test_issue_745(self):
data = text_file.read()
text_file.close()
json_content = json.loads(data)
+
licenses = json_content["license"]
for i, license_entry in enumerate(licenses):
@@ -203,19 +212,447 @@ def test_issue_745(self):
os.remove(test_data_path + "test_issue_745.json")
+ def test_issue_499(self):
+ """Checks whether a repository correctly extracts assets from release"""
+
+ # somef_cli.run_cli(threshold=0.8,
+ # ignore_classifiers=False,
+ # repo_url="https://github.com/dgarijo/Widoco",
+ # local_repo=None,
+ # doc_src=None,
+ # in_file=None,
+ # output=test_data_path + "test-499.json-ld",
+ # graph_out=None,
+ # graph_format="turtle",
+ # codemeta_out= None,
+ # pretty=True,
+ # missing=False,
+ # readme_only=False)
+
+ # text_file = open(test_data_path + "test-499.json-ld", "r")
+ # data = text_file.read()
+ # text_file.close()
+ # json_content = json.loads(data)
+
+ assert "releases" in self.json_content, "Missing 'releases' key in JSON content"
+ assert isinstance(self.json_content["releases"], list), "'releases' should be a list"
+
+ release_1425 = None
+ for release in self.json_content["releases"]:
+ if release.get("result", {}).get("tag") == "v1.4.25":
+ release_1425 = release
+ break
+
+ assert release_1425 is not None, "No release with tag 'v1.4.25' found"
+
+ assets = release_1425.get("result", {}).get("assets", [])
+ assert any(asset.get("name") == "widoco-1.4.25-jar-with-dependencies_JDK-11.jar" for asset in assets), \
+ "Asset 'widoco-1.4.25-jar-with-dependencies_JDK-11.jar' not found in release v1.4.25"
+
+ # os.remove(test_data_path + "test-499.json-ld")
+
+ def test_issue_577(self):
+ """Checks if there are idenfiers from Software Heritage in the README file"""
+ somef_cli.run_cli(threshold=0.8,
+ ignore_classifiers=False,
+ repo_url=None,
+ doc_src=test_data_path + "README-widoco-swh.md",
+ in_file=None,
+ output=test_data_path + "test-577.json",
+ graph_out=None,
+ graph_format="turtle",
+ codemeta_out=None,
+ pretty=True,
+ missing=True)
+
+ with open(test_data_path + "test-577.json", "r") as text_file:
+ data = json.load(text_file)
+
+ expected_values = {
+ "https://doi.org/10.5281/zenodo.11093793",
+ "https://archive.softwareheritage.org/swh:1:rev:fec66b89a4f4acb015a44c7f8cb671d49bec626a"
+ }
+ identifiers = data.get("identifier", [])
+ found_values = set()
+
+ for item in identifiers:
+ value = item["result"]["value"]
+ found_values.add(value)
+
+ for expected in expected_values:
+ assert expected in found_values, f"Expected identifier not found: {expected}"
+
+ os.remove(test_data_path + "test-577.json")
+
+
+ def test_issue_580(self):
+ """Checks if there are Project homepage in the readme file"""
+ somef_cli.run_cli(threshold=0.8,
+ ignore_classifiers=False,
+ repo_url=None,
+ doc_src=test_data_path + "README-citation-file-format.md",
+ in_file=None,
+ output=test_data_path + "test-580.json",
+ graph_out=None,
+ graph_format="turtle",
+ codemeta_out=None,
+ pretty=True,
+ missing=True)
+
+ with open(test_data_path + "test-580.json", "r") as text_file:
+ data = json.load(text_file)
+
+ found = False
+ homepage_entries = data.get("homepage", [])
+ for item in homepage_entries:
+ technique = item.get("technique")
+ result = item.get("result", {})
+ value = result.get("value")
+
+ if technique == "regular_expression" and value == "https://citation-file-format.github.io":
+ found = True
+ break
+
+ assert found, "Expected homepage not found"
+
+ os.remove(test_data_path + "test-580.json")
+
def test_issue_653(self):
"""Checks if json from widoco repo has more than 30 releases"""
assert len(self.json_content["releases"]) > 30, f"Expected more than 30 releases, found {len(self.json_content['release'])}"
- @classmethod
- def tearDownClass(cls):
- """delete temp file JSON just if all the test pass"""
- if os.path.exists(cls.json_file):
- try:
- os.remove(cls.json_file)
- print(f"Deleted {cls.json_file}")
- except Exception as e:
- print(f"Failed to delete {cls.json_file}: {e}")
-
-if __name__ == '__main__':
- unittest.main()
+ def test_not_recursive_folders(self):
+ """
+ Checks that build files in subfolders are ignored if they already exist in the root,
+ and that duplicates do not appear in the results.
+ pom.xml has been duplicated in docs/pom.xml
+ """
+
+ output_path = test_data_path + 'test_widoco_not_recursive_folders.json'
+
+ somef_cli.run_cli( threshold=0.8,
+ local_repo=test_data_repositories + "Widoco",
+ doc_src=None,
+ in_file=None,
+ output=output_path,
+ graph_out=None,
+ graph_format="turtle",
+ codemeta_out=None,
+ pretty=True,
+ missing=False,
+ readme_only=False)
+
+
+ with open(output_path, "r") as f:
+ json_content = json.load(f)
+
+ build_files = [
+ entry[constants.PROP_RESULT][constants.PROP_VALUE]
+ for key in json_content
+ for entry in json_content[key]
+ if key == constants.CAT_HAS_BUILD_FILE
+ ]
+
+ # root
+ assert any("pom.xml" in bf for bf in build_files), "The root pom.xml should be present"
+ # No docs/pom.xml
+ assert not any("docs/pom.xml" in bf for bf in build_files), "The pom.xml in docs/ should not be processed"
+ assert len(build_files) == len(set(build_files)), "There should be no duplicate build files"
+
+
+ def test_runtime_platform(self):
+ """
+ Checks that the runtime_platform information is correctly extracted from the project.
+ """
+
+ runtime_entries = [
+ entry[constants.PROP_RESULT]
+ for key in self.json_content
+ for entry in self.json_content[key]
+ if key == constants.CAT_RUNTIME_PLATFORM
+ ]
+
+ assert len(runtime_entries) > 0, "There should be at least one runtime_platform entry"
+
+ found_java = any(
+ entry.get("name") == "Java" and entry.get("value") == "Java: 1.8"
+ for entry in runtime_entries
+ )
+ assert found_java, "Java runtime with value Java: 1.8 should be present"
+
+
+ def test_issue_830(self):
+ """Checks if citattion have identifiers """
+ citation = self.json_content.get("citation", [])
+ assert citation, "No 'citation' found in JSON"
+ assert any(
+ entry.get("result", {}).get("format") == "cff"
+ and any(id.get("value") == "10.5281/zenodo.591294"
+ for id in entry.get("result", {}).get("identifier", [])
+ )
+ for entry in citation
+ ), "Citation.cff must have identifier id 10.5281/zenodo.591294"
+
+ # @classmethod
+ # def tearDownClass(cls):
+ # """delete temp file JSON just if all the test pass"""
+ # if os.path.exists(cls.json_file):
+ # try:
+ # os.remove(cls.json_file)
+ # print(f"Deleted {cls.json_file}")
+ # except Exception as e:
+ # print(f"Failed to delete {cls.json_file}: {e}")
+
+ def test_issue_862(self):
+ """Checks if this repository does not gets stuck when labeling headers"""
+ somef_cli.run_cli(threshold=0.8,
+ ignore_classifiers=False,
+ repo_url=None,
+ local_repo=test_data_repositories + "componentInstaller",
+ doc_src=None,
+ in_file=None,
+ output=test_data_path + "test_issue_862.json",
+ graph_out=None,
+ graph_format="turtle",
+ codemeta_out=None,
+ pretty=True,
+ missing=False,
+ readme_only=False)
+
+ text_file = open(test_data_path + "test_issue_862.json", "r")
+ data = text_file.read()
+ text_file.close()
+ json_content = json.loads(data)
+
+ assert "description" in json_content, "Missing 'description' property"
+
+ assert len(json_content["description"]) > 0, "Description list is empty"
+
+ first_desc = json_content["description"][0]["result"]
+ assert "value" in first_desc, "Missing 'value' in description result"
+ assert first_desc["value"], "Description 'value' is empty"
+
+ os.remove(test_data_path + "test_issue_862.json")
+
+ def test_issue_859(self):
+ """Checks whether a repository without content works fine. Must have just some results from the API."""
+
+ somef_cli.run_cli(threshold=0.8,
+ ignore_classifiers=False,
+ repo_url="https://github.com/shiningZZ/GU-CAFF",
+ local_repo=None,
+ doc_src=None,
+ in_file=None,
+ output=test_data_path + "test-859.json",
+ graph_out=None,
+ graph_format="turtle",
+ codemeta_out= None,
+ pretty=True,
+ missing=False,
+ readme_only=False)
+
+ with open(test_data_path + "test-859.json", "r") as text_file:
+ json_content = json.load(text_file)
+
+ assert "code_repository" in json_content
+ assert len(json_content["code_repository"]) > 0
+
+ for key, entries in json_content.items():
+ if isinstance(entries, list):
+ for entry in entries:
+ assert entry.get("technique") == "GitHub_API", \
+ f"Unexpected technique {entry.get('technique')} in key {key}"
+
+ os.remove(test_data_path + "test-859.json")
+
+ def test_issue_723(self):
+ """Checks if we exract the maintainers in the Codemeta output from the CODEOWNERS file.
+ But without -ai flag because requiere real requests to GitHub API and we want to avoid that in the tests.
+ """
+ somef_cli.run_cli(threshold=0.8,
+ ignore_classifiers=False,
+ repo_url=None,
+ local_repo=test_data_repositories + "tensorflow",
+ doc_src=None,
+ in_file=None,
+ output=test_data_path + "test_issue_723.json",
+ graph_out=None,
+ graph_format="turtle",
+ codemeta_out=None,
+ pretty=True,
+ missing=False,
+ readme_only=False)
+
+ text_file = open(test_data_path + "test_issue_723.json", "r")
+ data = text_file.read()
+ text_file.close()
+ json_content = json.loads(data)
+
+ maintainers= json_content.get("maintainer", [])
+ assert len(maintainers) == 10, f"Expected 10 maintainers, found {len(maintainers)}"
+ usernames = [m.get("result", {}).get("username") for m in maintainers]
+ assert "qqfish" in usernames, "Expected maintainer 'qqfish' not found"
+ assert "penpornk" in usernames, "Expected maintainer 'penpornk' not found"
+
+ os.remove(test_data_path + "test_issue_723.json")
+
+
+ def test_unify_json(self):
+ """
+ Checks that duplicated requirement entries extracted by different techniques
+ are unified into a single item, preserving all complementary information
+ (techniques, sources, and result fields).
+ """
+
+ output_path = test_data_path + 'test_widoco_unify.json'
+
+ somef_cli.run_cli( threshold=0.8,
+ local_repo=test_data_repositories + "Widoco",
+ doc_src=None,
+ in_file=None,
+ output=output_path,
+ graph_out=None,
+ graph_format="turtle",
+ codemeta_out=None,
+ pretty=True,
+ missing=False,
+ readme_only=False)
+
+
+ with open(output_path, "r") as f:
+ json_content = json.load(f)
+
+ requirements = json_content.get(constants.CAT_REQUIREMENTS, [])
+
+ unified_reqs = [ r for r in requirements if "You will need Java 1.8" in r["result"].get("value", "") ]
+ assert unified_reqs, "There should be at least one unified Java requirement entry"
+
+ req = unified_reqs[0]
+ assert set(req["technique"]) == {"code_parser", "header_analysis"},"Techniques should be merged from both extractors"
+
+ os.remove(test_data_path + "test_widoco_unify.json")
+
+ def test_unify_json_2(self):
+ """
+ Checks that duplicated requirement entries extracted by different techniques
+ are unified into a single item, preserving all complementary information
+ (techniques, sources, and result fields).
+ """
+
+ output_path = test_data_path + 'test_somef_unify.json'
+
+ somef_cli.run_cli( threshold=0.8,
+ local_repo=test_data_repositories + "somef_repo",
+ doc_src=None,
+ in_file=None,
+ output=output_path,
+ graph_out=None,
+ graph_format="turtle",
+ codemeta_out=None,
+ pretty=True,
+ missing=False,
+ readme_only=False)
+
+
+ with open(output_path, "r") as f:
+ json_content = json.load(f)
+
+ documentation = json_content.get(constants.CAT_DOCUMENTATION, [])
+
+ rtd_items = [
+ d for d in documentation
+ if d["result"].get("type") == "Url"
+ and d["result"].get("format") == "readthedocs"
+ ]
+
+ assert len(rtd_items) == 1, "There should be exactly one unified ReadTheDocs documentation entry"
+
+ rtd = rtd_items[0]
+
+ assert rtd["result"]["value"] == "https://somef.readthedocs.io/", "The unified ReadTheDocs URL must be canonical"
+
+ for d in documentation:
+ if d["result"].get("type") == "Url":
+ val = d["result"].get("value", "")
+ assert val == "https://somef.readthedocs.io/", f"Unexpected non canonical ReadTheDocs URL found: {val}"
+
+
+ os.remove(test_data_path + "test_somef_unify.json")
+
+
+ @unittest.skipIf(os.getenv("CI") == "true", "Skipped in CI because it is already verified locally")
+ def test_issue_gitlab_enrich_authors(self):
+ """Tests if a gitlab repository with codeowners file gets enriched with the information of the users in the codeowners file.
+ This test is skipped in CI because it requires real requests to GitLab API and we want to avoid hitting the rate limit.
+ """
+ output_file = test_data_path + "test-gitlab-reconcile.json"
+
+ somef_cli.run_cli(threshold=0.8,
+ ignore_classifiers=False,
+ repo_url="https://gitlab.com/sosy-lab/benchmarking/fm-tools/",
+ local_repo=None,
+ doc_src=None,
+ in_file=None,
+ output=output_file,
+ graph_out=None,
+ graph_format="turtle",
+ codemeta_out=None,
+ pretty=True,
+ missing=True,
+ readme_only=False,
+ reconcile_authors=True
+ )
+
+ with open(output_file, "r") as f:
+ json_content = json.load(f)
+
+ maintainers = json_content.get(constants.CAT_MAINTAINER, [])
+ self.assertTrue(len(maintainers) >= 2, "Expected at least two maintainers")
+
+ first = maintainers[0].get("result", {})
+ self.assertEqual(first.get("username"), "dbeyer")
+ self.assertEqual(first.get("name"), "Dirk Beyer")
+ self.assertEqual(first.get("email"), "dirk.beyer@sosy-lab.org")
+
+ second = maintainers[1].get("result", {})
+ self.assertEqual(second.get("username"), "FrNecas")
+ self.assertEqual(second.get("name"), "František Nečas")
+
+ os.remove(output_file)
+
+
+ @unittest.skipIf(os.getenv("CI") == "true", "Skipped in CI because it is already verified locally")
+ def test_issue_914(self):
+ """
+ Checks that somef can correctly handle and download a GitLab repository whose URL includes nested group/subgroup paths,
+ ensuring complex GitLab routes are processed without errors.
+ """
+
+ output_file = test_data_path + "test-issue-914.json"
+
+ somef_cli.run_cli(threshold=0.8,
+ ignore_classifiers=False,
+ repo_url="https://gitlab.com/gitlab-org/frontend/playground/batch-issue-creator",
+ local_repo=None,
+ doc_src=None,
+ in_file=None,
+ output=output_file,
+ graph_out=None,
+ graph_format="turtle",
+ codemeta_out=None,
+ pretty=True,
+ missing=True,
+ readme_only=False,
+ reconcile_authors=True
+ )
+
+ with open(output_file, "r") as f:
+ json_content = json.load(f)
+
+ title = json_content.get(constants.CAT_FULL_TITLE, [])
+ self.assertEqual(title[0].get("result", {}).get("value"), "batch-issue-creator")
+
+ requirements = json_content.get(constants.CAT_REQUIREMENTS, [])
+ self.assertEqual(requirements[0].get("result", {}).get("version"), "3.20.3")
+
+ os.remove(output_file)
\ No newline at end of file
diff --git a/src/somef_core/test/test_bower_parser.py b/src/somef_core/test/test_bower_parser.py
new file mode 100644
index 0000000..6e71994
--- /dev/null
+++ b/src/somef_core/test/test_bower_parser.py
@@ -0,0 +1,113 @@
+import unittest
+import os
+from pathlib import Path
+
+from somef_core.parser.bower_parser import parse_bower_json_file
+from somef_core.process_results import Result
+from somef_core.utils import constants
+
+test_data_path = str(Path(__file__).parent / "test_data") + os.path.sep
+test_data_repositories = str(Path(__file__).parent / "test_data" / "repositories") + os.path.sep
+
+class TestBowerParser(unittest.TestCase):
+
+ def test_parse_bower_json(self):
+ bower_file_path = test_data_repositories + os.path.sep + "js-template" + os.path.sep + "bower.json"
+
+ result = Result()
+
+ # metadata_result = parse_bower_json_file(bower_file_path, result, "https://example.org/bower.json")
+ metadata_result = parse_bower_json_file(bower_file_path, result, bower_file_path)
+ package_results = metadata_result.results.get(constants.CAT_HAS_PACKAGE_FILE, [])
+ self.assertTrue(len(package_results) > 0, "No package file info found")
+ # self.assertEqual(package_results[0]["result"]["value"], "bower.json")
+ self.assertEqual(package_results[0]["result"]["value"], bower_file_path)
+ self.assertEqual(package_results[0]["result"]["type"], constants.URL)
+
+ name_results = metadata_result.results.get(constants.CAT_NAME, [])
+ self.assertTrue(len(name_results) > 0, "No name found")
+ self.assertEqual(name_results[0]["result"]["value"], "alarm")
+ self.assertEqual(name_results[0]["result"]["type"], constants.STRING)
+
+ description_results = metadata_result.results.get(constants.CAT_DESCRIPTION, [])
+ self.assertTrue(len(description_results) > 0, "No description found")
+ self.assertEqual(description_results[0]["result"]["value"], "Alarm clock project during week1 day2 JS at Epicodus.")
+ self.assertEqual(description_results[0]["result"]["type"], constants.STRING)
+
+ # there is a homepage with "" in the bower. None, null and "" must not show in metadatos
+
+ # homepage_results = metadata_result.results.get(constants.CAT_HOMEPAGE, [])
+ # self.assertTrue(len(homepage_results) > 0, "No homepage found")
+ # self.assertEqual(homepage_results[0]["result"]["value"], "")
+ # self.assertEqual(homepage_results[0]["result"]["type"], constants.URL)
+
+ homepage_results = metadata_result.results.get(constants.CAT_HOMEPAGE, [])
+ self.assertEqual(len(homepage_results), 0, "Expected no homepage for this bower.json")
+
+ license_results = metadata_result.results.get(constants.CAT_LICENSE, [])
+ self.assertTrue(len(license_results) > 0, "No license found")
+ self.assertEqual(license_results[0]["result"]["value"], "ISC")
+ self.assertEqual(license_results[0]["result"]["type"], constants.LICENSE)
+
+ requirements_results = metadata_result.results.get(constants.CAT_REQUIREMENTS, [])
+ self.assertTrue(len(requirements_results) > 0, "No dependencies found")
+
+ authors_results = metadata_result.results.get(constants.CAT_AUTHORS, [])
+ self.assertTrue(len(authors_results) > 0, "No authors found")
+ self.assertEqual(len(authors_results), 2, "Expected two authors")
+
+ self.assertEqual(authors_results[0]["result"]["value"], "Andrew Accuardi ")
+ self.assertEqual(authors_results[1]["result"]["value"], "Another Author ")
+
+ found_jquery = False
+ found_bootstrap = False
+ for req_result in requirements_results:
+ dependency = req_result["result"]
+ if dependency.get("name") == "jquery" and dependency.get("dependency_type") == constants.DEPENDENCY_TYPE_RUNTIME:
+ found_jquery = True
+ self.assertEqual(dependency.get("dependency_resolver"),"bower","jQuery should come from the bower resolver")
+
+ self.assertTrue(found_jquery, "jQuery dependency not found")
+
+ def test_parse_2_bower_json(self):
+ bower_file_path = test_data_repositories + os.path.sep + "chosen" + os.path.sep + "bower.json"
+
+ result = Result()
+
+ # metadata_result = parse_bower_json_file(bower_file_path, result, "https://example.org/bower.json")
+ metadata_result = parse_bower_json_file(bower_file_path, result, bower_file_path)
+ package_results = metadata_result.results.get(constants.CAT_HAS_PACKAGE_FILE, [])
+ self.assertTrue(len(package_results) > 0, "No package file info found")
+ # self.assertEqual(package_results[0]["result"]["value"], "bower.json")
+ self.assertEqual(package_results[0]["result"]["value"], bower_file_path)
+ self.assertEqual(package_results[0]["result"]["type"], constants.URL)
+
+ name_results = metadata_result.results.get(constants.CAT_NAME, [])
+ self.assertTrue(len(name_results) > 0, "No name found")
+ self.assertEqual(name_results[0]["result"]["value"], "chosen")
+ self.assertEqual(name_results[0]["result"]["type"], constants.STRING)
+
+ description_results = metadata_result.results.get(constants.CAT_DESCRIPTION, [])
+ self.assertTrue(len(description_results) > 0, "No description found")
+ self.assertEqual(description_results[0]["result"]["value"], "Chosen is a JavaScript plugin that makes select boxes user-friendly. It is currently available in both jQuery and Prototype flavors.")
+ self.assertEqual(description_results[0]["result"]["type"], constants.STRING)
+
+
+ homepage_results = metadata_result.results.get(constants.CAT_HOMEPAGE, [])
+ self.assertEqual(homepage_results[0]["result"]["value"], "https://harvesthq.github.io/chosen/")
+
+ license_results = metadata_result.results.get(constants.CAT_LICENSE, [])
+ self.assertTrue(len(license_results) > 0, "No license found")
+ self.assertEqual(license_results[0]["result"]["value"], "https://github.com/harvesthq/chosen/blob/master/LICENSE.md")
+ self.assertEqual(license_results[0]["result"]["type"], constants.LICENSE)
+
+
+ authors_results = metadata_result.results.get(constants.CAT_AUTHORS, [])
+ self.assertTrue(len(authors_results) > 0, "No authors found")
+ self.assertEqual(len(authors_results), 4, "Expected two authors")
+
+ self.assertEqual(authors_results[0]["result"]["value"], "Patrick Filler")
+
+
+if __name__ == "__main__":
+ unittest.main()
\ No newline at end of file
diff --git a/src/somef_core/test/test_cabal_parser.py b/src/somef_core/test/test_cabal_parser.py
new file mode 100644
index 0000000..a137b54
--- /dev/null
+++ b/src/somef_core/test/test_cabal_parser.py
@@ -0,0 +1,94 @@
+import unittest
+import os
+from pathlib import Path
+
+from somef_core.parser.cabal_parser import parse_cabal_file
+from somef_core.process_results import Result
+from somef_core.utils import constants
+
+test_data_path = str(Path(__file__).parent / "test_data") + os.path.sep
+test_data_repositories = str(Path(__file__).parent / "test_data" / "repositories") + os.path.sep
+
+class TestCabalParser(unittest.TestCase):
+
+ def test_parse_cabal(self):
+ cabal_file_path = test_data_repositories + os.path.sep + "unused" + os.path.sep + "unused.cabal"
+ result = Result()
+
+ # metadata_result = parse_cabal_file(cabal_file_path, result, "https://example.org/unused.cabal")
+ metadata_result = parse_cabal_file(cabal_file_path, result, cabal_file_path)
+ package_results = metadata_result.results.get(constants.CAT_HAS_PACKAGE_FILE, [])
+ self.assertTrue(len(package_results) > 0, "No package file info found")
+ # self.assertEqual(package_results[0]["result"]["value"], "unused.cabal")
+ self.assertEqual(package_results[0]["result"]["value"], cabal_file_path)
+ self.assertEqual(package_results[0]["result"]["type"], constants.URL)
+
+ id_results = metadata_result.results.get(constants.CAT_PACKAGE_ID, [])
+ self.assertTrue(len(id_results) > 0, "No identifier found")
+ self.assertEqual(id_results[0]["result"]["value"], "unused")
+ self.assertEqual(id_results[0]["result"]["type"], constants.STRING)
+
+ description_results = metadata_result.results.get(constants.CAT_DESCRIPTION, [])
+ self.assertTrue(len(description_results) > 0, "No description found")
+ self.assertEqual(description_results[0]["result"]["value"], "Please see README.md")
+ self.assertEqual(description_results[0]["result"]["type"], constants.STRING)
+
+ homepage_results = metadata_result.results.get(constants.CAT_HOMEPAGE, [])
+ self.assertTrue(len(homepage_results) > 0, "No homepage found")
+ self.assertEqual(homepage_results[0]["result"]["value"], "https://github.com/joshuaclayton/unused#readme")
+ self.assertEqual(homepage_results[0]["result"]["type"], constants.URL)
+
+ license_results = metadata_result.results.get(constants.CAT_LICENSE, [])
+ self.assertTrue(len(license_results) > 0, "No license found")
+ self.assertEqual(license_results[0]["result"]["value"], "MIT")
+ self.assertEqual(license_results[0]["result"]["type"], constants.LICENSE)
+
+ requirements_results = metadata_result.results.get(constants.CAT_REQUIREMENTS, [])
+ self.assertTrue(len(requirements_results) > 0, "No dependencies found")
+
+ found_dep = False
+ for req_result in requirements_results:
+ dependency = req_result["result"]
+ if dependency.get("name") == "terminal-progress-bar" and dependency.get("dependency_type") == constants.DEPENDENCY_TYPE_RUNTIME:
+ found_dep = True
+ self.assertTrue(found_dep, "Dependency not found")
+
+ def test_parse_2_cabal(self):
+ cabal_file_path = test_data_repositories + os.path.sep + "haskell" + os.path.sep + "cabal.cabal"
+ result = Result()
+
+ # metadata_result = parse_cabal_file(cabal_file_path, result, "https://example.org/cabal.cabal")
+ metadata_result = parse_cabal_file(cabal_file_path, result, cabal_file_path)
+ package_results = metadata_result.results.get(constants.CAT_HAS_PACKAGE_FILE, [])
+ self.assertTrue(len(package_results) > 0, "No package file info found")
+ # self.assertEqual(package_results[0]["result"]["value"], "cabal.cabal")
+ self.assertEqual(package_results[0]["result"]["value"], cabal_file_path)
+ self.assertEqual(package_results[0]["result"]["type"], constants.URL)
+ description_results = metadata_result.results.get(constants.CAT_DESCRIPTION, [])
+ self.assertTrue(len(description_results) > 0, "No description found")
+ expected_start = "The Haskell Common Architecture"
+
+ self.assertTrue(
+ description_results[0]["result"]["value"].strip().startswith(expected_start)
+ )
+
+ homepage_results = metadata_result.results.get(constants.CAT_HOMEPAGE, [])
+ self.assertTrue(len(homepage_results) > 0, "No homepage found")
+ self.assertEqual(homepage_results[0]["result"]["value"], "http://www.haskell.org/cabal/")
+ self.assertEqual(homepage_results[0]["result"]["type"], constants.URL)
+
+ license_results = metadata_result.results.get(constants.CAT_LICENSE, [])
+ self.assertTrue(len(license_results) > 0, "No license found")
+ self.assertEqual(license_results[0]["result"]["value"], "BSD-3-Clause")
+ self.assertEqual(license_results[0]["result"]["type"], constants.LICENSE)
+
+ requirements_results = metadata_result.results.get(constants.CAT_REQUIREMENTS, [])
+ self.assertEqual(requirements_results[0]["result"]["name"], "Cabal-syntax")
+ self.assertEqual(requirements_results[1]["result"]["version"], ">= 0.4.0.1 && < 0.6")
+
+ issue_tracker_results = metadata_result.results.get(constants.CAT_ISSUE_TRACKER, [])
+ self.assertEqual(issue_tracker_results[0]["result"]["value"], "https://github.com/haskell/cabal/issues")
+
+
+if __name__ == "__main__":
+ unittest.main()
\ No newline at end of file
diff --git a/src/somef_core/test/test_cli.py b/src/somef_core/test/test_cli.py
index fa548e5..266affb 100644
--- a/src/somef_core/test/test_cli.py
+++ b/src/somef_core/test/test_cli.py
@@ -296,6 +296,7 @@ def test_issue_319_1(self):
assert data.find(constants.FORMAT_CFF) > 0
os.remove(test_data_path + "test-319-1.json")
+
def test_issue_319_2(self):
"""Test checks that the output format of citation files is specified in the output"""
somef_cli.run_cli(threshold=0.8,
@@ -406,17 +407,19 @@ def test_issue_314(self):
def test_issue_314_1(self):
"""Checks that the program can be run using only a single readme. GitHub"""
somef_cli.run_cli(threshold=0.8,
- ignore_classifiers=False,
- repo_url="https://github.com/tensorflow/tensorflow/tree/v2.6.0",
- doc_src=None,
- in_file=None,
- output=test_data_path + "test-314-1.json",
- graph_out=None,
- graph_format="turtle",
- codemeta_out=None,
- pretty=True,
- missing=True,
- readme_only=True)
+ ignore_classifiers=False,
+ # repo_url="https://github.com/tensorflow/tensorflow/tree/v2.6.0",
+ repo_url=None,
+ # doc_src=None,
+ doc_src= test_data_path + "README-tensorflow-2.6.0.md",
+ in_file=None,
+ output=test_data_path + "test-314-1.json",
+ graph_out=None,
+ graph_format="turtle",
+ codemeta_out=None,
+ pretty=True,
+ missing=True,
+ readme_only=True)
text_file = open(test_data_path + "test-314-1.json", "r")
data = text_file.read()
text_file.close()
@@ -425,11 +428,14 @@ def test_issue_314_1(self):
assert image is not None
os.remove(test_data_path + "test-314-1.json")
+
def test_issue_403(self):
"""Checks that the readme link returned by somef is correct"""
somef_cli.run_cli(threshold=0.8,
ignore_classifiers=False,
repo_url="https://github.com/oeg-upm/wot-hive",
+ # repo_url=None,
+ # local_repo=test_data_repositories + "wot-hive",
doc_src=None,
in_file=None,
output=test_data_path + "test-403.json",
@@ -489,7 +495,6 @@ def test_issue_225_406(self):
text_file = open(test_data_path + "test-225.json", "r")
data = text_file.read()
text_file.close()
- print(data)
assert data.find("\"" + constants.PROP_FORMAT + "\": \"" + constants.FORMAT_WIKI + "\"")
os.remove(test_data_path + "test-225.json")
@@ -517,24 +522,27 @@ def test_issue_406(self):
def test_issue_255(self):
"""Tests if somef can detect wiki articles"""
somef_cli.run_cli(threshold=0.8,
- ignore_classifiers=False,
- repo_url="https://github.com/mbloch/mapshaper/",
- local_repo=None,
- doc_src=None,
- in_file=None,
- output=test_data_path + "test-255.json",
- graph_out=None,
- graph_format="turtle",
- codemeta_out=None,
- pretty=True,
- missing=True,
- readme_only=False)
+ ignore_classifiers=False,
+ # repo_url="https://github.com/mbloch/mapshaper/",
+ repo_url=None,
+ local_repo=None,
+ # doc_src=None,
+ doc_src=test_data_path + "README-mapshaper.md",
+ in_file=None,
+ output=test_data_path + "test-255.json",
+ graph_out=None,
+ graph_format="turtle",
+ codemeta_out=None,
+ pretty=True,
+ missing=True,
+ readme_only=False)
text_file = open(test_data_path + "test-255.json", "r")
data = text_file.read()
text_file.close()
assert data.find("https://github.com/mbloch/mapshaper/wiki") != -1
os.remove(test_data_path + "test-255.json")
+ @unittest.skipIf(os.getenv("CI") == "true", "Skipped in CI because it is already verified locally")
def test_issue_255_1(self):
"""Tests if somef can detect the abscence of wikis if a repo does not have it."""
somef_cli.run_cli(threshold=0.8,
@@ -619,27 +627,6 @@ def test_issue_136_1(self):
assert data.find("doi") >= 0
os.remove(test_data_path + "test-136-1.json")
- def test_issue_353(self):
- """Tests that somef can successfully download a given github repo (reportedly failing)"""
- somef_cli.run_cli(threshold=0.8,
- ignore_classifiers=False,
- repo_url="https://github.com/proycon/analiticcl",
- local_repo=None,
- doc_src=None,
- in_file=None,
- output=test_data_path + "test-353.json",
- graph_out=None,
- graph_format="turtle",
- codemeta_out=None,
- pretty=True,
- missing=True,
- readme_only=False)
- text_file = open(test_data_path + "test-353.json", "r")
- data = text_file.read()
- text_file.close()
- assert data.find(constants.CAT_DESCRIPTION) >= 0
- os.remove(test_data_path + "test-353.json")
-
def test_issue_366(self):
"""Checks if somef can detect Docker compose files"""
somef_cli.run_cli(threshold=0.8,
@@ -661,6 +648,7 @@ def test_issue_366(self):
assert data.find(constants.FORMAT_DOCKERFILE) >= 0
os.remove(test_data_path + "test-366.json")
+
def test_issue_443(self):
somef_cli.run_cli(threshold=0.8,
ignore_classifiers=False,
@@ -705,6 +693,7 @@ def test_issue_443_3(self):
assert description is not None
os.remove(test_data_path + "test-443.json")
+
def test_issue_556(self):
"""
This test assesses whether documentation links that have been commented in the readme out are ignored.
@@ -778,3 +767,28 @@ def test_issue_567(self):
text_file.close()
assert constants.CAT_ACKNOWLEDGEMENT not in json_content
os.remove(test_data_path + "repositories/software_catalog/test-567.json")
+
+ def test_redundant_files(self):
+ """
+ This test checks if the redundant files for the repository TEC-Toolkit/CFO work correctly.
+ An error was detected in this repo
+ """
+ somef_cli.run_cli(threshold=0.8,
+ ignore_classifiers=False,
+ repo_url="https://github.com/Tec-Toolkit/ECFO",
+ local_repo=None,
+ doc_src=None,
+ in_file=None,
+ output=test_data_path + "test-ecfo.json",
+ graph_out=None,
+ graph_format="turtle",
+ codemeta_out=None,
+ pretty=True,
+ missing=True,
+ readme_only=False)
+ text_file = open(test_data_path + "test-ecfo.json", "r")
+ data = text_file.read()
+ text_file.close()
+ json_content = json.loads(data)
+ assert constants.CAT_TYPE not in json_content
+ os.remove(test_data_path + "test-ecfo.json")
diff --git a/src/somef_core/test/test_codemeta_export.py b/src/somef_core/test/test_codemeta_export.py
index e40b261..3033407 100644
--- a/src/somef_core/test/test_codemeta_export.py
+++ b/src/somef_core/test/test_codemeta_export.py
@@ -3,34 +3,57 @@
import json
from pathlib import Path
from .. import somef_cli
+from ..parser import pom_xml_parser
+from ..export import json_export
test_data_path = str(Path(__file__).parent / "test_data") + os.path.sep
+test_data_repositories = str(Path(__file__).parent / "test_data" / "repositories") + os.path.sep
+test_data_api_json = str(Path(__file__).parent / "test_data" / "api_responses") + os.path.sep
class TestCodemetaExport(unittest.TestCase):
-
+
+
@classmethod
def setUpClass(cls):
"""Runs somef_cli once and saves the JSON"""
cls.json_file = test_data_path + "test_json_codemeta_export.json"
- somef_cli.run_cli(
- threshold=0.8,
- ignore_classifiers=False,
- repo_url="https://github.com/tpronk/somef-demo-repo/",
- doc_src=None,
- in_file=None,
- output=None,
- graph_out=None,
- graph_format="turtle",
- codemeta_out=cls.json_file,
+ cls.api_results_file = test_data_api_json + "somef-demo.json"
+ with open(cls.api_results_file, "r", encoding="utf-8") as f:
+ cls.api_results = json.load(f)
+
+ json_export.save_codemeta_output(
+ cls.api_results,
+ cls.json_file,
pretty=True,
- missing=True,
- readme_only=False
+ requirements_mode=False
)
- with open(cls.json_file, "r") as f:
+ with open(cls.json_file, "r", encoding="utf-8") as f:
cls.json_content = json.load(f)
+ # somef_cli.run_cli(
+ # threshold=0.8,
+ # ignore_classifiers=False,
+ # repo_url="https://github.com/juanjemdIos/somef-demo-repo/",
+ # # repo_url=None,
+ # # local_repo= test_data_repositories + "somef-demo",
+ # doc_src=None,
+ # in_file=None,
+ # output=None,
+ # graph_out=None,
+ # graph_format="turtle",
+ # codemeta_out=cls.json_file,
+ # pretty=True,
+ # missing=True,
+ # readme_only=False
+ # )
+
+ # with open(cls.json_file, "r") as f:
+ # cls.json_content = json.load(f)
+
+ # print(cls.json_content)
+
def test_codemeta_version(self):
"""Checks if codemeta version is v3"""
@@ -210,19 +233,372 @@ def test_codemeta_relevants_programming_languages(self):
expected_languages = ["Jupyter Notebook"]
assert set(self.json_content["programmingLanguage"]) == set(expected_languages), f"Mismatch: {self.json_content['programmingLanguage']}"
+ def test_codemeta_author_file(self):
+ """Checks if codemeta file has extracted the authors in the author file"""
+ authors = [author.get("name") for author in self.json_content["author"] if author["@type"] == "Person"]
+ expected_authors = {"Daniel Garijo", "Juanje Mendoza"}
+ assert set(authors) >= expected_authors, f"Mismatch in authors: {authors}"
+
+ def test_issue_763(self):
+ """
+ Checks that several citations of same item in json become in one in codemeta
+
+ Export to codemeta: Reconcile Bibtex and CFF exports
+ """
+
+ somef_cli.run_cli(threshold=0.9,
+ ignore_classifiers=False,
+ repo_url=None,
+ doc_src=None,
+ local_repo=test_data_repositories + "inspect4py",
+ in_file=None,
+ output=None,
+ graph_out=None,
+ graph_format="turtle",
+ codemeta_out= test_data_path + 'test_codemeta_several_citations.json',
+ pretty=True,
+ missing=False)
+
+ json_file_path = test_data_path + "test_codemeta_several_citations.json"
+ text_file = open(json_file_path, "r")
+ data = text_file.read()
+ json_content = json.loads(data)
+ text_file.close()
+
+ reference_publications = json_content.get("referencePublication", [])
+
+ # just one reference
+ assert len(reference_publications) == 1, f"Expected 1 referencePublication, found {len(reference_publications)}"
+
+ # reference with doi expected
+ assert reference_publications[0].get("identifier") == "10.1145/3524842.3528497", \
+ f"Expected identifier '10.1145/3524842.3528497', found '{reference_publications[0].get('identifier')}'"
+ os.remove(json_file_path)
+
+ def test_codemeta_duplicate_dois(self):
+ """Checks if codemeta duplicates dois whith diferent format: doi.org, dx.doi...."""
+ somef_cli.run_cli(threshold=0.8,
+ ignore_classifiers=False,
+ repo_url=None,
+ doc_src=test_data_path + "README-widoco-duplicate-dois.md",
+ in_file=None,
+ output=None,
+ graph_out=None,
+ graph_format="turtle",
+ codemeta_out=test_data_path + 'test_codemeta_dup_dois.json',
+ pretty=True,
+ missing=True)
+
+ json_file_path = test_data_path + "test_codemeta_dup_dois.json"
+ # check if the file has been created in the correct path
+ assert os.path.exists(json_file_path), f"File {json_file_path} doesn't exist."
+
+ with open(json_file_path, "r") as f:
+ codemeta = json.load(f)
+
+ ref_pubs = codemeta.get("referencePublication", [])
+
+ dois = set()
+ for pub in ref_pubs:
+ if isinstance(pub, dict) and "identifier" in pub:
+ dois.add(pub["identifier"])
+ assert len(dois) == 1, f"Expected 1 DOI, got {len(dois)}: {dois}"
+
+ os.remove(json_file_path)
+
+ def test_requirements_mode(self):
+
+ """
+ Checks that when requirements_mode='v', only structured requirements from code parsers are exported,
+ and textual requirements (e.g. from README or codemeta.json) are excluded.
+ """
+ output_path = test_data_path + 'test_codemeta_widoco_requirements_v.json'
+
+ somef_cli.run_cli(threshold=0.9,
+ ignore_classifiers=False,
+ repo_url=None,
+ doc_src=None,
+ local_repo=test_data_repositories + "Widoco",
+ in_file=None,
+ output=None,
+ graph_out=None,
+ graph_format="turtle",
+ codemeta_out= output_path,
+ pretty=True,
+ missing=False,
+ requirements_mode="v")
+
+ with open(output_path, "r") as f:
+ json_content = json.load(f)
+
+ requirements = json_content.get("softwareRequirements", [])
+
+ assert all(isinstance(req, dict) and "name" in req for req in requirements), \
+ f"Expected only structured requirements, found: {requirements}"
+
+ assert all(not isinstance(req, str) for req in requirements), \
+ f"Found unexpected textual requirement entries: {[r for r in requirements if isinstance(r, str)]}"
+
+ os.remove(output_path)
+
+ def test_requirements_not_duplicate(self):
+
+ """
+ Checks that when requirements_mode='all', the exported softwareRequirements list contains no duplicates,
+ even if the same requirement appears from multiple sources or techniques.
+ """
+ output_path = test_data_path + 'test_codemeta_widoco_requirements_not_duplicate.json'
+
+ somef_cli.run_cli(threshold=0.9,
+ ignore_classifiers=False,
+ repo_url=None,
+ doc_src=None,
+ local_repo=test_data_repositories + "Widoco",
+ in_file=None,
+ output=None,
+ graph_out=None,
+ graph_format="turtle",
+ codemeta_out= output_path,
+ pretty=True,
+ missing=False,
+ requirements_mode="all")
+
+ with open(output_path, "r") as f:
+ json_content = json.load(f)
+
+ requirements = json_content.get("softwareRequirements", [])
+
+ seen = set()
+ for req in requirements:
+ if isinstance(req, dict):
+ key = f"{req['name'].strip()}|{req.get('version', '').strip()}"
+ else:
+ key = " ".join(req.strip().replace("\n", " ").split())
+ assert key not in seen, f"Duplicate requirement found: {key}"
+ seen.add(key)
+
+ os.remove(output_path)
+
+
+ def test_description_not_duplicate(self):
+ """
+ Checks that the 'description' field in the exported codemeta contains no duplicate entries,
+ even if the same description appears from multiple sources or techniques.
+ """
+
+ output_path = test_data_path + 'test_codemeta_widoco_description_not_duplicate.json'
+
+ somef_cli.run_cli(
+ threshold=0.9,
+ ignore_classifiers=False,
+ repo_url=None,
+ doc_src=None,
+ local_repo=test_data_repositories + "Widoco",
+ in_file=None,
+ output=None,
+ graph_out=None,
+ graph_format="turtle",
+ codemeta_out=output_path,
+ pretty=True,
+ missing=False,
+ requirements_mode="all"
+ )
+
+ with open(output_path, "r") as f:
+ json_content = json.load(f)
+
+ descriptions = json_content.get("description", [])
+ os.remove(output_path)
+
+ seen = set()
+ for desc in descriptions:
+ normalized = " ".join(desc.strip().replace("\n", " ").split())
+ assert normalized not in seen, f"Duplicate description found: {normalized}"
+ seen.add(normalized)
+
+
+ def test_codemeta_runtime(self):
+
+ """
+ Checks runtime in codemeta file
+ """
+
+ pom_xml_parser.processed_pom = False
+
+ output_path = test_data_path + 'test_codemeta_widoco_runtime_platform.json'
+ if os.path.exists(output_path):
+ os.remove(output_path)
+
+ somef_cli.run_cli(threshold=0.9,
+ ignore_classifiers=False,
+ repo_url=None,
+ doc_src=None,
+ local_repo=test_data_repositories + "Widoco",
+ in_file=None,
+ output=None,
+ graph_out=None,
+ graph_format="turtle",
+ codemeta_out= output_path,
+ pretty=True,
+ missing=False,
+ requirements_mode="v")
+
+ with open(output_path, "r") as f:
+ json_content = json.load(f)
+
+ runtime = json_content.get("runtimePlatform", [])
+ assert runtime == "Java: 1.8", f"It was expected 'Java: 1.8' but it was '{runtime}'"
+ os.remove(output_path)
+
+ def test_issue_832_join_authors(self):
+ """
+ Check that an author who appears multiple times in the JSON appears only once in the Codemeta output, merging their properties.
+ """
+
+ somef_cli.run_cli(threshold=0.9,
+ ignore_classifiers=False,
+ repo_url=None,
+ doc_src=None,
+ local_repo=test_data_repositories + "fuji",
+ in_file=None,
+ output=None,
+ graph_out=None,
+ graph_format="turtle",
+ codemeta_out= test_data_path + 'test_codemeta_join_authors.json',
+ pretty=True,
+ missing=False)
+
+ json_file_path = test_data_path + "test_codemeta_join_authors.json"
+ text_file = open(json_file_path, "r")
+ data = text_file.read()
+ json_content = json.loads(data)
+ text_file.close()
+
+ authors = json_content.get("author", [])
+ assert len(authors) == 9, f"Expected 9 author, found {len(authors)}"
+ assert authors[0].get("name") == "Robert Huber", "Second author must be Robert Huber"
+ assert authors[1].get("email") == "anusuriya.devaraju@googlemail.com", \
+ "Third author must have email anusuriya.devaraju@googlemail.com"
+
+ os.remove(json_file_path)
+
+ def test_issue_417(self):
+ """Checks whether a repository correctly extracts to Codemeta"""
+
+
+ somef_cli.run_cli(threshold=0.8,
+ ignore_classifiers=False,
+ repo_url=None,
+ local_repo=test_data_repositories + "Widoco",
+ doc_src=None,
+ in_file=None,
+ output=None,
+ graph_out=None,
+ graph_format="turtle",
+ codemeta_out=test_data_path + "test-417.json-ld",
+ pretty=True,
+ missing=False,
+ readme_only=False)
+
+ text_file = open(test_data_path + "test-417.json-ld", "r")
+ data = text_file.read()
+ text_file.close()
+ json_content = json.loads(data)
+ issue_tracker = json_content["issueTracker"] # JSON is in Codemeta format
+
+ #len(json_content["citation"])
+ #codemeta category citation is now referencePublication
+ assert issue_tracker == 'https://github.com/dgarijo/Widoco/issues' and len(json_content["referencePublication"]) > 0 and \
+ len(json_content["name"]) > 0 and len(json_content["identifier"]) > 0 and \
+ len(json_content["description"]) > 0 and len(json_content["readme"]) > 0 and \
+ len(json_content["buildInstructions"]) > 0 and \
+ len(json_content["softwareRequirements"]) > 0 and len(json_content["programmingLanguage"]) > 0 and \
+ len(json_content["keywords"]) > 0 and len(json_content["logo"]) > 0 and \
+ len(json_content["license"]) > 0 and len(json_content["dateCreated"]) > 0
+
+ os.remove(test_data_path + "test-417.json-ld")
+
+
+
+ def test_issue_723_codemeta(self):
+ """
+ Check that we extract the maintainers in the Codemeta output from the CODEOWNERS file. But without -ai flag
+ """
+
+ somef_cli.run_cli(threshold=0.9,
+ ignore_classifiers=False,
+ repo_url=None,
+ doc_src=None,
+ local_repo=test_data_repositories + "tensorflow",
+ in_file=None,
+ output=None,
+ graph_out=None,
+ graph_format="turtle",
+ codemeta_out= test_data_path + 'test_codemeta_codeowners.json',
+ pretty=True,
+ missing=False)
+
+ json_file_path = test_data_path + "test_codemeta_codeowners.json"
+ text_file = open(json_file_path, "r")
+ data = text_file.read()
+ json_content = json.loads(data)
+ text_file.close()
+
+ maintainers= json_content.get("maintainer", [])
+ assert len(maintainers) == 10, f"Expected 10 maintainers, found {len(maintainers)}"
+ identifiers = [m.get("identifier") for m in maintainers]
+ assert "qqfish" in identifiers, "Expected maintainer 'qqfish' not found"
+ assert "penpornk" in identifiers, "Expected maintainer 'penpornk' not found"
+
+ os.remove(json_file_path)
+
+
+ def test_issue_891(self):
+
+ """
+ Checks that the CodeMeta export include just structured requirements extracted by code parsers, excluding any textual
+ entries coming from readme, codemeta.json or other non structured sources.
+ """
+
+ output_path = test_data_path + 'test_codemeta_issue_891.json'
+
+ somef_cli.run_cli(threshold=0.9,
+ ignore_classifiers=False,
+ repo_url=None,
+ doc_src=None,
+ local_repo=test_data_repositories + "somef_repo",
+ in_file=None,
+ output=None,
+ graph_out=None,
+ graph_format="turtle",
+ codemeta_out= output_path,
+ pretty=True,
+ missing=False)
+
+ with open(output_path, "r") as f:
+ json_content = json.load(f)
+
+ requirements = json_content.get("softwareRequirements", [])
+ assert all(isinstance(req, dict) and "name" in req for req in requirements), \
+ f"Expected only structured requirement objects, found: {requirements}"
+
+ req_dict = {req["name"]: req for req in requirements}
+ assert "python" in req_dict, "Missing expected requirement: python"
+ assert req_dict["python"].get("version") == ">=3.9,<=3.13"
+
+ os.remove(output_path)
+
@classmethod
def tearDownClass(cls):
"""delete temp file JSON just if all the test pass"""
- if os.path.exists(cls.json_file): # Verifica que el archivo exista
+ if os.path.exists(cls.json_file):
try:
os.remove(cls.json_file)
- print(f"Deleted {cls.json_file}") # Mensaje para confirmar la eliminación
+ print(f"Deleted {cls.json_file}")
except Exception as e:
- print(f"Failed to delete {cls.json_file}: {e}") # Captura errores de eliminación
+ print(f"Failed to delete {cls.json_file}: {e}")
if __name__ == "__main__":
- unittest.main()
-
-
\ No newline at end of file
+ unittest.main()
\ No newline at end of file
diff --git a/src/somef_core/test/test_codemeta_gitlab_export.py b/src/somef_core/test/test_codemeta_gitlab_export.py
index 7f1a23a..19f39f2 100644
--- a/src/somef_core/test/test_codemeta_gitlab_export.py
+++ b/src/somef_core/test/test_codemeta_gitlab_export.py
@@ -31,29 +31,6 @@ def setUpClass(cls):
def test_codemeta_gitlab_export(self):
"""Checks if codemeta file has been exported from a gitlab repo"""
- # somef_cli.run_cli(threshold=0.8,
- # ignore_classifiers=False,
- # repo_url='https://gitlab.com/escape-ossr/eossr',
- # doc_src=None,
- # in_file=None,
- # output=None,
- # graph_out=None,
- # graph_format="turtle",
- # codemeta_out=test_data_path + 'test_gitlab.json',
- # pretty=False,
- # missing=False)
-
- # json_file_path = test_data_path + "test_gitlab.json"
- # # check if the file has been created in the correct path
- # assert os.path.exists(json_file_path), f"File {json_file_path} doesn't exist."
-
- # text_file = open(test_data_path + "test_gitlab.json", "r")
- # data = text_file.read()
- # text_file.close()
- # check if has been correctly created with the normal structure
- # assert data.find(constants.CAT_IDENTIFIER) > 0
- # os.remove(test_data_path + "test_gitlab.json")
- # assert self.json_content.find(constants.CAT_IDENTIFIER) > 0
assert constants.CAT_IDENTIFIER in self.json_content, f"Missing key {constants.CAT_IDENTIFIER} in JSON"
def test_gitlab_license(self):
diff --git a/src/somef_core/test/test_codemeta_parser.py b/src/somef_core/test/test_codemeta_parser.py
new file mode 100644
index 0000000..880cb0f
--- /dev/null
+++ b/src/somef_core/test/test_codemeta_parser.py
@@ -0,0 +1,66 @@
+import unittest
+import os
+import yaml
+from pathlib import Path
+
+from somef_core.parser.codemeta_parser import parse_codemeta_json_file
+from somef_core.process_results import Result
+from somef_core.utils import constants
+
+TEST_ROOT = Path(__file__).parent
+REPOS_DIR = TEST_ROOT / "test_data" / "repositories"
+EXPECT_DIR = TEST_ROOT / "test_data" / "expected"
+
+class TestCodemetaParser(unittest.TestCase):
+
+ def load_expected(self, repo_name):
+ """Load expected YAML for a given repo."""
+ yaml_path = EXPECT_DIR / f"{repo_name}.yaml"
+ if not yaml_path.exists():
+ self.skipTest(f"No expected YAML for repository '{repo_name}'")
+ with open(yaml_path, "r", encoding="utf-8") as f:
+ return yaml.safe_load(f)
+
+ def test_parse_multiple_codemeta_files(self):
+ for repo_folder in os.listdir(REPOS_DIR):
+ print(f"################# Testing {repo_folder} #################")
+ repo_path = REPOS_DIR / repo_folder
+ codemeta_path = repo_path / "codemeta.json"
+ if not codemeta_path.is_file():
+ continue
+
+ expected = self.load_expected(repo_folder)
+ result = Result()
+ metadata_result = parse_codemeta_json_file(
+ str(codemeta_path),
+ result,
+ "https://example.org/codemeta.json"
+ )
+
+ with self.subTest(repo=repo_folder):
+ # In order for us to check every test, we need every file in "expected" directory to be of .yaml,
+ # and make sure the name is the same as the repo folder
+ print(f"################# Processing expectation of {repo_folder} #################")
+ for cat_name, expected_val in expected.items():
+ cat_const = getattr(constants, cat_name)
+ actual_list = metadata_result.results.get(cat_const, [])
+ self.assertTrue(
+ actual_list,
+ f"[{repo_folder}] No results for {cat_name}"
+ )
+
+ first = actual_list[0]["result"]
+ if isinstance(expected_val, dict):
+ for key, val in expected_val.items():
+ self.assertEqual(
+ first.get(key), val,
+ f"[{repo_folder}] Mismatch in {cat_name}.{key}"
+ )
+ else:
+ self.assertEqual(
+ first.get("value"), expected_val,
+ f"[{repo_folder}] Mismatch in {cat_name}"
+ )
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/src/somef_core/test/test_composer_parser.py b/src/somef_core/test/test_composer_parser.py
new file mode 100644
index 0000000..245a169
--- /dev/null
+++ b/src/somef_core/test/test_composer_parser.py
@@ -0,0 +1,59 @@
+import unittest
+import os
+import sys
+import json
+from pathlib import Path
+from somef_core.process_results import Result
+from somef_core.parser.composer_parser import parse_composer_json
+from somef_core.utils import constants
+
+test_data_path = str(Path(__file__).parent / "test_data") + os.path.sep
+test_data_repositories = str(Path(__file__).parent / "test_data" / "repositories") + os.path.sep
+
+class TestComposerParser(unittest.TestCase):
+ def test_parse_composer_json(self):
+ composer_path = test_data_repositories + os.path.sep+ "composer"+ os.path.sep+ "composer.json"
+ result = Result()
+
+ metadata_result = parse_composer_json(composer_path, result, "https://example.org/composer.json")
+
+ self.assertIn(constants.CAT_PACKAGE_ID, metadata_result.results)
+ package_id = result.results[constants.CAT_PACKAGE_ID][0]["result"]["value"]
+ self.assertEqual(package_id, "composer/composer")
+
+ self.assertIn(constants.CAT_DESCRIPTION, metadata_result.results)
+ description = result.results[constants.CAT_DESCRIPTION][0]["result"]["value"]
+ self.assertEqual(description, "Composer helps you declare, manage and install dependencies of PHP projects. It ensures you have the right stack everywhere.")
+
+ self.assertIn(constants.CAT_LICENSE, metadata_result.results)
+ license_value = result.results[constants.CAT_LICENSE][0]["result"]["value"]
+ self.assertEqual(license_value, "MIT")
+
+ self.assertIn(constants.CAT_REQUIREMENTS, metadata_result.results)
+ dependencies = result.results[constants.CAT_REQUIREMENTS]
+ dep_names = [d["result"]["name"] for d in dependencies]
+ self.assertIn("php", dep_names)
+
+
+ def test_parse_composer_json_2(self):
+ composer_path = test_data_repositories + os.path.sep+ "codecov"+ os.path.sep+ "composer.json"
+ result = Result()
+
+ metadata_result = parse_composer_json(composer_path, result, "https://example.org/composer_2.json")
+
+ self.assertIn(constants.CAT_DESCRIPTION, metadata_result.results)
+ description = result.results[constants.CAT_DESCRIPTION][0]["result"]["value"]
+ self.assertEqual(description, "Codecov Example PHP")
+
+ self.assertIn(constants.CAT_PACKAGE_ID, metadata_result.results)
+ package_id = result.results[constants.CAT_PACKAGE_ID][0]["result"]["value"]
+ self.assertEqual(package_id, "codecov/codecov-php")
+
+ self.assertIn(constants.CAT_REQUIREMENTS, metadata_result.results)
+ dependencies = result.results[constants.CAT_REQUIREMENTS]
+ self.assertEqual(dependencies[0]["result"]["name"], "php")
+ self.assertEqual(dependencies[0]["result"]["dependency_type"], constants.DEPENDENCY_TYPE_RUNTIME)
+ self.assertEqual(dependencies[1]["result"]["dependency_type"], constants.DEPENDENCY_TYPE_DEVELOPMENT)
+
+if __name__ == "__main__":
+ unittest.main()
\ No newline at end of file
diff --git a/src/somef_core/test/test_conda_environment_parser.py b/src/somef_core/test/test_conda_environment_parser.py
new file mode 100644
index 0000000..1d81889
--- /dev/null
+++ b/src/somef_core/test/test_conda_environment_parser.py
@@ -0,0 +1,58 @@
+import json
+import os
+import unittest
+from pathlib import Path
+from .. import somef_cli
+from ..utils import constants
+
+test_data_path = str(Path(__file__).parent / "test_data") + os.path.sep
+test_data_repositories = str(Path(__file__).parent / "test_data" / "repositories") + os.path.sep
+test_data_api_json = str(Path(__file__).parent / "test_data" / "api_responses") + os.path.sep
+
+class TestCondaEnvironmentParser(unittest.TestCase):
+
+ def test_issue_489(self):
+
+ somef_cli.run_cli(threshold=0.8,
+ ignore_classifiers=False,
+ repo_url=None,
+ local_repo=test_data_repositories + "stable-diffusion",
+ doc_src=None,
+ in_file=None,
+ output=test_data_path + "test_issue_489.json",
+ graph_out=None,
+ graph_format="turtle",
+ codemeta_out=None,
+ pretty=True,
+ missing=False,
+ readme_only=False)
+
+ text_file = open(test_data_path + "test_issue_489.json", "r")
+ data = text_file.read()
+ text_file.close()
+ json_content = json.loads(data)
+
+ requeriments= json_content.get("requirements", [])
+
+ assert len(requeriments) == 26, f"Expected 26 requeriments, found {len(requeriments)}"
+ python_reqs = [
+ r for r in requeriments
+ if r["result"].get("name") == "python"
+ ]
+ assert python_reqs, "Expected python dependency not found"
+ assert python_reqs[0]["result"]["dependency_type"] == constants.DEPENDENCY_TYPE_RUNTIME
+ assert python_reqs[0]["result"]["dependency_resolver"] == "conda"
+ assert python_reqs[0]["result"]["version"] == "3.8.5"
+
+ albumentations_reqs = [
+ r for r in requeriments
+ if r["result"].get("name") == "albumentations"
+ ]
+ assert albumentations_reqs, "Expected albumentations dependency not found"
+ assert albumentations_reqs[0]["result"]["dependency_type"] == constants.DEPENDENCY_TYPE_RUNTIME
+ assert albumentations_reqs[0]["result"]["dependency_resolver"] == "pip"
+ assert albumentations_reqs[0]["result"]["version"] == "0.4.3"
+
+ os.remove(test_data_path + "test_issue_489.json")
+
+
\ No newline at end of file
diff --git a/src/somef_core/test/test_configuration.py b/src/somef_core/test/test_configuration.py
index c8bf9f9..6097a3f 100644
--- a/src/somef_core/test/test_configuration.py
+++ b/src/somef_core/test/test_configuration.py
@@ -1,6 +1,6 @@
import unittest
from ..configuration import get_configuration_file
-from somef.utils import constants
+from somef_core.utils import constants
class TestConfiguration(unittest.TestCase):
diff --git a/src/somef_core/test/test_data/README-EUVpy.md b/src/somef_core/test/test_data/README-EUVpy.md
new file mode 100644
index 0000000..26c2756
--- /dev/null
+++ b/src/somef_core/test/test_data/README-EUVpy.md
@@ -0,0 +1,166 @@
+
+
+
+[_(EUV imagery of the solar disk, taken by NASA SDO)_](https://www.nist.gov/image/1280px-sun-august12010jpg)
+
+# Overview
+`EUVpy` is a Python module that contains the NEUVAC empirical model of solar EUV irradiance for use primarily in thermosphere-ionosphere models.
+Conceptual development was carried out by Dr. Aaron J. Ridley and analysis and contributions were made by Dr. Daniel A. Brandt and Dr. Joseph Paki.
+
+`EUVpy` also contains code for running, comparing, and analyzing the performance of NEUVAC in comparison to other
+empirical models such as EUVAC, HEUVAC, HFG, and FISM2. As such, the package includes access to EUVAC, HEUVAC, and HFG,
+and allows the user to download the F10.7 index at will.
+
+# Installation
+
+You can obtain `EUVpy` with a simple git clone command. In the terminal, execute the following command:
+
+> git clone https://github.com/DanBrandt/EUVpy.git
+
+The EUVpy package provides access to the HEUVAC model, which is written in Fortran and requires gfortran to be installed. To
+install gfortran (if you don't already have it), open a terminal and execute the following command:
+
+> sudo apt-get install gfortran
+
+Now, enter into the package folder and run the following command in order to compile the Fortran code that HEUVAC
+alone depends upon:
+
+> cd EUVpy\
+> . install_heuvac.sh
+
+If this step gives you trouble, try executing the following code **first**, and then retry the above commands.
+
+> git submodule update --init --recursive --remote
+
+After HEUVAC is compiled once, you should never have to compile it again.
+
+Now you are in a position to install the rest of the EUVpy package. Before doing this, you will first want to initialize a Python virtual environment.
+
+> cd ~/.virtualenvs\
+> python**3.x** -m venv myVenv\
+> pip install --upgrade pip\
+> pip install wheel\
+> source myVenv/bin/activate
+
+Above, 'python**3.x**' refers to whatever version of Python 3 you have installed. EUVpy works with any version of Python 3.
+You may now use one of two ways to install EUVpy. You can install from the cloned repo directly, or you can install from PyPI.
+
+## Installing from the Cloned Repository
+
+Navigate back to the package folder:
+
+> cd\
+> cd myDirectory/EUVpy
+
+After this is done, simply execute the following command:
+
+> pip install .
+
+## Installing from PyPI
+
+A simpler way to install also involves just entering the following in the terminal:
+
+> pip install EUVpy
+
+**Note:** If you choose this route, you'll have to install HEUVAC differently than described above. You will have to
+navigate the directory containing the HEUVAC files, and execute the following command, as follows:
+
+> cd src/EUVpy/empiricalModels/models/HEUVAC/srcHEUVAC
+> . compile.sh
+
+You can then return to the top package directory. This should only have to be done once.
+This approach will be replaced with a user-friendly console script in the next release of this package.
+
+### Unit Testing
+
+**Note: this section only applies if you installed from the cloned repo.**
+
+Just to make sure everything installed correctly, while in the package folder, run the following command in the terminal.
+
+> pytest
+
+If all of the tests pass, you should be good to go! If you find an issue at this step (or any other) please don't hesitate
+to reach out!
+
+# Usage
+EUVpy contains modules for **4** different EUV irradiance models. These models include:
+* NEUVAC
+* EUVAC
+* HEUVAC
+* HFG (SOLOMON)
+
+We note that SOLOMON in the literature can either refer to the empirical model between F10.7 and 81 day-averaged F10.7
+centered on the current day (hereafter F10.7A) and EUV irradiance in 22 overlapping bands as described by Solomon and
+Qian 2005, or it can refer to _any_ EUV irradiance data summed into those 22 overlapping bins (referred to as the STAN
+BANDS). In this package, SOLOMON only refers to the former, though functionality does exist to run all other models in
+the STAN BANDS.
+
+## Finding your way around
+
+There are few folders in this package:
+* **empiricalModels**: Contains code and data for EUVAC, HEUVAC, and SOLOMON, as well as FISM:
+* **experiments**: Contains code and figures related to the publication associated with NEUVAC. In this folder, the file
+_fitNeuvac.py_ s used for actually performing the NEUVAC fits between F10.7, F10.7A, and FISM2, while _uncNeuvac.py_
+contains code for computing the correlation matrix used to enable running NEUVAC ensembles, as well as generating plots
+of the squared difference between NEUVAC and FISM2 in different bands.
+* **NEUVAC**: Contains the code for running NEUVAC.
+* **solarIndices**: Contains F10.7 solar index data, from both OMNIWeb and Penticton.
+* **tools**: Contains code for miscellaneous helper functions. In this folder appears the following:
+ * _EUV_: Contains numerous functions within fism2_process.py for reading in and rebinning FISM2 data.
+ * _processIndices.py_: Contains functions for reading in, downloading, and cleaning OMNIWeb data.
+ * _processIrradiances.py_: Contains functions for reading in data from TIMED/SEE, SDO/EVE, and FISM.
+ * _spectralAnalysis.py_: Contains functions for converting between solar spectral irradiance and solar spectral flux.
+ * _toolbox.py_: Contains miscellaneous helper functions that mainly focus on directory management, loading and saving data, statistics, and fitting.
+
+Each of the models in EUVpy requires F10.7 as an input. To grab F10.7 between any two dates, simply do the following:
+
+> from EUVpy.tools import processIndices
+>
+> f107times, f107, f107a, f107b = processIndices.getCLSF107('YYYY-MM-DD', 'YYYY-MM-DD', truncate=False)
+
+To import any of the models, simply do as follows:
+
+NEUVAC
+> from EUVpy.NEUVAC import neuvac
+>
+> neuvacIrr, _, _, _ = neuvac.neuvacEUV(f107, f107b, bands='EUVAC')
+
+EUVAC
+> from EUVpy.empiricalModels.models.EUVAC import euvac
+>
+> euvacFlux, euvacIrr, _, _, _ = euvac.euvac(f107, f107a)
+
+HEUVAC
+> from EUVpy.empiricalModels.models.HEUVAC import heuvac
+>
+> heuvac_wav, heuvacFlux, heuvacIrr, _, _, _ = heuvac.heuvac(f107, f107a, torr=True)
+
+SOLOMON
+> from EUVpy.empiricalModels.models.SOLOMON import solomon
+>
+> solomonFluxHFG, solomonIrrHFG = SOLOMON.solomon.solomon(f107, f107a, model='HFG')
+>
+> solomonFluxEUVAC, solomonIrrEUVAC = SOLOMON.solomon.solomon(f107, f107a, model='EUVAC')
+
+# Examples
+
+There is a [Python notebook](https://github.com/DanBrandt/EUVpy/blob/main/examples/euvpy_examples.ipynb) you can walk through in order to get familiar with the basic functionality of EUVpy.
+
+We encourage you to peruse [more examples](https://github.com/DanBrandt/EUVpy/blob/main/docs/source/examples.rst) in the documentation for guidelines on how to run each of the models.
+
+Due to the unique construction of NEUVAC, at present, we only recommend running ensembles for NEUVAC, and not any of the
+other models.
+
+# Code of Conduct
+In using this code, depending on the module used, proper citations should be given to the original authors responsible
+for developing each model or dataset:
+
+NEUVAC: [Brandt and Ridley, 2024](https://agupubs.onlinelibrary.wiley.com/doi/pdfdirect/10.1029/2024SW004043)
+
+EUVAC: [Richards, et al. 1994](https://agupubs.onlinelibrary.wiley.com/doi/abs/10.1029/94ja00518)
+
+HEUVAC [Richards, et al. 2006](https://www.sciencedirect.com/science/article/pii/S0273117705008288?casa_token=zEhwbyXrC8MAAAAA:qHFmKe0ZDE4gMsAX9qAHESvPyoEDFLBlhHuLaEsIwYFykhFXN79--XttCW-QDg1sA4wgD54ysFc)
+
+SOLOMON [Solomon and Qian, 2005](https://agupubs.onlinelibrary.wiley.com/doi/pdf/10.1029/2005JA011160)
+
+FISM2: [Chamberlin, et al. 2020](https://agupubs.onlinelibrary.wiley.com/doi/pdf/10.1029/2020SW002588)
diff --git a/src/somef_core/test/test_data/README-agora.md b/src/somef_core/test/test_data/README-agora.md
new file mode 100644
index 0000000..f87aba4
--- /dev/null
+++ b/src/somef_core/test/test_data/README-agora.md
@@ -0,0 +1,511 @@
+# agora-py
+
+
+**agora-py** is a Python library that supports *Web-scale Ontology-driven Access to Distributed Linked Data*.
+
+Currently, there is a huge number of *dereferenciable* Linked Data resources that are not being properly consumed neither explored.
+That is, the absolute majority of approaches for consuming Linked Data either ignore or inhibit such virtue,
+underutilizing the Web as a platform.
+Agora (agora-py) aims to be a tool that enables Linked Data consumers to live-query the Web of Data in a unified and explorative way:
+* Driven by known vocabularies;
+* Constrained by
+ * the scope of the given query,
+ * a set of seed resources whose URIs and types are known.
+
+Although Agora is designed as well to be deployed as a microservices infrastructure,
+it can be fully used as a Python middleware, without the need for any other external dependency than the Web of Data.
+
+## Install
+
+agora-py is still not uploaded to PyPi, however the current repository
+can be passed as source for pip:
+
+```
+$ pip install git+https://github.com/oeg-upm/agora-py.git
+```
+
+## Getting Started
+
+Before issuing the first query, Agora needs to know the following:
+1. The vocabulary that will drive the exploration;
+2. One seed resource whose URI and type (any Class in the vocabulary) is known.
+
+### Register vocabularies
+
+**agora-py** requires to be provided with the vocabularies (RDFS, OWL) that will be used
+to drive the exploration of Linked Data resources.
+
+```python
+from agora import Agora
+
+a = Agora()
+
+# movies.ttl is in path agora/examples/movies/movies.ttl
+with open('movies.ttl') as f:
+ a.fountain.add_vocabulary(f.read())
+
+print a.fountain.types
+
+```
+
+### Declare seeds
+
+Once Agora is provided with a vocabulary that defines at least one Class, seed
+resources can be declared as follows:
+
+```python
+from agora import Agora
+
+a = Agora()
+with open('movies.ttl') as f:
+ a.fountain.add_vocabulary(f.read())
+
+a.fountain.add_seed('http://dbpedia.org/resource/Blade_Runner', 'dbpedia-owl:Film')
+a.fountain.add_seed('http://dbpedia.org/resource/Braveheart', 'dbpedia-owl:Film')
+print a.fountain.seeds
+
+```
+
+### Query the Web
+
+Agora enables Linked Data consumers to live-query the Web of Data using
+SPARQL. The given queries (in fact, their BGP and filters) together with the registered
+vocabularies and provided seeds, define the scope of the link-traversal exploration.
+
+```python
+from agora import Agora
+
+a = Agora()
+with open('movies.ttl') as f:
+ a.fountain.add_vocabulary(f.read())
+
+a.fountain.add_seed('http://dbpedia.org/resource/Blade_Runner', 'dbpedia-owl:Film')
+a.fountain.add_seed('http://dbpedia.org/resource/Braveheart', 'dbpedia-owl:Film')
+
+query = """SELECT DISTINCT ?name ?actor WHERE {
+ [] foaf:name ?name ;
+ dbpedia-owl:starring [
+ dbp:birthName ?actor
+ ]
+ }"""
+
+for row in a.query(query):
+ print row
+```
+
+## Background
+
+### Linked Data
+
+The Linked Data principles [^1] enable the creation of the Web of Data:
+
+1. Use URIs as names for things.
+2. Use HTTP URIs so that people can look up those things.
+3. When someone looks up a URI, provide useful information, using the standards (RDF, SPARQL).
+4. Include links to other URIs (within this information) so that they can discover more things.
+
+#### Consuming Linked Data on the Web [^2]
+
+| | data warehousing | search engines | query federation | link traversal | [linked data fragments][ldf] |
+|-----------------------------|------------------------|----------------|--------------------|---------------------------------|-----------------------|
+| Universe of discourse (UoD) | loaded data | Web of Data | known data sources | Web of Data | known data sources |
+| Required source interface | mainly RDF dumps | arbitrary | SPARQL endpoints | **Linked Data (look up) interface** | LDF servers |
+| Access to original data | no | no | yes | yes | yes |
+| Supporting data structures | indices and statistics | crawled index | statistics | - | ? |
+| Response and throughput | fast / fast | fast / fast | slow / medium | medium / slow | medium / slow |
+| Recall (w.r.t. UoD) | 100% | <100% | 100% | <100% | 100% |
+| Precision | 100% | <100% | 100% | 100% | 100% |
+| Up-to-dateness | low | medium | high | high | high |
+
+#### What about Linked Data principles 2 and 3?
+Only link traversal leverages available Linked Data (look-up) interfaces. A URI should not just serve as a global identifier, but also as provider of a structured data representation of the identified entity. The absolute majority of implemented solutions ignore both principles.
+
+Why do not we rely on these HTTP look-up interfaces to directly consume Linked Data? Is it really necessary to give them up in favor of using SPARQL endpoints or any other (non-LD) interface to efficiently access and query Linked Data?
+
+### Web of Data
+
+#### Benefits
+The three main benefits of the Web of Data are:
+
+* Feasibility to perform live-querying over a dataspace that integrates a large number of interlinked datasets as if it was a huge multidatabase system.
+* Data sources may be considerably lighter, scalable and maintanable than (reliable) SPARQL endpoints. They can be interfaced as just RESTful APIs that provide RDF by dereferencing known resources.
+* Enables freshness and serendipitous discovery of data sources and results.
+
+#### Problems
+Some problems of (live-)querying the Web of Data:
+
+* Not any approach for executing queries that range over all Linked Data on the Web can guarantee complete query results.
+* Its openness and growth introduces data integration issues such as coreferencing and schema heterogeneity.
+* Looking up certain URIs may result in the retrieval of an unforeseeable large set of RDF triples.
+* Response times may vary significantly between different servers. Look-ups may take unexpectedly long or may not be answered at all.
+* Restrictions on clients such as sercing only a limited number of requests per second (rate limits).
+
+### Workload distribution
+
+Can we minimize server resource usage while still enabling clients to query data sources efficiently?
+
+
+
+
+
+## Approach
+### Ontology-driven link traversal
+Link traversal is focused on querying the whole Web of Data without any prior knowledge about the vocabularies that are being used to describe the data.
+
+Ontology-driven link traversal is less ambitious and aims only at querying the sub-dataspace that is described following the previously known vocabularies. In practice, it is only interested in those resources that are described so that they can be correctly interpreted, explored and consumed without extra effort.
+
+Assuming that data are linked using the properties specified in the selected vocabularies, we can extract and exploit the underlying cabigational paths to easily access reachable and query-relevant fragments of data.
+
+
+
+
+
+A set of known seeds of any type can be used as starting points of such navigational paths, so that they do not need to be explicitly included in queries. Using those seeds facilitates the selection fo data sources based on different criteria: reliability, security, etc.
+
+Given a graph pattern from a conjuctive query, an executable search plan describing the shortest paths from known seeds can be provided.
+
+
+
+
+
+## Concept
+The *gathering place* for Distributed Linked Data.
+
+The Agora was a central space or square in ancient Greek city-states. The literal meaning of the word is "gathering place" or "assembly".
+
+The agora was the centre of athletic, artistic, spiritual and political life of the city.
+
+
+
+
+
+## How it works
+The simplest Agora Engine is composed by a Fountain and a Planner.
+
+
+
+
+
+### Fountain
+The Fountain is the *place* where all navigational paths found in known vocabularies are exposed, taking into account a number of heterogeneous seeds to be later on proposed as starting points for search plans.
+
+#### Path extraction from vocabularies
+The Fountain queries the given vocabularies in order to create the underlying link graph. Basically, it tries to find out the domain and range of all properties in the vocabulary with the aim of identifying the set of nodes and edges that make up such link graph. In the end, (a subset of) concepts and properties in the ontology become the nodes and edges of the link graph, respectively.
+
+##### Vocabulary registration
+The Fountain accepts vocabularies for registration in two different formats: Turtle and RDF/XML. In order to identify the only ontology that should be described in the submitted content, the Fountain parses it and queries the resulting graph with:
+
+```sql
+SELECT ?o WHERE {
+ ?o a owl:Ontology FILTER (isURI(?o))
+}
+```
+
+Having the result set, the following restrictions are applied:
+
+* The **size** of the result set must be 1. That is, vocabularies have to be registered one at a time.
+* There must be a **declared prefix** that is equal to the URI binded by `?o`. The name of such prefix will be considered by Agora as the **identifier** of the vocabulary.
+
+For example, the following block is declaring the ontology ``, which will be identified by Agora as *onto*.
+
+```text
+@prefix rdf: .
+@prefix owl: .
+@prefix onto: .
+
+ rdf:type owl:Ontology .
+
+onto:Concept a owl:Class .
+```
+
+Onwards, when referring to a concept or property that belongs to that ontology, Agora will impose to do it prefixed (`onto:Concept`); otherwise (``), it won't understand us.
+
+##### Node extraction
+The nodes of the link graph are created from all those concepts described in a given ontology that belong to the result set of the following query:
+
+```sql
+SELECT DISTINCT ?c WHERE {
+ {
+ ?p a owl:ObjectProperty .
+ {
+ { ?p rdfs:range ?c }
+ UNION
+ { ?p rdfs:domain ?c }
+ }
+ }
+ UNION
+ {
+ ?p a owl:DatatypeProperty .
+ ?p rdfs:domain ?c .
+ }
+ UNION
+ { ?c a owl:Class }
+ UNION
+ { ?c a rdfs:Class }
+ UNION
+ { [] rdfs:subClassOf ?c }
+ UNION
+ { ?c rdfs:subClassOf [] }
+ UNION
+ {
+ ?r a owl:Restriction ;
+ owl:onProperty ?p .
+ {
+ ?p a owl:ObjectProperty .
+ { ?r owl:allValuesFrom ?c }
+ UNION
+ { ?r owl:someValuesFrom ?c }
+ }
+ UNION
+ { ?r owl:onClass ?c }
+ }
+ FILTER(isURI(?c))
+}
+```
+
+Thus, there are some rules that must be taken into account in order to let the Fountain *detect* nodes in ontologies. That is, nodes are all URIs that match at least one of the following:
+
+* It is a class, either an `owl:Class` or a `rdfs:Class`.
+* It has at least one subclass in the ontology or it is the superclass of any other.
+* It belongs to the domain of a datatype property.
+* Given an object property,
+ * it is a class that belongs to its range or/and domain.
+ * there may be a set of things for which such property may have values of it.
+
+It is important to note that no automatic reasoning is performed in this process. All required information must be materialized in the ontology description that is being submitted. Furthermore, existing conflicts and/or inconsistencies in definitions will not be treated; neither a warning nor an error message will be generated.
+
+##### Edge extraction
+Similarly to the process of node extraction, the detection of *valid* edges for the link graph in an ontology is built on the following query:
+
+```sql
+SELECT DISTINCT ?p WHERE {
+ { ?p a rdf:Property }
+ UNION
+ { ?p a owl:ObjectProperty }
+ UNION
+ { ?p a owl:DatatypeProperty }
+ UNION
+ {
+ [] a owl:Restriction ;
+ owl:onProperty ?p .
+ }
+ FILTER(isURI(?p))
+}
+```
+
+The result set of the corresponding query is composed of all the URIs that have been described in such a way that they can be considered as edges. The corresponding matching rules for edges are:
+
+* It is a `rdf:Property`, an `owl:ObjectProperty` or an `owl:DatatypeProperty`.
+* There is some restriction on it as a property.
+
+##### Node properties
+
+Once the Fountain has identified all nodes from the vocabularies, it is prepared to search for the incoming (references) and outgoing (properties) edges for each of them. To do so, it creates and keeps a tuple map that puts nodes and their properties together:
+
+```sql
+SELECT DISTINCT ?c ?p WHERE {
+ { ?c rdfs:subClassOf [ owl:onProperty ?p ] }
+ UNION
+ { ?p rdfs:domain ?c }
+ FILTER (isURI(?p) && isURI(?c))
+}
+```
+
+Given a node *n*, its properties are all those URIs that fulfill the following conditions:
+
+* *n* belongs to its domain.
+* *n* has a constraint on it.
+
+Having such map in memory, it is trivial to filter the properties of each node (fixing a value for *n*).
+
+##### Node references
+
+The process to obtain the incoming edges of all nodes is identical to that of properties. Here, the corresponding query that results in the required tuple map is the following:
+
+```sql
+SELECT ?c ?p WHERE {
+ {
+ ?r owl:onProperty ?p.
+ { ?r owl:someValuesFrom ?c }
+ UNION
+ { ?r owl:allValuesFrom ?c }
+ UNION
+ { ?r owl:onClass ?c }
+ }
+ UNION
+ { ?p rdfs:range ?c }
+ FILTER (isURI(?p) && isURI(?c))
+}
+```
+
+Given a node *n*, its references are all those URIs that fulfill the following conditions:
+
+* *n* belongs to its range.
+* There is a restriction that specifies that any of its values may be of the type represented by *n*.
+
+##### Edge domain
+
+The domain of an edge *e* is composed by all those nodes for which *e* is a property.
+
+```sql
+SELECT DISTINCT ?e ?c WHERE {
+ { ?p rdfs:domain ?c }
+ UNION
+ { ?c rdfs:subClassOf [ owl:onProperty ?e ] }
+ FILTER (isURI(?e) && isURI(?c))
+}
+```
+
+##### Edge range
+
+The range of an edge *e* is composed by:
+
+* All those nodes for which *e* is a reference.
+* Datatype URIs that appear in a data-range restriction of *e* for a certain node.
+
+```sql
+SELECT DISTINCT ?e ?r WHERE {
+ {?e rdfs:range ?r}
+ UNION
+ {
+ ?d owl:onProperty ?e.
+ { ?d owl:allValuesFrom ?r }
+ UNION
+ { ?d owl:someValuesFrom ?r }
+ UNION
+ { ?d owl:onClass ?r }
+ UNION
+ { ?d owl:onDataRange ?r }
+ }
+ FILTER(isURI(?e) && isURI(?r))
+}
+```
+
+##### Edge constraints
+TBD
+
+
+##### Example
+
+
A simple, light-weight and fast implementation of standard Twin Support Vector Machine
+
+
+
+
+
+
+
+
+
+
+
+1. [Introduction](#intro)
+2. [Installation Guide](#installation-guide)
+ - [Setup script](#setup-script-recommended)
+ - [Building manually](#building-manually)
+3. [User Guide](#user-guide)
+ - [Usage example](#an-exmaple-of-using-command-line-interface)
+ - [Tutorials](#tutorials)
+ - [API documentation](#api-documentation)
+4. [Dataset Format](#dataset-format)
+5. [Support](#support)
+6. [Citing LightTwinSVM](#citing-lighttwinsvm)
+7. [Contributing](#contributing)
+8. [FAQ](#frequently-asked-questions)
+9. [Donations](#donations)
+10. [Numerical Experiments](#numerical-experiments)
+
+## Intro
+LightTwinSVM is a simple and fast implementation of standard Twin Support Vector Machine. It is licensed under the terms of GNU GPL v3. Anyone who is interested in machine learning and classification can use this program for their work/projects.
+
+The main features of the program are the following:
+- A **simple console program** for running TwinSVM classifier
+- **Fast optimization algorithm:** The clipDCD algorithm was improved and is implemented in C++ for solving optimization problems of TwinSVM.
+- **Linear**, **RBF** kernel and Rectangular are supported.
+- Binary and **Multi-class classification** (One-vs-All & One-vs-One) are supported.
+- The OVO estimator is **compatible with scikit-learn** tools such as GridSearchCV, cross_val_score, etc.
+- The classifier can be evaluated using either **K-fold cross-validation** or **Training/Test** split.
+- It supports **grid search** over C and gamma parameters.
+- **CSV** and **LIBSVM** data files are supported.
+- Detailed classification result will be saved in a spreadsheet file.
+
+Twin Support Vector Machine classifier was proposed by:
+Khemchandani, R., & Chandra, S. (2007). Twin support vector machines for pattern classification. IEEE Transactions on pattern analysis and machine intelligence, 29(5), 905-910.
+
+The clipDCD algorithm was proposed by:
+Peng, X., Chen, D., & Kong, L. (2014). A clipping dual coordinate descent algorithm for solving support vector machines. Knowledge-Based Systems, 71, 266-278.
+
+## Installation Guide
+Currently, supported operating systems are as follows. Choose your OS from list below for detailed install instructions.
+- [Debian-based Linux systems](#linux--mac-os-x) (Ubuntu 14.04, Ubuntu 16.04, Ubuntu 17.10, Ubuntu 18.04 and Linux Mint 18)
+- [RPM-based Linux systems](#linux--mac-os-x) (Fedora)
+- [Mac OSX](#linux--mac-os-x)
+- [Microsoft Windows](#windows)
+
+### Dependencies
+First of all, [Python](https://www.python.org/) 3.5 interpreter or newer is required. Python 3 is usually installed by default on most Linux distributions.
+In order to build and run the program, the following Python packages are needed:
+- [NumPy](https://www.numpy.org)
+- [SciPy](https://www.scipy.org/)
+- [Scikit-learn](http://scikit-learn.org/stable/index.html)
+- [Pandas](https://pandas.pydata.org/)
+- [Pybind11](https://pybind11.readthedocs.io/en/stable/intro.html)
+- [Cython](https://cython.org/)(To build C++ extension module on Windows.)
+- [PyInstaller](https://www.pyinstaller.org/)(To generate a binary executable for Windows platform.)
+
+In order to build C++ extension module(Optimizer), the following tools and libraries are required:
+- [GNU C++ Compiler](https://gcc.gnu.org/) (For Linux systems)
+- [Apple XCode](https://developer.apple.com/xcode/) (For OSX systems)
+- [Visual Studio](https://visualstudio.microsoft.com/) (For Windows systems)
+- [Armadillo C++ Linear Algebra Library](http://arma.sourceforge.net/)
+- [LAPACK](http://www.netlib.org/lapack/) and [BLAS](http://www.netlib.org/blas/) Library
+
+
+### Setup script (Recommended)
+### Linux & Mac OS X
+**A shell script is created to help users download required dependencies and install program automatically.** However, make sure that [Git](https://git-scm.com/) and GNU C++ compiler is installed on your system.
+
+**A note for MacOS users:** Make sure that [Apple XCode](https://developer.apple.com/xcode/) is installed on your system.
+
+To install the program, open a terminal and execute the following commands:
+```
+git clone https://github.com/mir-am/LightTwinSVM.git
+cd LightTwinSVM && ./setup.sh
+```
+If the installation was successful, you'd be asked to delete temporary directory for installation. You can also run unit tests to check functionalities of the program. Finally, a Linux shell "ltsvm.sh" is created to run the program.
+After the successful installation, LightTwinSVM program should look like this in terminal:
+
+
+### Windows
+First, download Git program from [here](https://git-scm.com/) if it's not installed on your system. Also, [**Visual Studio 2015**](https://visualstudio.microsoft.com/) or newer should be installed so that C++ extension module can be compiled. Before proceeding further, make sure that all the required Python packages are installed. Dependencies are listed [here](#dependencies).
+
+**A note for Windows users:** If this is the first time that you will run a PowerShell script, then you need to make sure that the ExecutionPolicy is set on your system. Otherwise, you cannot run the setup script on Windows. Please check out this [answer](https://stackoverflow.com/a/4038991) on Stack Overflow that helps you set the ExecutionPolicy.
+
+To install the program on Windows, open a PowerShell terminal and run the following commands:
+```
+git clone https://github.com/mir-am/LightTwinSVM.git
+cd LightTwinSVM && .\win-setup.ps1
+```
+When the installation is finished, a batch file "ltsvm.bat" will be created to run the program.
+
+### Building manually
+It is highly recommended to install the LightTwinSVM program automatically using the setup script. If for some reasons you still want to build the program manually, a step-by-step guide is provided [here](https://github.com/mir-am/LightTwinSVM/wiki/Building-the-LightTwinSVM-manually-on-Linux-and-OSX-systems) for Linux and OSX systems.
+
+## User Guide
+### An example of using command line interface
+LightTwinSVM is a simple console application. It has 4 steps for doing classification. Each step is explained below:
+**Step 1:** Choose your dataset by pressing Enter key. A file dialog window will be shown to help you find and select your dataset. CSV and LIBSVM files are supported. It is highly recommended to normalize your dataset.
+
+**Step 2:** Choose a kernel function among Linear, Gaussian (RBF) and Rectangular. RBF kernel often produces better classification result but takes more time. However if you want to use non-linear kernel and your dataset is large, then consider choosing Rectangular kernel.
+
+```
+Step 2/4: Choose a kernel function:(Just type the number. e.g 1)
+1-Linear
+2-RBF
+3-RBF(Rectangular kernel)
+-> 2
+```
+**Step 3:** To evaluate TwinSVM performance, You can either use [K-Fold cross validation](https://towardsdatascience.com/cross-validation-in-machine-learning-72924a69872f) or split your data into training and test sets.
+```
+Step 3/4: Choose a test methodology:(Just type the number. e.g 1)
+1-K-fold cross validation
+2-Train/test split
+-> 1
+Determine number of folds for cross validation: (e.g. 5)
+-> 5
+```
+**Step 4:** You need to determine the range of C penalty parameter and gamma (If RBF kernel selected.) for exhaustive grid search.
+An example:
+```
+Step 4/4:Type the range of C penalty parameter for grid search:
+(Two integer numbers separated by space. e.g. -> -5 5
+-> -4 4
+```
+After completing the above steps, the exhaustive search will be started. When the search process is completed, a detailed classification result will be saved in a spreadsheet file. In this file, all the common evalaution metrics(e.g Accuracy, Recall, Precision and F1) are provided.
+A instance of spreadsheet file containing classification result can be seen [here](https://github.com/mir-am/LightTwinSVM/blob/misc/TSVM_RBF_5-F-CV_pima-indian_2018-05-23%2013:21.csv).
+
+### Tutorials
+LightTwinSVM can be imported as a Python package in your project. Currently, a Jupyter notebook is avaliable [here](https://github.com/mir-am/LightTwinSVM/tree/master/docs/notebooks), which is "A Step-by-Step Guide on How to Use Multi-class TwinSVM".
+
+To run the notebooks, make sure that Jupyter is installed on your system. If not, use the following command to install it:
+```
+pip3 install jupyter
+```
+For more details, check out [Jupyter documentation](https://jupyter.readthedocs.io/en/latest/index.html).
+
+### API documentation
+Aside from the program's command line interface, you may want to use the LightTwinSVM's Python package for your project. All you have to do is to copy-paste the "[ltsvm](https://github.com/mir-am/LightTwinSVM/tree/master/ltsvm)" folder (the **installed version**) into the root folder of your project. Next, you can import "ltsvm" package in a module of your interest.
+
+You can read about the documentation of the LightTwinSVM's estimators and tools [here](https://lighttwinsvm.readthedocs.io/en/latest/index.html).
+
+## Dataset Format
+- **LIBSVM** data files are supported. Note that the extension of this file should be '*.libsvm'.
+- For **comma separated value (CSV)** file, make sure that your dataset is consistent with the following rules:
+1. First row can be header names. (It's optional.)
+2. First column should be labels of samples. Moreover, labels of positive and negative samples should be 1 and -1, respectively.
+3. All the values in dataset except headernames should be numerical. Nominal values are not allowed.
+To help you prepare your dataset and test the program, three datasets are included [here](https://github.com/mir-am/LightTwinSVM/tree/master/dataset).
+
+## Support
+**Have a question about the software?**
+You can contact me via [email](mailto:mir-am@hotmail.com). Feedback and suggestions for improvements are welcome.
+
+**Have a problem with the software or found a bug?**
+To let me know and fix it, please open an issue [here](https://github.com/mir-am/LightTwinSVM/issues).
+To report a problem or bug, please provide the following information:
+1. Error messages
+2. Output of the program.
+3. Explain how to reproduce the problem if possible.
+
+## Citing LightTwinSVM
+
+[](http://joss.theoj.org/papers/a56708efcbf27c12352670b4647d92ca)
+
+If you use the LightTwinSVM program in your research work, please cite the following paper:
+
+- Mir et al., (2019). LightTwinSVM: A Simple and Fast Implementation of Standard Twin Support Vector Machine Classifier. Journal of Open Source Software, 4(35), 1252, https://doi.org/10.21105/joss.01252
+
+BibTeX entry:
+
+```
+@article{ltsvm2019,
+ title = {LightTwinSVM: A Simple and Fast Implementation of Standard Twin Support Vector Machine Classifier},
+ author = {Mir, Amir M. and Nasiri, Jalal A.},
+ journal = {Journal of Open Source Software},
+ volume = {4},
+ issue = {35},
+ pages = {1252},
+ year = {2019},
+ doi = {10.21105/joss.01252},
+ url = {https://doi.org/10.21105/joss.01252}
+}
+```
+
+## Contributing
+Thanks for considering contribution to the LightTwinSVM project. Contributions are highly appreciated and welcomed. For guidance on how to contribute to the LightTwinSVM project, please see the [contributing guideline](https://github.com/mir-am/LightTwinSVM/blob/master/CONTRIBUTING.md).
+
+## Frequently Asked Questions
+- What is the main idea of TwinSVM classifier?
+TwinSVM does classification by using two non-parallel hyperplanes as opposed to a single hyperplane in the standard SVM. In TwinSVM, each hyperplane is as close as possible to samples of its own class and far away from samples of other class. To know more about TwinSVM and its optimization problems, you can read [this blog post](https://mirblog.me/index.php/2018/12/07/a-brief-intro-to-twin-support-vector-machine-classifier/ "A brief Introduction to TwinSVM classifier").
+
+## Donations
+
+
+
+
+
+If you have used the LightTwinSVM program and found it helpful, please consider making a donation via [PayPal](http://paypal.me/mir33) to support this work. It also motivates me to maintain the program.
+
+## Numerical Experiments
+In order to indicate the effectiveness of the LightTwinSVM in terms of accuracy, experiments were conducted to compare it with [scikit-learn's SVM](https://scikit-learn.org/stable/modules/generated/sklearn.svm.SVC.html) on several UCI benchmark datasets. Similar to most research papers on classification, K-fold cross-validation is used to evaluate these classifiers (K was set to 5). Also, grid search was used to find the optimal values of hyper-parameters. Table below shows the accuracy comparison between the LightTwinSVM and scikit-learn's SVM.
+
+| Datasets | LightTwinSVM | scikit-learn's SVM | Difference in Accuracy |
+| ------------- | -------------------------- | -------------------------- | ------------------------- |
+| Pima-Indian | **78.91**±**3.73** | 78.26±2.62 | 0.65 |
+| Australian | **87.25**±**2.27** | 86.81±3.22 | 0.44 |
+| Haberman | 76.12±4.79 | **76.80**±**2.68** | -0.68 |
+| Cleveland | **85.14**±**5.45** | 84.82±4.04 | 0.32 |
+| Sonar | **84.62**±**4.89** | 64.42±6.81 | 20.2 |
+| Heart-Statlog | **85.56**±**2.96** | 85.19±2.62 | 0.37 |
+| Hepatitis | **86.45**±**5.16** | 83.23±3.55 | 3.22 |
+| WDBC | **98.24**±**1.36** | 98.07±0.85 | 0.17 |
+| Spectf | **81.68**±**5.35** | 79.78±0.19 | 1.9 |
+| Titanic | 81.93±2.59 | **82.27**±**1.83** | -0.34 |
+| Mean Accuracy | **84.59** | 81.94 | 2.65 |
+
+From the above table, it can be found that LightTwinSVM is more efficient in terms of accuracy. Therefore, it outperforms sklearn's SVM on most datasets. All in all, if you have used SVM for your task/project, the LightTwinSVM program may give you a better predication accuracy for your classification task. More information on this experiment can be found in the project's paper [here](https://github.com/mir-am/LightTwinSVM/tree/master/paper).
+
+## Acknowledgments
+- For test and experiment with the LightTwinSVM program, Wine and Pima-Indian datasets are included in the project from [UCI](https://archive.ics.uci.edu/ml/index.php) machine learning repository.
+- Thanks to [Stefan van der Walt](https://github.com/stefanv) and [Nicolas P. Rougier](https://github.com/rougier) for reviewing this project, which published in the Journal of Open Source Software. (March 31, 2019)
+- Thanks to [idejie](https://github.com/idejie) for test and support on the MacOS. (Dec 8, 2018)
\ No newline at end of file
diff --git a/src/somef_core/test/test_data/README-menty.md b/src/somef_core/test/test_data/README-menty.md
new file mode 100644
index 0000000..8ac3d87
--- /dev/null
+++ b/src/somef_core/test/test_data/README-menty.md
@@ -0,0 +1,94 @@
+
+
+The `mentpy` library is an open-source software for simulations of
+measurement-based quantum computing circuits. Currently, this package is in its alpha version and many features are still in development.
+
+## Installation
+
+You can install the stable release of `mentpy` from PyPI using:
+
+```bash
+pip install mentpy
+```
+
+For the latest, potentially unstable version, you can install directly from the source:
+
+```bash
+pip install git+https://github.com/mentpy/mentpy.git
+```
+
+If you're contributing to `mentpy` and need to install development dependencies, you can do so using:
+
+```bash
+git clone https://github.com/mentpy/mentpy.git
+cd mentpy
+pip install -e '.[dev]'
+```
+
+This command installs `mentpy` in editable mode with its additional development dependencies.
+
+## Usage
+To simulate a measurement pattern, you can use the `mp.PatternSimulator`.
+```python
+import mentpy as mp
+
+st = mp.templates.grid_cluster(2,4)
+ps = mp.PatternSimulator(st)
+output = ps(np.random.rand(len(st.outputc)))
+```
+
+For visualization of circuits, you can use the `mp.draw(st)` function
+
+
+
+To calculate the lie algebra of a model $G_\theta$, you can use the `mp.utils.calculate_lie_algebra` function
+
+```python
+lie_alg = mp.utils.calculate_lie_algebra(st)
+len(lie_alg)
+>> 16
+```
+
+## Documentation
+
+The documentation for `mentpy` can be found here.
+
+## Contributing
+
+We welcome contributions to `mentpy`! Please see our [contributing guidelines](./CONTRIBUTING.md) for more information.
+
+## How to cite
+
+If you use `mentpy` in your research, please cite it as follows:
+
+```
+@software{mantilla2023mentpy,
+ title = {{MentPy: A Python package for parametrized MBQC circuits}},
+ author = {Mantilla Calderón, Luis},
+ year = {2023},
+ url = {https://github.com/mentpy/mentpy},
+}
+```
\ No newline at end of file
diff --git a/src/somef_core/test/test_data/README-reggae.md b/src/somef_core/test/test_data/README-reggae.md
new file mode 100644
index 0000000..f85c2a7
--- /dev/null
+++ b/src/somef_core/test/test_data/README-reggae.md
@@ -0,0 +1,95 @@
+# Reggae: Dipole modes from `PBjam`
+
+
+
+
+
+
+
+
+
+Reggae is a diagnostic tool for generating optimal parameters describing dipole mixed modes under the nonasymptotic matrix-coupling scheme of [Ong & Basu (2020)](https://ui.adsabs.harvard.edu/abs/2020ApJ...898..127O/abstract) used in the second release of [PBjam](https://github.com/grd349/PBjam) (see [Nielsen et al. 2021](https://ui.adsabs.harvard.edu/abs/2021AJ....161...62N/abstract)).
+
+Since the primary samples of the [PLATO mission](https://ui.adsabs.harvard.edu/abs/arXiv:2406.05447) consist mainly of main-sequence and subgiant stars, PBjam implements a parameterisation of dipole modes suitable to these stars, outside the red-giant "asymptotic" regime. Reggae assists in the task of manually fine-tuning the dipole-mode model, and checking the quality of both our initial guesses and fitted solutions. An important part of this tuning is visual assessment of how well the data matches posterior samples for these parameters. Such asteroseismic visualisations often use the échelle power diagram near $\nu_{\mathrm{max}}$ as a diagnostic tool, with clearly-defined ridges emerging on this diagram for p-modes, such as in main-sequence stars.
+
+We found Reggae very helpful both for these tuning and visualisation tasks, and also as a didactic aid to understanding the dipole mixed-mode parameters. As such, we release it publicly in advance of the second PBjam version, as we believe the community will benefit from access to such a visualisation tool. This will also assist future users of PBjam in devising ad-hoc prior constraints on the mixed-mode parameters, should they wish not to rely on the prior included with it.
+
+
+
+For more details, please read our documentation here.
+
+## Installation
+
+`reggae` is not yet available on PyPI. We recommend you install it in a virtual environment, from a local copy of the repository. For example:
+
+```
+git clone git@github.com:darthoctopus/reggae.git
+cd reggae
+python -m venv venv
+. venv/bin/activate
+pip install -e .
+```
+
+Please check [our documentation](https://pb-reggae.readthedocs.io/en/latest/installation.html) for more detailed installation and troubleshooting instructions.
+
+## Usage
+
+Reggae may be operated in many ways. It may be easiest to run the GUI with the command
+
+```
+python -m reggae
+```
+
+For more detailed usage instructions, please refer to our [documentation](https://pb-reggae.readthedocs.io/en/latest/usage.html).
+
+## Generative model for mode frequencies
+
+
+
+We implement a generative model for dipole gravitoacoustic mixed modes using the parameterisation of [Ong & Basu (2020)](https://ui.adsabs.harvard.edu/abs/2020ApJ...898..127O/abstract). At present, the frequency-dependent coupling strength is described with two parameters (one for each of the two matrices entering into the parameterisation), with a conversion to the asymptotic $q$ provided by an expression in [Ong & Gehan (2023)](https://ui.adsabs.harvard.edu/abs/2023ApJ...946...92O/abstract). This expression is in turn used to generate stretched echelle power plots for diagnostic purposes.
+
+In full, the generative model accepts the following parameters:
+
+- $\Delta\Pi_0$, the notional period spacing of the g-mode cavity; this is related to the period spacing of any given $\ell$ as $\Delta\Pi_\ell = \Delta\Pi_0 / \sqrt{\ell(\ell + 1)}$.
+- $p_L$ and $p_D$, the two coupling parameters described above.
+- $\epsilon_g$, a g-mode phase offset.
+- $\log \left(\delta\omega_\mathrm{rot, g} / \mathrm{\mu Hz}\right)$ and $\log \left(\delta\omega_\mathrm{rot, p} / \mathrm{\mu Hz}\right)$ --- the implementation of the PSD model (below) accepts separate values of the core (g-mode) and envelope (p-mode) rotational splittings. The pure p- and g-modes are split into multiplets separately before mode-coupling calculations are performed, thereby fully accounting for near-degeneracy asymmetric rotational splittings.
+- $\delta_{01}$, an additional phase offset for the dipole p-modes relative to the asymptotic solution found by pbjam.
+- $\alpha_g$, a curvature parameter for the g-modes (mirroring that of the p-modes in pbjam's asymptotic parameterisation).
+- $i$, the inclination of the rotational axis.
+
+
+
+## PSD Model
+
+The above parameters generate a set of dipole-mode frequencies. These parameters are fitted to the power spectrum in a fashion analogous to pbjam's "asymptotic peakbagging" step, whereby the generative model is used to construct a model power spectrum which is compared directly to the observational power spectrum, in order to constrain the model parameters. In order to make the likelihood function more amenable to optimisation and sampling, the linewidths of this artifical model PSD are deliberately broadened in a uniform manner (by default, by $1/200 \Delta\nu$) --- this has the effect of smoothing out the likelihood landscape, thereby alleviating the issue of trapping in tight local minima caused by the very narrow linewidths of the g-dominated mixed modes.
+
+## GUI
+
+In addition to a `DipoleStar` class (analogous to `star` in `pbjam`), we provide a GUI console for fine-tuning an initial guess to these asymptotic parameters. The primary use case for this is to refine the search space (i.e. reduce size of the prior volume) for more expensive computational methods, such as nested sampling. Simple tasks, such as simplex and genetic-algorithm optimisation, can also be performed from within the GUI.
+
+
+
+## Contributing
+
+
+
+Reggae is currently developed by the following team:
+
+- Joel Ong (Mode frequency generative model + GUI)
+- Martin B. Nielsen (PSD model)
+- Guy R. Davies
+- Emily Hatt
+
+We welcome contributions from the community. Easy ways to get started include:
+
+- Finding bugs in our code — please open GitHub issues for these.
+- Implementing feature suggestions — we welcome pull requests!
+
+
+
+We expect contributors to adhere to our [code of conduct](CODE_OF_CONDUCT.md).
+
+## License
+
+Reggae is released under the MIT license.
\ No newline at end of file
diff --git a/src/somef_core/test/test_data/README-sunpy.rst b/src/somef_core/test/test_data/README-sunpy.rst
new file mode 100644
index 0000000..75c6d1a
--- /dev/null
+++ b/src/somef_core/test/test_data/README-sunpy.rst
@@ -0,0 +1,115 @@
+*********
+``sunpy``
+*********
+|SunPy Logo|
+
++-----------------------------------+-----------------------------------+-----------------------------------+
+| Release | Development | Community |
++===================================+===================================+===================================+
+| |Latest PyPi Version| | |Python Versions| | |Matrix Chat Room| |
++-----------------------------------+-----------------------------------+-----------------------------------+
+| |Latest Conda Version| | |Project Status: Active| | |OpenAstronomy Discourse| |
++-----------------------------------+-----------------------------------+-----------------------------------+
+| |Zenodo - Latest DOI| | |Continuous Integration Status| | |Google Groups Mailing List| |
++-----------------------------------+-----------------------------------+-----------------------------------+
+| |sunpy stable documentation| | |CodeCov Code Coverage| | |Powered by NumFOCUS| |
++-----------------------------------+-----------------------------------+-----------------------------------+
+| |sunpy citation| | | |pyOpenSci| |
++-----------------------------------+-----------------------------------+-----------------------------------+
+
+.. |SunPy Logo| image:: https://raw.githubusercontent.com/sunpy/sunpy-logo/master/generated/sunpy_logo_landscape.png
+ :width: 200px
+.. |Latest PyPi Version| image:: https://img.shields.io/pypi/v/sunpy.svg
+ :target: https://pypi.python.org/pypi/sunpy/
+.. |Python Versions| image:: https://img.shields.io/pypi/pyversions/sunpy
+ :target: https://pypi.python.org/pypi/sunpy/
+.. |Matrix Chat Room| image:: https://img.shields.io/matrix/sunpy:openastronomy.org.svg?colorB=%23FE7900&label=Chat&logo=matrix&server_fqdn=matrix.org
+ :target: https://app.element.io/#/room/#sunpy:openastronomy.org
+.. |Latest Conda Version| image:: https://anaconda.org/conda-forge/sunpy/badges/version.svg
+ :target: https://anaconda.org/conda-forge/sunpy
+.. |Project Status: Active| image:: https://www.repostatus.org/badges/latest/active.svg
+ :target: https://www.repostatus.org/#active
+.. |OpenAstronomy Discourse| image:: https://cdck-file-uploads-global.s3.dualstack.us-west-2.amazonaws.com/try2/original/1X/5e1e3b3cada2d7fbae4734d4bc53999933d71c95.svg
+ :height: 20px
+ :target: https://community.openastronomy.org/
+.. |Zenodo - Latest DOI| image:: https://zenodo.org/badge/2165383.svg
+ :target: https://zenodo.org/badge/latestdoi/2165383
+.. |Continuous Integration Status| image:: https://github.com/sunpy/sunpy/actions/workflows/ci.yml/badge.svg?branch=main
+ :target: https://github.com/sunpy/sunpy/actions/workflows/ci.yml
+.. |Google Groups Mailing List| image:: https://upload.wikimedia.org/wikipedia/commons/2/27/Google_Groups_logo.gif
+ :height: 20px
+ :target: https://groups.google.com/g/sunpy
+.. |sunpy stable documentation| image:: https://readthedocs.org/projects/sunpy/badge/?version=stable
+ :target: https://docs.sunpy.org/
+.. |CodeCov Code Coverage| image:: https://codecov.io/gh/sunpy/sunpy/branch/main/graph/badge.svg
+ :target: https://codecov.io/gh/sunpy/sunpy
+.. |Powered by NumFOCUS| image:: https://img.shields.io/badge/powered%20by-NumFOCUS-orange.svg?style=flat&colorA=E1523D&colorB=007D8A
+ :target: https://numfocus.org
+.. |sunpy citation| image:: https://img.shields.io/badge/cite-sunpy-orange
+ :target: https://docs.sunpy.org/en/stable/citation.html
+.. |pyOpenSci| image:: https://tinyurl.com/y22nb8up
+ :target: https://github.com/pyOpenSci/software-submission/issues/147
+
+``sunpy`` is a Python software package that provides fundamental tools for accessing, loading and interacting with solar physics data in Python.
+It includes an interface for searching and downloading data from multiple data providers, data containers for image and time series data, commonly used solar coordinate frames and associated transformations, as well as other functionality needed for solar data analysis.
+
+Installation
+============
+
+We recommended following the `installation guide `__ in the ``sunpy`` documentation.
+This will walk you through installing ``sunpy`` and all of its dependencies.
+
+Usage
+=====
+
+If you are new to ``sunpy``, the best place to start is the `tutorial `__.
+the `example gallery `__ also includes a collection of shorter and more specific examples of using ``sunpy``.
+
+Changes
+=======
+
+See our `changelog `__. for the latest changes in ``sunpy``.
+
+Getting Help
+============
+
+For more information or to ask questions about ``sunpy`` or any other SunPy Project package, please check out:
+
+- `sunpy documentation `__
+- `SunPy Affiliated Packages `__
+- `SunPy Chat`_
+- `SunPy mailing list `__
+- `SunPy Community forum `__
+
+Acknowledging or Citing ``sunpy``
+=================================
+
+If you use ``sunpy`` in your scientific work, we would appreciate your `citing it in your publications `__.
+The continued growth and development of ``sunpy`` is dependent on the community being aware of ``sunpy``.
+
+Contributing
+============
+
+The SunPy Project is a community-driven open-source project that welcomes any and all contributions.
+Whether you are a developer, student, or user, you can help by contributing code, documentation, or community support.
+
+If you would like to get involved, the `Newcomers Guide`_ guide explains the many different ways to contribute to the SunPy Project and also shows how to get set up with a development workflow.
+
+Help is always welcome, so come and say hello by joining the `SunPy Chat`_ and look over the `Good First Issues list`_ for the ideal places to start.
+
+.. _Newcomers Guide: https://docs.sunpy.org/en/latest/dev_guide/contents/newcomers.html
+.. _Good First Issues list: https://github.com/sunpy/sunpy/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22Good+First+Issue%22
+
+Usage of Generative AI
+======================
+
+We expect authentic engagement in our community.
+Be wary of posting output from Large Language Models or similar generative AI as comments on GitHub or any other platform, as such comments tend to be formulaic and low quality content.
+If you use generative AI tools as an aid in developing code or documentation changes, ensure that you fully understand the proposed changes and can explain why they are the correct approach and an improvement to the current state.
+
+Code of Conduct
+===============
+
+When you are interacting with the SunPy Community you are asked to follow our `Code of Conduct `__.
+
+.. _SunPy Chat: https://app.element.io/#/room/#sunpy:openastronomy.org
\ No newline at end of file
diff --git a/src/somef_core/test/test_data/README-tpronk.md b/src/somef_core/test/test_data/README-tpronk.md
new file mode 100644
index 0000000..63d4c19
--- /dev/null
+++ b/src/somef_core/test/test_data/README-tpronk.md
@@ -0,0 +1,75 @@
+# somef-demo-repo
+This repo aims to provide values for each metadata field that SOMEF (v0.9.4) can extract.
+
+# Acknowledgements
+This demonstration repo was created during the maSMP hackathon at [ZB MED](https://www.zbmed.de/en) sponsored by [NFDI4DataScience](https://www.nfdi4datascience.de). NFDI4DataScience is a consortium funded by the German Research Foundation (DFG), project number 460234259.
+
+# Citation
+Please cite this repo as Pronk, T. (2023). *somef-demo-repo: This repo aims to provide values for each metadata field that SOMEF (v0.9.4) can extract* (Version 0.0.1) [Computer software]. https://github.com/tpronk/somef-demo-repo
+
+# Contact
+Contact person responsible for maintaining a software component
+
+# Contributors
+Here could be a list of contributors to this software component
+
+# Documentation
+Where to find additional documentation about a software component.
+
+# Download
+Download instructions included in the repository.
+
+# Executable notebook
+Here you find a non-functioning executable notebook in Jupyter on top of Binder: https://mybinder.org/dummy-notebook
+
+# FAQ
+Frequently asked questions about a software component
+
+# Identifier
+Copied from the [deeprank2 repo](https://github.com/DeepRank/deeprank2)
+[](https://zenodo.org/badge/latestdoi/450496579)
+
+# Image
+Images used to illustrate the software component.
+
+Image different from logo
+
+
+# Installation instructions
+A set of instructions that indicate how to install a target repository
+
+# Invocation
+Execution command(s) needed to run a scientific software component. Copied from [https://github.com/MPDL/unibiAPC/](https://github.com/MPDL/unibiAPC/)
+
+```{r, echo=FALSE, results='asis', message = FALSE}\nmy_apc %>% select(institution, euro) %>% \n group_by(institution) %>% \n ezsummary::ezsummary(n = TRUE, digits= 0, median = TRUE,\n extra = c(\n sum = \"sum(., na.rm = TRUE)\",\n min = \"min(., na.rm = TRUE)\",\n max = \"max(., na.rm = TRUE)\"\n )) %>%\n mutate_all(format, big.mark=',') %>%\n ezsummary::ezmarkup('...[. (.)]..[. - .]') %>%\n#> get rid of blanks\n mutate(`mean (sd)` = gsub(\"\\\\( \", \"(\", .$`mean (sd)`)) %>% \n select(institution, n, sum, `mean (sd)`, median, `min - max`) %>%\n arrange(desc(n)) %>%\n knitr::kable(col.names = c(\"Institution\", \"Articles\", \"Spending total (in \u20ac)\", \"Mean (SD)\", \"Median\", \"Minimum - Maximum\"), align = c(\"l\",\"r\", \"r\", \"r\", \"r\", \"r\"))\n```
+
+# Logo
+Main logo used to represent the target software component.
+
+
+# Package distribution
+[](https://pypi.python.org/pypi/mapeathor)
+
+# Related documentation
+For instructions on using OBA to create your API server, go to the [documentation](https://oba.readthedocs.io/en/latest/)
+
+# Related papers
+[Yulun Zhang](http://yulunzhang.com/), [Yapeng Tian](http://yapengtian.org/), [Yu Kong](http://www1.ece.neu.edu/~yukong/), [Bineng Zhong](https://scholar.google.de/citations?user=hvRBydsAAAAJ&hl=en), and [Yun Fu](http://www1.ece.neu.edu/~yunfu/), "Residual Dense Network for Image Super-Resolution", CVPR 2018 (spotlight), [[arXiv]](https://arxiv.org/abs/1802.08797)
+
+# Repository status
+[](https://www.repostatus.org/#active)
+
+# Requirements
+Pre-requisites and dependencies needed to execute a software component.
+
+# Run
+There is no code in this repo that can be run.
+
+# Support
+Guidelines and links of where to obtain support for a software component
+
+# Support channels
+[](https://gitter.im/OpenGeoscience/geonotebook)
+
+# Usage examples
+Assumptions and considerations recorded by the authors when executing a software component, or examples on how to use it.
\ No newline at end of file
diff --git a/src/somef_core/test/test_data/README-unboundedsystems.md b/src/somef_core/test/test_data/README-unboundedsystems.md
new file mode 100644
index 0000000..7183625
--- /dev/null
+++ b/src/somef_core/test/test_data/README-unboundedsystems.md
@@ -0,0 +1,86 @@
+[](https://adaptjs.org)
+
+[](https://www.npmjs.com/package/@adpt/cli)
+[](https://gitter.im/UnboundedSystems/Adapt)
+[](https://opensource.org/licenses/Apache-2.0)
+
+# Adapt - ReactJS for your infrastructure.
+
+AdaptJS is a system to easily, reliably, and repeatably deploy your full-stack applications. Adapt specifications look like [React](https://reactjs.org) apps, but instead of rendering browser DOM elements like ``, or `
`, Adapt specifications use elements like AWS ``, Kubernetes ``, or `` database.
+
+If you're already familiar with React, you'll feel right at home with Adapt.
+But if not, don't worry, knowledge of React isn't required to start using Adapt.
+
+## Getting Started
+
+For a new project, it's easy to get started with Adapt by using a starter template.
+The [Getting Started Guide](https://adaptjs.org/docs/getting_started) will walk through installing Adapt and deploying your first starter app.
+
+## Creating and deploying an app
+
+This example creates a new full-stack app from a starter template.
+It has a [React](https://reactjs.org) UI, an [Nginx](https://nginx.org) web server, a [Node.js](https://nodejs.org) API server, and a [Postgres](https://postgresql.org) database, then deploys it to [Kubernetes](https://kubernetes.io/):
+
+```bash
+# Install adapt
+npm install -g @adpt/cli
+
+# Create a new app from a starter template
+adapt new hello-react-node-postgres ./myapp
+cd myapp/deploy
+
+# Deploy full stack locally using Docker
+adapt run laptop
+
+# Or just as easily deploy everything to Kubernetes
+adapt run k8s-test
+```
+
+An Adapt description for a complete front end and back end app stack looks like this:
+
+```jsx
+import Adapt from "@adpt/core";
+import { NodeService, ReactApp } from "@adpt/cloud/nodejs";
+import { Postgres } from "@adpt/cloud/postgres";
+
+function MyApp() {
+ const pg = Adapt.handle();
+
+ return (
+
+
+
+
+
+ );
+}
+```
+
+## Adapt in action
+
+This demo shows using Adapt to create and deploy a simple app called MovieDB that has a [React](https://reactjs.org) UI, an [Nginx](https://nginx.org) web server, an Nginx URL router, a [Node.js](https://nodejs.org) API server, and a [Postgres](https://postgresql.org) database, then deploys it to [Kubernetes](https://kubernetes.io/):
+
+
+
+## More info
+
+* [Adaptjs.org](https://adaptjs.org)
+
+ Learn more about Adapt.
+
+* [Getting Started Guide](https://adaptjs.org/docs/getting_started)
+
+ This guide will walk you through setting up Adapt and then deploying an example MovieDB app.
+
+* [Adapt Documentation](https://adaptjs.org/docs)
+
+ Adapt tutorials, API References, and more.
+
+## Getting Help
+
+[](https://gitter.im/UnboundedSystems/Adapt)
+
+We'd love to hear about your experience with Adapt!
+Join us on our [Gitter channel](https://gitter.im/UnboundedSystems/Adapt) to ask questions or to give us your feedback and suggestions.
+
+If you've found a bug, you can also [file an issue](https://gitlab.com/unboundedsystems/adapt/issues).
diff --git a/src/somef_core/test/test_data/README-uncbiag.md b/src/somef_core/test/test_data/README-uncbiag.md
new file mode 100644
index 0000000..49a45ef
--- /dev/null
+++ b/src/somef_core/test/test_data/README-uncbiag.md
@@ -0,0 +1,91 @@
+
+
+
+# ICON: Learning Regular Maps through Inverse Consistency
+
+[](https://github.com/uncbiag/ICON/actions)
+[](https://pypi.org/project/icon-registration)
+[](https://icon.readthedocs.io/en/master/)
+
+
+This is the official repository for
+
+**ICON: Learning Regular Maps through Inverse Consistency.**
+Hastings Greer, Roland Kwitt, Francois-Xavier Vialard, Marc Niethammer.
+_ICCV 2021_ https://arxiv.org/abs/2105.04459
+
+**GradICON: Approximate Diffeomorphisms via Gradient Inverse Consistency.**
+Lin Tian, Hastings Greer, Francois-Xavier Vialard, Roland Kwitt, Raúl San José Estépar, Marc Niethammer.
+_CVPR 2023_ https://arxiv.org/abs/2206.05897
+
+**Inverse Consistency by Construction for Multistep Deep Registration.**
+Hastings Greer, Lin Tian, Francois-Xavier Vialard, Roland Kwitt, Sylvain Bouix, Raul San Jose Estepar, Richard Rushmore and Marc Niethammer.
+_MICCAI 2023_ https://arxiv.org/abs/2305.00087
+
+## Cite this work
+```
+@InProceedings{Greer_2021_ICCV,
+ author = {Greer, Hastings and Kwitt, Roland and Vialard, Francois-Xavier and Niethammer, Marc},
+ title = {ICON: Learning Regular Maps Through Inverse Consistency},
+ booktitle = {Proceedings of the IEEE/CVF International Conference on Computer Vision (ICCV)},
+ month = {October},
+ year = {2021},
+ pages = {3396-3405}
+}
+```
+
+```
+@article{Tian_2022_arXiv,
+ title={GradICON: Approximate Diffeomorphisms via Gradient Inverse Consistency},
+ author={Tian, Lin and Greer, Hastings and Vialard, Fran{\c{c}}ois-Xavier and Kwitt, Roland and Est{\'e}par, Ra{\'u}l San Jos{\'e} and Niethammer, Marc},
+ journal={arXiv preprint arXiv:2206.05897},
+ year={2022}
+}
+```
+```
+@misc{greer2023inverseconsistencyconstructionmultistep,
+ title={Inverse Consistency by Construction for Multistep Deep Registration},
+ author={Hastings Greer and Lin Tian and Francois-Xavier Vialard and Roland Kwitt and Sylvain Bouix and Raul San Jose Estepar and Richard Rushmore and Marc Niethammer},
+ year={2023},
+ eprint={2305.00087},
+ archivePrefix={arXiv},
+ primaryClass={cs.CV},
+ url={https://arxiv.org/abs/2305.00087},
+}
+```
+
+
+## uniGradICON and multiGradICON
+
+If you are interested in general purpose deep learning registration approaches check out [uniGradICON and multiGradICON](https://github.com/uncbiag/uniGradICON). These networks were trained using the GradICON technology but over many different datasets allowing them to generalize to different unimodal and multimodal registration tasks.
+
+## Video Presentation
+
+[](https://youtu.be/7kZsJ3zWDCA)
+
+
+## Running our code
+
+We are available on PyPI!
+```bash
+pip install icon-registration
+```
+
+
+To run our pretrained model in the cloud on sample images from OAI knees, visit [our knee google colab notebook](https://colab.research.google.com/drive/1svftgw-vYWnLp9lSf3UkrG547atjbIrg?usp=sharing)
+
+To run our pretrained model for lung CT scans on an example from COPDgene, visit [our lung google colab notebook](https://colab.research.google.com/github/uncbiag/ICON/blob/master/notebooks/ICON_lung_demo.ipynb)
+
+----------------
+
+To train from scratch on the synthetic triangles and circles dataset:
+
+```bash
+git clone https://github.com/uncbiag/ICON
+cd ICON
+
+pip install -e .
+
+python training_scripts/2d_triangles_example.py
+```
+
diff --git a/src/somef_core/test/test_data/README-widoco-duplicate-dois.md b/src/somef_core/test/test_data/README-widoco-duplicate-dois.md
new file mode 100644
index 0000000..85fb4f0
--- /dev/null
+++ b/src/somef_core/test/test_data/README-widoco-duplicate-dois.md
@@ -0,0 +1,198 @@
+# WIzard for DOCumenting Ontologies (WIDOCO)
+[](https://zenodo.org/badge/latestdoi/11427075) [](https://jitpack.io/#dgarijo/Widoco)
+
+
+
+WIDOCO helps you to publish and create an enriched and customized documentation of your ontology automatically, by following a series of steps in a GUI.
+
+**Author**: Daniel Garijo Verdejo (@dgarijo)
+
+**Contributors**: María Poveda, Idafen Santana, Almudena Ruiz, Miguel Angel García, Oscar Corcho, Daniel Vila, Sergio Barrio, Martin Scharm, Maxime Lefrancois, Alfredo Serafini, @kartgk, Pat Mc Bennett, Christophe Camel, Jacobus Geluk, Martin Scharm, @rpietzsch, Jonathan Leitschuh, Jodi Schneider, Giacomo Lanza, Alejandra Gonzalez-Beltran, Mario Scrocca, Miguel Angel García and Flores Bakker.
+
+**Citing WIDOCO**: If you used WIDOCO in your work, please cite the ISWC 2017 paper: https://iswc2017.semanticweb.org/paper-138
+
+```bib
+@inproceedings{garijo2017widoco,
+ title={WIDOCO: a wizard for documenting ontologies},
+ author={Garijo, Daniel},
+ booktitle={International Semantic Web Conference},
+ pages={94--102},
+ year={2017},
+ organization={Springer, Cham},
+ doi = {10.1007/978-3-319-68204-4_9},
+ funding = {USNSF ICER-1541029, NIH 1R01GM117097-01},
+ url={http://dgarijo.com/papers/widoco-iswc2017.pdf}
+}
+
+@inproceedings{garijo2017widoco_duplicate,
+ title={WIDOCO: a wizard for documenting ontologies},
+ author={Garijo, Daniel},
+ booktitle={International Semantic Web Conference},
+ pages={94--102},
+ year={2017},
+ organization={Springer, Cham},
+ doi = {https://dx.doi.org/10.1007/978-3-319-68204-4_9},
+ url={http://dgarijo.com/papers/widoco-iswc2017.pdf}
+}
+```
+If you want to cite the latest version of the software, you can do so by using: https://zenodo.org/badge/latestdoi/11427075.
+
+## Downloading the executable
+
+To download WIDOCO, you need to download a JAR executable file. Check the latest release for more details: (https://github.com/dgarijo/WIDOCO/releases/latest).
+
+## Importing WIDOCO as a dependency
+Just add the dependency and repository to your `pom.xml` file as follows. See the [WIDOCO JitPack](https://jitpack.io/#dgarijo/Widoco) page to find alternative means to incorporate WIDOCO to your project.
+
+```xml
+
+
+ com.github.dgarijo
+ Widoco
+ v1.4.15
+
+
+
+[ ... ]
+
+
+
+ jitpack.io
+ https://jitpack.io
+
+
+```
+
+## Description
+WIDOCO helps you to publish and create an enriched and customized documentation of your ontology, by following a series of steps in a wizard. We extend the LODE framework by Silvio Peroni to describe the classes, properties and data properties of the ontology, the OOPS! webservice by María Poveda to print an evaluation and the Licensius service by Victor Rodriguez Doncel to determine the license URI and title being used. In addition, we use WebVowl to visualize the ontology and have extended Bubastis to show a complete changelog between different versions of your ontology.
+
+Features of WIDOCO:
+* Automatic documentation of the terms in your ontology (based on [LODE](http://www.essepuntato.it/lode/)). Now you can use Markdown on your class descriptions (see [example](doc/gallery/index.html))
+* Automatic annotation in JSON-LD snippets of the html produced.
+* Association of a provenance page which includes the history of your vocabulary (W3C PROV-O compliant).
+* Metadata extraction from the ontology plus the means to complete it on the fly when generating your ontology. Check the [best practice document](http://dgarijo.github.io/Widoco/doc/bestPractices/index-en.html) to know more about the terms recognized by WIDOCO.
+* Guidelines on the main sections that your document should have and how to complete them.
+* Integration with diagram creators ([WebVOWL](http://vowl.visualdataweb.org/webvowl/)).
+* Automatic changelog of differences between the actual and the previous version of the ontology (based on [Bubastis](http://www.ebi.ac.uk/efo/bubastis/)).
+* Separation of the sections of your html page so you can write them independently and replace only those needed.
+* Content negotiation and serialization of your ontology according to [W3C best practices](https://www.w3.org/TR/swbp-vocab-pub/)
+* Evaluation reports of your ontology (using the [OOPS! web service](http://oops.linkeddata.es/))
+* Integration with license metadata services ([Licensius](http://licensius.com/)) to automatically describe the license used in your ontology.
+
+## Examples
+Examples of the features of WIDOCO can be seen on [the gallery](http://dgarijo.github.io/Widoco/doc/gallery/)
+
+## GUI Tutorial
+A tutorial explaining the main features of the GUI can be found [here](http://dgarijo.github.io/Widoco/doc/tutorial/)
+
+## How to use WIDOCO
+
+### JAR execution
+
+Download the latest `.jar` [WIDOCO available release](https://github.com/dgarijo/WIDOCO/releases/latest) (it will be something like `widoco-VERSION-jar-with-dependencies.jar`). Then just double click the `.jar` file.
+
+You may also execute WIDOCO through the command line. Usage:
+```bash
+java -jar widoco-VERSION-jar-with-dependencies.jar [OPTIONS]
+```
+
+### Docker execution
+
+First build the image using the `Dockerfile` in project folder:
+
+```bash
+docker build -t dgarijo/widoco .
+```
+
+You can now execute WIDOCO through the command line. Usage:
+
+```bash
+docker run -ti --rm dgarijo/widoco [OPTIONS]
+```
+
+If you want to share data between the Docker Container and your Host, for instance to load a local ontology file (from PATH), you will need to mount the container
+with host directories. For instance:
+
+```bash
+docker run -ti --rm \
+ -v `pwd`/test:/usr/local/widoco/in \
+ -v `pwd`/target/generated-doc:/usr/local/widoco/out \
+ dgarijo/widoco -ontFile in/bne.ttl -outFolder out -rewriteAll
+```
+
+### Options
+
+`-ontFile PATH` [required (unless -ontURI is used)]: Load a local ontology file (from PATH) to document. This option is incompatible with -ontURI
+
+`-ontURI URI` [required (unless -ontFile is used)]: Load an ontology to document from its URI. This option is incompatible with -ontFile
+
+`-outFolder folderName`: Specifies the name of the folder where to save the documentation. By default is 'myDocumentation'
+
+`-confFile PATH`: Load your own configuration file for the ontology metadata. Incompatible with -getOntologyMetadata
+
+`-getOntologyMetadata`: Extract ontology metadata from the given ontology
+
+`-oops`: Create an html page with the evaluation from the OOPS service (http://oops.linkeddata.es/)
+
+`-rewriteAll`: Replace any existing files when documenting an ontology (e.g., from a previous execution)
+
+`-crossRef`: ONLY generate the overview and cross reference sections. The index document will NOT be generated. The htaccess, provenance page, etc., will not be generated unless requested by other flags. This flag is intended to be used only after a first version of the documentation exists.
+
+`-saveConfig PATH`: Save a configuration file on PATH with the properties of a given ontology
+
+`-useCustomStyle`: Export the documentation using alternate css files (by Daniel Vila).
+
+`-lang LANG1-LANG2`: Generate documentation in multiple languages (separated by "-"). Note that if the language is not supported, the system will load the labels in english. For example: en-pt-es
+
+`-includeImportedOntologies`: Indicates whether the terms of the imported ontologies of the current ontology should be documented as well or not.
+
+`-htaccess`: Create a bundle for publication ready to be deployed on your Apache server.
+
+`-webVowl`: Create a visualization based on WebVowl (http://vowl.visualdataweb.org/webvowl/index.html#) in the documentation.
+
+`-licensius`: Use the Licensius web services (http://licensius.com/apidoc/index.html) to retrieve license metadata. Only works if the -getOntologyMetadata flag is enabled.
+
+`-ignoreIndividuals`: Individuals will not be included in the documentation.
+
+`-includeAnnotationProperties`: Include annotation properties defined in your ontology in the documentation (by default they are not included)
+
+`-analytics CODE`: Add a code snippet for Google analytics to track your HTML documentation. You need to add your CODE next to the flag. For example: UA-1234
+
+`-doNotDisplaySerializations`: The serializations of the ontology will not be displayed.
+
+`-displayDirectImportsOnly`: Only those imported ontologies that are directly imported in the ontology being documented.
+
+`-rewriteBase PATH`: Change the default rewrite base path. The default value is "/". This flag can only be used with the htaccess option.
+
+`-excludeIntroduction`: Skip the introduction section in the documentation.
+
+`-uniteSections`: Write all HTML sections into a single HTML document.
+
+`-noPlaceHolderText`: Do not add any placeholder text (this will remove intro, abstract (if empty) and description sections).
+
+`--help`: Shows a help message and exits.
+
+
+## How can I make WIDOCO automatically recognize my vocabulary annotations?
+There are two alternative ways for making WIDOCO get your vocabulary metadata annotations and use them automatically to document the ontology.
+
+* The recommended way: add them in your OWL file. For guidelines on which ones to include, follow our [best practices document](https://w3id.org/widoco/bestPractices), which indicates which ones we recommend.
+* Alternatively, edit the project properties of /config/config.properties. This is a key-value pair file with metadata properties. Some people consider it easier than adding the property annotations to the OWL file, although I recommend doing the former option. Note that the character ";" is used for lists (for instance first author; second author; third author).
+
+## Browser issues (Why can't I see the generated documentation / visualization?)
+WIDOCO separates the contents of different sections in HTML files, which are then loaded in the `index.html` file. WIDOCO was designed this way because it's easier to edit your introduction or description sections independently without being all aggregated together in a huge HTML document. **When all the contents generated by WIDOCO are stored in a server, you will be able to see the documentation of your ontology using any browser**. However, if you open the `index.html` file **on your local browser**, you may see a document missing most of the sections in your documentation. This happens because browsers don't allow loading separate content when opening a file locally for security reasons. If you want to explore how your ontology would look locally, you have two options:
+
+* a) Execute WIDOCO with the `-uniteSections` flag; or select the option `add al sections in a single document` in the "load sections" step in the WIDOCO GUI. This will make all the sections of WIDOCO to be in the `index.html`; and you will be able to see it in your browser. Note that the **LODE visualization will not be available** when exploring your ontology locally.
+* b) Create a local server: Set up a local server (e.g., using XAMPP or Tomcat) and serve the files WIDOCO generates (in the `htdocs` folder for Apache servers).
+
+If you place the files generated by WIDOCO in a server and access them via its URL (for example, a Github page), you should be able to see your documentation appropriately.
+
+## Current improvements
+For a complete list of the current improvements and next features, check the [project open issues](https://github.com/dgarijo/Widoco/issues) and [milestones](https://github.com/dgarijo/Widoco/milestones) in the repository.
+
+## Requirements
+You will need Java 1.8 or higher (SDK 1.8 or JRE 8) for WIDOCO to work
+Otherwise, you will probably experience an "Unsupported major.minor version 52.0" exception when executing the JAR file.
+
+## Contribution guidelines
+Contributions to address any of the current issues are welcome. In order to push your contribution, just **push your pull request to the develop branch**. The master branch has only the code associated to the latest release.
diff --git a/src/somef_core/test/test_data/README-widoco-swh.md b/src/somef_core/test/test_data/README-widoco-swh.md
new file mode 100644
index 0000000..0f72c58
--- /dev/null
+++ b/src/somef_core/test/test_data/README-widoco-swh.md
@@ -0,0 +1,233 @@
+# WIzard for DOCumenting Ontologies (WIDOCO)
+[](https://doi.org/10.5281/zenodo.11093793) [](https://jitpack.io/#dgarijo/Widoco) [](https://www.repostatus.org/#active) [](https://archive.softwareheritage.org/browse/origin/?origin_url=https://github.com/dgarijo/Widoco) [](https://archive.softwareheritage.org/swh:1:dir:40d462bbecefc3a9c3e810567d1f0d7606e0fae7;origin=https://github.com/dgarijo/Widoco;visit=swh:1:snp:4d8570d390a18fee1d37f262ad79b36c021c611a;anchor=swh:1:rev:fec66b89a4f4acb015a44c7f8cb671d49bec626a)
+
+
+
+WIDOCO helps you to publish and create an enriched and customized documentation of your ontology automatically, by following a series of steps in a GUI.
+
+**Author**: Daniel Garijo Verdejo (@dgarijo)
+
+**Contributors**: María Poveda, Idafen Santana, Almudena Ruiz, Miguel Angel García, Oscar Corcho, Daniel Vila, Sergio Barrio, Martin Scharm, Maxime Lefrancois, Alfredo Serafini, @kartgk, Pat Mc Bennett, Christophe Camel, Jacobus Geluk, Martin Scharm, @rpietzsch, Jonathan Leitschuh, Jodi Schneider, Giacomo Lanza, Alejandra Gonzalez-Beltran, Mario Scrocca, Miguel Angel García, Flores Bakker, @JohnnyMoonlight, René Fritze, @telecsur, Jan Vlug, Han Kruiger, Johannes Theissen-Lipp, Roberto Polli, Victor Chavez, Sirko Schindler and Michaël Dierick.
+
+**Citing WIDOCO**: If you used WIDOCO in your work, please cite the ISWC 2017 paper: https://iswc2017.semanticweb.org/paper-138
+
+```bib
+@inproceedings{garijo2017widoco,
+ title={WIDOCO: a wizard for documenting ontologies},
+ author={Garijo, Daniel},
+ booktitle={International Semantic Web Conference},
+ pages={94--102},
+ year={2017},
+ organization={Springer, Cham},
+ doi = {10.1007/978-3-319-68204-4_9},
+ funding = {USNSF ICER-1541029, NIH 1R01GM117097-01},
+ url={http://dgarijo.com/papers/widoco-iswc2017.pdf}
+}
+```
+If you want to cite the latest version of the software, you can do so by using: https://zenodo.org/badge/latestdoi/11427075.
+
+## Downloading the executable
+
+To download WIDOCO, you need to download a JAR executable file. Check the latest release for more details: (https://github.com/dgarijo/WIDOCO/releases/latest).
+
+## Importing WIDOCO as a dependency
+Just add the dependency and repository to your `pom.xml` file as follows. See the [WIDOCO JitPack](https://jitpack.io/#dgarijo/Widoco) page to find alternative means to incorporate WIDOCO to your project.
+
+```xml
+
+
+ com.github.dgarijo
+ Widoco
+ v1.4.24
+
+
+
+[ ... ]
+
+
+
+ jitpack.io
+ https://jitpack.io
+
+
+```
+
+## Description
+WIDOCO helps you to publish and create an enriched and customized documentation of your ontology, by following a series of steps in a wizard. We extend the LODE framework by Silvio Peroni to describe the classes, properties and data properties of the ontology, the OOPS! webservice by María Poveda to print an evaluation and the Licensius service by Victor Rodriguez Doncel to determine the license URI and title being used. In addition, we use WebVowl to visualize the ontology and have extended Bubastis to show a complete changelog between different versions of your ontology.
+
+Features of WIDOCO:
+* Automatic documentation of the terms in your ontology (based on [LODE](http://www.essepuntato.it/lode/)). Now **you can use Markdown on your class descriptions** (see [example](https://dgarijo.github.io/Widoco/doc/gallery/index.html))
+* Massive metadata extraction and support: WIDOCO will enhance your ontology documentation based on your ontology annotations. Now you can add custom logos and images, edit the content of your sections, etc. by just editing metadata. See our [supported metadata](doc/metadataGuide/guide.md) and [recommendations](https://dgarijo.github.io/Widoco/doc/bestPractices/index-en.html) for more information.
+* Automatic annotation in JSON-LD snippets of the html produced.
+* Association of a provenance page which includes the history of your vocabulary (W3C PROV-O compliant).
+* Guidelines on the main sections that your document should have and how to complete them.
+* Integration with diagram creators ([WebVOWL](http://vowl.visualdataweb.org/webvowl/)).
+* Automatic changelog of differences between the actual and the previous version of the ontology (based on [Bubastis](http://www.ebi.ac.uk/efo/bubastis/)).
+* Separation of the sections of your html page so you can write them independently and replace only those needed.
+* Content negotiation and serialization of your ontology according to [W3C best practices](https://www.w3.org/TR/swbp-vocab-pub/)
+* Evaluation reports of your ontology (using the [OOPS! web service](https://oops.linkeddata.es/))
+* Integration with license metadata services ([Licensius](http://licensius.com/)) to automatically describe the license used in your ontology.
+
+## Examples
+Examples of the features of WIDOCO can be seen on [the gallery](https://dgarijo.github.io/Widoco/doc/gallery/)
+
+## GUI Tutorial
+A tutorial explaining the main features of the GUI can be found [here](https://dgarijo.github.io/Widoco/doc/tutorial/)
+
+## Metadata usage
+To see how WIDOCO recognizes metadata annotations in your ontology to create the documentation files, see [the WIDOCO metadata documentation](doc/metadataGuide/guide.md). To learn which metadata properties we recommend adding to your ontology for producing a nice-looking documentation, have a look at our [best practices guide](https://dgarijo.github.io/Widoco/doc/bestPractices/index-en.html).
+
+For example, in order to show your logo in your documentation you just need to use `foaf:logo` as an annotation, as follows:
+```
+@prefix owl: .
+@prefix foaf: .
+ a owl:Ontology ;
+ foaf:logo .
+```
+
+and it will show right next to the title. The [WIDOCO metadata documentation](doc/metadataGuide/guide.md) shows all supported metadata fields.
+
+## How to use WIDOCO
+
+### Building the JAR executable
+We provide JAR files for each release (see the [releases](https://github.com/dgarijo/Widoco/releases) page). However, if you want to build WIDOCO from scratch, just cd into the project folder and run:
+
+```bash
+mvn install
+```
+The JAR will be generated in a "JAR" folder. The name will follow the pattern: `widoco-{VERSION_ID}-jar-with-dependencies.jar`, where {VERSION_ID} is the version number of the tool.
+
+### JAR execution
+
+Download the latest `.jar` [WIDOCO available release](https://github.com/dgarijo/WIDOCO/releases/latest) (it will be something like `widoco-VERSION-jar-with-dependencies.jar`). Then just double click the `.jar` file.
+
+You may also execute WIDOCO through the command line. Usage:
+```bash
+java -jar widoco-VERSION-jar-with-dependencies.jar [OPTIONS]
+```
+
+### Docker execution
+If you don't want to use the JAR directly, you may run the project using a [Docker container](https://www.docker.com/). First you will need to download or build the image, and then run it.
+
+#### Reusing a pre-existing image
+We build containers in the [GitHub image registry](https://github.com/dgarijo/Widoco/pkgs/container/widoco) for all latest releases. In order to import one, just run the following command, stating the version of Widoco you prefer (e.g., for v1.4.23):
+
+```
+docker pull ghcr.io/dgarijo/widoco:v1.4.23
+```
+
+To browse all available images, see the [GitHub image registry](https://github.com/dgarijo/Widoco/pkgs/container/widoco).
+
+#### Building the image yourself
+
+Build the image using the `Dockerfile` in project folder:
+
+```bash
+docker build -t dgarijo/widoco .
+```
+
+#### Running WIDOCO's image
+
+You can now execute WIDOCO through the command line. Usage:
+
+```bash
+docker run -ti --rm dgarijo/widoco [OPTIONS]
+```
+
+**Note:** If you downloaded the image from the GitHub registry, you will have to change `dgarijo/widoco` with the name of the image you downloaded. For example `ghcr.io/dgarijo/widoco:v1.4.23`.
+
+If you want to share data between the Docker Container and your Host, for instance to load a local ontology file (from PATH), you will need to mount the container
+with host directories. For instance:
+
+```bash
+docker run -ti --rm \
+ -v `pwd`/test:/usr/local/widoco/in:Z \
+ -v `pwd`/target/generated-doc:/usr/local/widoco/out:Z \
+ dgarijo/widoco -ontFile in/bne.ttl -outFolder out -rewriteAll
+```
+
+### Execution options
+
+`-analytics CODE`: Add a code snippet for Google analytics to track your HTML documentation. You need to add your CODE next to the flag. For example: UA-1234
+
+`-confFile PATH`: Load your own configuration file for the ontology metadata. Use this option if you want to load your own HTML sections as well. Incompatible with -getOntologyMetadata. See [the configuration documentation](doc/configuration/configuration_doc.md) for more information about the accepted fields.
+
+`-crossRef`: ONLY generate the overview and cross reference sections. The index document will NOT be generated. The htaccess, provenance page, etc., will not be generated unless requested by other flags. This flag is intended to be used only after a first version of the documentation exists.
+
+`-displayDirectImportsOnly`: Only those imported ontologies that are directly imported in the ontology being documented.
+
+`-doNotDisplaySerializations`: The serializations of the ontology will not be displayed.
+
+`-excludeIntroduction`: Skip the introduction section in the documentation.
+
+`-excludeProvenance`: Do not add the link "Provenance of this page" in the metadata header section
+
+`-getOntologyMetadata`: Extract ontology metadata from the given ontology
+
+`--help`: Shows a help message and exits.
+
+`-htaccess`: Create a bundle for publication ready to be deployed on your Apache server.
+
+
+`-ignoreIndividuals`: Individuals will not be included in the documentation.
+
+`-includeAnnotationProperties`: Include annotation properties defined in your ontology in the documentation (by default they are not included)
+
+`-includeImportedOntologies`: Indicates whether the terms of the imported ontologies of the current ontology should be documented as well or not.
+
+`-import`: imports a local ontology (e.g., if you don't want to load an online ontology, you may load its local version)
+
+`-lang LANG1-LANG2`: Generate documentation in multiple languages (separated by "-"). Note that if the language is not supported, the system will load the labels in english. For example: en-pt-es
+
+`-licensius`: Use the Licensius web services (http://licensius.com/apidoc/index.html) to retrieve license metadata. Only works if the -getOntologyMetadata flag is enabled.
+
+`-noPlaceHolderText`: Do not add any placeholder text (this will remove intro, abstract (if empty) and description sections).
+
+`-ontFile PATH` [required (unless -ontURI is used)]: Load a local ontology file (from PATH) to document. This option is incompatible with -ontURI
+
+`-outFolder folderName`: Specifies the name of the folder where to save the documentation. By default is 'myDocumentation'
+
+`-ontURI URI` [required (unless -ontFile is used)]: Load an ontology to document from its URI. This option is incompatible with -ontFile
+
+`-oops`: Create an html page with the evaluation from the OOPS service (http://oops.linkeddata.es/)
+
+`-rewriteAll`: Replace any existing files when documenting an ontology (e.g., from a previous execution)
+
+`-rewriteBase PATH`: Change the default rewrite base path. The default value is "/". This flag can only be used with the htaccess option.
+
+`-saveConfig PATH`: Save a configuration file on PATH with the properties of a given ontology
+
+`-uniteSections`: Write all HTML sections into a single HTML document.
+
+`-useCustomStyle`: Export the documentation using alternate css files (by Daniel Vila).
+
+`--version`: Shows the current version of WIDOCO.
+
+`-webVowl`: Create a visualization based on WebVowl (http://vowl.visualdataweb.org/webvowl/index.html#) in the documentation.
+
+
+## How can I make WIDOCO automatically recognize my vocabulary annotations?
+There are two alternative ways for making WIDOCO get your vocabulary metadata annotations and use them automatically to document the ontology.
+
+* The recommended way: add them in your OWL file. For guidelines on which ones to include, follow our [best practices document](https://w3id.org/widoco/bestPractices), which indicates which ones we recommend.
+* Alternatively, edit the project properties of /config/config.properties. This is a key-value pair file with metadata properties. Some people consider it easier than adding the property annotations to the OWL file, although I recommend doing the former option. Note that the character ";" is used for lists (for instance first author; second author; third author).
+
+For more information, see the [Widoco metadata guide](doc/metadataGuide/guide.md)
+
+## Browser issues (Why can't I see the generated documentation / visualization?)
+WIDOCO separates the contents of different sections in HTML files, which are then loaded in the `index.html` file. WIDOCO was designed this way because it's easier to edit your introduction or description sections independently without being all aggregated together in a huge HTML document. **When all the contents generated by WIDOCO are stored in a server, you will be able to see the documentation of your ontology using any browser**. However, if you open the `index.html` file **on your local browser**, you may see a document missing most of the sections in your documentation. This happens because browsers don't allow loading separate content when opening a file locally for security reasons. If you want to explore how your ontology would look locally, you have two options:
+
+* a) Execute WIDOCO with the `-uniteSections` flag; or select the option `add al sections in a single document` in the "load sections" step in the WIDOCO GUI. This will make all the sections of WIDOCO to be in the `index.html`; and you will be able to see it in your browser. Note that the **LODE visualization will not be available** when exploring your ontology locally.
+* b) Create a local server: Set up a local server (e.g., using XAMPP or Tomcat) and serve the files WIDOCO generates (in the `htdocs` folder for Apache servers).
+
+If you place the files generated by WIDOCO in a server and access them via its URL (for example, a Github page), you should be able to see your documentation appropriately.
+
+## Current improvements
+For a complete list of the current improvements and next features, check the [project open issues](https://github.com/dgarijo/Widoco/issues) and [milestones](https://github.com/dgarijo/Widoco/milestones) in the repository.
+
+## Requirements
+You will need Java 1.8 or higher (SDK 1.8 or JRE 8) for WIDOCO to work
+Otherwise, you will probably experience an "Unsupported major.minor version 52.0" exception when executing the JAR file.
+
+## Contribution guidelines
+Contributions to address any of the current issues are welcome. In order to push your contribution, just **push your pull request to the develop branch**. The master branch has only the code associated to the latest release.
\ No newline at end of file
diff --git a/src/somef_core/test/test_data/api_responses/somef-demo.json b/src/somef_core/test/test_data/api_responses/somef-demo.json
new file mode 100644
index 0000000..15434bb
--- /dev/null
+++ b/src/somef_core/test/test_data/api_responses/somef-demo.json
@@ -0,0 +1,648 @@
+{
+ "somef_provenance": {
+ "somef_version": "0.9.12",
+ "somef_schema_version": "1.0.0",
+ "date": "2025-12-10 11:21:43"
+ },
+ "code_repository": [
+ {
+ "result": {
+ "value": "https://github.com/juanjemdIos/somef-demo-repo",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ }
+ ],
+ "owner": [
+ {
+ "result": {
+ "value": "juanjemdIos",
+ "type": "User"
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ }
+ ],
+ "date_created": [
+ {
+ "result": {
+ "value": "2025-04-23T13:26:06Z",
+ "type": "Date"
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ }
+ ],
+ "date_updated": [
+ {
+ "result": {
+ "value": "2025-05-05T06:57:47Z",
+ "type": "Date"
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ }
+ ],
+ "license": [
+ {
+ "result": {
+ "value": "https://api.github.com/licenses/agpl-3.0",
+ "type": "License",
+ "name": "GNU Affero General Public License v3.0",
+ "url": "https://api.github.com/licenses/agpl-3.0",
+ "spdx_id": "AGPL-3.0"
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "value": " GNU AFFERO GENERAL PUBLIC LICENSE\n Version 3, 19 November 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. \n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n Preamble\n\n The GNU Affero General Public License is a free, copyleft license for\nsoftware and other kinds of works, specifically designed to ensure\ncooperation with the community in the case of network server software.\n\n The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works. By contrast,\nour General Public Licenses are intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.\n\n When we speak of free software, we are referring to freedom, not\nprice. Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n Developers that use our General Public Licenses protect your rights\nwith two steps: (1) assert copyright on the software, and (2) offer\nyou this License which gives you legal permission to copy, distribute\nand/or modify the software.\n\n A secondary benefit of defending all users' freedom is that\nimprovements made in alternate versions of the program, if they\nreceive widespread use, become available for other developers to\nincorporate. Many developers of free software are heartened and\nencouraged by the resulting cooperation. However, in the case of\nsoftware used on network servers, this result may fail to come about.\nThe GNU General Public License permits making a modified version and\nletting the public access it on a server without ever releasing its\nsource code to the public.\n\n The GNU Affero General Public License is designed specifically to\nensure that, in such cases, the modified source code becomes available\nto the community. It requires the operator of a network server to\nprovide the source code of the modified version running there to the\nusers of that server. Therefore, public use of a modified version, on\na publicly accessible server, gives the public access to the source\ncode of the modified version.\n\n An older license, called the Affero General Public License and\npublished by Affero, was designed to accomplish similar goals. This is\na different license, not a version of the Affero GPL, but Affero has\nreleased a new version of the Affero GPL which permits relicensing under\nthis license.\n\n The precise terms and conditions for copying, distribution and\nmodification follow.\n\n TERMS AND CONDITIONS\n\n 0. Definitions.\n\n \"This License\" refers to version 3 of the GNU Affero General Public License.\n\n \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n \"The Program\" refers to any copyrightable work licensed under this\nLicense. Each licensee is addressed as \"you\". \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy. The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy. Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies. Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License. If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n 1. Source Code.\n\n The \"source code\" for a work means the preferred form of the work\nfor making modifications to it. \"Object code\" means any non-source\nform of a work.\n\n A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form. A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities. However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work. For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n The Corresponding Source for a work in source code form is that\nsame work.\n\n 2. Basic Permissions.\n\n All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met. This License explicitly affirms your unlimited\npermission to run the unmodified Program. The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work. This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force. You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright. Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n Conveying under any other circumstances is permitted solely under\nthe conditions stated below. Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n 3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n 4. Conveying Verbatim Copies.\n\n You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n 5. Conveying Modified Source Versions.\n\n You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n a) The work must carry prominent notices stating that you modified\n it, and giving a relevant date.\n\n b) The work must carry prominent notices stating that it is\n released under this License and any conditions added under section\n 7. This requirement modifies the requirement in section 4 to\n \"keep intact all notices\".\n\n c) You must license the entire work, as a whole, under this\n License to anyone who comes into possession of a copy. This\n License will therefore apply, along with any applicable section 7\n additional terms, to the whole of the work, and all its parts,\n regardless of how they are packaged. This License gives no\n permission to license the work in any other way, but it does not\n invalidate such permission if you have separately received it.\n\n d) If the work has interactive user interfaces, each must display\n Appropriate Legal Notices; however, if the Program has interactive\n interfaces that do not display Appropriate Legal Notices, your\n work need not make them do so.\n\n A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit. Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n 6. Conveying Non-Source Forms.\n\n You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n a) Convey the object code in, or embodied in, a physical product\n (including a physical distribution medium), accompanied by the\n Corresponding Source fixed on a durable physical medium\n customarily used for software interchange.\n\n b) Convey the object code in, or embodied in, a physical product\n (including a physical distribution medium), accompanied by a\n written offer, valid for at least three years and valid for as\n long as you offer spare parts or customer support for that product\n model, to give anyone who possesses the object code either (1) a\n copy of the Corresponding Source for all the software in the\n product that is covered by this License, on a durable physical\n medium customarily used for software interchange, for a price no\n more than your reasonable cost of physically performing this\n conveying of source, or (2) access to copy the\n Corresponding Source from a network server at no charge.\n\n c) Convey individual copies of the object code with a copy of the\n written offer to provide the Corresponding Source. This\n alternative is allowed only occasionally and noncommercially, and\n only if you received the object code with such an offer, in accord\n with subsection 6b.\n\n d) Convey the object code by offering access from a designated\n place (gratis or for a charge), and offer equivalent access to the\n Corresponding Source in the same way through the same place at no\n further charge. You need not require recipients to copy the\n Corresponding Source along with the object code. If the place to\n copy the object code is a network server, the Corresponding Source\n may be on a different server (operated by you or a third party)\n that supports equivalent copying facilities, provided you maintain\n clear directions next to the object code saying where to find the\n Corresponding Source. Regardless of what server hosts the\n Corresponding Source, you remain obligated to ensure that it is\n available for as long as needed to satisfy these requirements.\n\n e) Convey the object code using peer-to-peer transmission, provided\n you inform other peers where the object code and Corresponding\n Source of the work are being offered to the general public at no\n charge under subsection 6d.\n\n A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling. In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage. For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product. A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source. The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information. But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed. Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n 7. Additional Terms.\n\n \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law. If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit. (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.) You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n a) Disclaiming warranty or limiting liability differently from the\n terms of sections 15 and 16 of this License; or\n\n b) Requiring preservation of specified reasonable legal notices or\n author attributions in that material or in the Appropriate Legal\n Notices displayed by works containing it; or\n\n c) Prohibiting misrepresentation of the origin of that material, or\n requiring that modified versions of such material be marked in\n reasonable ways as different from the original version; or\n\n d) Limiting the use for publicity purposes of names of licensors or\n authors of the material; or\n\n e) Declining to grant rights under trademark law for use of some\n trade names, trademarks, or service marks; or\n\n f) Requiring indemnification of licensors and authors of that\n material by anyone who conveys the material (or modified versions of\n it) with contractual assumptions of liability to the recipient, for\n any liability that these contractual assumptions directly impose on\n those licensors and authors.\n\n All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10. If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term. If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n 8. Termination.\n\n You may not propagate or modify a covered work except as expressly\nprovided under this License. Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License. If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n 9. Acceptance Not Required for Having Copies.\n\n You are not required to accept this License in order to receive or\nrun a copy of the Program. Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance. However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work. These actions infringe copyright if you do\nnot accept this License. Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n 10. Automatic Licensing of Downstream Recipients.\n\n Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License. You are not responsible\nfor enforcing compliance by third parties with this License.\n\n An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations. If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License. For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n 11. Patents.\n\n A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based. The\nwork thus licensed is called the contributor's \"contributor version\".\n\n A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version. For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement). To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients. \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License. You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n 12. No Surrender of Others' Freedom.\n\n If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License. If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all. For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n 13. Remote Network Interaction; Use with the GNU General Public License.\n\n Notwithstanding any other provision of this License, if you modify the\nProgram, your modified version must prominently offer all users\ninteracting with it remotely through a computer network (if your version\nsupports such interaction) an opportunity to receive the Corresponding\nSource of your version by providing access to the Corresponding Source\nfrom a network server at no charge, through some standard or customary\nmeans of facilitating copying of software. This Corresponding Source\nshall include the Corresponding Source for any work covered by version 3\nof the GNU General Public License that is incorporated pursuant to the\nfollowing paragraph.\n\n Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU General Public License into a single\ncombined work, and to convey the resulting work. The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the work with which it is combined will remain governed by version\n3 of the GNU General Public License.\n\n 14. Revised Versions of this License.\n\n The Free Software Foundation may publish revised and/or new versions of\nthe GNU Affero General Public License from time to time. Such new versions\nwill be similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n Each version is given a distinguishing version number. If the\nProgram specifies that a certain numbered version of the GNU Affero General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation. If the Program does not specify a version number of the\nGNU Affero General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n If the Program specifies that a proxy can decide which future\nversions of the GNU Affero General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n Later license versions may give you additional or different\npermissions. However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n 15. Disclaimer of Warranty.\n\n THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n 16. Limitation of Liability.\n\n IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n 17. Interpretation of Sections 15 and 16.\n\n If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n END OF TERMS AND CONDITIONS\n\n How to Apply These Terms to Your New Programs\n\n If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n To do so, attach the following notices to the program. It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n \n Copyright (C) \n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published\n by the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see .\n\nAlso add information on how to contact you by electronic and paper mail.\n\n If your software can interact with users remotely through a computer\nnetwork, you should also make sure that it provides a way for users to\nget its source. For example, if your program is a web application, its\ninterface could display a \"Source\" link that leads users to an archive\nof the code. There are many ways you could offer source, and different\nsolutions will be better for different programs; see section 13 for the\nspecific requirements.\n\n You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU AGPL, see\n.\n",
+ "type": "File_dump",
+ "name": "GNU Affero General Public License v3.0",
+ "spdx_id": "AGPL-3.0"
+ },
+ "confidence": 1,
+ "technique": "file_exploration",
+ "source": "https://raw.githubusercontent.com/juanjemdIos/somef-demo-repo/main/LICENSE"
+ }
+ ],
+ "description": [
+ {
+ "result": {
+ "value": "This repo aims to provide values for each metadata field that SOMEF (v0.9.10) can extract",
+ "type": "String"
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "type": "Text_excerpt",
+ "value": "[Yulun Zhang](http://yulunzhang.com/), [Yapeng Tian](http://yapengtian.org/), [Yu Kong](http://www1.ece.neu.edu/~yukong/), [Bineng Zhong](https://scholar.google.de/citations?user=hvRBydsAAAAJ&hl=en), and [Yun Fu](http://www1.ece.neu.edu/~yunfu/), \"Residual Dense Network for Image Super-Resolution\", CVPR 2018 (spotlight), [[arXiv]](https://arxiv.org/abs/1802.08797) \n \n",
+ "original_header": "Related papers"
+ },
+ "confidence": 0.8559632720021659,
+ "technique": "supervised_classification",
+ "source": "https://raw.githubusercontent.com/juanjemdIos/somef-demo-repo/main/README.md"
+ }
+ ],
+ "name": [
+ {
+ "result": {
+ "value": "somef-demo-repo",
+ "type": "String"
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ }
+ ],
+ "full_name": [
+ {
+ "result": {
+ "value": "juanjemdIos/somef-demo-repo",
+ "type": "String"
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ }
+ ],
+ "issue_tracker": [
+ {
+ "result": {
+ "value": "https://api.github.com/repos/juanjemdIos/somef-demo-repo/issues",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ }
+ ],
+ "forks_url": [
+ {
+ "result": {
+ "value": "https://api.github.com/repos/juanjemdIos/somef-demo-repo/forks",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ }
+ ],
+ "keywords": [
+ {
+ "result": {
+ "value": "documentation, metadata, ontology, somef",
+ "type": "String"
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ }
+ ],
+ "download_url": [
+ {
+ "result": {
+ "value": "https://github.com/juanjemdIos/somef-demo-repo/releases",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ }
+ ],
+ "programming_languages": [
+ {
+ "result": {
+ "value": "Jupyter Notebook",
+ "name": "Jupyter Notebook",
+ "type": "Programming_language",
+ "size": 32402
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "value": "Common Workflow Language",
+ "name": "Common Workflow Language",
+ "type": "Programming_language",
+ "size": 492
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "value": "Dockerfile",
+ "name": "Dockerfile",
+ "type": "Programming_language",
+ "size": 265
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "value": "Shell",
+ "name": "Shell",
+ "type": "Programming_language",
+ "size": 35
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ }
+ ],
+ "releases": [
+ {
+ "result": {
+ "type": "Release",
+ "value": "https://api.github.com/repos/juanjemdIos/somef-demo-repo/releases/214448249",
+ "tag": "0.0.1",
+ "name": "First releases",
+ "author": {
+ "name": "juanjemdIos",
+ "type": "User"
+ },
+ "description": "This release only serves to be extracted by SOMEF into `releases`",
+ "tarball_url": "https://api.github.com/repos/juanjemdIos/somef-demo-repo/tarball/0.0.1",
+ "zipball_url": "https://api.github.com/repos/juanjemdIos/somef-demo-repo/zipball/0.0.1",
+ "html_url": "https://github.com/juanjemdIos/somef-demo-repo/releases/tag/0.0.1",
+ "url": "https://api.github.com/repos/juanjemdIos/somef-demo-repo/releases/214448249",
+ "release_id": 214448249,
+ "date_created": "2025-04-23T14:26:19Z",
+ "date_published": "2025-04-24T08:51:25Z",
+ "assets": []
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ }
+ ],
+ "code_of_conduct": [
+ {
+ "result": {
+ "value": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participation in our\ncommunity a harassment-free experience for everyone, regardless of age, body\nsize, visible or invisible disability, ethnicity, sex characteristics, gender\nidentity and expression, level of experience, education, socio-economic status,\nnationality, personal appearance, race, religion, or sexual identity\nand orientation.\n\nWe pledge to act and interact in ways that contribute to an open, welcoming,\ndiverse, inclusive, and healthy community.\n\n## Our Standards\n\nExamples of behavior that contributes to a positive environment for our\ncommunity include:\n\n* Demonstrating empathy and kindness toward other people\n* Being respectful of differing opinions, viewpoints, and experiences\n* Giving and gracefully accepting constructive feedback\n* Accepting responsibility and apologizing to those affected by our mistakes,\n and learning from the experience\n* Focusing on what is best not just for us as individuals, but for the\n overall community\n\nExamples of unacceptable behavior include:\n\n* The use of sexualized language or imagery, and sexual attention or\n advances of any kind\n* Trolling, insulting or derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or email\n address, without their explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n professional setting\n\n## Enforcement Responsibilities\n\nCommunity leaders are responsible for clarifying and enforcing our standards of\nacceptable behavior and will take appropriate and fair corrective action in\nresponse to any behavior that they deem inappropriate, threatening, offensive,\nor harmful.\n\nCommunity leaders have the right and responsibility to remove, edit, or reject\ncomments, commits, code, wiki edits, issues, and other contributions that are\nnot aligned to this Code of Conduct, and will communicate reasons for moderation\ndecisions when appropriate.\n\n## Scope\n\nThis Code of Conduct applies within all community spaces, and also applies when\nan individual is officially representing the community in public spaces.\nExamples of representing our community include using an official e-mail address,\nposting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported to the community leaders responsible for enforcement at\n.\nAll complaints will be reviewed and investigated promptly and fairly.\n\nAll community leaders are obligated to respect the privacy and security of the\nreporter of any incident.\n\n## Enforcement Guidelines\n\nCommunity leaders will follow these Community Impact Guidelines in determining\nthe consequences for any action they deem in violation of this Code of Conduct:\n\n### 1. Correction\n\n**Community Impact**: Use of inappropriate language or other behavior deemed\nunprofessional or unwelcome in the community.\n\n**Consequence**: A private, written warning from community leaders, providing\nclarity around the nature of the violation and an explanation of why the\nbehavior was inappropriate. A public apology may be requested.\n\n### 2. Warning\n\n**Community Impact**: A violation through a single incident or series\nof actions.\n\n**Consequence**: A warning with consequences for continued behavior. No\ninteraction with the people involved, including unsolicited interaction with\nthose enforcing the Code of Conduct, for a specified period of time. This\nincludes avoiding interactions in community spaces as well as external channels\nlike social media. Violating these terms may lead to a temporary or\npermanent ban.\n\n### 3. Temporary Ban\n\n**Community Impact**: A serious violation of community standards, including\nsustained inappropriate behavior.\n\n**Consequence**: A temporary ban from any sort of interaction or public\ncommunication with the community for a specified period of time. No public or\nprivate interaction with the people involved, including unsolicited interaction\nwith those enforcing the Code of Conduct, is allowed during this period.\nViolating these terms may lead to a permanent ban.\n\n### 4. Permanent Ban\n\n**Community Impact**: Demonstrating a pattern of violation of community\nstandards, including sustained inappropriate behavior, harassment of an\nindividual, or aggression toward or disparagement of classes of individuals.\n\n**Consequence**: A permanent ban from any sort of public interaction within\nthe community.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage],\nversion 2.0, available at\nhttps://www.contributor-covenant.org/version/2/0/code_of_conduct.html.\n\nCommunity Impact Guidelines were inspired by [Mozilla's code of conduct\nenforcement ladder](https://github.com/mozilla/diversity).\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see the FAQ at\nhttps://www.contributor-covenant.org/faq. Translations are available at\nhttps://www.contributor-covenant.org/translations.\n",
+ "type": "File_dump"
+ },
+ "confidence": 1,
+ "technique": "file_exploration",
+ "source": "https://raw.githubusercontent.com/juanjemdIos/somef-demo-repo/main/CODE_OF_CONDUCT.md"
+ }
+ ],
+ "executable_example": [
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/juanjemdIos/somef-demo-repo/main/executable_example.ipynb",
+ "type": "Url",
+ "format": "jupyter_notebook"
+ },
+ "confidence": 1,
+ "technique": "file_exploration",
+ "source": "https://raw.githubusercontent.com/juanjemdIos/somef-demo-repo/main/executable_example.ipynb"
+ }
+ ],
+ "has_script_file": [
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/juanjemdIos/somef-demo-repo/main/script_file.sh",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ }
+ ],
+ "authors": [
+ {
+ "result": {
+ "name": "Google Inc.",
+ "type": "Agent",
+ "value": "Google Inc."
+ },
+ "confidence": 1,
+ "technique": "file_exploration",
+ "source": "https://raw.githubusercontent.com/juanjemdIos/somef-demo-repo/main/AUTHORS"
+ },
+ {
+ "result": {
+ "name": "Daniel Garijo",
+ "type": "Agent",
+ "value": "Daniel Garijo",
+ "email": "daniel.garijo@upm.es",
+ "last_name": "Garijo",
+ "given_name": "Daniel"
+ },
+ "confidence": 1,
+ "technique": "file_exploration",
+ "source": "https://raw.githubusercontent.com/juanjemdIos/somef-demo-repo/main/AUTHORS"
+ },
+ {
+ "result": {
+ "name": "Juanje Mendoza",
+ "type": "Agent",
+ "value": "Juanje Mendoza",
+ "email": "juanjesus.mendoza@fi.upm.es",
+ "last_name": "Mendoza",
+ "given_name": "Juanje"
+ },
+ "confidence": 1,
+ "technique": "file_exploration",
+ "source": "https://raw.githubusercontent.com/juanjemdIos/somef-demo-repo/main/AUTHORS"
+ },
+ {
+ "result": {
+ "name": "Arm Ltd",
+ "type": "Agent",
+ "value": "Arm Ltd"
+ },
+ "confidence": 1,
+ "technique": "file_exploration",
+ "source": "https://raw.githubusercontent.com/juanjemdIos/somef-demo-repo/main/AUTHORS"
+ }
+ ],
+ "has_build_file": [
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/juanjemdIos/somef-demo-repo/main/Dockerfile",
+ "type": "Url",
+ "format": "dockerfile"
+ },
+ "confidence": 1,
+ "technique": "file_exploration",
+ "source": "https://raw.githubusercontent.com/juanjemdIos/somef-demo-repo/main/Dockerfile"
+ }
+ ],
+ "citation": [
+ {
+ "result": {
+ "value": "cff-version: 1.2.0\nmessage: If you use this software, please cite it using these metadata.\ntitle: SOMEF DEMO REPO\nabstract: Demo repository for new version (0.9.10) of SOMEF \nauthors:\n - family-names: Thomas\n given-names: Pronk\n orcid: https://orcid.org/0000-0001-9334-7190\nversion: 0.0.1\ndate-released: 2023-11-23\nlicense: AGPL-3.0\nurl: \"https://github.com/tpronk/somef-demo-repo\"",
+ "type": "File_dump",
+ "author": [
+ {
+ "type": "Agent",
+ "name": "Pronk Thomas",
+ "family_name": "Thomas",
+ "given_name": "Pronk",
+ "url": "https://orcid.org/0000-0001-9334-7190"
+ }
+ ],
+ "title": "SOMEF DEMO REPO",
+ "url": "https://github.com/tpronk/somef-demo-repo",
+ "format": "cff",
+ "datePublished": "2023"
+ },
+ "confidence": 1,
+ "technique": "file_exploration",
+ "source": "https://raw.githubusercontent.com/juanjemdIos/somef-demo-repo/main/CITATION.cff"
+ },
+ {
+ "result": {
+ "value": "Please cite this repo as Pronk, T. (2023). *somef-demo-repo: This repo aims to provide values for each metadata field that SOMEF (v0.9.4) can extract* (Version 0.0.1) [Computer software]. https://github.com/tpronk/somef-demo-repo\n",
+ "type": "Text_excerpt",
+ "original_header": "Citation"
+ },
+ "confidence": 1,
+ "technique": "header_analysis",
+ "source": "https://raw.githubusercontent.com/juanjemdIos/somef-demo-repo/main/README.md"
+ }
+ ],
+ "workflows": [
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/juanjemdIos/somef-demo-repo/main/workflow.cwl",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ }
+ ],
+ "readme_url": [
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/juanjemdIos/somef-demo-repo/main/README.md",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ }
+ ],
+ "contributing_guidelines": [
+ {
+ "result": {
+ "value": "These guidelines were copied from [PychoPy](https://github.com/psychopy/psychopy/edit/dev/CONTRIBUTING.md)\n\n# Have you ever contributed to an Open Source project?\nYour first contribution can be a bit intimidating, but feel free to give it a try. If you get stuck, don't hesitate to ask for help in our [developer forum](https://discourse.psychopy.org/c/dev). This is also a good place to pitch your idea. Next up:\n* **I won't program it myself.** Please file a [GitHub issue](https://github.com/psychopy/psychopy/issues).\n* **I'd like to take a shot.** Read on to find out how!\n\n# How to contribute\nContributing to PsychoPy consists of four steps:\n1. Getting your own copy\n2. Making your changes\n3. Committing your changes\n4. Submitting a Pull Request\n\n## 1. Getting your own copy of the PsychoPy codebase\nTo be sure your improvements can easily be integrated, follow these steps:\n1. **Make a [fork](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo) of the [PsychoPy repo](https://github.com/psychopy/psychopy).** This provides you with your own copy of the PsychoPy source code.\n2. **Inside your fork, make a new [branch](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-branches) for the feature you've got in mind.** If you'd like to fix a bug, base your new branch on the *release* branch. If you'd like to add a new feature, base it on the *dev* branch. We tend to name branches after the feature we're building. For example `olfactory_component`.\n3. **Clone your fork to your hard drive.** Next, switch to the new branch and you're all set up!\n\nLook [here](https://www.psychopy.org/developers/repository.html) to see how the PsychoPy repo is organized.\n\n## 2. Making your changes\nTo help you get started with modifying PsychoPy, we've got some [developer guides](https://www.psychopy.org/developers/index.html). To try out your modified PsychoPy, do a [developer's install](https://www.psychopy.org/download.html#developers-install).\n\n## 3. Committing your changes\nOnce you're happy with your changes, commit them to your GitHub repo. Please use the tags below in your commit and add an informative message.\n - **BF:** bug fix. For fixing bugs in the *release* branch.\n - **FF:** \u2018feature\u2019 fix. For fixing bugs in the *dev* branch.\n - **RF:** refactoring\n - **NF:** new feature\n - **ENH:** enhancement (to existing code, but don't worry too much about the difference between this and NF)\n - **DOC:** for all kinds of documentation related commits\n - **TEST:** for adding or changing tests\n\n## 4. File a Pull Request\nOnce you're done, it's time to add it to the central PsychoPy source code. File a [Pull Request](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request) from your own fork and branch to the PsychoPy repo. Be sure to target the right branch in PsychoPy (*release* or *dev*). Thanks for contributing!\n",
+ "type": "File_dump"
+ },
+ "confidence": 1,
+ "technique": "file_exploration",
+ "source": "https://raw.githubusercontent.com/juanjemdIos/somef-demo-repo/main/CONTRIBUTING.md"
+ }
+ ],
+ "ontologies": [
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/juanjemdIos/somef-demo-repo/main/ontology.ttl",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ }
+ ],
+ "acknowledgement": [
+ {
+ "result": {
+ "value": "This demonstration repo was created during the maSMP hackathon at [ZB MED](https://www.zbmed.de/en) sponsored by [NFDI4DataScience](https://www.nfdi4datascience.de). NFDI4DataScience is a consortium funded by the German Research Foundation (DFG), project number 460234259.\n",
+ "type": "Text_excerpt",
+ "original_header": "Acknowledgements"
+ },
+ "confidence": 1,
+ "technique": "header_analysis",
+ "source": "https://raw.githubusercontent.com/juanjemdIos/somef-demo-repo/main/README.md"
+ }
+ ],
+ "contact": [
+ {
+ "result": {
+ "value": "Contact person responsible for maintaining a software component\n",
+ "type": "Text_excerpt",
+ "original_header": "Contact"
+ },
+ "confidence": 1,
+ "technique": "header_analysis",
+ "source": "https://raw.githubusercontent.com/juanjemdIos/somef-demo-repo/main/README.md"
+ }
+ ],
+ "contributors": [
+ {
+ "result": {
+ "value": "Here could be a list of contributors to this software component\n",
+ "type": "Text_excerpt",
+ "original_header": "Contributors"
+ },
+ "confidence": 1,
+ "technique": "header_analysis",
+ "source": "https://raw.githubusercontent.com/juanjemdIos/somef-demo-repo/main/README.md"
+ }
+ ],
+ "documentation": [
+ {
+ "result": {
+ "value": "Where to find additional documentation about a software component.\n",
+ "type": "Text_excerpt",
+ "original_header": "Documentation"
+ },
+ "confidence": 1,
+ "technique": "header_analysis",
+ "source": "https://raw.githubusercontent.com/juanjemdIos/somef-demo-repo/main/README.md"
+ },
+ {
+ "result": {
+ "value": "For instructions on using OBA to create your API server, go to the [documentation](https://oba.readthedocs.io/en/latest/)\n",
+ "type": "Text_excerpt",
+ "original_header": "Related documentation"
+ },
+ "confidence": 1,
+ "technique": "header_analysis",
+ "source": "https://raw.githubusercontent.com/juanjemdIos/somef-demo-repo/main/README.md"
+ }
+ ],
+ "download": [
+ {
+ "result": {
+ "value": "Download instructions included in the repository.\n",
+ "type": "Text_excerpt",
+ "original_header": "Download"
+ },
+ "confidence": 1,
+ "technique": "header_analysis",
+ "source": "https://raw.githubusercontent.com/juanjemdIos/somef-demo-repo/main/README.md"
+ }
+ ],
+ "faq": [
+ {
+ "result": {
+ "value": "Frequently asked questions about a software component\n",
+ "type": "Text_excerpt",
+ "original_header": "FAQ"
+ },
+ "confidence": 1,
+ "technique": "header_analysis",
+ "source": "https://raw.githubusercontent.com/juanjemdIos/somef-demo-repo/main/README.md"
+ }
+ ],
+ "installation": [
+ {
+ "result": {
+ "value": "A set of instructions that indicate how to install a target repository\n",
+ "type": "Text_excerpt",
+ "original_header": "Installation instructions"
+ },
+ "confidence": 1,
+ "technique": "header_analysis",
+ "source": "https://raw.githubusercontent.com/juanjemdIos/somef-demo-repo/main/README.md"
+ }
+ ],
+ "requirements": [
+ {
+ "result": {
+ "value": "Pre-requisites and dependencies needed to execute a software component.\n",
+ "type": "Text_excerpt",
+ "original_header": "Requirements"
+ },
+ "confidence": 1,
+ "technique": "header_analysis",
+ "source": "https://raw.githubusercontent.com/juanjemdIos/somef-demo-repo/main/README.md"
+ }
+ ],
+ "run": [
+ {
+ "result": {
+ "value": "There is no code in this repo that can be run.\n",
+ "type": "Text_excerpt",
+ "original_header": "Run"
+ },
+ "confidence": 1,
+ "technique": "header_analysis",
+ "source": "https://raw.githubusercontent.com/juanjemdIos/somef-demo-repo/main/README.md"
+ }
+ ],
+ "support": [
+ {
+ "result": {
+ "value": "Guidelines and links of where to obtain support for a software component\n",
+ "type": "Text_excerpt",
+ "original_header": "Support"
+ },
+ "confidence": 1,
+ "technique": "header_analysis",
+ "source": "https://raw.githubusercontent.com/juanjemdIos/somef-demo-repo/main/README.md"
+ },
+ {
+ "result": {
+ "value": "[](https://gitter.im/OpenGeoscience/geonotebook)\n",
+ "type": "Text_excerpt",
+ "original_header": "Support channels"
+ },
+ "confidence": 1,
+ "technique": "header_analysis",
+ "source": "https://raw.githubusercontent.com/juanjemdIos/somef-demo-repo/main/README.md"
+ }
+ ],
+ "usage": [
+ {
+ "result": {
+ "value": "Assumptions and considerations recorded by the authors when executing a software component, or examples on how to use it.",
+ "type": "Text_excerpt",
+ "original_header": "Usage examples"
+ },
+ "confidence": 1,
+ "technique": "header_analysis",
+ "source": "https://raw.githubusercontent.com/juanjemdIos/somef-demo-repo/main/README.md"
+ }
+ ],
+ "identifier": [
+ {
+ "result": {
+ "type": "Url",
+ "value": "https://zenodo.org/badge/latestdoi/450496579"
+ },
+ "confidence": 1,
+ "technique": "regular_expression",
+ "source": "https://raw.githubusercontent.com/juanjemdIos/somef-demo-repo/main/README.md"
+ }
+ ],
+ "full_title": [
+ {
+ "result": {
+ "type": "String",
+ "value": "somef-demo-repo"
+ },
+ "confidence": 1,
+ "technique": "regular_expression",
+ "source": "https://raw.githubusercontent.com/juanjemdIos/somef-demo-repo/main/README.md"
+ }
+ ],
+ "related_documentation": [
+ {
+ "result": {
+ "type": "Url",
+ "value": "https://oba.readthedocs.io/",
+ "format": "readthedocs"
+ },
+ "confidence": 1,
+ "technique": "regular_expression",
+ "source": "https://raw.githubusercontent.com/juanjemdIos/somef-demo-repo/main/README.md"
+ }
+ ],
+ "repository_status": [
+ {
+ "result": {
+ "type": "Url",
+ "value": "https://www.repostatus.org/#active",
+ "description": "Active - The project has reached a stable, usable state and is being actively developed."
+ },
+ "confidence": 1,
+ "technique": "regular_expression",
+ "source": "https://raw.githubusercontent.com/juanjemdIos/somef-demo-repo/main/README.md"
+ }
+ ],
+ "support_channels": [
+ {
+ "result": {
+ "type": "Url",
+ "value": "https://gitter.im/OpenGeoscience/geonotebook"
+ },
+ "confidence": 1,
+ "technique": "regular_expression",
+ "source": "https://raw.githubusercontent.com/juanjemdIos/somef-demo-repo/main/README.md"
+ }
+ ],
+ "package_distribution": [
+ {
+ "result": {
+ "type": "Url",
+ "value": "https://pypi.org/project/mapeathor/"
+ },
+ "confidence": 1,
+ "technique": "regular_expression",
+ "source": "https://raw.githubusercontent.com/juanjemdIos/somef-demo-repo/main/README.md"
+ },
+ {
+ "result": {
+ "type": "Url",
+ "value": "https://pypi.org/project/mapeathor)/"
+ },
+ "confidence": 1,
+ "technique": "regular_expression",
+ "source": "https://raw.githubusercontent.com/juanjemdIos/somef-demo-repo/main/README.md"
+ }
+ ],
+ "logo": [
+ {
+ "result": {
+ "type": "Url",
+ "value": "https://raw.githubusercontent.com/juanjemdIos/somef-demo-repo//main/logo_directory/logo2.png"
+ },
+ "confidence": 1,
+ "technique": "regular_expression",
+ "source": "https://raw.githubusercontent.com/juanjemdIos/somef-demo-repo/main/README.md"
+ }
+ ],
+ "images": [
+ {
+ "result": {
+ "type": "Url",
+ "value": "https://raw.githubusercontent.com/juanjemdIos/somef-demo-repo//main/logo1.png"
+ },
+ "confidence": 1,
+ "technique": "regular_expression",
+ "source": "https://raw.githubusercontent.com/juanjemdIos/somef-demo-repo/main/README.md"
+ }
+ ],
+ "related_papers": [
+ {
+ "result": {
+ "type": "Url",
+ "value": "https://arxiv.org/abs/1802.08797"
+ },
+ "confidence": 1,
+ "technique": "regular_expression",
+ "source": "https://raw.githubusercontent.com/juanjemdIos/somef-demo-repo/main/README.md"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/somef_core/test/test_data/api_responses/widoco_api_response.json b/src/somef_core/test/test_data/api_responses/widoco_api_response.json
new file mode 100644
index 0000000..5d193d3
--- /dev/null
+++ b/src/somef_core/test/test_data/api_responses/widoco_api_response.json
@@ -0,0 +1,2779 @@
+{
+ "somef_provenance": {
+ "somef_version": "0.9.12",
+ "somef_schema_version": "1.0.0",
+ "date": "2025-12-10 11:44:17"
+ },
+ "code_repository": [
+ {
+ "result": {
+ "value": "https://github.com/dgarijo/Widoco",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "value": "https://github.com/dgarijo/Widoco",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "code_parser",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/codemeta.json"
+ }
+ ],
+ "owner": [
+ {
+ "result": {
+ "value": "dgarijo",
+ "type": "User"
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ }
+ ],
+ "date_created": [
+ {
+ "result": {
+ "value": "2013-07-15T15:51:51Z",
+ "type": "Date"
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "value": "2013-07-15",
+ "type": "String"
+ },
+ "confidence": 1,
+ "technique": "code_parser",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/codemeta.json"
+ }
+ ],
+ "date_updated": [
+ {
+ "result": {
+ "value": "2025-12-06T16:00:45Z",
+ "type": "Date"
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "value": "2025-01-31",
+ "type": "String"
+ },
+ "confidence": 1,
+ "technique": "code_parser",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/codemeta.json"
+ }
+ ],
+ "license": [
+ {
+ "result": {
+ "value": "https://api.github.com/licenses/apache-2.0",
+ "type": "License",
+ "name": "Apache License 2.0",
+ "url": "https://api.github.com/licenses/apache-2.0",
+ "spdx_id": "Apache-2.0"
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "value": " Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets \"{}\"\n replaced with your own identifying information. (Don't include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same \"printed page\" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright {2016} {Daniel Garijo, Information Sciences Institute, USC.}\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n\n",
+ "type": "File_dump",
+ "name": "Apache License 2.0",
+ "spdx_id": "Apache-2.0"
+ },
+ "confidence": 1,
+ "technique": "file_exploration",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/LICENSE"
+ },
+ {
+ "result": {
+ "value": "Apache License 2.0",
+ "type": "License",
+ "url": "https://raw.githubusercontent.com/dgarijo/Widoco/master/LICENSE",
+ "identifier": "https://spdx.org/licenses/Apache-2.0",
+ "spdx_id": "Apache-2.0"
+ },
+ "confidence": 1,
+ "technique": "code_parser",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/codemeta.json"
+ }
+ ],
+ "description": [
+ {
+ "result": {
+ "value": "Wizard for documenting ontologies. WIDOCO is a step by step generator of HTML templates with the documentation of your ontology. It uses the LODE environment to create part of the template.",
+ "type": "String"
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "value": [
+ "Wizard for documenting ontologies. WIDOCO is a step by step generator of HTML templates with the documentation of your ontology. It uses the LODE environment to create part of the template.",
+ "WIDOCO helps you to publish and create an enriched and customized documentation of your ontology, by following a series of steps in a wizard. We extend the LODE framework by Silvio Peroni to describe the classes, properties and data properties of the ontology, the OOPS! webservice by Mar\u00eda Poveda to print an evaluation and the Licensius service by Victor Rodriguez Doncel to determine the license URI and title being used. In addition, we use WebVowl to visualize the ontology and have extended Bubastis to show a complete changelog between different versions of your ontology.\n\nFeatures of WIDOCO:\n* Automatic documentation of the terms in your ontology (based on [LODE](http://www.essepuntato.it/lode/)). Now **you can use Markdown on your class descriptions** (see [example](https://dgarijo.github.io/Widoco/doc/gallery/index.html))\n* Massive metadata extraction and support: WIDOCO will enhance your ontology documentation based on your ontology annotations. Now you can add custom logos and images, edit the content of your sections, etc. by just editing metadata. See our [supported metadata](doc/metadataGuide/guide.md) and [recommendations](https://dgarijo.github.io/Widoco/doc/bestPractices/index-en.html) for more information.\n* Automatic annotation in JSON-LD snippets of the html produced.\n* Association of a provenance page which includes the history of your vocabulary (W3C PROV-O compliant).\n* Guidelines on the main sections that your document should have and how to complete them.\n* Integration with diagram creators ([WebVOWL](http://vowl.visualdataweb.org/webvowl/)).\n* Automatic changelog of differences between the actual and the previous version of the ontology (based on [Bubastis](http://www.ebi.ac.uk/efo/bubastis/)).\n* Separation of the sections of your html page so you can write them independently and replace only those needed.\n* Content negotiation and serialization of your ontology according to [W3C best practices](https://www.w3.org/TR/swbp-vocab-pub/)\n* Evaluation reports of your ontology (using the [OOPS! web service](https://oops.linkeddata.es/))\n* Integration with license metadata services ([Licensius](http://licensius.com/)) to automatically describe the license used in your ontology.\n"
+ ],
+ "type": "String"
+ },
+ "confidence": 1,
+ "technique": "code_parser",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/codemeta.json"
+ },
+ {
+ "result": {
+ "value": "WIDOCO helps you to publish and create an enriched and customized documentation of your ontology, by following a series of steps in a wizard. We extend the LODE framework by Silvio Peroni to describe the classes, properties and data properties of the ontology, the OOPS! webservice by Mar\u00eda Poveda to print an evaluation and the Licensius service by Victor Rodriguez Doncel to determine the license URI and title being used. In addition, we use WebVowl to visualize the ontology and have extended Bubastis to show a complete changelog between different versions of your ontology.\n\nFeatures of WIDOCO:\n* Automatic documentation of the terms in your ontology (based on [LODE](http://www.essepuntato.it/lode/)). Now **you can use Markdown on your class descriptions** (see [example](https://dgarijo.github.io/Widoco/doc/gallery/index.html))\n* Massive metadata extraction and support: WIDOCO will enhance your ontology documentation based on your ontology annotations. Now you can add custom logos and images, edit the content of your sections, etc. by just editing metadata. See our [supported metadata](doc/metadataGuide/guide.md) and [recommendations](https://dgarijo.github.io/Widoco/doc/bestPractices/index-en.html) for more information.\n* Automatic annotation in JSON-LD snippets of the html produced.\n* Association of a provenance page which includes the history of your vocabulary (W3C PROV-O compliant).\n* Guidelines on the main sections that your document should have and how to complete them.\n* Integration with diagram creators ([WebVOWL](http://vowl.visualdataweb.org/webvowl/)).\n* Automatic changelog of differences between the actual and the previous version of the ontology (based on [Bubastis](http://www.ebi.ac.uk/efo/bubastis/)).\n* Separation of the sections of your html page so you can write them independently and replace only those needed.\n* Content negotiation and serialization of your ontology according to [W3C best practices](https://www.w3.org/TR/swbp-vocab-pub/)\n* Evaluation reports of your ontology (using the [OOPS! web service](https://oops.linkeddata.es/))\n* Integration with license metadata services ([Licensius](http://licensius.com/)) to automatically describe the license used in your ontology.\n",
+ "type": "Text_excerpt",
+ "original_header": "Description",
+ "parent_header": [
+ "WIzard for DOCumenting Ontologies (WIDOCO)"
+ ]
+ },
+ "confidence": 1,
+ "technique": "header_analysis",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/README.md"
+ },
+ {
+ "result": {
+ "type": "Text_excerpt",
+ "value": "WIDOCO helps you to publish and create an enriched and customized documentation of your ontology automatically, by following a series of steps in a GUI. \n",
+ "original_header": "WIzard for DOCumenting Ontologies (WIDOCO)"
+ },
+ "confidence": 0.9336412604289143,
+ "technique": "supervised_classification",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/README.md"
+ },
+ {
+ "result": {
+ "type": "Text_excerpt",
+ "value": "For a complete list of the current improvements and next features, check the [project open issues](https://github.com/dgarijo/Widoco/issues) and [milestones](https://github.com/dgarijo/Widoco/milestones) in the repository.\n \n",
+ "original_header": "Current improvements"
+ },
+ "confidence": 0.9682942149713352,
+ "technique": "supervised_classification",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/README.md"
+ },
+ {
+ "result": {
+ "type": "Text_excerpt",
+ "value": "Contributions to address any of the current issues are welcome. In order to push your contribution, just **push your pull request to the develop branch**. The master branch has only the code associated to the latest release. \n",
+ "original_header": "Contribution guidelines"
+ },
+ "confidence": 0.892416273463637,
+ "technique": "supervised_classification",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/README.md"
+ }
+ ],
+ "name": [
+ {
+ "result": {
+ "value": "Widoco",
+ "type": "String"
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "value": "Widoco",
+ "type": "String"
+ },
+ "confidence": 1,
+ "technique": "code_parser",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/codemeta.json"
+ }
+ ],
+ "full_name": [
+ {
+ "result": {
+ "value": "dgarijo/Widoco",
+ "type": "String"
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ }
+ ],
+ "issue_tracker": [
+ {
+ "result": {
+ "value": "https://api.github.com/repos/dgarijo/Widoco/issues",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "value": "https://github.com/dgarijo/Widoco/issues",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "code_parser",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/codemeta.json"
+ }
+ ],
+ "forks_url": [
+ {
+ "result": {
+ "value": "https://api.github.com/repos/dgarijo/Widoco/forks",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ }
+ ],
+ "stargazers_count": [
+ {
+ "result": {
+ "value": 341,
+ "type": "Number"
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ }
+ ],
+ "keywords": [
+ {
+ "result": {
+ "value": "documentation, metadata, ontology, ontology-diagram, ontology-evaluation, wizard",
+ "type": "String"
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "value": [
+ "documentation",
+ "metadata",
+ "ontology",
+ "ontology-diagram",
+ "ontology-evaluation",
+ "wizard"
+ ],
+ "type": "String"
+ },
+ "confidence": 1,
+ "technique": "code_parser",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/codemeta.json"
+ }
+ ],
+ "forks_count": [
+ {
+ "result": {
+ "value": 100,
+ "type": "Number"
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ }
+ ],
+ "download_url": [
+ {
+ "result": {
+ "value": "https://github.com/dgarijo/Widoco/releases",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "value": "https://github.com/dgarijo/Widoco/releases",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "code_parser",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/codemeta.json"
+ }
+ ],
+ "programming_languages": [
+ {
+ "result": {
+ "value": "JavaScript",
+ "name": "JavaScript",
+ "type": "Programming_language",
+ "size": 1124896
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "value": "Java",
+ "name": "Java",
+ "type": "Programming_language",
+ "size": 594537
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "value": "XSLT",
+ "name": "XSLT",
+ "type": "Programming_language",
+ "size": 226430
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "value": "CSS",
+ "name": "CSS",
+ "type": "Programming_language",
+ "size": 84383
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "value": "HTML",
+ "name": "HTML",
+ "type": "Programming_language",
+ "size": 35918
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "value": "Dockerfile",
+ "name": "Dockerfile",
+ "type": "Programming_language",
+ "size": 520
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "name": "JavaScript",
+ "value": "JavaScript",
+ "type": "Programming_language"
+ },
+ "confidence": 1,
+ "technique": "code_parser",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/codemeta.json"
+ },
+ {
+ "result": {
+ "name": "Java",
+ "value": "Java",
+ "type": "Programming_language"
+ },
+ "confidence": 1,
+ "technique": "code_parser",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/codemeta.json"
+ },
+ {
+ "result": {
+ "name": "XSLT",
+ "value": "XSLT",
+ "type": "Programming_language"
+ },
+ "confidence": 1,
+ "technique": "code_parser",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/codemeta.json"
+ },
+ {
+ "result": {
+ "name": "CSS",
+ "value": "CSS",
+ "type": "Programming_language"
+ },
+ "confidence": 1,
+ "technique": "code_parser",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/codemeta.json"
+ },
+ {
+ "result": {
+ "name": "HTML",
+ "value": "HTML",
+ "type": "Programming_language"
+ },
+ "confidence": 1,
+ "technique": "code_parser",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/codemeta.json"
+ },
+ {
+ "result": {
+ "name": "Dockerfile",
+ "value": "Dockerfile",
+ "type": "Programming_language"
+ },
+ "confidence": 1,
+ "technique": "code_parser",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/codemeta.json"
+ }
+ ],
+ "releases": [
+ {
+ "result": {
+ "type": "Release",
+ "value": "https://api.github.com/repos/dgarijo/Widoco/releases/163195556",
+ "tag": "v1.4.25",
+ "name": "WiDOCO 1.4.25: Documentation fixes and new provenance flag",
+ "author": {
+ "name": "dgarijo",
+ "type": "User"
+ },
+ "description": "This version of WIDOCO fixes documentation broken links, clarifies how to download Docker images and includes a new option for excluding the provenance page from documentation (thanks to @MikiDi) \r\n\r\n## What's Changed\r\n* Bump docker/build-push-action from 5.3.0 to 5.4.0 by @dependabot in https://github.com/dgarijo/Widoco/pull/709\r\n* CLI option to exclude provenance link by @MikiDi in https://github.com/dgarijo/Widoco/pull/710\r\n* Improved documentation (broken links)\r\n\r\n\r\n**Full Changelog**: https://github.com/dgarijo/Widoco/compare/v1.4.24...v1.4.25",
+ "tarball_url": "https://api.github.com/repos/dgarijo/Widoco/tarball/v1.4.25",
+ "zipball_url": "https://api.github.com/repos/dgarijo/Widoco/zipball/v1.4.25",
+ "html_url": "https://github.com/dgarijo/Widoco/releases/tag/v1.4.25",
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/163195556",
+ "release_id": 163195556,
+ "date_created": "2024-06-30T09:43:24Z",
+ "date_published": "2024-06-30T09:48:00Z",
+ "assets": [
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/176791751",
+ "name": "widoco-1.4.25-jar-with-dependencies_JDK-11.jar",
+ "content_size": 40858575,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.4.25/widoco-1.4.25-jar-with-dependencies_JDK-11.jar",
+ "encoding_format": "application/x-java-archive",
+ "upload_date": "2024-06-30T09:47:25Z",
+ "download_count": 2481
+ },
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/176791780",
+ "name": "widoco-1.4.25-jar-with-dependencies_JDK-14.jar",
+ "content_size": 40861516,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.4.25/widoco-1.4.25-jar-with-dependencies_JDK-14.jar",
+ "encoding_format": "application/x-java-archive",
+ "upload_date": "2024-06-30T09:47:40Z",
+ "download_count": 220
+ },
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/176791807",
+ "name": "widoco-1.4.25-jar-with-dependencies_JDK-17.jar",
+ "content_size": 40861591,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.4.25/widoco-1.4.25-jar-with-dependencies_JDK-17.jar",
+ "encoding_format": "application/x-java-archive",
+ "upload_date": "2024-06-30T09:47:48Z",
+ "download_count": 2576
+ }
+ ]
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "type": "Release",
+ "value": "https://api.github.com/repos/dgarijo/Widoco/releases/153527742",
+ "tag": "v1.4.24",
+ "name": "WiDOCO 1.4.24: Hotfix for SWRL rules",
+ "author": {
+ "name": "dgarijo",
+ "type": "User"
+ },
+ "description": "This release introduces a hotfix for ontologies with SWRL rules and empty labels.\r\n\r\n## What's Changed\r\n* External Property Parser: Fix comparison to optional variable by @vChavezB in https://github.com/dgarijo/Widoco/pull/693\r\n* Develop by @dgarijo in https://github.com/dgarijo/Widoco/pull/701\r\n\r\n\r\n**Full Changelog**: https://github.com/dgarijo/Widoco/compare/v1.4.23...v1.4.24",
+ "tarball_url": "https://api.github.com/repos/dgarijo/Widoco/tarball/v1.4.24",
+ "zipball_url": "https://api.github.com/repos/dgarijo/Widoco/zipball/v1.4.24",
+ "html_url": "https://github.com/dgarijo/Widoco/releases/tag/v1.4.24",
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/153527742",
+ "release_id": 153527742,
+ "date_created": "2024-04-30T15:04:14Z",
+ "date_published": "2024-04-30T15:12:35Z",
+ "assets": [
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/165158401",
+ "name": "widoco-1.4.24-jar-with-dependencies_JDK-11.jar",
+ "content_size": 40858514,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.4.24/widoco-1.4.24-jar-with-dependencies_JDK-11.jar",
+ "encoding_format": "application/x-java-archive",
+ "upload_date": "2024-04-30T15:12:24Z",
+ "download_count": 99
+ },
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/165158392",
+ "name": "widoco-1.4.24-jar-with-dependencies_JDK-14.jar",
+ "content_size": 40861450,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.4.24/widoco-1.4.24-jar-with-dependencies_JDK-14.jar",
+ "encoding_format": "application/x-java-archive",
+ "upload_date": "2024-04-30T15:12:17Z",
+ "download_count": 37
+ },
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/165158374",
+ "name": "widoco-1.4.24-jar-with-dependencies_JDK-17.jar",
+ "content_size": 40861525,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.4.24/widoco-1.4.24-jar-with-dependencies_JDK-17.jar",
+ "encoding_format": "application/x-java-archive",
+ "upload_date": "2024-04-30T15:12:10Z",
+ "download_count": 314
+ }
+ ]
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "type": "Release",
+ "value": "https://api.github.com/repos/dgarijo/Widoco/releases/150115431",
+ "tag": "v1.4.23",
+ "name": "WiDOCO 1.4.23: Bugfixes",
+ "author": {
+ "name": "dgarijo",
+ "type": "User"
+ },
+ "description": "Quick release for three minor bugs detected by @SirkoS:\r\n\r\n- Error in dark mode table\r\n- Markdown support when using the `uniteSections` flag\r\n- Content override when HTML sections were set up by hand.\r\n\r\n## What's Changed\r\n* bugfixes by @SirkoS in https://github.com/dgarijo/Widoco/pull/690\r\n* Merging latest master/develop by @dgarijo in https://github.com/dgarijo/Widoco/pull/691\r\n\r\n## New Contributors\r\n* @SirkoS made their first contribution in https://github.com/dgarijo/Widoco/pull/690\r\n\r\n**Full Changelog**: https://github.com/dgarijo/Widoco/compare/v1.4.22...v1.4.23",
+ "tarball_url": "https://api.github.com/repos/dgarijo/Widoco/tarball/v1.4.23",
+ "zipball_url": "https://api.github.com/repos/dgarijo/Widoco/zipball/v1.4.23",
+ "html_url": "https://github.com/dgarijo/Widoco/releases/tag/v1.4.23",
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/150115431",
+ "release_id": 150115431,
+ "date_created": "2024-04-07T16:31:54Z",
+ "date_published": "2024-04-07T16:37:55Z",
+ "assets": [
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/160798750",
+ "name": "widoco-1.4.23-jar-with-dependencies_JDK-11.jar",
+ "content_size": 40858475,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.4.23/widoco-1.4.23-jar-with-dependencies_JDK-11.jar",
+ "encoding_format": "application/x-java-archive",
+ "upload_date": "2024-04-07T16:36:21Z",
+ "download_count": 170
+ },
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/160798754",
+ "name": "widoco-1.4.23-jar-with-dependencies_JDK-14.jar",
+ "content_size": 40861411,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.4.23/widoco-1.4.23-jar-with-dependencies_JDK-14.jar",
+ "encoding_format": "application/x-java-archive",
+ "upload_date": "2024-04-07T16:36:30Z",
+ "download_count": 43
+ },
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/160798761",
+ "name": "widoco-1.4.23-jar-with-dependencies_JDK-17.jar",
+ "content_size": 40861484,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.4.23/widoco-1.4.23-jar-with-dependencies_JDK-17.jar",
+ "encoding_format": "application/x-java-archive",
+ "upload_date": "2024-04-07T16:36:37Z",
+ "download_count": 161
+ }
+ ]
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "type": "Release",
+ "value": "https://api.github.com/repos/dgarijo/Widoco/releases/149821484",
+ "tag": "v1.4.22",
+ "name": "WiDOCO 1.4.22: Dark mode and flexibility for loading references",
+ "author": {
+ "name": "dgarijo",
+ "type": "User"
+ },
+ "description": "This version of Widoco adds a great dark mode by @vChavezB and some fixes to allow pointing to the code repo where a vocabulary is maintained, removing obsolete html annotations and allowing to load your own reference section from a config file. \r\n\r\n## What's Changed\r\n* Run widoco with unprivileged user in container by @janvlug in https://github.com/dgarijo/Widoco/pull/669\r\n* Fixed SWRL Toc by @vChavezB in https://github.com/dgarijo/Widoco/pull/670\r\n* Bump docker/metadata-action from 5.4.0 to 5.5.0 by @dependabot in https://github.com/dgarijo/Widoco/pull/673\r\n* Added darkmode by @vChavezB in https://github.com/dgarijo/Widoco/pull/672\r\n* Parsing external owl entities (direct/indirect imports) + individual facts with external owl entites by @vChavezB in https://github.com/dgarijo/Widoco/pull/668\r\n* Bump docker/metadata-action from 5.5.0 to 5.5.1 by @dependabot in https://github.com/dgarijo/Widoco/pull/678\r\n* feat: Considering owl:unionOf for rdfs:domain and rdfs:range by @Murloc6 in https://github.com/dgarijo/Widoco/pull/679\r\n* Allow loading local ontologies for imports by @vChavezB in https://github.com/dgarijo/Widoco/pull/674\r\n* Fix #618 by @dgarijo in https://github.com/dgarijo/Widoco/pull/685\r\n* fix(extraction): Do not force lower-case if URI do not have label by @Murloc6 in https://github.com/dgarijo/Widoco/pull/686\r\n* Develop by @dgarijo in https://github.com/dgarijo/Widoco/pull/687\r\n* version update by @dgarijo in https://github.com/dgarijo/Widoco/pull/689\r\n\r\n## New Contributors\r\n* @Murloc6 made their first contribution in https://github.com/dgarijo/Widoco/pull/679\r\n\r\n**Full Changelog**: https://github.com/dgarijo/Widoco/compare/v1.4.21...v1.4.22",
+ "tarball_url": "https://api.github.com/repos/dgarijo/Widoco/tarball/v1.4.22",
+ "zipball_url": "https://api.github.com/repos/dgarijo/Widoco/zipball/v1.4.22",
+ "html_url": "https://github.com/dgarijo/Widoco/releases/tag/v1.4.22",
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/149821484",
+ "release_id": 149821484,
+ "date_created": "2024-04-04T17:40:30Z",
+ "date_published": "2024-04-04T17:48:33Z",
+ "assets": [
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/160331115",
+ "name": "widoco-1.4.22-jar-with-dependencies_JDK-11.jar",
+ "content_size": 40858426,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.4.22/widoco-1.4.22-jar-with-dependencies_JDK-11.jar",
+ "encoding_format": "application/x-java-archive",
+ "upload_date": "2024-04-04T17:56:01Z",
+ "download_count": 11
+ },
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/160331165",
+ "name": "widoco-1.4.22-jar-with-dependencies_JDK-14.jar",
+ "content_size": 40861365,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.4.22/widoco-1.4.22-jar-with-dependencies_JDK-14.jar",
+ "encoding_format": "application/x-java-archive",
+ "upload_date": "2024-04-04T17:56:15Z",
+ "download_count": 7
+ },
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/160331135",
+ "name": "widoco-1.4.22-jar-with-dependencies_JDK-17.jar",
+ "content_size": 40861440,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.4.22/widoco-1.4.22-jar-with-dependencies_JDK-17.jar",
+ "encoding_format": "application/x-java-archive",
+ "upload_date": "2024-04-04T17:56:08Z",
+ "download_count": 1491
+ }
+ ]
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "type": "Release",
+ "value": "https://api.github.com/repos/dgarijo/Widoco/releases/135444538",
+ "tag": "v1.4.21",
+ "name": "WiDOCO 1.4.21: Better support for rules and minor fixes",
+ "author": {
+ "name": "dgarijo",
+ "type": "User"
+ },
+ "description": "This release introduces the following changes:\r\n* Better support for SWRL rules thanks to @vChavezB \r\n* Fix #658 problems displaying custom introduction sections\r\n* Improved documentation and README\r\n* Now the affiliation parenthesis and commas are more consistent for authors (#572)\r\n* Fixed a problem when using the flag `-confFile` making disappear the namespace declaration section\r\n\r\n## New Contributors\r\n* @MikiDi made their first contribution in https://github.com/dgarijo/Widoco/pull/644\r\n* @vChavezB made their first contribution in https://github.com/dgarijo/Widoco/pull/653\r\n\r\n**Full Changelog**: https://github.com/dgarijo/Widoco/compare/v1.4.20...v1.4.21",
+ "tarball_url": "https://api.github.com/repos/dgarijo/Widoco/tarball/v1.4.21",
+ "zipball_url": "https://api.github.com/repos/dgarijo/Widoco/zipball/v1.4.21",
+ "html_url": "https://github.com/dgarijo/Widoco/releases/tag/v1.4.21",
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/135444538",
+ "release_id": 135444538,
+ "date_created": "2023-12-31T19:35:36Z",
+ "date_published": "2023-12-31T19:46:00Z",
+ "assets": [
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/143178266",
+ "name": "widoco-1.4.21-jar-with-dependencies_JDK-11.jar",
+ "content_size": 40397382,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.4.21/widoco-1.4.21-jar-with-dependencies_JDK-11.jar",
+ "encoding_format": "application/x-java-archive",
+ "upload_date": "2023-12-31T19:50:32Z",
+ "download_count": 495
+ },
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/143178261",
+ "name": "widoco-1.4.21-jar-with-dependencies_JDK-14.jar",
+ "content_size": 40400218,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.4.21/widoco-1.4.21-jar-with-dependencies_JDK-14.jar",
+ "encoding_format": "application/x-java-archive",
+ "upload_date": "2023-12-31T19:50:24Z",
+ "download_count": 41
+ },
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/143178254",
+ "name": "widoco-1.4.21-jar-with-dependencies_JDK-17.jar",
+ "content_size": 40400273,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.4.21/widoco-1.4.21-jar-with-dependencies_JDK-17.jar",
+ "encoding_format": "application/x-java-archive",
+ "upload_date": "2023-12-31T19:50:19Z",
+ "download_count": 1112
+ }
+ ]
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "type": "Release",
+ "value": "https://api.github.com/repos/dgarijo/Widoco/releases/119064737",
+ "tag": "v1.4.20",
+ "name": "WiDOCO 1.4.20: Increased Metadata support and many fixes",
+ "author": {
+ "name": "dgarijo",
+ "type": "User"
+ },
+ "description": "This new release addresses the issues listed below, and it updates the metadata guides:\r\n\r\n- Name is expanded and documented in table and guides (614).\r\n- DateRelease and dateModified overlap (fixed) (614)\r\n- Support for http and https in schema.org properties (before only https). This flexibility is to address potential mismatch properties.\r\n- Fixed Licensius integration issues (607)\r\n- Placed JSON-LD before than HTML in htaccess so we can import ontologies in the JSON-LD playground (i.e., from browser)\r\n- Support for extended ontologies added from the ontology metadata (with voaf:extends) (614)\r\n- Separated abstract from description. Used ontology description for the description section (544) \r\n- Imported ontologies have been added to the documentation table (614)\r\n- skos:editorialNote (592)\r\n- editorialNote is not added in guide (now added)\r\n- Added preferred metadata terms in checklist, and added examples for each (242)\r\n- Added dce and dct as elements and terms for dublin core.\r\n- Added support for:\r\n - pav:version (http://purl.org/pav/version) (versionInfo)\r\n - http://purl.org/dc/terms/hasVersion (versionInfo)\r\n - http://purl.org/pav/lastUpdatedOn (modified)\r\n - http://www.w3.org/2007/05/powder-s#describedBy (source)\r\n- Added support for logos (and guidelines to add them #497).\r\n- Fixed error: when saving properties, there are some nulls in publisher.\r\n- Added support for mod:status.\r\n- Added support for describedby (source)\r\n- Add editorialNote in metadata guide.\r\n- Add source in Widoco (ontology level). And load/save in properties. And form. And HTML.\r\n- Add seeAlso in Widoco (ontology level). And load/save in properties. And form. And HTML.\r\n- Explain how to run from code (#574)\r\n- add support for schema:creativeWorkStatus.\r\n- Sort NS table (#610)\r\n- Checked equivalentClass works correctly.\r\n- JFileChooser in GUI (602) (except a minor one for browsing folders)\r\n- Fix #605 about the main gui when saving withot the --rewriteAll option.\r\n- If multiple labels (skos:prefLabel, rdfs:label) are provided, just pick just one. #556 \r\n- Added funding and funder information \r\n- Intro section (new property) (#544, #612) \r\n- Intro: load it from properties file if available.\r\n- Supported serializations (new prop) \r\n- Fixed problem with funders.\r\n- Added tests.\r\n- Separate namespace declaration in a different html (#609)\r\n- isDefinedBy in data properties does not work well (#588)\r\n- Bug: properties extending annotation properties do not work (530) \r\n- Rules: add a new section + doc (#591)\r\n",
+ "tarball_url": "https://api.github.com/repos/dgarijo/Widoco/tarball/v1.4.20",
+ "zipball_url": "https://api.github.com/repos/dgarijo/Widoco/zipball/v1.4.20",
+ "html_url": "https://github.com/dgarijo/Widoco/releases/tag/v1.4.20",
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/119064737",
+ "release_id": 119064737,
+ "date_created": "2023-08-29T12:04:47Z",
+ "date_published": "2023-08-29T12:17:07Z",
+ "assets": [
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/123609987",
+ "name": "widoco-1.4.20-jar-with-dependencies_JDK-11.jar",
+ "content_size": 41013250,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.4.20/widoco-1.4.20-jar-with-dependencies_JDK-11.jar",
+ "encoding_format": "application/x-java-archive",
+ "upload_date": "2023-08-29T12:24:28Z",
+ "download_count": 186
+ },
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/123610019",
+ "name": "widoco-1.4.20-jar-with-dependencies_JDK-14.jar",
+ "content_size": 41016047,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.4.20/widoco-1.4.20-jar-with-dependencies_JDK-14.jar",
+ "encoding_format": "application/x-java-archive",
+ "upload_date": "2023-08-29T12:24:33Z",
+ "download_count": 61
+ },
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/123610684",
+ "name": "widoco-1.4.20-jar-with-dependencies_JDK-17.jar",
+ "content_size": 41016100,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.4.20/widoco-1.4.20-jar-with-dependencies_JDK-17.jar",
+ "encoding_format": "application/x-java-archive",
+ "upload_date": "2023-08-29T12:27:11Z",
+ "download_count": 1662
+ }
+ ]
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "type": "Release",
+ "value": "https://api.github.com/repos/dgarijo/Widoco/releases/105715227",
+ "tag": "v1.4.19",
+ "name": "WiDOCO 1.4.19: Small fixes on redirections and version",
+ "author": {
+ "name": "dgarijo",
+ "type": "User"
+ },
+ "description": "This new release of WIDOCO:\r\n- Addresses a problem with the name of the files for the rdf/xml serialization (at times `.owl`, at time `.rdf`). Now it's always `.owl`.\r\n- Adds a small icon (RDF) to the documentation pages\r\n- Adds the version of the WIDOCO JAR explicitly with the command `--version`\r\n- OOPS! tests are now static, not requiring the service to work for the build to pass.\r\n\r\n\r\n**Full Changelog**: https://github.com/dgarijo/Widoco/compare/v1.4.18...v1.4.19",
+ "tarball_url": "https://api.github.com/repos/dgarijo/Widoco/tarball/v1.4.19",
+ "zipball_url": "https://api.github.com/repos/dgarijo/Widoco/zipball/v1.4.19",
+ "html_url": "https://github.com/dgarijo/Widoco/releases/tag/v1.4.19",
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/105715227",
+ "release_id": 105715227,
+ "date_created": "2023-06-01T16:30:11Z",
+ "date_published": "2023-06-01T16:37:43Z",
+ "assets": [
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/110831279",
+ "name": "widoco-1.4.19-jar-with-dependencies_JDK-11.jar",
+ "content_size": 41000835,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.4.19/widoco-1.4.19-jar-with-dependencies_JDK-11.jar",
+ "encoding_format": "application/x-java-archive",
+ "upload_date": "2023-06-01T16:42:00Z",
+ "download_count": 252
+ },
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/110831269",
+ "name": "widoco-1.4.19-jar-with-dependencies_JDK-14.jar",
+ "content_size": 41003361,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.4.19/widoco-1.4.19-jar-with-dependencies_JDK-14.jar",
+ "encoding_format": "application/x-java-archive",
+ "upload_date": "2023-06-01T16:41:55Z",
+ "download_count": 294
+ },
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/110831290",
+ "name": "widoco-1.4.19-jar-with-dependencies_JDK-17.jar",
+ "content_size": 41003414,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.4.19/widoco-1.4.19-jar-with-dependencies_JDK-17.jar",
+ "encoding_format": "application/x-java-archive",
+ "upload_date": "2023-06-01T16:42:08Z",
+ "download_count": 497
+ }
+ ]
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "type": "Release",
+ "value": "https://api.github.com/repos/dgarijo/Widoco/releases/100962807",
+ "tag": "v1.4.18",
+ "name": "WIDOCO 1.4.18: Fix javascript issues",
+ "author": {
+ "name": "dgarijo",
+ "type": "User"
+ },
+ "description": "This version fixes:\r\n- Minor problems in the generated HTML.\r\n- Minor problems with mixed content in the javascript\r\n- Clarifies documentation on which fields Widoco adopts.\r\n\r\n## New Contributors\r\nThanks to: \r\n* @telecsur made their first contribution in https://github.com/dgarijo/Widoco/pull/517\r\n* @janvlug made their first contribution in https://github.com/dgarijo/Widoco/pull/538\r\n* @HanKruiger made their first contribution in https://github.com/dgarijo/Widoco/pull/540\r\n* @JohannesLipp made their first contribution in https://github.com/dgarijo/Widoco/pull/547\r\n* @ioggstream made their first contribution in https://github.com/dgarijo/Widoco/pull/561\r\n\r\n**Full Changelog**: https://github.com/dgarijo/Widoco/compare/v1.4.17...v1.4.18",
+ "tarball_url": "https://api.github.com/repos/dgarijo/Widoco/tarball/v1.4.18",
+ "zipball_url": "https://api.github.com/repos/dgarijo/Widoco/zipball/v1.4.18",
+ "html_url": "https://github.com/dgarijo/Widoco/releases/tag/v1.4.18",
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/100962807",
+ "release_id": 100962807,
+ "date_created": "2023-04-21T15:46:00Z",
+ "date_published": "2023-04-26T14:08:45Z",
+ "assets": [
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/105401521",
+ "name": "widoco-1.4.18-jar-with-dependencies-java-11.jar",
+ "content_size": 40526768,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.4.18/widoco-1.4.18-jar-with-dependencies-java-11.jar",
+ "encoding_format": "application/x-java-archive",
+ "upload_date": "2023-04-26T14:28:45Z",
+ "download_count": 85
+ },
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/105563934",
+ "name": "widoco-1.4.18-jar-with-dependencies_JDK-14.jar",
+ "content_size": 40529324,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.4.18/widoco-1.4.18-jar-with-dependencies_JDK-14.jar",
+ "encoding_format": "application/x-java-archive",
+ "upload_date": "2023-04-27T09:11:42Z",
+ "download_count": 28
+ },
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/105563920",
+ "name": "widoco-1.4.18-jar-with-dependencies_JDK-17.jar",
+ "content_size": 40529380,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.4.18/widoco-1.4.18-jar-with-dependencies_JDK-17.jar",
+ "encoding_format": "application/x-java-archive",
+ "upload_date": "2023-04-27T09:11:32Z",
+ "download_count": 66
+ }
+ ]
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "type": "Release",
+ "value": "https://api.github.com/repos/dgarijo/Widoco/releases/64724790",
+ "tag": "v1.4.17",
+ "name": "WIDOCO 1.4.17: Update OOPS! Web service. GitHub actions",
+ "author": {
+ "name": "dgarijo",
+ "type": "User"
+ },
+ "description": "This version fixes OOPS! (newest URL) and sets up GitHub actions so users with different versions of Java can use Widoco.\r\n\r\n## What's Changed\r\n* update readme and pom with proper versions by @dgarijo in https://github.com/dgarijo/Widoco/pull/502\r\n* switch import fonts to always use https scheme by @renefritze in https://github.com/dgarijo/Widoco/pull/509\r\n* [ci] setup gh actions + dependabot by @renefritze in https://github.com/dgarijo/Widoco/pull/507\r\n* Github actions set up by @renefritze by @dgarijo in https://github.com/dgarijo/Widoco/pull/512\r\n\r\n## New Contributors\r\n* @renefritze made their first contribution in https://github.com/dgarijo/Widoco/pull/509\r\n\r\n**Full Changelog**: https://github.com/dgarijo/Widoco/compare/v1.4.16...v1.4.17",
+ "tarball_url": "https://api.github.com/repos/dgarijo/Widoco/tarball/v1.4.17",
+ "zipball_url": "https://api.github.com/repos/dgarijo/Widoco/zipball/v1.4.17",
+ "html_url": "https://github.com/dgarijo/Widoco/releases/tag/v1.4.17",
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/64724790",
+ "release_id": 64724790,
+ "date_created": "2022-04-19T10:57:14Z",
+ "date_published": "2022-04-19T11:10:33Z",
+ "assets": [
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/62988840",
+ "name": "java-11-widoco-1.4.17-jar-with-dependencies.jar",
+ "content_size": 40505030,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.4.17/java-11-widoco-1.4.17-jar-with-dependencies.jar",
+ "encoding_format": "application/x-java-archive",
+ "upload_date": "2022-04-19T11:07:47Z",
+ "download_count": 2664
+ },
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/62988900",
+ "name": "java-14-widoco-1.4.17-jar-with-dependencies.jar",
+ "content_size": 40507499,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.4.17/java-14-widoco-1.4.17-jar-with-dependencies.jar",
+ "encoding_format": "application/x-java-archive",
+ "upload_date": "2022-04-19T11:08:34Z",
+ "download_count": 214
+ },
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/62988955",
+ "name": "java-17-widoco-1.4.17-jar-with-dependencies.jar",
+ "content_size": 40507555,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.4.17/java-17-widoco-1.4.17-jar-with-dependencies.jar",
+ "encoding_format": "application/x-java-archive",
+ "upload_date": "2022-04-19T11:09:22Z",
+ "download_count": 1624
+ }
+ ]
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "type": "Release",
+ "value": "https://api.github.com/repos/dgarijo/Widoco/releases/62144298",
+ "tag": "v1.4.16",
+ "name": "WIDOCO 1.4.16: Support for German and Dutch",
+ "author": {
+ "name": "dgarijo",
+ "type": "User"
+ },
+ "description": "This version of WIDOCO includes support for German and Dutch (thanks to Flores Bakker), extraction of metadata from local authors and organizations URIs (if you describe authors and their organizations in your ontology, WIDOCO will automatically detect it) and additional support for adding images in your documentation.\r\n \r\n## What's Changed\r\n* Fix/duplicate namespace declarations by @pmcb55 in https://github.com/dgarijo/Widoco/pull/471\r\n* Only show legend entries if relevant by @pmcb55 in https://github.com/dgarijo/Widoco/pull/472\r\n* Dockerize project by @ccamel in https://github.com/dgarijo/Widoco/pull/479\r\n* Support for adding images with GUI by @dgarijo in https://github.com/dgarijo/Widoco/pull/492\r\n* Fix #262 by @dgarijo in https://github.com/dgarijo/Widoco/pull/493\r\n* German translation by @JohnnyMoonlight in https://github.com/dgarijo/Widoco/pull/494\r\n* Support for it and de by @dgarijo in https://github.com/dgarijo/Widoco/pull/495\r\n* Fix #498 by @engapa in https://github.com/dgarijo/Widoco/pull/500\r\n\r\n## New Contributors\r\n* @ccamel made their first contribution in https://github.com/dgarijo/Widoco/pull/479\r\n* @JohnnyMoonlight made their first contribution in https://github.com/dgarijo/Widoco/pull/494\r\n* @engapa made their first contribution in https://github.com/dgarijo/Widoco/pull/500\r\n\r\n**Full Changelog**: https://github.com/dgarijo/Widoco/compare/v1.4.15...v1.4.16",
+ "tarball_url": "https://api.github.com/repos/dgarijo/Widoco/tarball/v1.4.16",
+ "zipball_url": "https://api.github.com/repos/dgarijo/Widoco/zipball/v1.4.16",
+ "html_url": "https://github.com/dgarijo/Widoco/releases/tag/v1.4.16",
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/62144298",
+ "release_id": 62144298,
+ "date_created": "2022-03-17T21:38:26Z",
+ "date_published": "2022-03-17T22:13:15Z",
+ "assets": [
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/59812154",
+ "name": "widoco-1.4.16-jar-with-dependencies.jar",
+ "content_size": 40504976,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.4.16/widoco-1.4.16-jar-with-dependencies.jar",
+ "encoding_format": "application/x-java-archive",
+ "upload_date": "2022-03-17T22:11:40Z",
+ "download_count": 495
+ }
+ ]
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "type": "Release",
+ "value": "https://api.github.com/repos/dgarijo/Widoco/releases/45273016",
+ "tag": "v1.4.15",
+ "name": "WIDOCO 1.4.15: Major bug fixes",
+ "author": {
+ "name": "dgarijo",
+ "type": "User"
+ },
+ "description": "This release addresses the following problems:\r\n- fixed return types for jsonld and rdf (#454)\r\n- fixed small caps issue for case sensitivity files in Unix (#455)\r\n- fix wrong metadata in prefix (#453) \r\n- Incorporated previous prereleases fixes.\r\n\r\nAll bugs fixed in this release can be found [here](https://github.com/dgarijo/Widoco/milestone/2?closed=1)",
+ "tarball_url": "https://api.github.com/repos/dgarijo/Widoco/tarball/v1.4.15",
+ "zipball_url": "https://api.github.com/repos/dgarijo/Widoco/zipball/v1.4.15",
+ "html_url": "https://github.com/dgarijo/Widoco/releases/tag/v1.4.15",
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/45273016",
+ "release_id": 45273016,
+ "date_created": "2021-06-26T11:29:58Z",
+ "date_published": "2021-06-26T11:39:10Z",
+ "assets": [
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/40200251",
+ "name": "widoco-1.4.15-jar-with-dependencies.jar",
+ "content_size": 39590551,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.4.15/widoco-1.4.15-jar-with-dependencies.jar",
+ "encoding_format": "application/x-java-archive",
+ "upload_date": "2021-07-13T08:45:02Z",
+ "download_count": 1787
+ }
+ ]
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "type": "Release",
+ "value": "https://api.github.com/repos/dgarijo/Widoco/releases/35216684",
+ "tag": "v1.4.15_1",
+ "name": "WIDOCO 1.4.15_1 (pre-release): Namespace prefixes fixes and WebVowl update",
+ "author": {
+ "name": "dgarijo",
+ "type": "User"
+ },
+ "description": "This pre-release fixes issues regarding namespace prefixes (now they will be rad directly from the ontology instead of using LODE); it updates WebVOWL to the latest version (which let's you change the settings in your visualization)\r\n\r\nMore information on the addressed issues can be seen in: https://github.com/dgarijo/Widoco/milestone/2?closed=1",
+ "tarball_url": "https://api.github.com/repos/dgarijo/Widoco/tarball/v1.4.15_1",
+ "zipball_url": "https://api.github.com/repos/dgarijo/Widoco/zipball/v1.4.15_1",
+ "html_url": "https://github.com/dgarijo/Widoco/releases/tag/v1.4.15_1",
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/35216684",
+ "release_id": 35216684,
+ "date_created": "2020-12-14T04:07:24Z",
+ "date_published": "2020-12-14T04:18:58Z",
+ "assets": [
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/29515905",
+ "name": "widoco-1.4.15-jar-with-dependencies.jar",
+ "content_size": 40032888,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.4.15_1/widoco-1.4.15-jar-with-dependencies.jar",
+ "encoding_format": "application/x-java-archive",
+ "upload_date": "2020-12-14T04:14:32Z",
+ "download_count": 1935
+ }
+ ]
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "type": "Release",
+ "value": "https://api.github.com/repos/dgarijo/Widoco/releases/28719056",
+ "tag": "v1.4.15_0",
+ "name": "WIDOCO 1.4.15 (pre-release): Bug fixes and FAQ",
+ "author": {
+ "name": "dgarijo",
+ "type": "User"
+ },
+ "description": "(This is a pre-relase. For more information about the next release see https://github.com/dgarijo/Widoco/milestone/2)\r\nThis pre-release of WIDOCO fixes some bugs and adds a FAQ for common questions posted as issues. In particular, this solves:\r\n- External links will now open in another window instead of appearing dotted.\r\n- Fixed issue that made the index not be generated when including all the sections in a single html.\r\n- Added a FAQ explaining why by default the documentation cannot be seen locally.\r\n- Added a FAQ regarding how to use markdown on class definitions.\r\n- Error with a missing path when the CLI was run, which resulted in the /sections folder not being generated.\r\n- Fixed a typo in acknowledgments \r\n- Fixed typo in schema.org snippet.\r\n- Fixed a few URLs from http -> https",
+ "tarball_url": "https://api.github.com/repos/dgarijo/Widoco/tarball/v1.4.15_0",
+ "zipball_url": "https://api.github.com/repos/dgarijo/Widoco/zipball/v1.4.15_0",
+ "html_url": "https://github.com/dgarijo/Widoco/releases/tag/v1.4.15_0",
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/28719056",
+ "release_id": 28719056,
+ "date_created": "2020-07-19T22:55:20Z",
+ "date_published": "2020-07-19T23:11:42Z",
+ "assets": [
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/23035620",
+ "name": "widoco-1.4.15-jar-with-dependencies.jar",
+ "content_size": 39372460,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.4.15_0/widoco-1.4.15-jar-with-dependencies.jar",
+ "encoding_format": "application/octet-stream",
+ "upload_date": "2020-07-19T23:11:10Z",
+ "download_count": 83
+ }
+ ]
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "type": "Release",
+ "value": "https://api.github.com/repos/dgarijo/Widoco/releases/26848705",
+ "tag": "v1.4.14",
+ "name": "WIDOCO 1.4.14: Multiple updates and bug fixes",
+ "author": {
+ "name": "dgarijo",
+ "type": "User"
+ },
+ "description": "This version of WIDOCO updates some of the web services used:\r\n * Updated a comprehensive --help command.\r\n * Reviewed metadata properties (added incompatibleWith)\r\n * Removed JENA dependencies (now only OWLAPI is used)\r\n * Unified logging (now only SLF4J is used).\r\n * Fixed pointers in WIDOCO guidelines.\r\n * Updated OOPS! webservice call.\r\n * Fixed invalid ttl file when generating the provenance of a documentation.",
+ "tarball_url": "https://api.github.com/repos/dgarijo/Widoco/tarball/v1.4.14",
+ "zipball_url": "https://api.github.com/repos/dgarijo/Widoco/zipball/v1.4.14",
+ "html_url": "https://github.com/dgarijo/Widoco/releases/tag/v1.4.14",
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/26848705",
+ "release_id": 26848705,
+ "date_created": "2020-05-25T03:55:27Z",
+ "date_published": "2020-05-25T04:02:40Z",
+ "assets": [
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/21025783",
+ "name": "widoco-1.4.14-jar-with-dependencies.jar",
+ "content_size": 39384905,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.4.14/widoco-1.4.14-jar-with-dependencies.jar",
+ "encoding_format": "application/octet-stream",
+ "upload_date": "2020-05-25T04:02:00Z",
+ "download_count": 2175
+ }
+ ]
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "type": "Release",
+ "value": "https://api.github.com/repos/dgarijo/Widoco/releases/22775882",
+ "tag": "v1.4.13",
+ "name": "WIDOCO 1.4.13: Linking evaluation in documentation",
+ "author": {
+ "name": "dgarijo",
+ "type": "User"
+ },
+ "description": "This version of WIDOCO removes a few of unused dependencies and, if desired, shows a link to the OOPS! evaluation generated in the documentation. Before, this link had to be added manually. \r\n\r\nThanks to @HaydarAk for the suggestions on how to show it in the main page.\r\n\r\n",
+ "tarball_url": "https://api.github.com/repos/dgarijo/Widoco/tarball/v1.4.13",
+ "zipball_url": "https://api.github.com/repos/dgarijo/Widoco/zipball/v1.4.13",
+ "html_url": "https://github.com/dgarijo/Widoco/releases/tag/v1.4.13",
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/22775882",
+ "release_id": 22775882,
+ "date_created": "2020-01-12T22:19:15Z",
+ "date_published": "2020-01-12T22:25:11Z",
+ "assets": [
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/17295468",
+ "name": "widoco-1.4.13-jar-with-dependencies.jar",
+ "content_size": 41627773,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.4.13/widoco-1.4.13-jar-with-dependencies.jar",
+ "encoding_format": "application/octet-stream",
+ "upload_date": "2020-01-12T22:22:32Z",
+ "download_count": 20613
+ }
+ ]
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "type": "Release",
+ "value": "https://api.github.com/repos/dgarijo/Widoco/releases/21002818",
+ "tag": "v1.4.12",
+ "name": "WIDOCO 1.4.12: Big fixes and new configurations",
+ "author": {
+ "name": "dgarijo",
+ "type": "User"
+ },
+ "description": "This version of WIDOCO addresses the following issues (thanks to @pmcb55 for his contributions!!)\r\n * #354: The page would display centered in the visualization instead of the element loaded.\r\n * #362: Upgraded to latest version of OWL API\r\n * #361: Allowed adding a \"-uniteAllSections\" flag that will print all sections on the same document. Hence, dynamic loading will be removed.\r\n * Tidying up PROV rendering.\r\n * Small typo fixes in the default html ",
+ "tarball_url": "https://api.github.com/repos/dgarijo/Widoco/tarball/v1.4.12",
+ "zipball_url": "https://api.github.com/repos/dgarijo/Widoco/zipball/v1.4.12",
+ "html_url": "https://github.com/dgarijo/Widoco/releases/tag/v1.4.12",
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/21002818",
+ "release_id": 21002818,
+ "date_created": "2019-10-27T03:41:36Z",
+ "date_published": "2019-10-27T03:56:44Z",
+ "assets": [
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/15756430",
+ "name": "widoco-1.4.12-jar-with-dependencies.jar",
+ "content_size": 41626814,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.4.12/widoco-1.4.12-jar-with-dependencies.jar",
+ "encoding_format": "application/octet-stream",
+ "upload_date": "2019-10-27T03:56:00Z",
+ "download_count": 198
+ }
+ ]
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "type": "Release",
+ "value": "https://api.github.com/repos/dgarijo/Widoco/releases/17596778",
+ "tag": "v1.4.11",
+ "name": "WIDOCO 1.4.11: Better logging and bug fixes",
+ "author": {
+ "name": "dgarijo",
+ "type": "User"
+ },
+ "description": "This version of WIDOCO integrates the pull requests from @seralf to improve logging and simplify dependencies.\r\nIn addition, it addresses the following issues:\r\n * #344: Now double \"#\" errors have been removed from changelog.\r\n * #350: Fixed an issue in which the references would overwrite the main section ",
+ "tarball_url": "https://api.github.com/repos/dgarijo/Widoco/tarball/v1.4.11",
+ "zipball_url": "https://api.github.com/repos/dgarijo/Widoco/zipball/v1.4.11",
+ "html_url": "https://github.com/dgarijo/Widoco/releases/tag/v1.4.11",
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/17596778",
+ "release_id": 17596778,
+ "date_created": "2019-05-26T23:51:42Z",
+ "date_published": "2019-05-26T23:55:13Z",
+ "assets": [
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/12880234",
+ "name": "widoco-1.4.11-jar-with-dependencies.jar",
+ "content_size": 41614957,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.4.11/widoco-1.4.11-jar-with-dependencies.jar",
+ "encoding_format": "application/octet-stream",
+ "upload_date": "2019-05-26T23:53:22Z",
+ "download_count": 413
+ }
+ ]
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "type": "Release",
+ "value": "https://api.github.com/repos/dgarijo/Widoco/releases/17029600",
+ "tag": "v1.4.10",
+ "name": "WIDOCO 1.4.10: Pull requests, updates and bug fixes",
+ "author": {
+ "name": "dgarijo",
+ "type": "User"
+ },
+ "description": "This version of WIDOCO incorporates pull requests from @seralf and @jgeluk (updating to the latest version of WebVowl) and fixes the following issues:\r\n* #333 Now if there is an error in one of the imports the application will continue running instead of failing.\r\n* #217 Now the changelog can be expanded and collapsed, which makes it easier to navigate\r\n* #313 A link will be added in the index page if provided in the config file (using contextURI=\"YOUR URL\")\r\n* #339 Now WIDOCO will generate the folders four your htaccess file as well.\r\n\r\nIn addition, now the base encoding is UTF-8.",
+ "tarball_url": "https://api.github.com/repos/dgarijo/Widoco/tarball/v1.4.10",
+ "zipball_url": "https://api.github.com/repos/dgarijo/Widoco/zipball/v1.4.10",
+ "html_url": "https://github.com/dgarijo/Widoco/releases/tag/v1.4.10",
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/17029600",
+ "release_id": 17029600,
+ "date_created": "2019-04-29T00:26:07Z",
+ "date_published": "2019-04-29T00:31:54Z",
+ "assets": [
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/12283704",
+ "name": "widoco-1.4.10-jar-with-dependencies.jar",
+ "content_size": 41613738,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.4.10/widoco-1.4.10-jar-with-dependencies.jar",
+ "encoding_format": "application/octet-stream",
+ "upload_date": "2019-04-29T00:31:17Z",
+ "download_count": 58
+ }
+ ]
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "type": "Release",
+ "value": "https://api.github.com/repos/dgarijo/Widoco/releases/15737608",
+ "tag": "v1.4.9",
+ "name": "WIDOCO 1.4.9: Supporting schema.org",
+ "author": {
+ "name": "dgarijo",
+ "type": "User"
+ },
+ "description": "This version of WIDOCO addresses the following:\r\n* #274: Now WIDOCO will include the domain and range if you use schema:domainIncludes and schema:rangeIncludes to declare it in your object properties or data properties with . Note that WIDOCO still does not support plain rdf:Property.\r\n* Reduced the level of logging",
+ "tarball_url": "https://api.github.com/repos/dgarijo/Widoco/tarball/v1.4.9",
+ "zipball_url": "https://api.github.com/repos/dgarijo/Widoco/zipball/v1.4.9",
+ "html_url": "https://github.com/dgarijo/Widoco/releases/tag/v1.4.9",
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/15737608",
+ "release_id": 15737608,
+ "date_created": "2019-02-24T00:23:18Z",
+ "date_published": "2019-02-24T00:30:33Z",
+ "assets": [
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/11213134",
+ "name": "widoco-1.4.9-jar-with-dependencies.jar",
+ "content_size": 41691275,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.4.9/widoco-1.4.9-jar-with-dependencies.jar",
+ "encoding_format": "application/octet-stream",
+ "upload_date": "2019-02-24T00:28:43Z",
+ "download_count": 146
+ }
+ ]
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "type": "Release",
+ "value": "https://api.github.com/repos/dgarijo/Widoco/releases/15474861",
+ "tag": "v1.4.8",
+ "name": "WIDOCO 1.4.8: Logging and automated citation",
+ "author": {
+ "name": "dgarijo",
+ "type": "User"
+ },
+ "description": "This release of WIDOCO includes:\r\n* Fixes from @jgeluk to do proper logging.\r\n* Fix #264: now an automated citation will be produced for your ontology by default (if the appropriate metadata is there: creators, revision, and URL (this version)\r\n* JenaCatalogIRIMapper now supports Jena's ont-policy.rdf file to resolve imports with local files\r\n* Fixed #321, now WIDOCO uses a more recent version of OWL API \r\n",
+ "tarball_url": "https://api.github.com/repos/dgarijo/Widoco/tarball/v1.4.8",
+ "zipball_url": "https://api.github.com/repos/dgarijo/Widoco/zipball/v1.4.8",
+ "html_url": "https://github.com/dgarijo/Widoco/releases/tag/v1.4.8",
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/15474861",
+ "release_id": 15474861,
+ "date_created": "2019-02-11T03:30:12Z",
+ "date_published": "2019-02-11T03:38:13Z",
+ "assets": [
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/11023656",
+ "name": "widoco-1.4.8-jar-with-dependencies.jar",
+ "content_size": 41511458,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.4.8/widoco-1.4.8-jar-with-dependencies.jar",
+ "encoding_format": "application/octet-stream",
+ "upload_date": "2019-02-11T03:32:51Z",
+ "download_count": 56
+ }
+ ]
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "type": "Release",
+ "value": "https://api.github.com/repos/dgarijo/Widoco/releases/13944540",
+ "tag": "v1.4.7",
+ "name": "WIDOCO 1.4.7: Dependency and bug fixes",
+ "author": {
+ "name": "dgarijo",
+ "type": "User"
+ },
+ "description": "This release of WIDOCO fixes the following issues:\r\n* #305: Now when you launch WIDOCO without parameters, the console will also show a description of the tool and will explain how to run it. \r\n* #309 Removed the absolute layout dependency that prevented WIDOCO JAR from being run.\r\n* #311 Now the publisher will show on the GUI correctly (before it would only if it had a URL)\r\n* #314: Added an html tag around the external sections so the browser won't complain\r\n* #316 Fixed the generation of the table of contents so \"back to TOC\" wouldn't appear\r\n* #317 Fixed the GUI so the \"Imported ontologies\" label appears as imported and not \"extended\"\r\nIt also removes the restlet dependency, which had a vulnerability and was not used anymore.",
+ "tarball_url": "https://api.github.com/repos/dgarijo/Widoco/tarball/v1.4.7",
+ "zipball_url": "https://api.github.com/repos/dgarijo/Widoco/zipball/v1.4.7",
+ "html_url": "https://github.com/dgarijo/Widoco/releases/tag/v1.4.7",
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/13944540",
+ "release_id": 13944540,
+ "date_created": "2018-11-12T03:27:02Z",
+ "date_published": "2018-11-12T03:42:29Z",
+ "assets": [
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/9650269",
+ "name": "widoco-1.4.7-jar-with-dependencies.jar",
+ "content_size": 41317020,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.4.7/widoco-1.4.7-jar-with-dependencies.jar",
+ "encoding_format": "application/octet-stream",
+ "upload_date": "2018-11-12T03:37:07Z",
+ "download_count": 187
+ }
+ ]
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "type": "Release",
+ "value": "https://api.github.com/repos/dgarijo/Widoco/releases/13019723",
+ "tag": "v1.4.6",
+ "name": "WIDOCO 1.4.6: Tutorial and bug fixes",
+ "author": {
+ "name": "dgarijo",
+ "type": "User"
+ },
+ "description": "This version of WIDOCO includes the following updates:\r\n* Updated the OWL2Vowl library to the latest version (old library was producing errors in some ontologies)\r\n* Included a tutorial (http://dgarijo.github.io/Widoco/doc/tutorial/) describing the GUI step by step and all its functionality\r\n* Added support for selecting ontologies in JSON\r\n* Added contributing guidelines into readme.md\r\n* Fixed namespaces when selecting documentation in several languages\r\n* Now it's possible to edit the rewrite base path in the .htaccess file.\r\n* Fixed a typo in the .htaccess for serving json\r\n* Fixed an encoding problem when showing french labels.\r\n* @kartgk fixed an error with the import closure of ontologies (in case not all imports of imported ontologies wanted to be displayed)\r\n* Added the correct version numbers to pom file.\r\n* Fixed an issue in lang cs that made the table of namespaces not to show correctly",
+ "tarball_url": "https://api.github.com/repos/dgarijo/Widoco/tarball/v1.4.6",
+ "zipball_url": "https://api.github.com/repos/dgarijo/Widoco/zipball/v1.4.6",
+ "html_url": "https://github.com/dgarijo/Widoco/releases/tag/v1.4.6",
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/13019723",
+ "release_id": 13019723,
+ "date_created": "2018-09-21T08:03:14Z",
+ "date_published": "2018-09-21T08:19:40Z",
+ "assets": [
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/8774684",
+ "name": "widoco-1.4.6-jar-with-dependencies.jar",
+ "content_size": 42009392,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.4.6/widoco-1.4.6-jar-with-dependencies.jar",
+ "encoding_format": "application/octet-stream",
+ "upload_date": "2018-09-21T08:08:43Z",
+ "download_count": 552
+ }
+ ]
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "type": "Release",
+ "value": "https://api.github.com/repos/dgarijo/Widoco/releases/12049594",
+ "tag": "v1.4.5",
+ "name": "WIDOCO 1.4.5: Updated WebVowl and support for Czech",
+ "author": {
+ "name": "dgarijo",
+ "type": "User"
+ },
+ "description": "This version of WIDOCO includes the following updates:\r\n* Updated webVowl to version 1.1.2 (latest)\r\n* Allowed disabling showing ontology serializations (-doNotDisplaySerializations flag). Both in the console and GUI\r\n* Added support for Czech localization (also in GUI)\r\n\r\nThanks to Petr Kremen for contributing with the Czech localization",
+ "tarball_url": "https://api.github.com/repos/dgarijo/Widoco/tarball/v1.4.5",
+ "zipball_url": "https://api.github.com/repos/dgarijo/Widoco/zipball/v1.4.5",
+ "html_url": "https://github.com/dgarijo/Widoco/releases/tag/v1.4.5",
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/12049594",
+ "release_id": 12049594,
+ "date_created": "2018-07-23T07:15:14Z",
+ "date_published": "2018-07-23T07:23:58Z",
+ "assets": [
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/7970893",
+ "name": "widoco-1.4.5-jar-with-dependencies.jar",
+ "content_size": 43008958,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.4.5/widoco-1.4.5-jar-with-dependencies.jar",
+ "encoding_format": "application/octet-stream",
+ "upload_date": "2018-07-23T07:22:14Z",
+ "download_count": 220
+ }
+ ]
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "type": "Release",
+ "value": "https://api.github.com/repos/dgarijo/Widoco/releases/11300894",
+ "tag": "v1.4.4",
+ "name": "WIDOCO 1.4.4: Added support for Google Analytics",
+ "author": {
+ "name": "dgarijo",
+ "type": "User"
+ },
+ "description": "This version of WIDOCO allows to add a Google Analytics code to track the traffic received in one of the pages. WIDOCO assumes you have a UA code. You can use it on the GUI, through a config file or in the command line by adding `-analytics yourcode`\r\n\r\n",
+ "tarball_url": "https://api.github.com/repos/dgarijo/Widoco/tarball/v1.4.4",
+ "zipball_url": "https://api.github.com/repos/dgarijo/Widoco/zipball/v1.4.4",
+ "html_url": "https://github.com/dgarijo/Widoco/releases/tag/v1.4.4",
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/11300894",
+ "release_id": 11300894,
+ "date_created": "2018-06-04T01:12:31Z",
+ "date_published": "2018-06-04T01:17:34Z",
+ "assets": [
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/7395302",
+ "name": "widoco-1.4.4-jar-with-dependencies.jar",
+ "content_size": 42927288,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.4.4/widoco-1.4.4-jar-with-dependencies.jar",
+ "encoding_format": "application/octet-stream",
+ "upload_date": "2018-06-04T01:16:33Z",
+ "download_count": 61
+ }
+ ]
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "type": "Release",
+ "value": "https://api.github.com/repos/dgarijo/Widoco/releases/10556972",
+ "tag": "v1.4.3",
+ "name": "WIDOCO 1.4.3: Bug Fixes and support for french",
+ "author": {
+ "name": "dgarijo",
+ "type": "User"
+ },
+ "description": "This release fixes the following issues:\r\n* Added support for french (thanks to Maxime Lefrancois)\r\n* When imported ontologies had characters like \"<\" they were not showing.\r\n* Added JSON-LD serialization for ontologies automatically\r\n* When annotation properties have blank nodes, they will now be ignored instead of failing\r\n* Updated contributor list\r\n* Blank nodes are now absent from the provenance page.\r\n* Fixed default language when restarting WIDOCO.\r\n* Fixed the language form (now french can be selected, and it has the proper icons)\r\n",
+ "tarball_url": "https://api.github.com/repos/dgarijo/Widoco/tarball/v1.4.3",
+ "zipball_url": "https://api.github.com/repos/dgarijo/Widoco/zipball/v1.4.3",
+ "html_url": "https://github.com/dgarijo/Widoco/releases/tag/v1.4.3",
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/10556972",
+ "release_id": 10556972,
+ "date_created": "2018-04-16T05:17:23Z",
+ "date_published": "2018-04-16T05:23:25Z",
+ "assets": [
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/6856085",
+ "name": "widoco-1.4.3-jar-with-dependencies.jar",
+ "content_size": 42926579,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.4.3/widoco-1.4.3-jar-with-dependencies.jar",
+ "encoding_format": "application/octet-stream",
+ "upload_date": "2018-04-16T05:22:47Z",
+ "download_count": 96
+ }
+ ]
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "type": "Release",
+ "value": "https://api.github.com/repos/dgarijo/Widoco/releases/9846306",
+ "tag": "v1.4.2",
+ "name": "WIDOCO 1.4.2: Small fixes and merged contributions",
+ "author": {
+ "name": "dgarijo",
+ "type": "User"
+ },
+ "description": "This release includes the latest contributions to the project:\r\n* Fixes in HTML\r\n* Fixes in how OOPS! is called\r\nAlso, now the license name is the URI if no name is provided (and URI is) instead of \"license name goes here\", and the java encoding has been included in the pom file to prevent UTF-8 errors in compilation.",
+ "tarball_url": "https://api.github.com/repos/dgarijo/Widoco/tarball/v1.4.2",
+ "zipball_url": "https://api.github.com/repos/dgarijo/Widoco/zipball/v1.4.2",
+ "html_url": "https://github.com/dgarijo/Widoco/releases/tag/v1.4.2",
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/9846306",
+ "release_id": 9846306,
+ "date_created": "2018-02-27T02:55:24Z",
+ "date_published": "2018-02-27T03:02:50Z",
+ "assets": [
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/6336641",
+ "name": "widoco-1.4.2-jar-with-dependencies.jar",
+ "content_size": 42914602,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.4.2/widoco-1.4.2-jar-with-dependencies.jar",
+ "encoding_format": "application/octet-stream",
+ "upload_date": "2018-02-27T02:58:44Z",
+ "download_count": 231
+ }
+ ]
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "type": "Release",
+ "value": "https://api.github.com/repos/dgarijo/Widoco/releases/6527938",
+ "tag": "v1.4.1",
+ "name": "WIDOCO 1.4.1: Metadata support and major bug fixes",
+ "author": {
+ "name": "dgarijo",
+ "type": "User"
+ },
+ "description": "WIDOCO now will comply with most of the terms listed in https://w3id.org/widoco/bestPractices. A few of them are still unsopported (e.g., rdfs:seeAlso) and will be added in next releases.\r\n\r\nIn this version, I have added support for all the deprecation terms in LODE, as well as support for skos:prefLabel and better recognition of some of the terms when the range can be a String or a resource.\r\n\r\nChanges:\r\n* Now the examples have a \"pre\" label, so if they include rdf text or json text they show properly.\r\n* A pointer to the guidelines has been added in the interface\r\n* When invoking the program using command line, the default behavior is to load the properties from the ontology. Only if a config file is provided, the properties won't be loaded. There is still a flag, which I have preferred to leave for possible legacy issues.\r\n* Markup is now recognized in all sections\r\n* The diagram is now optional (by default it was always produced in older versions)\r\n* This release also includes a doc folder with guidelines on the metadata properties to use, a benchmark of ontologies used for testing, a gallery with what WIDOCO can accomplish and a summary of projects that have adopted WIDOCO.\r\n\r\nBug fixes: \r\n* Changed the pom file with another plugin for creating the JAR, because some of the RIO parsers were not creating the appropriate serializations of the ontology.\r\n* Added robustness when the changelog was created. If a wrong URI is provided, then the changelog will not be created.\r\n* Changed how the ontology is chosen so there won't be errors in MAC systems.\r\n* Fixed some of the language tags.\r\n* Polished some errors related to the interface.",
+ "tarball_url": "https://api.github.com/repos/dgarijo/Widoco/tarball/v1.4.1",
+ "zipball_url": "https://api.github.com/repos/dgarijo/Widoco/zipball/v1.4.1",
+ "html_url": "https://github.com/dgarijo/Widoco/releases/tag/v1.4.1",
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/6527938",
+ "release_id": 6527938,
+ "date_created": "2017-05-28T10:17:58Z",
+ "date_published": "2017-05-28T18:46:39Z",
+ "assets": [
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/3978689",
+ "name": "widoco-1.4.1-jar-with-dependencies.jar",
+ "content_size": 42914378,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.4.1/widoco-1.4.1-jar-with-dependencies.jar",
+ "encoding_format": "application/octet-stream",
+ "upload_date": "2017-05-28T18:36:04Z",
+ "download_count": 386
+ }
+ ]
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "type": "Release",
+ "value": "https://api.github.com/repos/dgarijo/Widoco/releases/6390471",
+ "tag": "v1.4.0",
+ "name": "WIDOCO 1.4.0: Inline visualization diagrams",
+ "author": {
+ "name": "dgarijo",
+ "type": "User"
+ },
+ "description": "This release removes the external dependency to WebVowl, and creates a visualization that can be seen in the ontology document itself (overview section). In addition, the following changes have been made:\r\n * Migrated the framework to use a recent version of OWL API (hence the size of the JAR)\r\n * Bug fixes in meta data (more to come in future releases)\r\n * Made WIDOCO more robust to errors\r\n * Published a benchmark of ontologies used to test WIDOCO. If you want us to try a new one, just open an issue!\r\n * Published a draft of the best practices for ontology annotation (currently under work). These are meant\r\n * Published a showcase gallery with examples of WIDOCO's features\r\n * Published a report on the known usages by WIDOCO. \r\n\r\n\r\n",
+ "tarball_url": "https://api.github.com/repos/dgarijo/Widoco/tarball/v1.4.0",
+ "zipball_url": "https://api.github.com/repos/dgarijo/Widoco/zipball/v1.4.0",
+ "html_url": "https://github.com/dgarijo/Widoco/releases/tag/v1.4.0",
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/6390471",
+ "release_id": 6390471,
+ "date_created": "2017-05-13T23:26:12Z",
+ "date_published": "2017-05-15T16:53:34Z",
+ "assets": [
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/3879303",
+ "name": "widoco-1.4.0-jar-with-dependencies.jar",
+ "content_size": 41119076,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.4.0/widoco-1.4.0-jar-with-dependencies.jar",
+ "encoding_format": "application/octet-stream",
+ "upload_date": "2017-05-15T16:46:54Z",
+ "download_count": 28
+ }
+ ]
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "type": "Release",
+ "value": "https://api.github.com/repos/dgarijo/Widoco/releases/5951267",
+ "tag": "v1.3.1",
+ "name": "Widoco 1.3.1: Bug fixes and metadata enrichment",
+ "author": {
+ "name": "dgarijo",
+ "type": "User"
+ },
+ "description": "This release fixes several issues and extends LODE to accept more metadata options for defining terms and adding examples.\r\n* Fixed an issue with the changelog that produced an empty bullet when the domain or range of a property was an union or intersection.\r\n* Added tests with different ontologies that help inspecting whether a new change leads to errors or not.\r\n* Extended LODE to accept: prov:definition, vann:example, obo:definition (with the appropriate class name), obo:usageExample, vaem:rationale, skos:definition and terms:source.\r\n* Extended Widoco to accept terms:bibliographicCitation, bibo:status, bibo:doi and owl:backwardsCompatibleWith\r\n* If an error is produced while generating the changelog or the evaluation reports, the section is not generated. Before, an empty section would remain.\r\n* Fixed table of contents not being translated.\r\n* When generating the documentation of an ontology, if it imports another ontology that cannot be imported for any reason, Widoco will ignore the import instead of failing.\r\n* Added the possibility to ignore namedIndividuals when generating the documentation through console.\r\n\r\n",
+ "tarball_url": "https://api.github.com/repos/dgarijo/Widoco/tarball/v1.3.1",
+ "zipball_url": "https://api.github.com/repos/dgarijo/Widoco/zipball/v1.3.1",
+ "html_url": "https://github.com/dgarijo/Widoco/releases/tag/v1.3.1",
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/5951267",
+ "release_id": 5951267,
+ "date_created": "2017-04-03T00:33:14Z",
+ "date_published": "2017-04-03T00:48:48Z",
+ "assets": [
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/3558233",
+ "name": "widoco-1.3.1-jar-with-dependencies.jar",
+ "content_size": 15577816,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.3.1/widoco-1.3.1-jar-with-dependencies.jar",
+ "encoding_format": "application/octet-stream",
+ "upload_date": "2017-04-04T03:00:37Z",
+ "download_count": 34
+ }
+ ]
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "type": "Release",
+ "value": "https://api.github.com/repos/dgarijo/Widoco/releases/5659212",
+ "tag": "v1.3.0",
+ "name": "Widoco 1.3.0: Automated changelog section",
+ "author": {
+ "name": "dgarijo",
+ "type": "User"
+ },
+ "description": "This version of Widoco creates an automated changelog section of your ontology. For all classes, properties and data properties, Widoco will calculate the new changes, additions and deletions and present them in a human-readable manner. In order to benefit from this feature, you just have to annotate your ontology with the URI of the previous version. You can use owl:priorVersion, dc:replaces or prov:wasRevisionOf to annotate your ontology (Widoco will only consider one file to compare against).\r\n\r\nAdditional improvements:\r\n* RDF-a annotations are replaced with JSON-LD snippets. The page is just annotated with schema.org. The rdf-a annotations made it difficult to maintain, and the structured extraction tool provided by Google did not work. With the JSON-LD snippet everything is clearer. The provenance of the page itself is published separately.\r\n* Now it is possible to generate ONLY the cross reference section of an ontology.\r\n* Bug fix that led to errors when opening some ontologies like OBI\r\n\r\nOverview sample of the changelog:\r\n\r\n",
+ "tarball_url": "https://api.github.com/repos/dgarijo/Widoco/tarball/v1.3.0",
+ "zipball_url": "https://api.github.com/repos/dgarijo/Widoco/zipball/v1.3.0",
+ "html_url": "https://github.com/dgarijo/Widoco/releases/tag/v1.3.0",
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/5659212",
+ "release_id": 5659212,
+ "date_created": "2017-03-07T03:06:28Z",
+ "date_published": "2017-03-07T03:17:36Z",
+ "assets": [
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/3335677",
+ "name": "widoco-1.3.0-jar-with-dependencies.jar",
+ "content_size": 15423937,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.3.0/widoco-1.3.0-jar-with-dependencies.jar",
+ "encoding_format": "application/octet-stream",
+ "upload_date": "2017-03-07T03:16:43Z",
+ "download_count": 36
+ }
+ ]
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "type": "Release",
+ "value": "https://api.github.com/repos/dgarijo/Widoco/releases/4659138",
+ "tag": "v1.2.3",
+ "name": "Widoco 1.2.3: [Bug fix] Licensius is optional",
+ "author": {
+ "name": "dgarijo",
+ "type": "User"
+ },
+ "description": "The Licensius web service has been unstable lately. This led to long waits from the server, even with a timer on the request. Therefore retrieving the license using this service is now optional and loading metadata properties from the ontology is faster.\n",
+ "tarball_url": "https://api.github.com/repos/dgarijo/Widoco/tarball/v1.2.3",
+ "zipball_url": "https://api.github.com/repos/dgarijo/Widoco/zipball/v1.2.3",
+ "html_url": "https://github.com/dgarijo/Widoco/releases/tag/v1.2.3",
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/4659138",
+ "release_id": 4659138,
+ "date_created": "2016-11-15T03:11:37Z",
+ "date_published": "2016-11-15T03:17:26Z",
+ "assets": [
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/2645844",
+ "name": "widoco-1.2.3-jar-with-dependencies.jar",
+ "content_size": 15411854,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.2.3/widoco-1.2.3-jar-with-dependencies.jar",
+ "encoding_format": "application/x-java-archive",
+ "upload_date": "2016-11-15T03:16:18Z",
+ "download_count": 91
+ }
+ ]
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "type": "Release",
+ "value": "https://api.github.com/repos/dgarijo/Widoco/releases/3832172",
+ "tag": "v1.2.2",
+ "name": "Widoco 1.2.2: Major bug fixes and improvements",
+ "author": {
+ "name": "dgarijo",
+ "type": "User"
+ },
+ "description": "This version fixes the following issues/improvements:\n- 188: lang separator doesn't work on unix systems. Changed \";\" to \"-\" as separator\n- 189: return a non-zero value if there are problems generating the doc\n- 151: text area in the table should be bigger \n- 177: Ability to provide authors institution URI in the config \n- 155: allow possibility of adding Zenodo URI (DOI) \n- 132: could improve placeholder text\n- 174: Dynamic sections \n- 170: Check why isDefinedBy is not recognized sometimes\n- 171: Separate configuration from vocabulary metadata \n\nIn addition, I have fixed the custom style CSS, saving the publisher in the config file, producing an alt in images to avoid broken images when no connection is available and issues with the consistency in sections and back to toc titles.\n\nFinally, the toc will be now dynamic, so it will get updated if new headers with class \"list\" are added to the document.\n",
+ "tarball_url": "https://api.github.com/repos/dgarijo/Widoco/tarball/v1.2.2",
+ "zipball_url": "https://api.github.com/repos/dgarijo/Widoco/zipball/v1.2.2",
+ "html_url": "https://github.com/dgarijo/Widoco/releases/tag/v1.2.2",
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/3832172",
+ "release_id": 3832172,
+ "date_created": "2016-08-07T20:39:13Z",
+ "date_published": "2016-08-07T20:49:16Z",
+ "assets": [
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/2111104",
+ "name": "widoco-1.2.2-jar-with-dependencies.jar",
+ "content_size": 15390961,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.2.2/widoco-1.2.2-jar-with-dependencies.jar",
+ "encoding_format": "application/octet-stream",
+ "upload_date": "2016-08-08T08:22:33Z",
+ "download_count": 39
+ }
+ ]
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "type": "Release",
+ "value": "https://api.github.com/repos/dgarijo/Widoco/releases/3701886",
+ "tag": "v1.2.1",
+ "name": "Widoco 1.2.1: Interface bug fixes",
+ "author": {
+ "name": "dgarijo",
+ "type": "User"
+ },
+ "description": "In this release a couple of bugs have been fixed.\nFirst, a bug that froze the program after hitting the \"back\" button, once the ontology properties had been loaded.\nAnother bug fix is that now \"publisher\" will not appear if it's not filled in the .properties file.\n",
+ "tarball_url": "https://api.github.com/repos/dgarijo/Widoco/tarball/v1.2.1",
+ "zipball_url": "https://api.github.com/repos/dgarijo/Widoco/zipball/v1.2.1",
+ "html_url": "https://github.com/dgarijo/Widoco/releases/tag/v1.2.1",
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/3701886",
+ "release_id": 3701886,
+ "date_created": "2016-07-20T16:54:43Z",
+ "date_published": "2016-07-20T16:58:10Z",
+ "assets": [
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/2021317",
+ "name": "widoco-1.2.1-jar-with-dependencies.jar",
+ "content_size": 15363104,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.2.1/widoco-1.2.1-jar-with-dependencies.jar",
+ "encoding_format": "application/octet-stream",
+ "upload_date": "2016-07-20T16:57:51Z",
+ "download_count": 27
+ }
+ ]
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "type": "Release",
+ "value": "https://api.github.com/repos/dgarijo/Widoco/releases/3683077",
+ "tag": "v1.2.0",
+ "name": "Widoco 1.2.0: Extended metadata enrichment",
+ "author": {
+ "name": "dgarijo",
+ "type": "User"
+ },
+ "description": "This version automatically extracts the properties \"publisher\" and \"versionIRI\" from the ontology. In addition, it adds a link to webVowl and fixes some minor issues.\n",
+ "tarball_url": "https://api.github.com/repos/dgarijo/Widoco/tarball/v1.2.0",
+ "zipball_url": "https://api.github.com/repos/dgarijo/Widoco/zipball/v1.2.0",
+ "html_url": "https://github.com/dgarijo/Widoco/releases/tag/v1.2.0",
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/3683077",
+ "release_id": 3683077,
+ "date_created": "2016-07-18T16:47:41Z",
+ "date_published": "2016-07-18T16:52:10Z",
+ "assets": [
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/2007860",
+ "name": "widoco-1.2.0-jar-with-dependencies.jar",
+ "content_size": 15363881,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.2.0/widoco-1.2.0-jar-with-dependencies.jar",
+ "encoding_format": "application/octet-stream",
+ "upload_date": "2016-07-18T16:51:55Z",
+ "download_count": 15
+ }
+ ]
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "type": "Release",
+ "value": "https://api.github.com/repos/dgarijo/Widoco/releases/3673322",
+ "tag": "v1.1.1",
+ "name": "Widoco1.1.1: Integration with Licensius is back online",
+ "author": {
+ "name": "dgarijo",
+ "type": "User"
+ },
+ "description": "This release contains the time outs for using Licensius (for retrieveing license names from the ontology) and OOPS! (for evaluation)\nIn addition, it also fixes some small issued with content negotiation when loading Prot\u00e9g\u00e9. By default, the .htaccess file will serve rdf/xml when asking for \\* / \\* and turtle when asking for text/*\n",
+ "tarball_url": "https://api.github.com/repos/dgarijo/Widoco/tarball/v1.1.1",
+ "zipball_url": "https://api.github.com/repos/dgarijo/Widoco/zipball/v1.1.1",
+ "html_url": "https://github.com/dgarijo/Widoco/releases/tag/v1.1.1",
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/3673322",
+ "release_id": 3673322,
+ "date_created": "2016-07-16T15:53:39Z",
+ "date_published": "2016-07-16T15:59:49Z",
+ "assets": [
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/2000539",
+ "name": "widoco-1.1.1-jar-with-dependencies.jar",
+ "content_size": 15358279,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.1.1/widoco-1.1.1-jar-with-dependencies.jar",
+ "encoding_format": "application/octet-stream",
+ "upload_date": "2016-07-16T16:09:51Z",
+ "download_count": 16
+ }
+ ]
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "type": "Release",
+ "value": "https://api.github.com/repos/dgarijo/Widoco/releases/3599226",
+ "tag": "v1.1.0",
+ "name": "Content negotiation bundle",
+ "author": {
+ "name": "dgarijo",
+ "type": "User"
+ },
+ "description": "Now Widoco can create a content negotiation bundle that helps you deploy your vocabulary on your server. In addition, now different serializations of a vocabulary are saved locally, in order to facilitate users pointing to them.\n\nNote: due to time outs in Licensius, it is disabled in this version\n",
+ "tarball_url": "https://api.github.com/repos/dgarijo/Widoco/tarball/v1.1.0",
+ "zipball_url": "https://api.github.com/repos/dgarijo/Widoco/zipball/v1.1.0",
+ "html_url": "https://github.com/dgarijo/Widoco/releases/tag/v1.1.0",
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/3599226",
+ "release_id": 3599226,
+ "date_created": "2016-07-06T15:57:53Z",
+ "date_published": "2016-07-06T16:01:06Z",
+ "assets": [
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/1950415",
+ "name": "widoco-1.1.0-jar-with-dependencies.jar",
+ "content_size": 15358060,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.1.0/widoco-1.1.0-jar-with-dependencies.jar",
+ "encoding_format": "application/octet-stream",
+ "upload_date": "2016-07-06T16:00:40Z",
+ "download_count": 18
+ }
+ ]
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ },
+ {
+ "result": {
+ "type": "Release",
+ "value": "https://api.github.com/repos/dgarijo/Widoco/releases/3263232",
+ "tag": "v1.0.0",
+ "name": "First stable release of Widoco",
+ "author": {
+ "name": "dgarijo",
+ "type": "User"
+ },
+ "description": "This release fixes multiple bugs from the earlier release, and allows customizing a label on the side with the current status of the document.\n\nThis version also improves how an ontology is loaded, applying content negotiation when necessary.\n",
+ "tarball_url": "https://api.github.com/repos/dgarijo/Widoco/tarball/v1.0.0",
+ "zipball_url": "https://api.github.com/repos/dgarijo/Widoco/zipball/v1.0.0",
+ "html_url": "https://github.com/dgarijo/Widoco/releases/tag/v1.0.0",
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/3263232",
+ "release_id": 3263232,
+ "date_created": "2016-05-19T16:43:03Z",
+ "date_published": "2016-05-19T16:49:39Z",
+ "assets": [
+ {
+ "url": "https://api.github.com/repos/dgarijo/Widoco/releases/assets/1712555",
+ "name": "widoco-1.0.0-jar-with-dependencies.jar",
+ "content_size": 15354399,
+ "content_url": "https://github.com/dgarijo/Widoco/releases/download/v1.0.0/widoco-1.0.0-jar-with-dependencies.jar",
+ "encoding_format": "application/octet-stream",
+ "upload_date": "2016-05-19T16:47:08Z",
+ "download_count": 23
+ }
+ ]
+ },
+ "confidence": 1,
+ "technique": "GitHub_API"
+ }
+ ],
+ "has_build_file": [
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/Dockerfile",
+ "type": "Url",
+ "format": "dockerfile"
+ },
+ "confidence": 1,
+ "technique": "file_exploration",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/Dockerfile"
+ },
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/pom.xml",
+ "type": "Url",
+ "format": "pom.xml"
+ },
+ "confidence": 1,
+ "technique": "file_exploration",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/pom.xml"
+ }
+ ],
+ "citation": [
+ {
+ "result": {
+ "value": "title: \"WIDOCO: A Wizard for documenting ontologies\"\nlicense: Apache-2.0\nauthors:\n - family-names: Garijo\n given-names: Daniel\n orcid: \"http://orcid.org/0000-0003-0454-7145\"\ncff-version: 1.2.0\nmessage: \"If you use this software, please cite both the article from preferred-citation and the software itself.\"\npreferred-citation:\n authors:\n - family-names: Garijo\n given-names: Daniel\n title: \"WIDOCO: A wizard for documenting ontologies\"\n type: article\n year: 2017\n doi: 10.1007/978-3-319-68204-4_9\nidentifiers:\n - description: \"Collection of archived snapshots for WIDOCO\"\n type: doi\n value: 10.5281/zenodo.591294\n",
+ "type": "File_dump",
+ "identifier": [
+ {
+ "description": "Collection of archived snapshots for WIDOCO",
+ "type": "doi",
+ "value": "10.5281/zenodo.591294"
+ }
+ ],
+ "author": [
+ {
+ "type": "Agent",
+ "name": "Daniel Garijo",
+ "family_name": "Garijo",
+ "given_name": "Daniel",
+ "url": "http://orcid.org/0000-0003-0454-7145"
+ }
+ ],
+ "title": "WIDOCO: A Wizard for documenting ontologies",
+ "url": "https://doi.org/10.5281/zenodo.591294",
+ "doi": "10.1007/978-3-319-68204-4_9",
+ "format": "cff",
+ "datePublished": "2017"
+ },
+ "confidence": 1,
+ "technique": "file_exploration",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/CITATION.cff"
+ },
+ {
+ "result": {
+ "value": "WIDOCO: a wizard for documenting ontologies",
+ "title": "WIDOCO: a wizard for documenting ontologies",
+ "type": "Scholarly_article",
+ "url": "http://dgarijo.com/papers/widoco-iswc2017.pdf",
+ "date_published": "2017",
+ "doi": "10.1007/978-3-319-68204-4_9"
+ },
+ "confidence": 1,
+ "technique": "code_parser",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/codemeta.json"
+ },
+ {
+ "result": {
+ "value": "@inproceedings{garijo2017widoco,\n url = {http://dgarijo.com/papers/widoco-iswc2017.pdf},\n funding = {USNSF ICER-1541029, NIH 1R01GM117097-01},\n doi = {10.1007/978-3-319-68204-4_9},\n organization = {Springer, Cham},\n year = {2017},\n pages = {94--102},\n booktitle = {International Semantic Web Conference},\n author = {Garijo, Daniel},\n title = {WIDOCO: a wizard for documenting ontologies},\n}",
+ "type": "Text_excerpt",
+ "format": "bibtex",
+ "doi": "10.1007/978-3-319-68204-4_9",
+ "title": "WIDOCO: a wizard for documenting ontologies",
+ "author": "Garijo, Daniel",
+ "url": "http://dgarijo.com/papers/widoco-iswc2017.pdf"
+ },
+ "confidence": 1,
+ "technique": "regular_expression",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/README.md"
+ }
+ ],
+ "package_id": [
+ {
+ "result": {
+ "value": "es.oeg.widoco",
+ "type": "String"
+ },
+ "confidence": 1,
+ "technique": "code_parser",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/pom.xml"
+ }
+ ],
+ "version": [
+ {
+ "result": {
+ "value": "1.4.26",
+ "type": "Release"
+ },
+ "confidence": 1,
+ "technique": "code_parser",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/pom.xml"
+ },
+ {
+ "result": {
+ "value": "v1.4.25",
+ "type": "String"
+ },
+ "confidence": 1,
+ "technique": "code_parser",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/codemeta.json"
+ }
+ ],
+ "requirements": [
+ {
+ "result": {
+ "value": "org.apache.maven.maven-model",
+ "name": "maven-model",
+ "version": "3.9.0",
+ "type": "Software_application"
+ },
+ "confidence": 1,
+ "technique": "code_parser",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/pom.xml"
+ },
+ {
+ "result": {
+ "value": "org.jsoup.jsoup",
+ "name": "jsoup",
+ "version": "1.17.2",
+ "type": "Software_application"
+ },
+ "confidence": 1,
+ "technique": "code_parser",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/pom.xml"
+ },
+ {
+ "result": {
+ "value": "com.github.VisualDataWeb.OWL2VOWL",
+ "name": "OWL2VOWL",
+ "version": "0.3.5",
+ "type": "Software_application"
+ },
+ "confidence": 1,
+ "technique": "code_parser",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/pom.xml"
+ },
+ {
+ "result": {
+ "value": "org.slf4j.slf4j-api",
+ "name": "slf4j-api",
+ "version": "1.7.30",
+ "type": "Software_application"
+ },
+ "confidence": 1,
+ "technique": "code_parser",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/pom.xml"
+ },
+ {
+ "result": {
+ "value": "org.slf4j.slf4j-simple",
+ "name": "slf4j-simple",
+ "version": "1.7.30",
+ "type": "Software_application"
+ },
+ "confidence": 1,
+ "technique": "code_parser",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/pom.xml"
+ },
+ {
+ "result": {
+ "value": "net.sf.saxon.Saxon-HE",
+ "name": "Saxon-HE",
+ "version": "9.4",
+ "type": "Software_application"
+ },
+ "confidence": 1,
+ "technique": "code_parser",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/pom.xml"
+ },
+ {
+ "result": {
+ "value": "net.sourceforge.owlapi.owlapi-distribution",
+ "name": "owlapi-distribution",
+ "version": "5.1.18",
+ "type": "Software_application"
+ },
+ "confidence": 1,
+ "technique": "code_parser",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/pom.xml"
+ },
+ {
+ "result": {
+ "value": "com.googlecode.json-simple.json-simple",
+ "name": "json-simple",
+ "version": "1.1",
+ "type": "Software_application"
+ },
+ "confidence": 1,
+ "technique": "code_parser",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/pom.xml"
+ },
+ {
+ "result": {
+ "value": "junit.junit",
+ "name": "junit",
+ "version": "4.13.1",
+ "type": "Software_application"
+ },
+ "confidence": 1,
+ "technique": "code_parser",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/pom.xml"
+ },
+ {
+ "result": {
+ "value": "org.hamcrest.hamcrest-core",
+ "name": "hamcrest-core",
+ "version": "1.3",
+ "type": "Software_application"
+ },
+ "confidence": 1,
+ "technique": "code_parser",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/pom.xml"
+ },
+ {
+ "result": {
+ "value": "Just add the dependency and repository to your `pom.xml` file as follows. See the [WIDOCO JitPack](https://jitpack.io/#dgarijo/Widoco) page to find alternative means to incorporate WIDOCO to your project.\n\n```xml\n\n \n com.github.dgarijo\n Widoco\n v1.4.24\n \n\n\n[ ... ]\n\n\n\t\n\t jitpack.io\n\t https://jitpack.io\n\t\n\n```",
+ "type": "Software_application"
+ },
+ "confidence": 1,
+ "technique": "code_parser",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/codemeta.json"
+ },
+ {
+ "result": {
+ "value": "You will need Java 1.8 or higher (SDK 1.8 or JRE 8) for WIDOCO to work\nOtherwise, you will probably experience an \"Unsupported major.minor version 52.0\" exception when executing the JAR file.",
+ "type": "Software_application"
+ },
+ "confidence": 1,
+ "technique": "code_parser",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/codemeta.json"
+ },
+ {
+ "result": {
+ "value": "Just add the dependency and repository to your `pom.xml` file as follows. See the [WIDOCO JitPack](https://jitpack.io/#dgarijo/Widoco) page to find alternative means to incorporate WIDOCO to your project.\n\n```xml\n\n \n com.github.dgarijo\n Widoco\n v1.4.24\n \n\n\n[ ... ]\n\n\n\t\n\t jitpack.io\n\t https://jitpack.io\n\t\n\n```\n",
+ "type": "Text_excerpt",
+ "original_header": "Importing WIDOCO as a dependency",
+ "parent_header": [
+ "WIzard for DOCumenting Ontologies (WIDOCO)"
+ ]
+ },
+ "confidence": 1,
+ "technique": "header_analysis",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/README.md"
+ },
+ {
+ "result": {
+ "value": "You will need Java 1.8 or higher (SDK 1.8 or JRE 8) for WIDOCO to work\nOtherwise, you will probably experience an \"Unsupported major.minor version 52.0\" exception when executing the JAR file.\n",
+ "type": "Text_excerpt",
+ "original_header": "Requirements",
+ "parent_header": [
+ "WIzard for DOCumenting Ontologies (WIDOCO)"
+ ]
+ },
+ "confidence": 1,
+ "technique": "header_analysis",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/README.md"
+ }
+ ],
+ "package_distribution": [
+ {
+ "result": {
+ "value": "jitpack.io",
+ "url": "https://jitpack.io",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "code_parser",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/pom.xml"
+ },
+ {
+ "result": {
+ "value": "unknown-jars-temp-repo",
+ "name": "A temporary repository created by NetBeans for libraries and jars it could not identify. Please replace the dependencies in this repository with correct ones and delete this repository.",
+ "url": "file:${project.basedir}/lib",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "code_parser",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/pom.xml"
+ }
+ ],
+ "runtime_platform": [
+ {
+ "result": {
+ "value": "Java: 1.8",
+ "version": "1.8",
+ "name": "Java",
+ "type": "String"
+ },
+ "confidence": 1,
+ "technique": "code_parser",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/pom.xml"
+ }
+ ],
+ "has_package_file": [
+ {
+ "result": {
+ "value": "pom.xml",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "code_parser",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/pom.xml"
+ }
+ ],
+ "readme_url": [
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/README.md",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ },
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/README.md",
+ "type": "String"
+ },
+ "confidence": 1,
+ "technique": "code_parser",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/codemeta.json"
+ }
+ ],
+ "contributing_guidelines": [
+ {
+ "result": {
+ "value": "## Contribution guidelines\nContributions to address any of the current issues are welcome. In order to push your contribution, just **push your pull request to the develop branch**. The master branch has only the code associated to the latest release. \n",
+ "type": "File_dump"
+ },
+ "confidence": 1,
+ "technique": "file_exploration",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/CONTRIBUTING.md"
+ }
+ ],
+ "identifier": [
+ {
+ "result": {
+ "value": "https://doi.org/10.5281/zenodo.11093793",
+ "type": "String"
+ },
+ "confidence": 1,
+ "technique": "code_parser",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/codemeta.json"
+ },
+ {
+ "result": {
+ "type": "Url",
+ "value": "https://doi.org/10.5281/zenodo.11093793"
+ },
+ "confidence": 1,
+ "technique": "regular_expression",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/README.md"
+ },
+ {
+ "result": {
+ "type": "Url",
+ "value": "https://archive.softwareheritage.org/swh:1:rev:fec66b89a4f4acb015a44c7f8cb671d49bec626a"
+ },
+ "confidence": 1,
+ "technique": "regular_expression",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/README.md"
+ }
+ ],
+ "continuous_integration": [
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/.github/workflows/ci.yml",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ }
+ ],
+ "ontologies": [
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/doc/benchmark/otalex.owl",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ },
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/doc/benchmark/ebg-ontology_edited.ttl",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ },
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/doc/benchmark/p-plan.owl",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ },
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/doc/benchmark/bne.ttl",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ },
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/doc/benchmark/NBAMovementsv2%20-%20copia.owl",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ },
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/doc/benchmark/System.owl",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ },
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/doc/benchmark/retele.ttl",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ },
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/doc/benchmark/geolinkeddata.owl",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ },
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/doc/benchmark/contaminacion-acustica.owl",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ },
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/doc/benchmark/instrument.ttl",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ },
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/doc/benchmark/IFC4_ADD1.ttl",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ },
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/doc/benchmark/disk.ttl",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ },
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/doc/benchmark/oa.ttl",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ },
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/doc/benchmark/ssn.owl",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ },
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/doc/benchmark/Tissue.ttl",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ },
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/doc/benchmark/wot.ttl",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ },
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/doc/benchmark/charity_org_rdf_new.owl",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ },
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/doc/benchmark/schema_sample.ttl",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ },
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/doc/benchmark/smartProtocol.owl",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ },
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/doc/benchmark/ontosoft-v1.0.owl",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ },
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/doc/benchmark/IFC4_ADD1_wrongImport.ttl",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ },
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/doc/benchmark/saref4envi.ttl",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ },
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/doc/benchmark/retele-share.owl",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ },
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/doc/benchmark/coil.owl",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ },
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/doc/benchmark/NBAMovementsv2.owl",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ },
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/doc/benchmark/diff/coreNew.ttl",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ },
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/doc/benchmark/diff/coilNew.owl",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ },
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/doc/benchmark/diff/coilOld.owl",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ },
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/doc/benchmark/diff/coreOld.ttl",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ },
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/doc/benchmark/LinkedEarth/sensor.ttl",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ },
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/doc/benchmark/LinkedEarth/observation.ttl",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ },
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/doc/benchmark/LinkedEarth/archive.ttl",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ },
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/doc/benchmark/LinkedEarth/instrument.ttl",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ },
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/doc/benchmark/LinkedEarth/inferredVariables.ttl",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ },
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/doc/benchmark/LinkedEarth/core.ttl",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ },
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/doc/benchmark/RO/roevo.owl",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ },
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/doc/benchmark/RO/roterms.owl",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ },
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/doc/benchmark/RO/wf4ever.owl",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ },
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/doc/benchmark/RO/wfdesc.owl",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ },
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/doc/benchmark/RO/ro.owl",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ },
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/doc/benchmark/RO/wfprov.owl",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ },
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/doc/benchmark/Disk/disk.ttl",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ },
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/doc/vocabulary/1.0.0/ontology.nt",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ },
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/doc/vocabulary/1.0.0/ontology.owl",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ },
+ {
+ "result": {
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/doc/vocabulary/1.0.0/ontology.ttl",
+ "type": "Url"
+ },
+ "confidence": 1,
+ "technique": "file_exploration"
+ }
+ ],
+ "type": [
+ {
+ "result": {
+ "value": "commandline-application",
+ "type": "String"
+ },
+ "confidence": 0.82,
+ "technique": "software_type_heuristics"
+ }
+ ],
+ "download": [
+ {
+ "result": {
+ "value": "To download WIDOCO, you need to download a JAR executable file. Check the latest release for more details: (https://github.com/dgarijo/WIDOCO/releases/latest).\n",
+ "type": "Text_excerpt",
+ "original_header": "Downloading the executable",
+ "parent_header": [
+ "WIzard for DOCumenting Ontologies (WIDOCO)"
+ ]
+ },
+ "confidence": 1,
+ "technique": "header_analysis",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/README.md"
+ }
+ ],
+ "usage": [
+ {
+ "result": {
+ "value": "Examples of the features of WIDOCO can be seen on [the gallery](https://dgarijo.github.io/Widoco/doc/gallery/)\n",
+ "type": "Text_excerpt",
+ "original_header": "Examples",
+ "parent_header": [
+ "WIzard for DOCumenting Ontologies (WIDOCO)"
+ ]
+ },
+ "confidence": 1,
+ "technique": "header_analysis",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/README.md"
+ },
+ {
+ "result": {
+ "value": "A tutorial explaining the main features of the GUI can be found [here](https://dgarijo.github.io/Widoco/doc/tutorial/) \n",
+ "type": "Text_excerpt",
+ "original_header": "GUI Tutorial",
+ "parent_header": [
+ "WIzard for DOCumenting Ontologies (WIDOCO)"
+ ]
+ },
+ "confidence": 1,
+ "technique": "header_analysis",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/README.md"
+ },
+ {
+ "result": {
+ "value": "To see how WIDOCO recognizes metadata annotations in your ontology to create the documentation files, see [the WIDOCO metadata documentation](doc/metadataGuide/guide.md). To learn which metadata properties we recommend adding to your ontology for producing a nice-looking documentation, have a look at our [best practices guide](https://dgarijo.github.io/Widoco/doc/bestPractices/index-en.html).\n\nFor example, in order to show your logo in your documentation you just need to use `foaf:logo` as an annotation, as follows:\n```\n@prefix owl: .\n@prefix foaf: .\n a owl:Ontology ;\n foaf:logo .\n```\n\nand it will show right next to the title. The [WIDOCO metadata documentation](doc/metadataGuide/guide.md) shows all supported metadata fields.\n",
+ "type": "Text_excerpt",
+ "original_header": "Metadata usage",
+ "parent_header": [
+ "WIzard for DOCumenting Ontologies (WIDOCO)"
+ ]
+ },
+ "confidence": 1,
+ "technique": "header_analysis",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/README.md"
+ },
+ {
+ "result": {
+ "value": "We provide JAR files for each release (see the [releases](https://github.com/dgarijo/Widoco/releases) page). However, if you want to build WIDOCO from scratch, just cd into the project folder and run:\n\n```bash\nmvn install\n```\nThe JAR will be generated in a \"JAR\" folder. The name will follow the pattern: `widoco-{VERSION_ID}-jar-with-dependencies.jar`, where {VERSION_ID} is the version number of the tool.\n",
+ "type": "Text_excerpt",
+ "original_header": "Building the JAR executable",
+ "parent_header": [
+ "WIzard for DOCumenting Ontologies (WIDOCO)",
+ "How to use WIDOCO"
+ ]
+ },
+ "confidence": 1,
+ "technique": "header_analysis",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/README.md"
+ },
+ {
+ "result": {
+ "value": "Download the latest `.jar` [WIDOCO available release](https://github.com/dgarijo/WIDOCO/releases/latest) (it will be something like `widoco-VERSION-jar-with-dependencies.jar`). Then just double click the `.jar` file.\n\nYou may also execute WIDOCO through the command line. Usage:\n```bash\njava -jar widoco-VERSION-jar-with-dependencies.jar [OPTIONS]\n```\n",
+ "type": "Text_excerpt",
+ "original_header": "JAR execution",
+ "parent_header": [
+ "WIzard for DOCumenting Ontologies (WIDOCO)",
+ "How to use WIDOCO"
+ ]
+ },
+ "confidence": 1,
+ "technique": "header_analysis",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/README.md"
+ },
+ {
+ "result": {
+ "value": "If you don't want to use the JAR directly, you may run the project using a [Docker container](https://www.docker.com/). First you will need to download or build the image, and then run it.\n",
+ "type": "Text_excerpt",
+ "original_header": "Docker execution",
+ "parent_header": [
+ "WIzard for DOCumenting Ontologies (WIDOCO)",
+ "How to use WIDOCO"
+ ]
+ },
+ "confidence": 1,
+ "technique": "header_analysis",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/README.md"
+ },
+ {
+ "result": {
+ "value": "We build containers in the [GitHub image registry](https://github.com/dgarijo/Widoco/pkgs/container/widoco) for all latest releases. In order to import one, just run the following command, stating the version of Widoco you prefer (e.g., for v1.4.23):\n\n```\ndocker pull ghcr.io/dgarijo/widoco:v1.4.23\n```\n\nTo browse all available images, see the [GitHub image registry](https://github.com/dgarijo/Widoco/pkgs/container/widoco).\n",
+ "type": "Text_excerpt",
+ "original_header": "Reusing a pre-existing image",
+ "parent_header": [
+ "WIzard for DOCumenting Ontologies (WIDOCO)",
+ "How to use WIDOCO",
+ "Docker execution"
+ ]
+ },
+ "confidence": 1,
+ "technique": "header_analysis",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/README.md"
+ },
+ {
+ "result": {
+ "value": "Build the image using the `Dockerfile` in project folder:\n\n```bash\ndocker build -t dgarijo/widoco .\n```\n",
+ "type": "Text_excerpt",
+ "original_header": "Building the image yourself",
+ "parent_header": [
+ "WIzard for DOCumenting Ontologies (WIDOCO)",
+ "How to use WIDOCO",
+ "Docker execution"
+ ]
+ },
+ "confidence": 1,
+ "technique": "header_analysis",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/README.md"
+ },
+ {
+ "result": {
+ "value": "`-analytics CODE`: Add a code snippet for Google analytics to track your HTML documentation. You need to add your CODE next to the flag. For example: UA-1234\n\n`-confFile PATH`: Load your own configuration file for the ontology metadata. Use this option if you want to load your own HTML sections as well. Incompatible with -getOntologyMetadata. See [the configuration documentation](doc/configuration/configuration_doc.md) for more information about the accepted fields.\n\n`-crossRef`: ONLY generate the overview and cross reference sections. The index document will NOT be generated. The htaccess, provenance page, etc., will not be generated unless requested by other flags. This flag is intended to be used only after a first version of the documentation exists.\n\n`-displayDirectImportsOnly`: Only those imported ontologies that are directly imported in the ontology being documented.\n\n`-doNotDisplaySerializations`: The serializations of the ontology will not be displayed.\n\n`-excludeIntroduction`: Skip the introduction section in the documentation.\n\n`-excludeProvenance`: Do not add the link \"Provenance of this page\" in the metadata header section\n\n`-getOntologyMetadata`: Extract ontology metadata from the given ontology\n\n`--help`: Shows a help message and exits.\n\n`-htaccess`: Create a bundle for publication ready to be deployed on your Apache server.\n\n\n`-ignoreIndividuals`: Individuals will not be included in the documentation.\n\n`-includeAnnotationProperties`: Include annotation properties defined in your ontology in the documentation (by default they are not included)\n\n`-includeImportedOntologies`: Indicates whether the terms of the imported ontologies of the current ontology should be documented as well or not.\n\n`-import`: imports a local ontology (e.g., if you don't want to load an online ontology, you may load its local version)\n\n`-lang LANG1-LANG2`: Generate documentation in multiple languages (separated by \"-\"). Note that if the language is not supported, the system will load the labels in english. For example: en-pt-es\n\n`-licensius`: Use the Licensius web services (http://licensius.com/apidoc/index.html) to retrieve license metadata. Only works if the -getOntologyMetadata flag is enabled.\n\n`-noPlaceHolderText`: Do not add any placeholder text (this will remove intro, abstract (if empty) and description sections).\n\n`-ontFile PATH` [required (unless -ontURI is used)]: Load a local ontology file (from PATH) to document. This option is incompatible with -ontURI\n\n`-outFolder folderName`: Specifies the name of the folder where to save the documentation. By default is 'myDocumentation'\n\n`-ontURI URI` [required (unless -ontFile is used)]: Load an ontology to document from its URI. This option is incompatible with -ontFile\n\n`-oops`: Create an html page with the evaluation from the OOPS service (http://oops.linkeddata.es/)\n\n`-rewriteAll`: Replace any existing files when documenting an ontology (e.g., from a previous execution)\n\n`-rewriteBase PATH`: Change the default rewrite base path. The default value is \"/\". This flag can only be used with the htaccess option.\n\n`-saveConfig PATH`: Save a configuration file on PATH with the properties of a given ontology\n\n`-uniteSections`: Write all HTML sections into a single HTML document.\n\n`-useCustomStyle`: Export the documentation using alternate css files (by Daniel Vila).\n\n`--version`: Shows the current version of WIDOCO.\n\n`-webVowl`: Create a visualization based on WebVowl (http://vowl.visualdataweb.org/webvowl/index.html#) in the documentation.\n\n",
+ "type": "Text_excerpt",
+ "original_header": "Execution options",
+ "parent_header": [
+ "WIzard for DOCumenting Ontologies (WIDOCO)",
+ "How to use WIDOCO"
+ ]
+ },
+ "confidence": 1,
+ "technique": "header_analysis",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/README.md"
+ }
+ ],
+ "run": [
+ {
+ "result": {
+ "value": "You can now execute WIDOCO through the command line. Usage:\n\n```bash\ndocker run -ti --rm dgarijo/widoco [OPTIONS]\n```\n\n**Note:** If you downloaded the image from the GitHub registry, you will have to change `dgarijo/widoco` with the name of the image you downloaded. For example `ghcr.io/dgarijo/widoco:v1.4.23`.\n\nIf you want to share data between the Docker Container and your Host, for instance to load a local ontology file (from PATH), you will need to mount the container\nwith host directories. For instance:\n\n```bash\ndocker run -ti --rm \\\n -v `pwd`/test:/usr/local/widoco/in:Z \\\n -v `pwd`/target/generated-doc:/usr/local/widoco/out:Z \\\n dgarijo/widoco -ontFile in/bne.ttl -outFolder out -rewriteAll\n```\n",
+ "type": "Text_excerpt",
+ "original_header": "Running WIDOCO's image",
+ "parent_header": [
+ "WIzard for DOCumenting Ontologies (WIDOCO)",
+ "How to use WIDOCO",
+ "Docker execution"
+ ]
+ },
+ "confidence": 1,
+ "technique": "header_analysis",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/README.md"
+ }
+ ],
+ "documentation": [
+ {
+ "result": {
+ "value": "WIDOCO separates the contents of different sections in HTML files, which are then loaded in the `index.html` file. WIDOCO was designed this way because it's easier to edit your introduction or description sections independently without being all aggregated together in a huge HTML document. **When all the contents generated by WIDOCO are stored in a server, you will be able to see the documentation of your ontology using any browser**. However, if you open the `index.html` file **on your local browser**, you may see a document missing most of the sections in your documentation. This happens because browsers don't allow loading separate content when opening a file locally for security reasons. If you want to explore how your ontology would look locally, you have two options:\n\n* a) Execute WIDOCO with the `-uniteSections` flag; or select the option `add al sections in a single document` in the \"load sections\" step in the WIDOCO GUI. This will make all the sections of WIDOCO to be in the `index.html`; and you will be able to see it in your browser. Note that the **LODE visualization will not be available** when exploring your ontology locally.\n* b) Create a local server: Set up a local server (e.g., using XAMPP or Tomcat) and serve the files WIDOCO generates (in the `htdocs` folder for Apache servers).\n\nIf you place the files generated by WIDOCO in a server and access them via its URL (for example, a Github page), you should be able to see your documentation appropriately.\n",
+ "type": "Text_excerpt",
+ "original_header": "Browser issues (Why can't I see the generated documentation / visualization?)",
+ "parent_header": [
+ "WIzard for DOCumenting Ontologies (WIDOCO)"
+ ]
+ },
+ "confidence": 1,
+ "technique": "header_analysis",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/README.md"
+ }
+ ],
+ "application_domain": [
+ {
+ "result": {
+ "type": "String",
+ "value": "Semantic web"
+ },
+ "confidence": 0.921888526958785,
+ "technique": "supervised_classification"
+ }
+ ],
+ "full_title": [
+ {
+ "result": {
+ "type": "String",
+ "value": "WIzard for DOCumenting Ontologies (WIDOCO)"
+ },
+ "confidence": 1,
+ "technique": "regular_expression",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/README.md"
+ }
+ ],
+ "repository_status": [
+ {
+ "result": {
+ "type": "Url",
+ "value": "https://www.repostatus.org/#active",
+ "description": "Active \u2013 The project has reached a stable, usable state and is being actively developed."
+ },
+ "confidence": 1,
+ "technique": "regular_expression",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/README.md"
+ }
+ ],
+ "logo": [
+ {
+ "result": {
+ "type": "Url",
+ "value": "https://raw.githubusercontent.com/dgarijo/Widoco/master/src/main/resources/logo/logo2.png"
+ },
+ "confidence": 1,
+ "technique": "regular_expression",
+ "source": "https://raw.githubusercontent.com/dgarijo/Widoco/master/README.md"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/somef_core/test/test_data/authors_files/docker_AUTHORS.txt b/src/somef_core/test/test_data/authors_files/docker_AUTHORS.txt
new file mode 100644
index 0000000..3d3fe3c
--- /dev/null
+++ b/src/somef_core/test/test_data/authors_files/docker_AUTHORS.txt
@@ -0,0 +1,718 @@
+# This file lists all individuals having contributed content to the repository.
+# For how it is generated, see `project/generate-authors.sh`.
+
+Aanand Prasad
+Aaron Feng
+Aaron Huslage
+Abel Muiño
+Abhinav Ajgaonkar
+Abin Shahab
+Adam Miller
+Adam Singer
+Aditya
+Adrian Mouat
+Adrien Folie
+Ahmet Alp Balkan
+AJ Bowen
+alambike
+Alan Thompson
+Albert Callarisa
+Albert Zhang
+Aleksa Sarai
+Alexander Larsson
+Alexander Shopov
+Alexandr Morozov
+Alexey Kotlyarov
+Alexey Shamrin
+Alex Gaynor
+Alexis THOMAS
+Alex Warhawk
+almoehi
+Al Tobey
+Álvaro Lázaro
+amangoel
+Amit Bakshi
+AnandkumarPatel
+Anand Patil
+Andrea Luzzardi
+Andreas Köhler
+Andreas Savvides
+Andreas Tiefenthaler
+Andrea Turli
+Andre Dublin <81dublin@gmail.com>
+Andrew Duckworth
+Andrew France
+Andrew Macgregor
+Andrew Munsell
+Andrews Medina
+Andrew Weiss
+Andrew Williams
+Andrey Petrov
+Andrey Stolbovsky
+Andy Chambers
+andy diller
+Andy Goldstein
+Andy Kipp
+Andy Rothfusz
+Andy Smith
+Andy Wilson
+Anthony Bishopric
+Anton Löfgren
+Anton Nikitin
+Antony Messerli
+apocas
+ArikaChen
+Arnaud Porterie
+Arthur Gautier
+Asbjørn Enge
+averagehuman
+Avi Miller
+Barnaby Gray
+Barry Allard
+Bartłomiej Piotrowski
+bdevloed
+Ben Firshman
+Benjamin Atkin
+Benoit Chesneau
+Ben Sargent
+Ben Toews
+Ben Wiklund
+Bernerd Schaefer
+Bert Goethals
+Bhiraj Butala
+bin liu
+Blake Geno
+Bouke Haarsma
+Boyd Hemphill
+Brandon Liu
+Brandon Philips
+Brandon Rhodes
+Brett Kochendorfer
+Brian (bex) Exelbierd
+Brian Dorsey
+Brian Flad
+Brian Goff
+Brian McCallister
+Brian Olsen
+Brian Shumate
+Brice Jaglin
+Briehan Lombaard
+Bruno Bigras
+Bruno Binet
+Bruno Renié
+Bryan Bess
+Bryan Matsuo
+Bryan Murphy
+Burke Libbey
+Byung Kang
+Caleb Spare
+Calen Pennington
+Cameron Boehmer
+Carl X. Su
+Charles Hooper
+Charles Lindsay
+Charles Merriam
+Charlie Lewis
+Chewey
+Chia-liang Kao
+Chris Alfonso
+Chris Armstrong
+chrismckinnel
+Chris Snow
+Chris St. Pierre
+Christian Berendt
+ChristoperBiscardi
+Christopher Currie
+Christopher Rigor
+Christophe Troestler
+Ciro S. Costa
+Clayton Coleman
+Colin Dunklau
+Colin Rice
+Colin Walters
+Cory Forsyth
+cressie176
+Cruceru Calin-Cristian
+Daan van Berkel
+Daehyeok.Mun
+Dafydd Crosby
+Dan Buch
+Dan Cotora
+Dan Griffin
+Dan Hirsch
+Daniel, Dao Quang Minh
+Daniel Exner
+Daniel Farrell
+Daniel Garcia
+Daniel Gasienica
+Daniel Menet
+Daniel Mizyrycki
+Daniel Norberg
+Daniel Nordberg
+Daniel Robinson
+Daniel Von Fange
+Daniel YC Lin
+Dan Keder
+Dan McPherson
+Danny Berger
+Danny Yates
+Dan Stine
+Dan Walsh
+Dan Williams
+Darren Coxall
+Darren Shepherd
+David Anderson
+David Calavera
+David Corking
+Davide Ceretti
+David Gageot
+David Gebler
+David Mcanulty
+David Pelaez
+David Röthlisberger
+David Sissitka
+Dawn Chen
+decadent
+Deni Bertovic
+Derek
+Derek McGowan
+Deric Crago
+Deshi Xiao
+Dinesh Subhraveti
+Djibril Koné