somef_core 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- somef_core/__init__.py +3 -0
- somef_core/__main__.py +293 -0
- somef_core/configuration.py +177 -0
- somef_core/export/__init__.py +0 -0
- somef_core/export/google_codemeta_export.py +171 -0
- somef_core/export/json_export.py +1197 -0
- somef_core/extract_software_type.py +408 -0
- somef_core/extract_workflows.py +74 -0
- somef_core/header_analysis.py +712 -0
- somef_core/parser/__init__.py +0 -0
- somef_core/parser/authors_parser.py +121 -0
- somef_core/parser/bower_parser.py +185 -0
- somef_core/parser/cabal_parser.py +222 -0
- somef_core/parser/codemeta_parser.py +926 -0
- somef_core/parser/codeowners_parser.py +198 -0
- somef_core/parser/composer_parser.py +218 -0
- somef_core/parser/conda_environment_parser.py +91 -0
- somef_core/parser/create_excerpts.py +71 -0
- somef_core/parser/description_parser.py +228 -0
- somef_core/parser/dockerfile_parser.py +227 -0
- somef_core/parser/gemspec_parser.py +224 -0
- somef_core/parser/mardown_parser.py +536 -0
- somef_core/parser/package_json_parser.py +270 -0
- somef_core/parser/pom_xml_parser.py +405 -0
- somef_core/parser/publiccode_parser.py +385 -0
- somef_core/parser/python_parser.py +432 -0
- somef_core/parser/setupcfg_parser.py +192 -0
- somef_core/parser/toml_parser.py +717 -0
- somef_core/process_files.py +893 -0
- somef_core/process_repository.py +1793 -0
- somef_core/process_results.py +87 -0
- somef_core/regular_expressions.py +1333 -0
- somef_core/somef_cli.py +446 -0
- somef_core/test/__init__.py +1 -0
- somef_core/test/conftest.py +7 -0
- somef_core/test/test_JSON_export.py +1177 -0
- somef_core/test/test_bitbucket.py +137 -0
- somef_core/test/test_bower_parser.py +113 -0
- somef_core/test/test_cabal_parser.py +94 -0
- somef_core/test/test_cli.py +816 -0
- somef_core/test/test_codeberg.py +183 -0
- somef_core/test/test_codemeta_export.py +1135 -0
- somef_core/test/test_codemeta_gitlab_export.py +64 -0
- somef_core/test/test_codemeta_parser.py +167 -0
- somef_core/test/test_composer_parser.py +59 -0
- somef_core/test/test_conda_environment_parser.py +58 -0
- somef_core/test/test_configuration.py +40 -0
- somef_core/test/test_data/README-EUVpy.md +166 -0
- somef_core/test/test_data/README-FTR.md +50 -0
- somef_core/test/test_data/README-GENI.md +109 -0
- somef_core/test/test_data/README-MEL-TNNT.md +112 -0
- somef_core/test/test_data/README-OBA-SPARQL.md +87 -0
- somef_core/test/test_data/README-OWL-To-OAS.md +6 -0
- somef_core/test/test_data/README-PPool.md +26 -0
- somef_core/test/test_data/README-TINTO.md +213 -0
- somef_core/test/test_data/README-agora.md +511 -0
- somef_core/test/test_data/README-almost-empty.md +1 -0
- somef_core/test/test_data/README-base-images.md +1 -0
- somef_core/test/test_data/README-ci-sample-project.md +102 -0
- somef_core/test/test_data/README-citation-file-format.md +158 -0
- somef_core/test/test_data/README-componentinstaller.md +139 -0
- somef_core/test/test_data/README-csl-editor.md +57 -0
- somef_core/test/test_data/README-devilog.md +99 -0
- somef_core/test/test_data/README-easytv-annotator.md +74 -0
- somef_core/test/test_data/README-epw2rdf-contents.md +13 -0
- somef_core/test/test_data/README-epw2rdf.md +10 -0
- somef_core/test/test_data/README-fair-ontologies.md +53 -0
- somef_core/test/test_data/README-fastify.md +420 -0
- somef_core/test/test_data/README-gammalearn.md +193 -0
- somef_core/test/test_data/README-gammapy.rst +103 -0
- somef_core/test/test_data/README-kgtk-notebooks.md +81 -0
- somef_core/test/test_data/README-laueNN.md +123 -0
- somef_core/test/test_data/README-licensius.md +59 -0
- somef_core/test/test_data/README-lighttwin.md +234 -0
- somef_core/test/test_data/README-loupe-api.md +13 -0
- somef_core/test/test_data/README-manim.md +117 -0
- somef_core/test/test_data/README-mapeathor.md +45 -0
- somef_core/test/test_data/README-mapshaper.md +101 -0
- somef_core/test/test_data/README-menty.md +94 -0
- somef_core/test/test_data/README-os-platforms.md +196 -0
- somef_core/test/test_data/README-panda.md +16 -0
- somef_core/test/test_data/README-pylops.md +229 -0
- somef_core/test/test_data/README-pytorch.md +424 -0
- somef_core/test/test_data/README-raidologist.md +60 -0
- somef_core/test/test_data/README-reggae.md +95 -0
- somef_core/test/test_data/README-stract.md +66 -0
- somef_core/test/test_data/README-sunpy.rst +115 -0
- somef_core/test/test_data/README-tada-gam.md +130 -0
- somef_core/test/test_data/README-tensorflow-2.6.0.md +156 -0
- somef_core/test/test_data/README-tokei.md +85 -0
- somef_core/test/test_data/README-tpronk.md +75 -0
- somef_core/test/test_data/README-unboundedsystems.md +86 -0
- somef_core/test/test_data/README-uncbiag.md +91 -0
- somef_core/test/test_data/README-urllib3.md +110 -0
- somef_core/test/test_data/README-widoco-duplicate-dois.md +198 -0
- somef_core/test/test_data/README-widoco-swh.md +233 -0
- somef_core/test/test_data/README-widoco.md +187 -0
- somef_core/test/test_data/README-wothive.md +111 -0
- somef_core/test/test_data/README-ya2o.md +372 -0
- somef_core/test/test_data/SimulatedReads2Map.wdl +80 -0
- somef_core/test/test_data/api_responses/bitbucket/bitbucket_response.json +127 -0
- somef_core/test/test_data/api_responses/bitbucket/bitbucket_tags.json +85 -0
- somef_core/test/test_data/api_responses/codeberg/codeberg_forgejo.json +104 -0
- somef_core/test/test_data/api_responses/codeberg/codeberg_forgejo_languages.json +16 -0
- somef_core/test/test_data/api_responses/codeberg/codeberg_forgejo_releases.json +261 -0
- somef_core/test/test_data/api_responses/somef-demo.json +648 -0
- somef_core/test/test_data/api_responses/widoco_api_response.json +2779 -0
- somef_core/test/test_data/authors_files/docker_AUTHORS.txt +718 -0
- somef_core/test/test_data/authors_files/ireeOrg_AUTHORS.txt +13 -0
- somef_core/test/test_data/authors_files/jetBrain_AUTHORS.txt +10 -0
- somef_core/test/test_data/authors_files/pluginsGLPI_AUTHORS.txt +9 -0
- somef_core/test/test_data/authors_files/tensorFlow_AUTHORS.txt +11 -0
- somef_core/test/test_data/expected/Widoco.yaml +29 -0
- somef_core/test/test_data/expected/aladin-lite.yaml +22 -0
- somef_core/test/test_data/expected/gammapy.yaml +31 -0
- somef_core/test/test_data/expected/r3broot2.yaml +44 -0
- somef_core/test/test_data/expected/runtime_platform_repo.yaml +9 -0
- somef_core/test/test_data/export_test.json +142 -0
- somef_core/test/test_data/extract_bash_code.txt +37 -0
- somef_core/test/test_data/input-test.txt +2 -0
- somef_core/test/test_data/package_neors.json +29 -0
- somef_core/test/test_data/package_npm.json +258 -0
- somef_core/test/test_data/pyansys-README.rst +222 -0
- somef_core/test/test_data/rasterio-README.md +360 -0
- somef_core/test/test_data/repositories/A-Dynamic-E-Commerce-Website-master/README.md +12 -0
- somef_core/test/test_data/repositories/A-Dynamic-E-Commerce-Website-master/admin.php +75 -0
- somef_core/test/test_data/repositories/A-Dynamic-E-Commerce-Website-master/admin_orders.php +53 -0
- somef_core/test/test_data/repositories/A-Dynamic-E-Commerce-Website-master/delete_from_cart.php +20 -0
- somef_core/test/test_data/repositories/A-Dynamic-E-Commerce-Website-master/mangola.sql +327 -0
- somef_core/test/test_data/repositories/Clamp/README.md +222 -0
- somef_core/test/test_data/repositories/Fairwinds/Dockerfile +14 -0
- somef_core/test/test_data/repositories/Fermi/README.md +164 -0
- somef_core/test/test_data/repositories/Flux.jl/Project.toml +71 -0
- somef_core/test/test_data/repositories/JAFFA-master/README +9 -0
- somef_core/test/test_data/repositories/JAFFA-master/README.md +4 -0
- somef_core/test/test_data/repositories/JAFFA-master/cwl/converter.cwl +33 -0
- somef_core/test/test_data/repositories/JAFFA-master/cwl/smcFusion-jaffa-workflow.cwl +62 -0
- somef_core/test/test_data/repositories/JAFFA-master/cwl/tar.cwl +23 -0
- somef_core/test/test_data/repositories/JAFFA-master/cwl/twoBitToFa.cwl +37 -0
- somef_core/test/test_data/repositories/OWL-To-OAS-Specification-master/README.md +6 -0
- somef_core/test/test_data/repositories/OWL-To-OAS-Specification-master/mkdocs.yml +13 -0
- somef_core/test/test_data/repositories/OWL-To-OAS-Specification-master/requirements.txt +1 -0
- somef_core/test/test_data/repositories/Pluto.jl/Project.toml +84 -0
- somef_core/test/test_data/repositories/Prometeus/Dockerfile +31 -0
- somef_core/test/test_data/repositories/RDFChess/.gitignore +1 -0
- somef_core/test/test_data/repositories/RDFChess/README.md +20 -0
- somef_core/test/test_data/repositories/RDFChess/pom.xml +202 -0
- somef_core/test/test_data/repositories/RDFChess/rdfchess.config +0 -0
- somef_core/test/test_data/repositories/RDFChess/run.bat +1 -0
- somef_core/test/test_data/repositories/Widoco/CITATION.cff +20 -0
- somef_core/test/test_data/repositories/Widoco/CONTRIBUTING.md +2 -0
- somef_core/test/test_data/repositories/Widoco/Dockerfile +18 -0
- somef_core/test/test_data/repositories/Widoco/LICENSE +202 -0
- somef_core/test/test_data/repositories/Widoco/README.md +187 -0
- somef_core/test/test_data/repositories/Widoco/codemeta.json +62 -0
- somef_core/test/test_data/repositories/Widoco/docs/pom.sml +167 -0
- somef_core/test/test_data/repositories/Widoco/docs/pom.xml +167 -0
- somef_core/test/test_data/repositories/Widoco/example_onto/CITATION.cff +20 -0
- somef_core/test/test_data/repositories/Widoco/example_onto/README.md +188 -0
- somef_core/test/test_data/repositories/Widoco/example_onto/license.md +14 -0
- somef_core/test/test_data/repositories/Widoco/example_onto/ontology.nt +153 -0
- somef_core/test/test_data/repositories/Widoco/example_onto/ontology.ttl +159 -0
- somef_core/test/test_data/repositories/Widoco/example_onto/test.ttl +9 -0
- somef_core/test/test_data/repositories/Widoco/pom.xml +172 -0
- somef_core/test/test_data/repositories/Widoco/target/maven-archiver/pom.properties +3 -0
- somef_core/test/test_data/repositories/Widoco/test/CITATION.cff +20 -0
- somef_core/test/test_data/repositories/Widoco/test/test/CITATION.cff +20 -0
- somef_core/test/test_data/repositories/Widoco/test/test/test/CITATION.cff +20 -0
- somef_core/test/test_data/repositories/ack/ACKNOWLEDGMENT.md +1 -0
- somef_core/test/test_data/repositories/ack/README.md +4 -0
- somef_core/test/test_data/repositories/aladin-lite/codemeta.json +83 -0
- somef_core/test/test_data/repositories/auroral-ontology-core/ontology.rdf +2306 -0
- somef_core/test/test_data/repositories/auroral-ontology-core/ontology.xml +981 -0
- somef_core/test/test_data/repositories/auroral-ontology-core/readme.md +34 -0
- somef_core/test/test_data/repositories/basis_functions_approach_to_GP-master/BF_1D_notebook.Rmd +1041 -0
- somef_core/test/test_data/repositories/basis_functions_approach_to_GP-master/Diagnosis_HSGP_v2.Rmd +938 -0
- somef_core/test/test_data/repositories/basis_functions_approach_to_GP-master/Rnb_case3D_diagnosis.Rmd +1082 -0
- somef_core/test/test_data/repositories/bimerr-epw/LICENSE +201 -0
- somef_core/test/test_data/repositories/bimerr-epw/README.md +23 -0
- somef_core/test/test_data/repositories/bootstrap-datepicker-rails/bootstrap-datepicker-rails-2.gemspec +22 -0
- somef_core/test/test_data/repositories/bootstrap-datepicker-rails/bootstrap-datepicker-rails.gemspec +22 -0
- somef_core/test/test_data/repositories/captum/.gitattributes +2 -0
- somef_core/test/test_data/repositories/captum/.gitignore +117 -0
- somef_core/test/test_data/repositories/captum/AWESOME_LIST.md +28 -0
- somef_core/test/test_data/repositories/captum/CITATION +8 -0
- somef_core/test/test_data/repositories/captum/CODE_OF_CONDUCT.md +76 -0
- somef_core/test/test_data/repositories/captum/CONTRIBUTING.md +134 -0
- somef_core/test/test_data/repositories/captum/LICENSE +29 -0
- somef_core/test/test_data/repositories/captum/README.md +494 -0
- somef_core/test/test_data/repositories/captum/environment.yml +6 -0
- somef_core/test/test_data/repositories/captum/pyproject.toml +2 -0
- somef_core/test/test_data/repositories/captum/setup.cfg +12 -0
- somef_core/test/test_data/repositories/captum/setup.py +171 -0
- somef_core/test/test_data/repositories/chosen/bower.json +42 -0
- somef_core/test/test_data/repositories/codecov/composer.json +25 -0
- somef_core/test/test_data/repositories/codemeta_repo/codemeta.json +214 -0
- somef_core/test/test_data/repositories/componentInstaller/CITATION.cff +23 -0
- somef_core/test/test_data/repositories/componentInstaller/LICENSE +674 -0
- somef_core/test/test_data/repositories/componentInstaller/README.md +138 -0
- somef_core/test/test_data/repositories/composer/composer.json +110 -0
- somef_core/test/test_data/repositories/corpuser/README.md +38 -0
- somef_core/test/test_data/repositories/cropwater/DESCRIPTION +61 -0
- somef_core/test/test_data/repositories/cropwater/LICENSE.md +21 -0
- somef_core/test/test_data/repositories/cropwater/README.md +649 -0
- somef_core/test/test_data/repositories/cropwater/codemeta.json +159 -0
- somef_core/test/test_data/repositories/fair-ontologies/README.md +117 -0
- somef_core/test/test_data/repositories/fair-ontologies/citation.cff +23 -0
- somef_core/test/test_data/repositories/fair-ontologies/codemeta.json +133 -0
- somef_core/test/test_data/repositories/fair-ontologies/pom.xml +186 -0
- somef_core/test/test_data/repositories/fuji/AUTHORS +19 -0
- somef_core/test/test_data/repositories/fuji/CITATION.cff +35 -0
- somef_core/test/test_data/repositories/fuji/README.md +265 -0
- somef_core/test/test_data/repositories/fuji/pyproject.toml +169 -0
- somef_core/test/test_data/repositories/gammalearn/pyproject.toml +79 -0
- somef_core/test/test_data/repositories/gammapy/codemeta.json +376 -0
- somef_core/test/test_data/repositories/ggplot2/DESCRIPTION +132 -0
- somef_core/test/test_data/repositories/ggstatsplot/DESCRIPTION +86 -0
- somef_core/test/test_data/repositories/ggstatsplot/codemeta.json +638 -0
- somef_core/test/test_data/repositories/haskell/cabal.cabal +419 -0
- somef_core/test/test_data/repositories/inspect4py/CITATION.cff +25 -0
- somef_core/test/test_data/repositories/inspect4py/README.md +255 -0
- somef_core/test/test_data/repositories/inspect4py/docs/requirements.txt +1 -0
- somef_core/test/test_data/repositories/inspect4py/pyproject.toml +15 -0
- somef_core/test/test_data/repositories/inspect4py/requirements.txt +12 -0
- somef_core/test/test_data/repositories/inspect4py/setup.py +51 -0
- somef_core/test/test_data/repositories/ipynb-master/__init__.py +29 -0
- somef_core/test/test_data/repositories/ipynb-master/bogus_ipynb.ipynb +2 -0
- somef_core/test/test_data/repositories/ipynb-master/r_notebook.ipynb +40 -0
- somef_core/test/test_data/repositories/js-template/bower.json +26 -0
- somef_core/test/test_data/repositories/js-template/package.json +25 -0
- somef_core/test/test_data/repositories/kgtk-notebooks/LICENSE +21 -0
- somef_core/test/test_data/repositories/kgtk-notebooks/README.md +81 -0
- somef_core/test/test_data/repositories/kgtk-notebooks/tutorial/01-kgtk-introduction.ipynb +2455 -0
- somef_core/test/test_data/repositories/kgtk-notebooks/tutorial/02-kg-profiling.ipynb +4924 -0
- somef_core/test/test_data/repositories/kgtk-notebooks/tutorial/03-kg-graph-embeddings.ipynb +819 -0
- somef_core/test/test_data/repositories/kgtk-notebooks/tutorial/04-kg-enrichment-with-csv.ipynb +3570 -0
- somef_core/test/test_data/repositories/kgtk-notebooks/tutorial/05-kg-enrichment-with-lod.ipynb +3297 -0
- somef_core/test/test_data/repositories/kgtk-notebooks/tutorial/06-kg-network-analysis.ipynb +2363 -0
- somef_core/test/test_data/repositories/kgtk-notebooks/tutorial/07-kg-constraint-validation.ipynb +1423 -0
- somef_core/test/test_data/repositories/kgtk-notebooks/tutorial/kgtk-browser.ipynb +308 -0
- somef_core/test/test_data/repositories/kgtk-notebooks/use-cases/Visualize-force-graph-example.ipynb +937 -0
- somef_core/test/test_data/repositories/kgtk-notebooks/use-cases/browser-profiling.ipynb +1583 -0
- somef_core/test/test_data/repositories/kgtk-notebooks/use-cases/class-visualization.ipynb +7509 -0
- somef_core/test/test_data/repositories/kgtk-notebooks/use-cases/ontology-examples.ipynb +2002 -0
- somef_core/test/test_data/repositories/kgtk-notebooks/use-cases/properties-for-this-type.ipynb +1474 -0
- somef_core/test/test_data/repositories/maven/pom.xml +1275 -0
- somef_core/test/test_data/repositories/maykinMedia/publiccode.yaml +112 -0
- somef_core/test/test_data/repositories/mir_eval/LICENSE.txt +21 -0
- somef_core/test/test_data/repositories/mir_eval/README.rst +25 -0
- somef_core/test/test_data/repositories/mir_eval/setup.py +37 -0
- somef_core/test/test_data/repositories/mira/LICENSE +201 -0
- somef_core/test/test_data/repositories/mira/README.md +124 -0
- somef_core/test/test_data/repositories/nemo/CITATION.cff +23 -0
- somef_core/test/test_data/repositories/nemo/README.md +185 -0
- somef_core/test/test_data/repositories/pcake/README.md +4 -0
- somef_core/test/test_data/repositories/pcake/setup.py +28 -0
- somef_core/test/test_data/repositories/probot-12.1.1/.editorconfig +9 -0
- somef_core/test/test_data/repositories/probot-12.1.1/.gitignore +12 -0
- somef_core/test/test_data/repositories/probot-12.1.1/CODE_OF_CONDUCT.md +132 -0
- somef_core/test/test_data/repositories/probot-12.1.1/CONTRIBUTING.md +63 -0
- somef_core/test/test_data/repositories/probot-12.1.1/LICENSE +15 -0
- somef_core/test/test_data/repositories/probot-12.1.1/README.md +51 -0
- somef_core/test/test_data/repositories/probot-12.1.1/README.pt-br.md +40 -0
- somef_core/test/test_data/repositories/probot-12.1.1/codecov.yml +11 -0
- somef_core/test/test_data/repositories/publicCode-Example/publiccode.yml +131 -0
- somef_core/test/test_data/repositories/r3broot2/codemeta.json +437 -0
- somef_core/test/test_data/repositories/rdflib-6.0.2/CHANGELOG.md +2255 -0
- somef_core/test/test_data/repositories/rdflib-6.0.2/CONTRIBUTORS +57 -0
- somef_core/test/test_data/repositories/rdflib-6.0.2/LICENSE +29 -0
- somef_core/test/test_data/repositories/rdflib-6.0.2/MANIFEST.in +13 -0
- somef_core/test/test_data/repositories/rdflib-6.0.2/Makefile +20 -0
- somef_core/test/test_data/repositories/rdflib-6.0.2/README.md +226 -0
- somef_core/test/test_data/repositories/rdflib-6.0.2/black.toml +31 -0
- somef_core/test/test_data/repositories/rdflib-6.0.2/docker-compose.tests.yml +24 -0
- somef_core/test/test_data/repositories/rdflib-6.0.2/pull_request_template.md +7 -0
- somef_core/test/test_data/repositories/rdflib-6.0.2/requirements.dev.txt +12 -0
- somef_core/test/test_data/repositories/rdflib-6.0.2/requirements.txt +3 -0
- somef_core/test/test_data/repositories/rdflib-6.0.2/run_tests.py +92 -0
- somef_core/test/test_data/repositories/rdflib-6.0.2/run_tests.sh +9 -0
- somef_core/test/test_data/repositories/rdflib-6.0.2/run_tests_with_coverage_report.sh +9 -0
- somef_core/test/test_data/repositories/rdflib-6.0.2/setup.cfg +38 -0
- somef_core/test/test_data/repositories/rdflib-6.0.2/setup.py +90 -0
- somef_core/test/test_data/repositories/rdflib-6.0.2/skiptests.list +59 -0
- somef_core/test/test_data/repositories/rdflib-6.0.2/tox.ini +35 -0
- somef_core/test/test_data/repositories/repos_oeg/bimerr-core/README.md +1 -0
- somef_core/test/test_data/repositories/resolver_deco/LICENSE.txt +12 -0
- somef_core/test/test_data/repositories/resolver_deco/README.txt +56 -0
- somef_core/test/test_data/repositories/resolver_deco/resolver_deco.py +49 -0
- somef_core/test/test_data/repositories/resolver_deco/setup.py +20 -0
- somef_core/test/test_data/repositories/runtime_platform_repo/codemeta.json +6 -0
- somef_core/test/test_data/repositories/rustdesk/Cargo.toml +223 -0
- somef_core/test/test_data/repositories/soca/README.md +301 -0
- somef_core/test/test_data/repositories/soca/codemeta.json +75 -0
- somef_core/test/test_data/repositories/soca/pyproject.toml +6 -0
- somef_core/test/test_data/repositories/soca/setup.cfg +45 -0
- somef_core/test/test_data/repositories/software_catalog/LICENSE +201 -0
- somef_core/test/test_data/repositories/software_catalog/README.md +21 -0
- somef_core/test/test_data/repositories/software_catalog/repo_icons/acknowledgement.png +0 -0
- somef_core/test/test_data/repositories/software_catalog/repo_icons/citation.png +0 -0
- somef_core/test/test_data/repositories/somef_repo/CITATION.cff +43 -0
- somef_core/test/test_data/repositories/somef_repo/README.md +361 -0
- somef_core/test/test_data/repositories/somef_repo/lib/python/pip-24.2.dist-info/AUTHORS.txt +796 -0
- somef_core/test/test_data/repositories/somef_repo/pyproject.toml +55 -0
- somef_core/test/test_data/repositories/sprint-main/netex_accounting_version.xsd.rdf +425 -0
- somef_core/test/test_data/repositories/sprint-main/setup.sh +2 -0
- somef_core/test/test_data/repositories/stable-diffusion/README.md +214 -0
- somef_core/test/test_data/repositories/stable-diffusion/environment.yaml +31 -0
- somef_core/test/test_data/repositories/sunpy/CITATION.cff +79 -0
- somef_core/test/test_data/repositories/sunpy/pyproject.toml +256 -0
- somef_core/test/test_data/repositories/sunpy_short_desc/README.rst +82 -0
- somef_core/test/test_data/repositories/sunpy_short_desc/pyproject.toml +256 -0
- somef_core/test/test_data/repositories/tensorflow/CODEOWNERS +18 -0
- somef_core/test/test_data/repositories/tensorflow/README.md +173 -0
- somef_core/test/test_data/repositories/termlex-main/README.md +14 -0
- somef_core/test/test_data/repositories/termlex-main/ontology/termlex.ttl +2150 -0
- somef_core/test/test_data/repositories/termlex-main/termlex_core.png +0 -0
- somef_core/test/test_data/repositories/tidyverse/DESCRIPTION +63 -0
- somef_core/test/test_data/repositories/unused/unused.cabal +127 -0
- somef_core/test/test_data/repositories/wav2letter/CMakeLists.txt +168 -0
- somef_core/test/test_data/repositories/wav2letter/CODE_OF_CONDUCT.md +5 -0
- somef_core/test/test_data/repositories/wav2letter/CONTRIBUTING.md +27 -0
- somef_core/test/test_data/repositories/wav2letter/Decode.cpp +440 -0
- somef_core/test/test_data/repositories/wav2letter/LICENSE +30 -0
- somef_core/test/test_data/repositories/wav2letter/README.md +39 -0
- somef_core/test/test_data/repositories/wav2letter/Test.cpp +184 -0
- somef_core/test/test_data/repositories/wav2letter/Train.cpp +564 -0
- somef_core/test/test_data/repositories/wav2letter/docs/DecoderSketch.png +0 -0
- somef_core/test/test_data/repositories/wav2letter/docs/arch.md +68 -0
- somef_core/test/test_data/repositories/wav2letter/docs/decoder.md +80 -0
- somef_core/test/test_data/repositories/wav2letter/docs/installation.md +50 -0
- somef_core/test/test_data/repositories/wav2letter/docs/train.md +86 -0
- somef_core/test/test_data/repositories/wav2letter/scripts/OptimizeSortParams.cpp +156 -0
- somef_core/test/test_data/repositories/wav2letter/scripts/Seq2SeqAnalysis.cpp +215 -0
- somef_core/test/test_data/repositories/wav2letter/scripts/arrayfire_parser.py +65 -0
- somef_core/test/test_data/repositories/wav2letter/scripts/fair_cluster/w2l_fair_cluster_build.sh +37 -0
- somef_core/test/test_data/repositories/wav2letter/scripts/fair_cluster/w2l_fair_cluster_decode.sh +34 -0
- somef_core/test/test_data/repositories/wav2letter/scripts/fair_cluster/w2l_fair_cluster_distributed_training_benchmarks.sh +59 -0
- somef_core/test/test_data/repositories/wav2letter/scripts/fair_cluster/w2l_fair_cluster_train.sh +48 -0
- somef_core/test/test_data/repositories/wav2letter/scripts/imgcat +118 -0
- somef_core/test/test_data/repositories/wav2letter/scripts/launch_distributed_chronos.sh +60 -0
- somef_core/test/test_data/repositories/wav2letter/scripts/tensorboard.py +206 -0
- somef_core/test/test_data/repositories/wav2letter/scripts/wavdiff +53 -0
- somef_core/test/test_data/repositories/wav2letter/scripts/wavextract +180 -0
- somef_core/test/test_data/repositories/wav2letter/scripts/wavplot +170 -0
- somef_core/test/test_data/repositories/wav2letter/scripts/wavstatus +94 -0
- somef_core/test/test_data/repositories/website-static-master/fonts.css +15 -0
- somef_core/test/test_data/repositories/website-static-master/header.js +32 -0
- somef_core/test/test_data/repositories/website-static-master/image.js +32 -0
- somef_core/test/test_data/repositories/website-static-master/index.html +1 -0
- somef_core/test/test_data/repositories/website-static-master/index.js +126 -0
- somef_core/test/test_data/repositories/website-static-master/layout.js +102 -0
- somef_core/test/test_data/repositories/website-static-master/privacy.js +47 -0
- somef_core/test/test_data/repositories/website-static-master/terms.js +119 -0
- somef_core/test/test_data/repositories/workloopr/LICENSE +674 -0
- somef_core/test/test_data/repositories/workloopr/README.md +172 -0
- somef_core/test/test_data/repositories/workloopr/codemeta.json +144 -0
- somef_core/test/test_data/repositories/wot-hive/README.md +111 -0
- somef_core/test/test_data/repositories/wot-hive/docker/auroral-hive/Dockerfile +15 -0
- somef_core/test/test_data/repositories/wot-hive/docker/docker-compose.yml +26 -0
- somef_core/test/test_data/repositories/wot-hive/docker/fuseki/Dockerfile +9 -0
- somef_core/test/test_data/repostatus-README-2.md +50 -0
- somef_core/test/test_data/repostatus-README.md +50 -0
- somef_core/test/test_data/test_465.md +388 -0
- somef_core/test/test_data/test_arxiv_links.txt +95 -0
- somef_core/test/test_data/test_extract_bibtex.txt +17 -0
- somef_core/test/test_data/test_extract_binder_links.txt +4 -0
- somef_core/test/test_data/test_extract_dois.txt +8 -0
- somef_core/test/test_data/test_extract_gitter_chat.txt +2 -0
- somef_core/test/test_data/test_extract_header_content_hash.txt +13 -0
- somef_core/test/test_data/test_extract_header_content_hash_complex.txt +11 -0
- somef_core/test/test_data/test_extract_header_content_underline.txt +18 -0
- somef_core/test/test_data/test_extract_readthedocs_1.txt +2 -0
- somef_core/test/test_data/test_extract_readthedocs_2.txt +6 -0
- somef_core/test/test_data/test_extract_readthedocs_3.txt +6 -0
- somef_core/test/test_data/test_extract_title_hash.txt +6 -0
- somef_core/test/test_data/test_extract_title_underline.txt +6 -0
- somef_core/test/test_data/test_extract_title_with_md.txt +2 -0
- somef_core/test/test_data/test_images.txt +68 -0
- somef_core/test/test_data/test_issue_181.txt +16 -0
- somef_core/test/test_data/test_issue_181_2.txt +135 -0
- somef_core/test/test_data/test_issue_181_3.txt +110 -0
- somef_core/test/test_data/test_issue_270.txt +15 -0
- somef_core/test/test_data/test_issue_images.txt +9 -0
- somef_core/test/test_data/test_logo.txt +5 -0
- somef_core/test/test_data/test_logo2.txt +5 -0
- somef_core/test/test_data/test_logo_tensorflow.txt +157 -0
- somef_core/test/test_data/test_logo_uscisii2.txt +95 -0
- somef_core/test/test_data/test_pypi.md +26 -0
- somef_core/test/test_data/test_repo_status.txt +10 -0
- somef_core/test/test_data/test_wiki.md +46 -0
- somef_core/test/test_data/widoco_readme.md +152 -0
- somef_core/test/test_description_parser.py +116 -0
- somef_core/test/test_dockerfile_parser.py +190 -0
- somef_core/test/test_enrichment.py +169 -0
- somef_core/test/test_extract_authors.py +71 -0
- somef_core/test/test_extract_workflows.py +18 -0
- somef_core/test/test_gemspec_parser.py +77 -0
- somef_core/test/test_gitlab_selfhosted.py +26 -0
- somef_core/test/test_gitlab_token_handling.py +118 -0
- somef_core/test/test_google_compliant_export.py +50 -0
- somef_core/test/test_header_analysis.py +284 -0
- somef_core/test/test_package_json_parser.py +124 -0
- somef_core/test/test_parser_somef.py +95 -0
- somef_core/test/test_pom_parser.py +118 -0
- somef_core/test/test_process_repository.py +1350 -0
- somef_core/test/test_process_results.py +23 -0
- somef_core/test/test_publiccode_parser.py +144 -0
- somef_core/test/test_python_parser.py +56 -0
- somef_core/test/test_regular_expressions.py +677 -0
- somef_core/test/test_setupcfg_parser.py +53 -0
- somef_core/test/test_software_type.py +139 -0
- somef_core/test/test_toml_parser.py +375 -0
- somef_core/test/test_verify_token.py +62 -0
- somef_core/test/test_wiki.py +69 -0
- somef_core/utils/__init__.py +0 -0
- somef_core/utils/constants.py +794 -0
- somef_core/utils/enrichment.py +223 -0
- somef_core/utils/markdown_utils.py +79 -0
- somef_core-0.0.1.dist-info/METADATA +545 -0
- somef_core-0.0.1.dist-info/RECORD +423 -0
- somef_core-0.0.1.dist-info/WHEEL +4 -0
- somef_core-0.0.1.dist-info/entry_points.txt +3 -0
- somef_core-0.0.1.dist-info/licenses/LICENSE +19 -0
|
@@ -0,0 +1,1197 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
import re
|
|
4
|
+
import unicodedata
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from urllib.parse import urlparse, urlunparse
|
|
7
|
+
from typing import List, Dict
|
|
8
|
+
import yaml
|
|
9
|
+
from dateutil import parser as date_parser
|
|
10
|
+
from ..utils import constants
|
|
11
|
+
from ..regular_expressions import detect_license_spdx,extract_scholarly_article_natural, extract_scholarly_article_properties
|
|
12
|
+
|
|
13
|
+
def save_json_output(repo_data, out_path, missing, pretty=False):
|
|
14
|
+
"""
|
|
15
|
+
Function that saves the final json Object in the output file
|
|
16
|
+
Parameters
|
|
17
|
+
----------
|
|
18
|
+
@param repo_data: dictionary with the metadata to be saved
|
|
19
|
+
@param out_path: output path where to save the JSON
|
|
20
|
+
@param missing: print the categories SOMEF was not able to find
|
|
21
|
+
@param pretty: format option to print the JSON in a human-readable way
|
|
22
|
+
|
|
23
|
+
Returns
|
|
24
|
+
-------
|
|
25
|
+
@return: Does not return a value
|
|
26
|
+
"""
|
|
27
|
+
logging.info("Saving json data to %s", out_path)
|
|
28
|
+
if missing:
|
|
29
|
+
# add a new key-value papir to the dictionary
|
|
30
|
+
repo_data[constants.CAT_MISSING] = create_missing_fields(repo_data)
|
|
31
|
+
with open(out_path, 'w') as output:
|
|
32
|
+
if pretty:
|
|
33
|
+
json.dump(repo_data, output, sort_keys=True, indent=2)
|
|
34
|
+
else:
|
|
35
|
+
json.dump(repo_data, output)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def save_codemeta_output(repo_data, outfile, pretty=False, requirements_mode='all'):
|
|
39
|
+
"""
|
|
40
|
+
Function that saves a Codemeta JSONLD file with a summary of the results
|
|
41
|
+
|
|
42
|
+
Parameters
|
|
43
|
+
----------
|
|
44
|
+
@param repo_data: JSON with the results to translate to Codemeta
|
|
45
|
+
@param outfile: path where to save the codemeta file
|
|
46
|
+
@param pretty: option to show the JSON results in a nice format
|
|
47
|
+
@param requriments_mode: option to show all requriments or just machine readable
|
|
48
|
+
"""
|
|
49
|
+
def format_date(date_string):
|
|
50
|
+
date_object = date_parser.parse(date_string)
|
|
51
|
+
return date_object.strftime("%Y-%m-%d")
|
|
52
|
+
|
|
53
|
+
code_repository = None
|
|
54
|
+
if constants.CAT_CODE_REPOSITORY in repo_data:
|
|
55
|
+
code_repository = repo_data[constants.CAT_CODE_REPOSITORY][0][constants.PROP_RESULT][constants.PROP_VALUE]
|
|
56
|
+
|
|
57
|
+
codemeta_output = {
|
|
58
|
+
"@context": "https://w3id.org/codemeta/3.0",
|
|
59
|
+
"@type": ["SoftwareSourceCode", "SoftwareApplication"]
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
author_name = None
|
|
63
|
+
# add a check for the existence of the 'description' property, similar way to most properties in the method.
|
|
64
|
+
descriptions = None
|
|
65
|
+
if constants.CAT_DESCRIPTION in repo_data:
|
|
66
|
+
descriptions = repo_data[constants.CAT_DESCRIPTION]
|
|
67
|
+
|
|
68
|
+
descriptions_text = []
|
|
69
|
+
if descriptions is not None:
|
|
70
|
+
|
|
71
|
+
# priority descriptions: Codemeta, without codemeta but confidence 1, rest
|
|
72
|
+
codemeta_desc = [d for d in descriptions if d.get("technique") == "code_parser" and "codemeta.json" in d.get("source", "")]
|
|
73
|
+
github_desc = [d for d in descriptions if d.get("technique") == constants.GITHUB_API]
|
|
74
|
+
readme_desc = [d for d in descriptions if d.get("technique") not in ("code_parser", constants.GITHUB_API)]
|
|
75
|
+
|
|
76
|
+
if codemeta_desc:
|
|
77
|
+
# If codemeta just these
|
|
78
|
+
selected = codemeta_desc
|
|
79
|
+
elif github_desc:
|
|
80
|
+
# whitout codemeta, but we have descripciont with confidence 1
|
|
81
|
+
threshold_1 = [d for d in github_desc if d.get("confidence", 0) == 1]
|
|
82
|
+
selected = threshold_1 if threshold_1 else github_desc
|
|
83
|
+
else:
|
|
84
|
+
# Rest of descriptions
|
|
85
|
+
selected = sorted(readme_desc, key=lambda x: x.get("confidence", 0), reverse=True)[:1]
|
|
86
|
+
|
|
87
|
+
flat_descriptions = []
|
|
88
|
+
for d in selected:
|
|
89
|
+
value = d[constants.PROP_RESULT][constants.PROP_VALUE]
|
|
90
|
+
if isinstance(value, list):
|
|
91
|
+
for v in value:
|
|
92
|
+
if v not in flat_descriptions:
|
|
93
|
+
flat_descriptions.append(v)
|
|
94
|
+
else:
|
|
95
|
+
if value not in flat_descriptions:
|
|
96
|
+
flat_descriptions.append(value)
|
|
97
|
+
|
|
98
|
+
descriptions_text = flat_descriptions
|
|
99
|
+
|
|
100
|
+
if constants.CAT_LICENSE in repo_data:
|
|
101
|
+
# We mix the name of the license from github API with the URL of the file (if found)
|
|
102
|
+
l_result = {}
|
|
103
|
+
is_gitlab = False
|
|
104
|
+
|
|
105
|
+
for l in repo_data[constants.CAT_LICENSE]:
|
|
106
|
+
if constants.PROP_NAME in l[constants.PROP_RESULT].keys():
|
|
107
|
+
l_result["name"] = l[constants.PROP_RESULT][constants.PROP_NAME]
|
|
108
|
+
if l[constants.PROP_TECHNIQUE] == "GitLab_API" and l[constants.PROP_RESULT].get("type") == "Url":
|
|
109
|
+
is_gitlab = True
|
|
110
|
+
l_result["url"] = l[constants.PROP_RESULT][constants.PROP_VALUE]
|
|
111
|
+
|
|
112
|
+
if is_gitlab:
|
|
113
|
+
if l[constants.PROP_RESULT][constants.PROP_TYPE] == "File_dump":
|
|
114
|
+
license_info = detect_license_spdx(l[constants.PROP_RESULT][constants.PROP_VALUE], 'CODEMETA')
|
|
115
|
+
if license_info:
|
|
116
|
+
l_result["name"] = license_info['name']
|
|
117
|
+
l_result["identifier"] = license_info['identifier']
|
|
118
|
+
break
|
|
119
|
+
|
|
120
|
+
# checking if PROP_URL is in the keys PROP_RESULT and key "url" is not in results
|
|
121
|
+
if "url" not in l_result.keys() and constants.PROP_URL in l[constants.PROP_RESULT].keys():
|
|
122
|
+
l_result["url"] = l[constants.PROP_RESULT][constants.PROP_URL]
|
|
123
|
+
# else:
|
|
124
|
+
# print(f"PROP_URL key not found in: {l[constants.PROP_RESULT].keys()}")
|
|
125
|
+
|
|
126
|
+
# Thist block run if url is not found in the previous
|
|
127
|
+
if l[constants.PROP_TECHNIQUE] == constants.TECHNIQUE_FILE_EXPLORATION and constants.PROP_SOURCE in l.keys():
|
|
128
|
+
|
|
129
|
+
if "url" in l_result and "api.github.com" in l_result["url"]:
|
|
130
|
+
l_result["url"] = l[constants.PROP_SOURCE]
|
|
131
|
+
else:
|
|
132
|
+
if "url" not in l_result.keys() and constants.PROP_URL in l[constants.PROP_RESULT].keys():
|
|
133
|
+
l_result["url"] = l[constants.PROP_RESULT][constants.PROP_URL]
|
|
134
|
+
if constants.PROP_SPDX_ID in l[constants.PROP_RESULT].keys():
|
|
135
|
+
l_result["identifier"] = constants.SPDX_BASE + l[constants.PROP_RESULT][constants.PROP_SPDX_ID]
|
|
136
|
+
# spdx_id does not exist in codemeta
|
|
137
|
+
# l_result["spdx_id"] = l[constants.PROP_RESULT][constants.PROP_SPDX_ID]
|
|
138
|
+
|
|
139
|
+
codemeta_output[constants.CAT_CODEMETA_LICENSE] = l_result
|
|
140
|
+
|
|
141
|
+
if code_repository is not None:
|
|
142
|
+
codemeta_output[constants.CAT_CODEMETA_CODEREPOSITORY] = code_repository
|
|
143
|
+
trackers = repo_data.get(constants.CAT_ISSUE_TRACKER, [])
|
|
144
|
+
codemeta_output[constants.CAT_CODEMETA_ISSUETRACKER] = resolve_issue_tracker(trackers, code_repository)
|
|
145
|
+
|
|
146
|
+
if constants.CAT_DATE_CREATED in repo_data:
|
|
147
|
+
value = repo_data[constants.CAT_DATE_CREATED][0][constants.PROP_RESULT][constants.PROP_VALUE]
|
|
148
|
+
if value:
|
|
149
|
+
codemeta_output[constants.CAT_CODEMETA_DATECREATED] = format_date(value)
|
|
150
|
+
if constants.CAT_DATE_UPDATED in repo_data:
|
|
151
|
+
value = repo_data[constants.CAT_DATE_UPDATED][0][constants.PROP_RESULT][constants.PROP_VALUE]
|
|
152
|
+
if value:
|
|
153
|
+
codemeta_output[constants.CAT_CODEMETA_DATEMODIFIED] = format_date(value)
|
|
154
|
+
if constants.CAT_COPYRIGHT in repo_data:
|
|
155
|
+
codemeta_output[constants.CAT_CODEMETA_COPYRIGHTHOLDER] = []
|
|
156
|
+
for copyright in repo_data[constants.CAT_COPYRIGHT]:
|
|
157
|
+
holder = copyright[constants.PROP_RESULT][constants.PROP_VALUE]
|
|
158
|
+
year = copyright[constants.PROP_RESULT].get(constants.PROP_YEAR)
|
|
159
|
+
if holder:
|
|
160
|
+
codemeta_output[constants.CAT_CODEMETA_COPYRIGHTHOLDER].append({constants.PROP_NAME: holder})
|
|
161
|
+
if year:
|
|
162
|
+
codemeta_output[constants.CAT_CODEMETA_COPYRIGHTYEAR] = year
|
|
163
|
+
if constants.CAT_DOWNLOAD_URL in repo_data:
|
|
164
|
+
codemeta_output[constants.CAT_CODEMETA_DOWNLOADURL] = repo_data[constants.CAT_DOWNLOAD_URL][0][constants.PROP_RESULT][constants.PROP_VALUE]
|
|
165
|
+
if constants.CAT_NAME in repo_data:
|
|
166
|
+
codemeta_output[constants.CAT_CODEMETA_NAME] = repo_data[constants.CAT_NAME][0][constants.PROP_RESULT][constants.PROP_VALUE]
|
|
167
|
+
if constants.CAT_LOGO in repo_data:
|
|
168
|
+
codemeta_output[constants.CAT_CODEMETA_LOGO] = repo_data[constants.CAT_LOGO][0][constants.PROP_RESULT][constants.PROP_VALUE]
|
|
169
|
+
if constants.CAT_KEYWORDS in repo_data:
|
|
170
|
+
codemeta_output[constants.CAT_CODEMETA_KEYWORDS] = []
|
|
171
|
+
for key in repo_data[constants.CAT_KEYWORDS]:
|
|
172
|
+
key_value = key[constants.PROP_RESULT][constants.PROP_VALUE]
|
|
173
|
+
if isinstance(key_value, str):
|
|
174
|
+
items = [s.strip() for s in key_value.split(",") if s.strip()]
|
|
175
|
+
elif isinstance(key_value, list):
|
|
176
|
+
items = key_value
|
|
177
|
+
else:
|
|
178
|
+
continue
|
|
179
|
+
|
|
180
|
+
for item in items:
|
|
181
|
+
if item not in codemeta_output[constants.CAT_CODEMETA_KEYWORDS]:
|
|
182
|
+
codemeta_output[constants.CAT_CODEMETA_KEYWORDS].append(item)
|
|
183
|
+
|
|
184
|
+
if constants.CAT_FEATURES in repo_data:
|
|
185
|
+
codemeta_output[constants.CAT_CODEMETA_FEATURELIST] = []
|
|
186
|
+
for feature in repo_data[constants.CAT_FEATURES]:
|
|
187
|
+
feature_value = feature[constants.PROP_RESULT][constants.PROP_VALUE]
|
|
188
|
+
if feature_value and feature_value not in codemeta_output[constants.CAT_CODEMETA_FEATURELIST]:
|
|
189
|
+
codemeta_output[constants.CAT_CODEMETA_FEATURELIST].append(feature_value)
|
|
190
|
+
|
|
191
|
+
if constants.CAT_PROGRAMMING_LANGUAGES in repo_data:
|
|
192
|
+
# Calculate the total code size of all the programming languages
|
|
193
|
+
codemeta_output[constants.CAT_CODEMETA_PROGRAMMINGLANGUAGE] = []
|
|
194
|
+
language_data = repo_data.get(constants.CAT_PROGRAMMING_LANGUAGES, [])
|
|
195
|
+
total_size = 0
|
|
196
|
+
for item in language_data:
|
|
197
|
+
result = item.get(constants.PROP_RESULT, {})
|
|
198
|
+
size = result.get(constants.PROP_SIZE)
|
|
199
|
+
if size is not None:
|
|
200
|
+
total_size += size
|
|
201
|
+
# Discard languages below 10% of the total code size, only if size is available
|
|
202
|
+
for item in language_data:
|
|
203
|
+
result = item.get(constants.PROP_RESULT, {})
|
|
204
|
+
size = result.get(constants.PROP_SIZE)
|
|
205
|
+
value = result.get(constants.PROP_VALUE)
|
|
206
|
+
if value not in codemeta_output[constants.CAT_CODEMETA_PROGRAMMINGLANGUAGE]:
|
|
207
|
+
if size is not None and value is not None and total_size > 0:
|
|
208
|
+
percentage = (size / total_size) * 100
|
|
209
|
+
if percentage > constants.MINIMUM_PERCENTAGE_LANGUAGE_PROGRAMMING:
|
|
210
|
+
codemeta_output[constants.CAT_CODEMETA_PROGRAMMINGLANGUAGE].append(value)
|
|
211
|
+
elif size is None: # when size is not available, it comes from the parsers
|
|
212
|
+
codemeta_output[constants.CAT_CODEMETA_PROGRAMMINGLANGUAGE].append(value)
|
|
213
|
+
|
|
214
|
+
if constants.CAT_REQUIREMENTS in repo_data:
|
|
215
|
+
|
|
216
|
+
code_parser_requirements = []
|
|
217
|
+
seen_structured = set()
|
|
218
|
+
for x in repo_data[constants.CAT_REQUIREMENTS]:
|
|
219
|
+
technique = x.get(constants.PROP_TECHNIQUE)
|
|
220
|
+
technique_list = technique if isinstance(technique, list) else [technique]
|
|
221
|
+
if constants.TECHNIQUE_CODE_CONFIG_PARSER in technique_list:
|
|
222
|
+
|
|
223
|
+
source = x.get("source", "")
|
|
224
|
+
source_list = source if isinstance(source, list) else [source]
|
|
225
|
+
if any(src in s for s in source_list for src in constants.STRUCTURED_REQUIREMENTS_SOURCES):
|
|
226
|
+
name = x[constants.PROP_RESULT].get(constants.PROP_NAME) or x[constants.PROP_RESULT].get(constants.PROP_VALUE)
|
|
227
|
+
version = x[constants.PROP_RESULT].get(constants.PROP_VERSION)
|
|
228
|
+
# key = f"{name.strip()}|{version.strip() if version else ''}"
|
|
229
|
+
|
|
230
|
+
key = name.strip().lower()
|
|
231
|
+
|
|
232
|
+
if key not in seen_structured:
|
|
233
|
+
entry = {"name": name.strip()}
|
|
234
|
+
req_type = x[constants.PROP_RESULT].get("type")
|
|
235
|
+
if req_type:
|
|
236
|
+
entry["@type"] = map_requirement_type(req_type)
|
|
237
|
+
|
|
238
|
+
if version:
|
|
239
|
+
if isinstance(version, str):
|
|
240
|
+
entry["version"] = version.strip()
|
|
241
|
+
else:
|
|
242
|
+
entry["version"] = str(version)
|
|
243
|
+
|
|
244
|
+
code_parser_requirements.append(entry)
|
|
245
|
+
seen_structured.add(key)
|
|
246
|
+
|
|
247
|
+
other_requirements = []
|
|
248
|
+
seen_text = set()
|
|
249
|
+
for x in repo_data[constants.CAT_REQUIREMENTS]:
|
|
250
|
+
technique = x.get(constants.PROP_TECHNIQUE)
|
|
251
|
+
technique_list = technique if isinstance(technique, list) else [technique]
|
|
252
|
+
source = x.get("source")
|
|
253
|
+
source_list = source if isinstance(source, list) else [source] if source is not None else []
|
|
254
|
+
if not (
|
|
255
|
+
constants.TECHNIQUE_CODE_CONFIG_PARSER in technique_list
|
|
256
|
+
and source is not None
|
|
257
|
+
and any(src in s for s in source_list for src in constants.STRUCTURED_REQUIREMENTS_SOURCES)
|
|
258
|
+
):
|
|
259
|
+
result = x.get(constants.PROP_RESULT, {})
|
|
260
|
+
req_type = result.get("type", "")
|
|
261
|
+
if req_type in ("Url", "Text_excerpt"):
|
|
262
|
+
continue
|
|
263
|
+
|
|
264
|
+
value = result.get(constants.PROP_VALUE, "").strip().replace("\n", " ")
|
|
265
|
+
normalized = " ".join(value.split())
|
|
266
|
+
if normalized not in seen_text:
|
|
267
|
+
other_requirements.append(value)
|
|
268
|
+
seen_text.add(normalized)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
if requirements_mode == "v":
|
|
272
|
+
codemeta_output[constants.CAT_CODEMETA_SOFTWAREREQUIREMENTS] = code_parser_requirements
|
|
273
|
+
else:
|
|
274
|
+
if code_parser_requirements:
|
|
275
|
+
codemeta_output[constants.CAT_CODEMETA_SOFTWAREREQUIREMENTS] = code_parser_requirements
|
|
276
|
+
else:
|
|
277
|
+
codemeta_output[constants.CAT_CODEMETA_SOFTWAREREQUIREMENTS] = other_requirements
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
if constants.CAT_CONTINUOUS_INTEGRATION in repo_data:
|
|
281
|
+
codemeta_output[constants.CAT_CODEMETA_CONTINUOUSINTEGRATION] = repo_data[constants.CAT_CONTINUOUS_INTEGRATION][0][constants.PROP_RESULT][constants.PROP_VALUE]
|
|
282
|
+
# if constants.CAT_WORKFLOWS in repo_data:
|
|
283
|
+
# codemeta_output["workflows"] = repo_data[constants.CAT_WORKFLOWS][0][constants.PROP_RESULT][constants.PROP_VALUE]
|
|
284
|
+
if constants.CAT_RELEASES in repo_data:
|
|
285
|
+
|
|
286
|
+
latest_date = None
|
|
287
|
+
latest_version = None
|
|
288
|
+
latest_description = None
|
|
289
|
+
oldest_date = None
|
|
290
|
+
oldest_version = None
|
|
291
|
+
|
|
292
|
+
for l in repo_data[constants.CAT_RELEASES]:
|
|
293
|
+
release_date_str = l[constants.PROP_RESULT].get(constants.PROP_DATE_PUBLISHED)
|
|
294
|
+
|
|
295
|
+
if release_date_str:
|
|
296
|
+
release_date = datetime.fromisoformat(release_date_str.replace("Z", "+00:00"))
|
|
297
|
+
|
|
298
|
+
if latest_date is None or release_date > latest_date:
|
|
299
|
+
latest_date = release_date
|
|
300
|
+
latest_version = l[constants.PROP_RESULT].get(constants.PROP_TAG)
|
|
301
|
+
latest_description = l[constants.PROP_RESULT].get(constants.PROP_DESCRIPTION)
|
|
302
|
+
|
|
303
|
+
if oldest_date is None or release_date < oldest_date:
|
|
304
|
+
oldest_date = release_date
|
|
305
|
+
oldest_version = l[constants.PROP_RESULT].get(constants.PROP_TAG)
|
|
306
|
+
|
|
307
|
+
if latest_description is not None:
|
|
308
|
+
codemeta_output[constants.CAT_CODEMETA_RELEASENOTES] = latest_description
|
|
309
|
+
if latest_version is not None:
|
|
310
|
+
codemeta_output[constants.CAT_CODEMETA_SOFTWAREVERSION] = latest_version
|
|
311
|
+
if oldest_date is not None:
|
|
312
|
+
codemeta_output[constants.CAT_CODEMETA_DATEPUBLISHED] = format_date(oldest_date.isoformat())
|
|
313
|
+
|
|
314
|
+
install_links = []
|
|
315
|
+
if constants.CAT_INSTALLATION in repo_data:
|
|
316
|
+
for inst in repo_data[constants.CAT_INSTALLATION]:
|
|
317
|
+
if inst[constants.PROP_TECHNIQUE] == constants.TECHNIQUE_HEADER_ANALYSIS and constants.PROP_SOURCE in inst.keys():
|
|
318
|
+
install_links.append(inst[constants.PROP_SOURCE])
|
|
319
|
+
elif inst[constants.PROP_TECHNIQUE] == constants.TECHNIQUE_FILE_EXPLORATION:
|
|
320
|
+
install_links.append(inst[constants.PROP_RESULT][constants.PROP_VALUE])
|
|
321
|
+
|
|
322
|
+
if constants.CAT_DOCUMENTATION in repo_data:
|
|
323
|
+
for inst in repo_data[constants.CAT_DOCUMENTATION]:
|
|
324
|
+
|
|
325
|
+
if inst[constants.PROP_TECHNIQUE] == constants.TECHNIQUE_HEADER_ANALYSIS and constants.PROP_SOURCE in inst.keys():
|
|
326
|
+
install_links.append(inst[constants.PROP_SOURCE])
|
|
327
|
+
elif inst[constants.PROP_TECHNIQUE] == constants.TECHNIQUE_FILE_EXPLORATION or \
|
|
328
|
+
inst[constants.PROP_TECHNIQUE] == constants.TECHNIQUE_REGULAR_EXPRESSION:
|
|
329
|
+
install_links.append(inst[constants.PROP_RESULT][constants.PROP_VALUE])
|
|
330
|
+
if len(install_links) > 0:
|
|
331
|
+
# remove duplicates and generate codemeta
|
|
332
|
+
install_links = list(set(install_links))
|
|
333
|
+
codemeta_output[constants.CAT_CODEMETA_BUILDINSTRUCTIONS] = install_links
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
if constants.CAT_AUTHORS in repo_data:
|
|
337
|
+
if "author" not in codemeta_output:
|
|
338
|
+
codemeta_output[constants.CAT_CODEMETA_AUTHOR] = []
|
|
339
|
+
|
|
340
|
+
for author in repo_data[constants.CAT_AUTHORS]:
|
|
341
|
+
value_author = author[constants.PROP_RESULT].get(constants.PROP_VALUE)
|
|
342
|
+
name_author = author[constants.PROP_RESULT].get(constants.PROP_NAME)
|
|
343
|
+
if value_author and re.search(constants.REGEXP_LTD_INC, value_author, re.IGNORECASE):
|
|
344
|
+
type_author = "Organization"
|
|
345
|
+
else:
|
|
346
|
+
type_author = "Person"
|
|
347
|
+
|
|
348
|
+
author_l = {
|
|
349
|
+
"@type": type_author
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if type_author == "Organization":
|
|
353
|
+
if name_author:
|
|
354
|
+
author_l['name'] = name_author
|
|
355
|
+
elif value_author:
|
|
356
|
+
author_l['name'] = value_author
|
|
357
|
+
if author[constants.PROP_RESULT].get('email'):
|
|
358
|
+
author_l['email'] = author[constants.PROP_RESULT].get('email')
|
|
359
|
+
else:
|
|
360
|
+
family_name = None
|
|
361
|
+
given_name = None
|
|
362
|
+
|
|
363
|
+
if author[constants.PROP_RESULT].get('last_name'):
|
|
364
|
+
family_name = author[constants.PROP_RESULT].get('last_name')
|
|
365
|
+
author_l['familyName'] = family_name
|
|
366
|
+
if author[constants.PROP_RESULT].get('given_name'):
|
|
367
|
+
given_name = author[constants.PROP_RESULT].get('given_name')
|
|
368
|
+
author_l['givenName'] = given_name
|
|
369
|
+
if author[constants.PROP_RESULT].get('email'):
|
|
370
|
+
mail_author = author[constants.PROP_RESULT].get('email')
|
|
371
|
+
author_l['email'] = mail_author
|
|
372
|
+
|
|
373
|
+
if name_author:
|
|
374
|
+
author_l['name'] = name_author
|
|
375
|
+
else:
|
|
376
|
+
author_l['name'] = value_author
|
|
377
|
+
|
|
378
|
+
existing_authors = codemeta_output.get(constants.CAT_CODEMETA_AUTHOR, [])
|
|
379
|
+
# existing = next((a for a in existing_authors if a.get("name") == author_l["name"]), None)
|
|
380
|
+
existing = next(
|
|
381
|
+
(a for a in existing_authors
|
|
382
|
+
if (author_l.get("name") and a.get("name") == author_l.get("name"))
|
|
383
|
+
or (author_l.get("email") and a.get("email") == author_l.get("email"))
|
|
384
|
+
or (author_l.get("name") and a.get(constants.PROP_IDENTIFIER) == value_author)),
|
|
385
|
+
None
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
if existing:
|
|
389
|
+
for key, val in author_l.items():
|
|
390
|
+
if key not in existing or not existing[key]:
|
|
391
|
+
existing[key] = val
|
|
392
|
+
else:
|
|
393
|
+
codemeta_output[constants.CAT_CODEMETA_AUTHOR].append(author_l)
|
|
394
|
+
# if not any(a.get('name') == author_l['name'] for a in existing_authors):
|
|
395
|
+
# codemeta_output[constants.CAT_CODEMETA_AUTHOR].append(author_l)
|
|
396
|
+
|
|
397
|
+
if constants.CAT_OWNER in repo_data:
|
|
398
|
+
# if user then person, otherwise organization
|
|
399
|
+
codemeta_authors = []
|
|
400
|
+
codemeta_owners = []
|
|
401
|
+
for owner in repo_data[constants.CAT_OWNER]:
|
|
402
|
+
result_owner = owner.get("result", {})
|
|
403
|
+
type_aux = repo_data[constants.CAT_OWNER][0][constants.PROP_RESULT][constants.PROP_TYPE]
|
|
404
|
+
if type_aux == "User":
|
|
405
|
+
type_aux = "Person"
|
|
406
|
+
|
|
407
|
+
if type_aux == "Organization":
|
|
408
|
+
owner_obj = {"@type": "Organization"}
|
|
409
|
+
if result_owner.get(constants.PROP_VALUE):
|
|
410
|
+
owner_obj["name"] = result_owner.get(constants.PROP_VALUE)
|
|
411
|
+
codemeta_owners.append(owner_obj)
|
|
412
|
+
else:
|
|
413
|
+
author_obj = { "@type": type_aux }
|
|
414
|
+
|
|
415
|
+
if "name" in result_owner and result_owner[constants.PROP_AUTHOR_NAME]:
|
|
416
|
+
author_obj[constants.PROP_AUTHOR_NAME] = result_owner[constants.PROP_AUTHOR_NAME]
|
|
417
|
+
if "value" in result_owner and result_owner[constants.PROP_VALUE]:
|
|
418
|
+
author_obj[constants.PROP_IDENTIFIER] = result_owner[constants.PROP_VALUE]
|
|
419
|
+
author_obj["@id"] = "https://github.com/" + result_owner[constants.PROP_VALUE]
|
|
420
|
+
if "affiliation" in result_owner and result_owner[constants.PROP_AFFILIATION]:
|
|
421
|
+
author_obj[constants.PROP_AFFILIATION] = result_owner[constants.PROP_AFFILIATION]
|
|
422
|
+
if "email" in result_owner and result_owner[constants.PROP_EMAIL]:
|
|
423
|
+
author_obj[constants.PROP_EMAIL] = result_owner[constants.PROP_EMAIL]
|
|
424
|
+
|
|
425
|
+
existing = codemeta_output.get(constants.CAT_CODEMETA_AUTHOR, [])
|
|
426
|
+
if existing:
|
|
427
|
+
if existing[0].get("@type") == author_obj.get("@type"):
|
|
428
|
+
# ernich the existing author
|
|
429
|
+
for key, val in author_obj.items():
|
|
430
|
+
if key not in existing[0] or not existing[0][key]:
|
|
431
|
+
existing[0][key] = val
|
|
432
|
+
else:
|
|
433
|
+
codemeta_authors.append(author_obj)
|
|
434
|
+
|
|
435
|
+
if codemeta_owners:
|
|
436
|
+
codemeta_output[constants.CAT_CODEMETA_OWNER] = codemeta_owners
|
|
437
|
+
|
|
438
|
+
if codemeta_authors:
|
|
439
|
+
codemeta_output[constants.CAT_CODEMETA_AUTHOR] = codemeta_authors
|
|
440
|
+
|
|
441
|
+
if constants.CAT_CITATION in repo_data:
|
|
442
|
+
codemeta_output[constants.CAT_CODEMETA_REFERENCEPUBLICATION] = []
|
|
443
|
+
# credit_text_list = []
|
|
444
|
+
credit_candidates = {}
|
|
445
|
+
author_orcids = {}
|
|
446
|
+
all_reference_publications = []
|
|
447
|
+
|
|
448
|
+
if constants.CAT_CITATION in repo_data:
|
|
449
|
+
publications_source = repo_data[constants.CAT_CITATION]
|
|
450
|
+
else:
|
|
451
|
+
publications_source = []
|
|
452
|
+
|
|
453
|
+
if publications_source:
|
|
454
|
+
for cit in publications_source:
|
|
455
|
+
cit_result = cit[constants.PROP_RESULT]
|
|
456
|
+
scholarlyArticle = {"@type": "ScholarlyArticle"}
|
|
457
|
+
authors = cit_result.get("author", [])
|
|
458
|
+
doi = None
|
|
459
|
+
identifier_doi = None
|
|
460
|
+
title = None
|
|
461
|
+
is_bibtex = False
|
|
462
|
+
|
|
463
|
+
if constants.PROP_FORMAT in cit[constants.PROP_RESULT] and cit[constants.PROP_RESULT][constants.PROP_FORMAT] == "cff":
|
|
464
|
+
# yaml_content = yaml.safe_load(cit[constants.PROP_RESULT]["value"])
|
|
465
|
+
cff_value = cit[constants.PROP_RESULT]["value"]
|
|
466
|
+
try:
|
|
467
|
+
yaml_content = yaml.safe_load(cff_value)
|
|
468
|
+
except Exception:
|
|
469
|
+
# Remove HTML tags that can break YAML parsing
|
|
470
|
+
# ej:" <a href="..."> " has unescaped quotes
|
|
471
|
+
cleaned = re.sub(constants.REGEXP_CLEAN_HTML_TAGS, '', cff_value)
|
|
472
|
+
yaml_content = yaml.safe_load(cleaned)
|
|
473
|
+
|
|
474
|
+
preferred_citation = yaml_content.get("preferred-citation", {})
|
|
475
|
+
doi = yaml_content.get("doi") or preferred_citation.get("doi")
|
|
476
|
+
identifiers = yaml_content.get("identifiers", [])
|
|
477
|
+
url_citation = preferred_citation.get("url") or yaml_content.get("url")
|
|
478
|
+
identifier_url = next((id["value"] for id in identifiers if id["type"] == "url"), None)
|
|
479
|
+
identifier_doi = next((id["value"] for id in identifiers if id["type"] == "doi"), None)
|
|
480
|
+
if not authors:
|
|
481
|
+
authors = yaml_content.get("authors", []) or preferred_citation.get("authors", [])
|
|
482
|
+
raw_title = preferred_citation.get("title") or yaml_content.get("title")
|
|
483
|
+
title = normalize_title(raw_title)
|
|
484
|
+
|
|
485
|
+
if identifier_doi:
|
|
486
|
+
final_url = f"https://doi.org/{identifier_doi}"
|
|
487
|
+
elif doi:
|
|
488
|
+
final_url = f"https://doi.org/{doi}"
|
|
489
|
+
elif identifier_url:
|
|
490
|
+
final_url = identifier_url
|
|
491
|
+
elif url_citation:
|
|
492
|
+
final_url = url_citation
|
|
493
|
+
else:
|
|
494
|
+
final_url = ''
|
|
495
|
+
|
|
496
|
+
scholarlyArticle[constants.PROP_NAME] = raw_title
|
|
497
|
+
if doi or identifier_doi:
|
|
498
|
+
scholarlyArticle[constants.CAT_IDENTIFIER] = doi or identifier_doi
|
|
499
|
+
scholarlyArticle[constants.PROP_URL] = final_url
|
|
500
|
+
|
|
501
|
+
else:
|
|
502
|
+
if constants.PROP_DOI in cit[constants.PROP_RESULT].keys():
|
|
503
|
+
doi = cit[constants.PROP_RESULT][constants.PROP_DOI]
|
|
504
|
+
scholarlyArticle[constants.CAT_IDENTIFIER] = cit[constants.PROP_RESULT][constants.PROP_DOI]
|
|
505
|
+
|
|
506
|
+
if constants.PROP_URL in cit[constants.PROP_RESULT].keys():
|
|
507
|
+
scholarlyArticle[constants.PROP_URL] = cit[constants.PROP_RESULT][constants.PROP_URL]
|
|
508
|
+
|
|
509
|
+
if constants.PROP_TITLE in cit[constants.PROP_RESULT].keys():
|
|
510
|
+
title = normalize_title(cit[constants.PROP_RESULT][constants.PROP_TITLE])
|
|
511
|
+
scholarlyArticle[constants.PROP_NAME] = cit[constants.PROP_RESULT][constants.PROP_TITLE]
|
|
512
|
+
|
|
513
|
+
if constants.PROP_ORIGINAL_HEADER in cit[constants.PROP_RESULT].keys():
|
|
514
|
+
if cit[constants.PROP_RESULT][constants.PROP_ORIGINAL_HEADER] == "Citation":
|
|
515
|
+
if constants.PROP_SOURCE in cit.keys():
|
|
516
|
+
scholarlyArticle[constants.PROP_URL] = cit[constants.PROP_SOURCE]
|
|
517
|
+
|
|
518
|
+
is_bibtex = True
|
|
519
|
+
|
|
520
|
+
author_list = []
|
|
521
|
+
for author in authors:
|
|
522
|
+
family_name = author.get(constants.PROP_FAMILY_NAME)
|
|
523
|
+
given_name = author.get(constants.PROP_GIVEN_NAME)
|
|
524
|
+
orcid = author.get("orcid") or author.get(constants.PROP_URL)
|
|
525
|
+
name = author.get(constants.PROP_NAME)
|
|
526
|
+
|
|
527
|
+
if family_name and given_name:
|
|
528
|
+
author_entry = {
|
|
529
|
+
"@type": "Person",
|
|
530
|
+
"familyName": family_name,
|
|
531
|
+
"givenName": given_name
|
|
532
|
+
}
|
|
533
|
+
if orcid:
|
|
534
|
+
if not orcid.startswith("http"): # check if orcid is a url
|
|
535
|
+
orcid = f"https://orcid.org/{orcid}"
|
|
536
|
+
author_entry["@id"] = orcid
|
|
537
|
+
elif name:
|
|
538
|
+
# If there is only a name, we assume this to be an Organization.
|
|
539
|
+
# it could be not enough acurate
|
|
540
|
+
|
|
541
|
+
author_entry = {
|
|
542
|
+
"@type": "Organization",
|
|
543
|
+
"name": name
|
|
544
|
+
}
|
|
545
|
+
else:
|
|
546
|
+
continue
|
|
547
|
+
|
|
548
|
+
if family_name and given_name and orcid:
|
|
549
|
+
key = (family_name.lower(), given_name.lower())
|
|
550
|
+
author_orcids[key] = orcid
|
|
551
|
+
|
|
552
|
+
author_list.append({k: v for k, v in author_entry.items() if v is not None})
|
|
553
|
+
|
|
554
|
+
if author_list:
|
|
555
|
+
scholarlyArticle[constants.PROP_AUTHOR] = author_list
|
|
556
|
+
|
|
557
|
+
if len(scholarlyArticle) > 1:
|
|
558
|
+
|
|
559
|
+
is_article = is_scholarly_article(cit_result)
|
|
560
|
+
|
|
561
|
+
if not is_article:
|
|
562
|
+
if authors or title or doi or identifier_doi:
|
|
563
|
+
credit_str = format_to_credit_text(authors, title, doi, identifier_doi,repo_link=code_repository)
|
|
564
|
+
# credit_text_list.append(credit_str)
|
|
565
|
+
key_credit= _normalize_key(f"{title}-{code_repository}")
|
|
566
|
+
if key_credit not in credit_candidates or len(authors) > credit_candidates[key_credit][0]:
|
|
567
|
+
credit_candidates[key_credit] = (len(authors), credit_str)
|
|
568
|
+
else:
|
|
569
|
+
# For CFF citations, the YAML was already parsed above (title, authors, DOI, URL).
|
|
570
|
+
# The problem was re run againt the regexp because yaml.dump
|
|
571
|
+
# injects double quotes around accented names (e.g. "Poveda-Villal\xF3n")
|
|
572
|
+
is_cff = cit[constants.PROP_RESULT].get(constants.PROP_FORMAT) == "cff"
|
|
573
|
+
if not is_cff:
|
|
574
|
+
if re.search(r'@\w+\{', cit[constants.PROP_RESULT][constants.PROP_VALUE]):
|
|
575
|
+
scholarlyArticle = extract_scholarly_article_properties(cit[constants.PROP_RESULT][constants.PROP_VALUE], scholarlyArticle, 'CODEMETA')
|
|
576
|
+
else:
|
|
577
|
+
scholarlyArticle = extract_scholarly_article_natural(cit[constants.PROP_RESULT][constants.PROP_VALUE], scholarlyArticle, 'CODEMETA')
|
|
578
|
+
|
|
579
|
+
all_reference_publications.append({
|
|
580
|
+
**scholarlyArticle,
|
|
581
|
+
"_source_format": "cff" if not is_bibtex else "bibtex"
|
|
582
|
+
})
|
|
583
|
+
|
|
584
|
+
for article in all_reference_publications:
|
|
585
|
+
if "author" in article:
|
|
586
|
+
for author in article["author"]:
|
|
587
|
+
family_name = author.get("familyName", "").strip()
|
|
588
|
+
given_name = author.get("givenName", "").strip()
|
|
589
|
+
key = (family_name.lower(), given_name.lower()) if given_name else None
|
|
590
|
+
|
|
591
|
+
if key and key in author_orcids:
|
|
592
|
+
author["@id"] = author_orcids[key]
|
|
593
|
+
|
|
594
|
+
codemeta_output[constants.CAT_CODEMETA_REFERENCEPUBLICATION] = deduplicate_publications(all_reference_publications)
|
|
595
|
+
# if credit_text_list:
|
|
596
|
+
if credit_candidates:
|
|
597
|
+
# codemeta_output[constants.CAT_CODEMETA_CREDITTEXT] = list(set(credit_text_list))
|
|
598
|
+
codemeta_output[constants.CAT_CODEMETA_CREDITTEXT] = [c for _, c in credit_candidates.values()]
|
|
599
|
+
|
|
600
|
+
if constants.CAT_STATUS in repo_data:
|
|
601
|
+
url_status = repo_data[constants.CAT_STATUS][0]['result'].get('value', '')
|
|
602
|
+
status = url_status.split('#')[-1] if '#' in url_status else None
|
|
603
|
+
if status:
|
|
604
|
+
codemeta_output[constants.CAT_CODEMETA_DEVELOPMENTSTATUS] = status
|
|
605
|
+
|
|
606
|
+
if constants.CAT_APPLICATION_DOMAIN in repo_data:
|
|
607
|
+
codemeta_output[constants.CAT_CODEMETA_APPLICATIONCATEGORY] = []
|
|
608
|
+
for domain in repo_data[constants.CAT_APPLICATION_DOMAIN]:
|
|
609
|
+
value = domain[constants.PROP_RESULT][constants.PROP_VALUE]
|
|
610
|
+
if value not in codemeta_output[constants.CAT_CODEMETA_APPLICATIONCATEGORY]:
|
|
611
|
+
codemeta_output[constants.CAT_CODEMETA_APPLICATIONCATEGORY].append(value)
|
|
612
|
+
|
|
613
|
+
if constants.CAT_IDENTIFIER in repo_data:
|
|
614
|
+
codemeta_output[constants.CAT_CODEMETA_IDENTIFIER] = []
|
|
615
|
+
|
|
616
|
+
for identifier in repo_data[constants.CAT_IDENTIFIER]:
|
|
617
|
+
value = identifier[constants.PROP_RESULT][constants.PROP_VALUE]
|
|
618
|
+
if value not in codemeta_output[constants.CAT_CODEMETA_IDENTIFIER]:
|
|
619
|
+
codemeta_output[constants.CAT_CODEMETA_IDENTIFIER].append(value)
|
|
620
|
+
|
|
621
|
+
if constants.CAT_HOMEPAGE in repo_data:
|
|
622
|
+
|
|
623
|
+
# example
|
|
624
|
+
# homepage_urls = {"http://foo.com", "https://foo.com", "http://bar.com"}
|
|
625
|
+
# Result--> filtered_urls = {"https://foo.com", "http://bar.com"}
|
|
626
|
+
# Prioritize https
|
|
627
|
+
|
|
628
|
+
homepage_urls = {hp[constants.PROP_RESULT][constants.PROP_VALUE].strip()
|
|
629
|
+
for hp in repo_data[constants.CAT_HOMEPAGE]}
|
|
630
|
+
|
|
631
|
+
filtered_urls = set()
|
|
632
|
+
roots_with_https = {url[len("https://"):] for url in homepage_urls if url.startswith("https://")}
|
|
633
|
+
|
|
634
|
+
for url in homepage_urls:
|
|
635
|
+
root = url.replace("http://", "").replace("https://", "")
|
|
636
|
+
if url.startswith("http://") and root in roots_with_https:
|
|
637
|
+
continue
|
|
638
|
+
filtered_urls.add(url)
|
|
639
|
+
|
|
640
|
+
codemeta_output[constants.CAT_CODEMETA_URL] = list(filtered_urls)
|
|
641
|
+
|
|
642
|
+
# codemeta_output["identifier"] = repo_data[constants.CAT_IDENTIFIER][0][constants.PROP_RESULT][constants.PROP_VALUE]
|
|
643
|
+
if constants.CAT_README_URL in repo_data:
|
|
644
|
+
codemeta_output[constants.CAT_CODEMETA_README] = repo_data[constants.CAT_README_URL][0][constants.PROP_RESULT][constants.PROP_VALUE]
|
|
645
|
+
|
|
646
|
+
if constants.CAT_MAINTAINER in repo_data:
|
|
647
|
+
codemeta_maintainers = []
|
|
648
|
+
for maintainer in repo_data[constants.CAT_MAINTAINER]:
|
|
649
|
+
result_maint = maintainer.get("result", {})
|
|
650
|
+
maint_obj = { "@type": result_maint.get("type", "Person") }
|
|
651
|
+
|
|
652
|
+
if "name" in result_maint and result_maint[constants.PROP_AUTHOR_NAME]:
|
|
653
|
+
maint_obj[constants.PROP_AUTHOR_NAME] = result_maint[constants.PROP_AUTHOR_NAME]
|
|
654
|
+
if "username" in result_maint and result_maint[constants.PROP_USERNAME]:
|
|
655
|
+
maint_obj[constants.PROP_IDENTIFIER] = result_maint[constants.PROP_USERNAME]
|
|
656
|
+
if "email" in result_maint and result_maint[constants.PROP_EMAIL]:
|
|
657
|
+
maint_obj[constants.PROP_EMAIL] = result_maint[constants.PROP_EMAIL]
|
|
658
|
+
|
|
659
|
+
codemeta_maintainers.append(maint_obj)
|
|
660
|
+
|
|
661
|
+
if codemeta_maintainers:
|
|
662
|
+
codemeta_output[constants.CAT_CODEMETA_MAINTAINER] = codemeta_maintainers
|
|
663
|
+
|
|
664
|
+
if constants.CAT_RUNTIME_PLATFORM in repo_data:
|
|
665
|
+
runtimes = []
|
|
666
|
+
|
|
667
|
+
for runtime_entry in repo_data[constants.CAT_RUNTIME_PLATFORM]:
|
|
668
|
+
result = runtime_entry.get("result", {})
|
|
669
|
+
# name = result.get("name")
|
|
670
|
+
value = result.get("value")
|
|
671
|
+
if value:
|
|
672
|
+
# runtimes.append(f"{name} {version}")
|
|
673
|
+
runtimes.append(value)
|
|
674
|
+
|
|
675
|
+
if runtimes:
|
|
676
|
+
codemeta_output[constants.CAT_CODEMETA_RUNTIMEPLATFORM] = ", ".join(runtimes)
|
|
677
|
+
|
|
678
|
+
if constants.CAT_CONTRIBUTORS in repo_data:
|
|
679
|
+
raw_contributors = repo_data[constants.CAT_CONTRIBUTORS]
|
|
680
|
+
codemeta_output[constants.CAT_CODEMETA_CONTRIBUTOR] = parse_contributors(raw_contributors)
|
|
681
|
+
|
|
682
|
+
if constants.CAT_APPLICATION_DOMAIN in repo_data:
|
|
683
|
+
application_categories = []
|
|
684
|
+
for entry in repo_data[constants.CAT_APPLICATION_DOMAIN]:
|
|
685
|
+
value = entry[constants.PROP_RESULT][constants.PROP_VALUE]
|
|
686
|
+
if value not in application_categories:
|
|
687
|
+
application_categories.append(value)
|
|
688
|
+
if application_categories:
|
|
689
|
+
codemeta_output[constants.CAT_CODEMETA_APPLICATIONCATEGORY] = application_categories
|
|
690
|
+
|
|
691
|
+
if constants.CAT_FUNDING in repo_data:
|
|
692
|
+
grants_export = []
|
|
693
|
+
|
|
694
|
+
for entry in repo_data[constants.CAT_FUNDING]:
|
|
695
|
+
res = entry.get(constants.PROP_RESULT, {})
|
|
696
|
+
|
|
697
|
+
if res.get(constants.PROP_TYPE) == constants.TEXT_EXCERPT:
|
|
698
|
+
continue
|
|
699
|
+
|
|
700
|
+
if isinstance(res, dict):
|
|
701
|
+
grant_item = {
|
|
702
|
+
"@type": res.get("type", constants.TYPE_GRANT)
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
if constants.PROP_IDENTIFIER in res:
|
|
706
|
+
grant_item["identifier"] = res[constants.PROP_IDENTIFIER]
|
|
707
|
+
if constants.PROP_NAME in res:
|
|
708
|
+
grant_item["name"] = res[constants.PROP_NAME]
|
|
709
|
+
if constants.PROP_URL in res:
|
|
710
|
+
grant_item["url"] = res[constants.PROP_URL]
|
|
711
|
+
|
|
712
|
+
if constants.PROP_FUNDER in res:
|
|
713
|
+
funder = res[constants.PROP_FUNDER]
|
|
714
|
+
if isinstance(funder, dict):
|
|
715
|
+
funder_obj = {
|
|
716
|
+
"@type": funder.get("type", constants.TYPE_ORGANIZATION),
|
|
717
|
+
"name": funder.get("name")
|
|
718
|
+
}
|
|
719
|
+
if constants.PROP_URL in funder:
|
|
720
|
+
funder_obj["url"] = funder[constants.PROP_URL]
|
|
721
|
+
grant_item["funder"] = funder_obj
|
|
722
|
+
elif isinstance(funder, str):
|
|
723
|
+
grant_item["funder"] = {
|
|
724
|
+
"@type": constants.TYPE_ORGANIZATION,
|
|
725
|
+
"name": funder
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
if constants.PROP_NAME not in grant_item and constants.PROP_VALUE in res:
|
|
729
|
+
grant_item[constants.PROP_NAME] = res[constants.PROP_VALUE]
|
|
730
|
+
|
|
731
|
+
if grant_item not in grants_export:
|
|
732
|
+
grants_export.append(grant_item)
|
|
733
|
+
|
|
734
|
+
elif isinstance(res, str):
|
|
735
|
+
grant_item = {
|
|
736
|
+
"@type": constants.TYPE_GRANT,
|
|
737
|
+
constants.PROP_NAME: res
|
|
738
|
+
}
|
|
739
|
+
if grant_item not in grants_export:
|
|
740
|
+
grants_export.append(grant_item)
|
|
741
|
+
|
|
742
|
+
if grants_export:
|
|
743
|
+
codemeta_output[constants.CAT_CODEMETA_FUNDING] = grants_export
|
|
744
|
+
|
|
745
|
+
# A person is expected, and we extract text at the moment
|
|
746
|
+
if descriptions_text:
|
|
747
|
+
codemeta_output[constants.CAT_CODEMETA_DESCRIPTION] = descriptions_text
|
|
748
|
+
# if published_date != "":
|
|
749
|
+
# commenting this out because we cannot assume the last published date to be the published date
|
|
750
|
+
# codemeta_output["datePublished"] = published_date
|
|
751
|
+
pruned_output = {}
|
|
752
|
+
|
|
753
|
+
for key, value in codemeta_output.items():
|
|
754
|
+
if not (value is None or ((isinstance(value, list) or isinstance(value, tuple)) and len(value) == 0)):
|
|
755
|
+
pruned_output[key] = value
|
|
756
|
+
# now, prune out the variables that are None
|
|
757
|
+
save_json_output(pruned_output, outfile, None, pretty=pretty)
|
|
758
|
+
|
|
759
|
+
|
|
760
|
+
def create_missing_fields(result):
|
|
761
|
+
"""Function to create a small report with the categories SOMEF was not able to find.
|
|
762
|
+
The categories are added to the JSON results. This won't be added if you export TTL or Codemeta"""
|
|
763
|
+
missing = []
|
|
764
|
+
repo_data = result
|
|
765
|
+
# for c in constants.categories_files_header:
|
|
766
|
+
for c in constants.all_categories:
|
|
767
|
+
if c not in repo_data:
|
|
768
|
+
missing.append(c)
|
|
769
|
+
return missing
|
|
770
|
+
|
|
771
|
+
# def normalize_title(title):
|
|
772
|
+
# return re.sub(r"\s+", " ", title.strip().lower()) if title else None
|
|
773
|
+
|
|
774
|
+
def normalize_title(title: str) -> str:
|
|
775
|
+
if not title:
|
|
776
|
+
return None
|
|
777
|
+
title = re.sub(r'[{}]', '', title)
|
|
778
|
+
title = re.sub(r'\s+', ' ', title)
|
|
779
|
+
return title.strip().lower()
|
|
780
|
+
|
|
781
|
+
|
|
782
|
+
def deduplicate_publications(publications: List[Dict]) -> List[Dict]:
|
|
783
|
+
seen = {}
|
|
784
|
+
for pub in publications:
|
|
785
|
+
# doi = pub.get("identifier", "").lower().strip()
|
|
786
|
+
# doi = (pub.get("identifier") or "").lower().strip()
|
|
787
|
+
doi = extract_doi(pub.get("identifier") or "")
|
|
788
|
+
title = normalize_title(pub.get("name", ""))
|
|
789
|
+
key = (doi, title)
|
|
790
|
+
|
|
791
|
+
if key not in seen:
|
|
792
|
+
seen[key] = pub
|
|
793
|
+
else:
|
|
794
|
+
existing = seen[key]
|
|
795
|
+
existing_format = existing.get("_source_format", "")
|
|
796
|
+
new_format = pub.get("_source_format", "")
|
|
797
|
+
existing_url = (existing.get("url") or "").lower()
|
|
798
|
+
new_url = (pub.get("url") or "").lower()
|
|
799
|
+
|
|
800
|
+
# is_doi_url_existing = existing_url.startswith("https://doi.org/")
|
|
801
|
+
# is_doi_url_new = new_url.startswith("https://doi.org/")
|
|
802
|
+
doi_existing = extract_doi(existing_url)
|
|
803
|
+
doi_new = extract_doi(new_url)
|
|
804
|
+
|
|
805
|
+
is_doi_url_existing = bool(doi_existing)
|
|
806
|
+
is_doi_url_new = bool(doi_new)
|
|
807
|
+
|
|
808
|
+
# Priority CFF
|
|
809
|
+
if existing_format != "cff" and new_format == "cff":
|
|
810
|
+
seen[key] = pub
|
|
811
|
+
|
|
812
|
+
# Priority DOI URL
|
|
813
|
+
elif existing_format == new_format:
|
|
814
|
+
if not is_doi_url_existing and is_doi_url_new:
|
|
815
|
+
seen[key] = pub
|
|
816
|
+
|
|
817
|
+
result = []
|
|
818
|
+
for pub in seen.values():
|
|
819
|
+
pub.pop("_source_format", None)
|
|
820
|
+
result.append(pub)
|
|
821
|
+
|
|
822
|
+
return result
|
|
823
|
+
|
|
824
|
+
|
|
825
|
+
def extract_doi(url: str) -> str:
|
|
826
|
+
if not url:
|
|
827
|
+
return ""
|
|
828
|
+
|
|
829
|
+
match = re.search(constants.REGEXP_ALL_DOIS, url, re.IGNORECASE)
|
|
830
|
+
return match.group(0).lower() if match else ""
|
|
831
|
+
|
|
832
|
+
def map_requirement_type(t):
|
|
833
|
+
"""
|
|
834
|
+
Maps a free text requirement type to a Schema.org software (soft application, soft source....)
|
|
835
|
+
Keyword matching is used and unmatched values default to SoftwareApplication.
|
|
836
|
+
"""
|
|
837
|
+
t = t.lower()
|
|
838
|
+
|
|
839
|
+
for key, mapped in constants.REQUIREMENT_ENTRIES_TYPE_MAP.items():
|
|
840
|
+
if key in t:
|
|
841
|
+
return mapped
|
|
842
|
+
# default
|
|
843
|
+
return constants.SCHEMA_SOFTWARE_APPLICATION
|
|
844
|
+
|
|
845
|
+
def parse_contributors(raw):
|
|
846
|
+
contributors = []
|
|
847
|
+
seen = set()
|
|
848
|
+
|
|
849
|
+
for entry in raw:
|
|
850
|
+
result = entry.get("result", {})
|
|
851
|
+
rtype = result.get("type")
|
|
852
|
+
name = result.get("value")
|
|
853
|
+
|
|
854
|
+
if not name:
|
|
855
|
+
continue
|
|
856
|
+
|
|
857
|
+
if rtype == "Agent":
|
|
858
|
+
|
|
859
|
+
if name not in seen:
|
|
860
|
+
|
|
861
|
+
if re.search(constants.REGEXP_LTD_INC, name, re.IGNORECASE):
|
|
862
|
+
type_contributor = constants.TYPE_CONTRIBUTOR_ORGANIZATION
|
|
863
|
+
else:
|
|
864
|
+
type_contributor = constants.TYPE_CONTRIBUTOR_PERSON
|
|
865
|
+
|
|
866
|
+
contributor = {
|
|
867
|
+
constants.PROP_CODEMETA_TYPE: type_contributor,
|
|
868
|
+
constants.PROP_NAME: name
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
if "given_name" in result:
|
|
872
|
+
contributor[constants.PROP_CODEMETA_GIVENAME] = result["given_name"]
|
|
873
|
+
|
|
874
|
+
if "last_name" in result:
|
|
875
|
+
contributor[constants.PROP_CODEMETA_FAMILYNAME] = result["last_name"]
|
|
876
|
+
|
|
877
|
+
if "email" in result:
|
|
878
|
+
contributor[constants.PROP_EMAIL] = result["email"]
|
|
879
|
+
|
|
880
|
+
if "identifier" in result:
|
|
881
|
+
contributor[constants.PROP_CODEMETA_ID] = result["identifier"]
|
|
882
|
+
|
|
883
|
+
contributors.append(contributor)
|
|
884
|
+
seen.add(name)
|
|
885
|
+
|
|
886
|
+
if rtype == "File_dump":
|
|
887
|
+
for line in result.get("value", "").splitlines():
|
|
888
|
+
line = line.strip()
|
|
889
|
+
|
|
890
|
+
if (not line or line.startswith(("#", "##", "|")) or "[" in line):
|
|
891
|
+
continue
|
|
892
|
+
|
|
893
|
+
# avoid sentences
|
|
894
|
+
if len(line.split()) > 4:
|
|
895
|
+
continue
|
|
896
|
+
|
|
897
|
+
if line in seen:
|
|
898
|
+
continue
|
|
899
|
+
|
|
900
|
+
if re.search(constants.REGEXP_LTD_INC, line, re.IGNORECASE):
|
|
901
|
+
type_contributor = constants.TYPE_CONTRIBUTOR_ORGANIZATION
|
|
902
|
+
else:
|
|
903
|
+
type_contributor = constants.TYPE_CONTRIBUTOR_PERSON
|
|
904
|
+
|
|
905
|
+
contributors.append({
|
|
906
|
+
constants.PROP_CODEMETA_TYPE: type_contributor,
|
|
907
|
+
constants.PROP_NAME: line
|
|
908
|
+
})
|
|
909
|
+
|
|
910
|
+
seen.add(line)
|
|
911
|
+
|
|
912
|
+
|
|
913
|
+
return contributors
|
|
914
|
+
|
|
915
|
+
|
|
916
|
+
def is_scholarly_article(article_dict):
|
|
917
|
+
|
|
918
|
+
if article_dict.get("doi") or article_dict.get("journal"):
|
|
919
|
+
return True
|
|
920
|
+
if article_dict.get("type") in ["SoftwareApplication", "software"]:
|
|
921
|
+
return False
|
|
922
|
+
if article_dict.get("type") == "ScholarlyArticle":
|
|
923
|
+
return True
|
|
924
|
+
|
|
925
|
+
return False
|
|
926
|
+
|
|
927
|
+
|
|
928
|
+
def format_to_credit_text(authors, title, doi_or_url, identifier_doi = None, repo_link=None):
|
|
929
|
+
|
|
930
|
+
if authors:
|
|
931
|
+
author_names = []
|
|
932
|
+
for a in authors:
|
|
933
|
+
f = a.get("family-names") or a.get("family_name")
|
|
934
|
+
g = a.get("given-names") or a.get("given_name")
|
|
935
|
+
if f and g:
|
|
936
|
+
author_names.append(f"{f}, {g[0]}.")
|
|
937
|
+
|
|
938
|
+
# too many authors
|
|
939
|
+
if len(author_names) > 5:
|
|
940
|
+
authors_str = ", ".join(author_names[:5]) + ", et al."
|
|
941
|
+
else:
|
|
942
|
+
authors_str = ", ".join(author_names)
|
|
943
|
+
else:
|
|
944
|
+
authors_str = "Unknown Authors"
|
|
945
|
+
|
|
946
|
+
final_id = identifier_doi if identifier_doi else doi_or_url
|
|
947
|
+
|
|
948
|
+
credit_str = f"{authors_str} ({title}). {final_id if final_id else ''}"
|
|
949
|
+
|
|
950
|
+
if repo_link:
|
|
951
|
+
credit_str = f"{credit_str.rstrip('. ')}. Available at: {repo_link}"
|
|
952
|
+
|
|
953
|
+
return credit_str
|
|
954
|
+
|
|
955
|
+
def resolve_issue_tracker(trackers, code_repository):
|
|
956
|
+
|
|
957
|
+
if len(trackers) == 1:
|
|
958
|
+
return trackers[0][constants.PROP_RESULT][constants.PROP_VALUE]
|
|
959
|
+
|
|
960
|
+
if len(trackers) > 1:
|
|
961
|
+
return select_best_tracker(trackers)
|
|
962
|
+
|
|
963
|
+
if code_repository:
|
|
964
|
+
return f"{code_repository}/issues"
|
|
965
|
+
|
|
966
|
+
return None
|
|
967
|
+
|
|
968
|
+
def select_best_tracker(trackers):
|
|
969
|
+
|
|
970
|
+
try:
|
|
971
|
+
for t in trackers:
|
|
972
|
+
result = t.get(constants.PROP_RESULT, {})
|
|
973
|
+
if isinstance(result, dict):
|
|
974
|
+
url = result.get(constants.PROP_VALUE)
|
|
975
|
+
if url and "api." not in url:
|
|
976
|
+
return url
|
|
977
|
+
|
|
978
|
+
return trackers[0][constants.PROP_RESULT][constants.PROP_VALUE]
|
|
979
|
+
except Exception:
|
|
980
|
+
return None
|
|
981
|
+
|
|
982
|
+
"""
|
|
983
|
+
This part of code implements the post processing and unification logic applied to the
|
|
984
|
+
raw JSON extracted by SOMEF. Different extractors may produce duplicated or
|
|
985
|
+
slightly divergent entries for the same underlying resource (e.g., documentation
|
|
986
|
+
URLs, identifiers, authors). The functions below normalize values, detect
|
|
987
|
+
equivalent items, and merge them while preserving all available information.
|
|
988
|
+
|
|
989
|
+
Key ideas:
|
|
990
|
+
- Canonicalize simple URL values to avoid redundant entries.
|
|
991
|
+
- Never canonicalize structured objects (e.g., Release, Agent).
|
|
992
|
+
- Merge complementary fields extracted by different techniques.
|
|
993
|
+
- Combine techniques and sources without losing provenance.
|
|
994
|
+
"""
|
|
995
|
+
|
|
996
|
+
def canonicalize_value(value, value_type):
|
|
997
|
+
"""Canonicalization for SOMEF:
|
|
998
|
+
- If URL points to a file (has extension), keep full path (no unification)
|
|
999
|
+
- Otherwise, unify to scheme://domain (documentation, badges, pages)
|
|
1000
|
+
- Always remove query, fragment, trailing slash
|
|
1001
|
+
"""
|
|
1002
|
+
if isinstance(value, (list, dict)):
|
|
1003
|
+
logging.warning(f"Warning: Unexpected structured value for type {value_type}. Skipping canonicalization.")
|
|
1004
|
+
return value
|
|
1005
|
+
|
|
1006
|
+
if value_type == constants.RELEASE:
|
|
1007
|
+
return value
|
|
1008
|
+
|
|
1009
|
+
if value_type == constants.URL:
|
|
1010
|
+
parsed = urlparse(value)
|
|
1011
|
+
|
|
1012
|
+
# Remove query and fragment
|
|
1013
|
+
path = parsed.path.rstrip('/')
|
|
1014
|
+
|
|
1015
|
+
# Detect if last segment has a file extension
|
|
1016
|
+
last_segment = path.split('/')[-1]
|
|
1017
|
+
if '.' in last_segment:
|
|
1018
|
+
# It's a file → do NOT unify
|
|
1019
|
+
clean_path = path
|
|
1020
|
+
return urlunparse((parsed.scheme, parsed.netloc, clean_path, '', '', ''))
|
|
1021
|
+
|
|
1022
|
+
domains_to_keep_path = ['github.com', 'api.github.com', 'gitlab.com', 'bitbucket.org']
|
|
1023
|
+
if any(d in parsed.netloc for d in domains_to_keep_path):
|
|
1024
|
+
return urlunparse((parsed.scheme, parsed.netloc, path, '', '', ''))
|
|
1025
|
+
|
|
1026
|
+
# It's a directory/page → unify to domain
|
|
1027
|
+
return f"{parsed.scheme}://{parsed.netloc}"
|
|
1028
|
+
|
|
1029
|
+
if isinstance(value, str):
|
|
1030
|
+
return value.strip()
|
|
1031
|
+
|
|
1032
|
+
return value
|
|
1033
|
+
|
|
1034
|
+
|
|
1035
|
+
def normalize_type(result):
|
|
1036
|
+
value = result.get(constants.PROP_VALUE, "")
|
|
1037
|
+
rtype = result.get(constants.PROP_TYPE, "")
|
|
1038
|
+
|
|
1039
|
+
# Only normalize if the object ONLY has type + value
|
|
1040
|
+
# (i.e., it's a simple URL, not a structured object like Release)
|
|
1041
|
+
if isinstance(value, str) and value.startswith("http"):
|
|
1042
|
+
if set(result.keys()) <= {constants.PROP_TYPE, constants.PROP_VALUE}:
|
|
1043
|
+
return constants.URL
|
|
1044
|
+
|
|
1045
|
+
return rtype
|
|
1046
|
+
|
|
1047
|
+
|
|
1048
|
+
def choose_more_general(a, b):
|
|
1049
|
+
"""
|
|
1050
|
+
If both values are strings and one contains the other, return the shorter one.
|
|
1051
|
+
Otherwise, return 'a'.
|
|
1052
|
+
"""
|
|
1053
|
+
if isinstance(a, str) and isinstance(b, str):
|
|
1054
|
+
if a in b:
|
|
1055
|
+
return a
|
|
1056
|
+
if b in a:
|
|
1057
|
+
return b
|
|
1058
|
+
return a
|
|
1059
|
+
|
|
1060
|
+
|
|
1061
|
+
def unify_results(repo_data: dict) -> dict:
|
|
1062
|
+
"""
|
|
1063
|
+
Merge and normalize the raw extraction results produced by SOMEF.
|
|
1064
|
+
Different extractors may return duplicated or
|
|
1065
|
+
partially overlapping entries for the same underlying resource (urls, identifiers, authors...).
|
|
1066
|
+
This function canonicalizes simple values, detects equivalent items
|
|
1067
|
+
and merges them into a single unified entry while preserving all available information.
|
|
1068
|
+
"""
|
|
1069
|
+
unified_data = {}
|
|
1070
|
+
|
|
1071
|
+
for category, items in repo_data.items():
|
|
1072
|
+
if not isinstance(items, list):
|
|
1073
|
+
unified_data[category] = items
|
|
1074
|
+
continue
|
|
1075
|
+
|
|
1076
|
+
seen = {}
|
|
1077
|
+
|
|
1078
|
+
for item in items:
|
|
1079
|
+
|
|
1080
|
+
result = item.get(constants.PROP_RESULT, {})
|
|
1081
|
+
normalized_type = normalize_type(result)
|
|
1082
|
+
result[constants.PROP_TYPE] = normalized_type
|
|
1083
|
+
value = result.get(constants.PROP_VALUE)
|
|
1084
|
+
value_type = result.get(constants.PROP_TYPE)
|
|
1085
|
+
|
|
1086
|
+
# Descriptions of <5 words should probably be removed
|
|
1087
|
+
if category == constants.CAT_DESCRIPTION:
|
|
1088
|
+
value = result.get(constants.PROP_VALUE, "")
|
|
1089
|
+
source = item.get(constants.PROP_SOURCE, "")
|
|
1090
|
+
if isinstance(value, str) and len(value.split()) < 5:
|
|
1091
|
+
if isinstance(source, str) and "readme" in source.lower():
|
|
1092
|
+
continue
|
|
1093
|
+
|
|
1094
|
+
# --- SPECIAL LOGIC FOR LICENSES and citations ---
|
|
1095
|
+
if category == constants.CAT_LICENSE and result.get(constants.PROP_SPDX_ID):
|
|
1096
|
+
# If we have SPDX, that is our unification key
|
|
1097
|
+
key = f"LICENSE-{result[constants.PROP_SPDX_ID]}"
|
|
1098
|
+
elif category == constants.CAT_CITATION:
|
|
1099
|
+
doi_citation = result.get("doi")
|
|
1100
|
+
if not doi_citation:
|
|
1101
|
+
# if result.get("format") == "bibtex" or item.get("technique") == "header_analysis":
|
|
1102
|
+
doi_citation = extract_doi(result.get(constants.PROP_VALUE, ""))
|
|
1103
|
+
|
|
1104
|
+
if doi_citation:
|
|
1105
|
+
clean_doi = extract_doi(doi_citation).lower().strip()
|
|
1106
|
+
key = f"CITATION-DOI-{clean_doi}"
|
|
1107
|
+
# result.pop(constants.PROP_FORMAT, None)
|
|
1108
|
+
else:
|
|
1109
|
+
canonical = canonicalize_value(value, value_type)
|
|
1110
|
+
key = str(canonical)
|
|
1111
|
+
elif category == constants.CAT_REQUIREMENTS:
|
|
1112
|
+
req_name = result.get("name", "").strip().lower()
|
|
1113
|
+
req_version = result.get("version", "").strip()
|
|
1114
|
+
if req_name:
|
|
1115
|
+
key = f"REQ-{req_name}-{req_version}"
|
|
1116
|
+
else:
|
|
1117
|
+
canonical = canonicalize_value(value, value_type)
|
|
1118
|
+
key = str(canonical)
|
|
1119
|
+
elif category == constants.CAT_RUNTIME_PLATFORM:
|
|
1120
|
+
rt_name = result.get("name", "").strip().lower()
|
|
1121
|
+
rt_version = result.get("version", "").strip()
|
|
1122
|
+
if rt_name:
|
|
1123
|
+
key = f"RT-{rt_name}-{rt_version}"
|
|
1124
|
+
else:
|
|
1125
|
+
canonical = canonicalize_value(value, value_type)
|
|
1126
|
+
key = str(canonical)
|
|
1127
|
+
else:
|
|
1128
|
+
# Normal behavior for the rest of the categories
|
|
1129
|
+
canonical = canonicalize_value(value, value_type)
|
|
1130
|
+
key = str(canonical)
|
|
1131
|
+
# --------------------------------------------------
|
|
1132
|
+
# canonical = canonicalize_value(value, value_type)
|
|
1133
|
+
|
|
1134
|
+
# key = str(canonical)
|
|
1135
|
+
if key in seen:
|
|
1136
|
+
existing = seen[key]
|
|
1137
|
+
|
|
1138
|
+
if category == constants.CAT_CITATION:
|
|
1139
|
+
priorities_citation = {"cff": 3, "codemeta": 2, "bibtex": 1}
|
|
1140
|
+
new_fmt_citation = result.get(constants.PROP_FORMAT, "").lower()
|
|
1141
|
+
old_fmt_citation = existing[constants.PROP_RESULT].get(constants.PROP_FORMAT, "").lower()
|
|
1142
|
+
if category == constants.CAT_LICENSE:
|
|
1143
|
+
# prefer SPDX ID if available for licenses
|
|
1144
|
+
if result.get(constants.PROP_SPDX_ID):
|
|
1145
|
+
existing[constants.PROP_RESULT][constants.PROP_VALUE] = result[constants.PROP_SPDX_ID]
|
|
1146
|
+
else:
|
|
1147
|
+
# If types match, merge normally
|
|
1148
|
+
existing[constants.PROP_RESULT][constants.PROP_VALUE] = choose_more_general(
|
|
1149
|
+
existing[constants.PROP_RESULT][constants.PROP_VALUE], value
|
|
1150
|
+
)
|
|
1151
|
+
|
|
1152
|
+
# merge other result fields because different techniques might have extracted different information
|
|
1153
|
+
# (e.g., email in authors extracted by file exploration or code parser.
|
|
1154
|
+
for field, new_val in result.items():
|
|
1155
|
+
if field in (constants.PROP_VALUE, constants.PROP_TYPE):
|
|
1156
|
+
continue
|
|
1157
|
+
old_val = existing[constants.PROP_RESULT].get(field)
|
|
1158
|
+
should_overwrite = False
|
|
1159
|
+
|
|
1160
|
+
if category == constants.CAT_CITATION:
|
|
1161
|
+
should_overwrite = (
|
|
1162
|
+
priorities_citation.get(new_fmt_citation, 0) > priorities_citation.get(old_fmt_citation, 0)
|
|
1163
|
+
)
|
|
1164
|
+
|
|
1165
|
+
if old_val in (None, "", []) or should_overwrite:
|
|
1166
|
+
existing[constants.PROP_RESULT][field] = new_val
|
|
1167
|
+
if category == constants.CAT_CITATION:
|
|
1168
|
+
existing[constants.PROP_RESULT].pop(constants.PROP_FORMAT, None)
|
|
1169
|
+
# if category == constants.CAT_CITATION and "CITATION-DOI-" in key:
|
|
1170
|
+
# existing[constants.PROP_RESULT].pop(constants.PROP_FORMAT, None)
|
|
1171
|
+
|
|
1172
|
+
# join techniques
|
|
1173
|
+
t1 = existing.get("technique", [])
|
|
1174
|
+
t2 = item.get("technique", [])
|
|
1175
|
+
if not isinstance(t1, list): t1 = [t1]
|
|
1176
|
+
if not isinstance(t2, list): t2 = [t2]
|
|
1177
|
+
existing["technique"] = list(set(t1 + t2))
|
|
1178
|
+
|
|
1179
|
+
# join sources
|
|
1180
|
+
s1 = existing.get("source", [])
|
|
1181
|
+
s2 = item.get("source", [])
|
|
1182
|
+
if s1 and not isinstance(s1, list): s1 = [s1]
|
|
1183
|
+
if s2 and not isinstance(s2, list): s2 = [s2]
|
|
1184
|
+
if s1 or s2:
|
|
1185
|
+
existing["source"] = list(set(s1 + s2))
|
|
1186
|
+
|
|
1187
|
+
else:
|
|
1188
|
+
seen[key] = item
|
|
1189
|
+
|
|
1190
|
+
unified_data[category] = list(seen.values())
|
|
1191
|
+
|
|
1192
|
+
return unified_data
|
|
1193
|
+
|
|
1194
|
+
|
|
1195
|
+
def _normalize_key(text):
|
|
1196
|
+
text = unicodedata.normalize("NFKD", text).encode("ascii", "ignore").decode()
|
|
1197
|
+
return text.casefold().strip()
|