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.
Files changed (423) hide show
  1. somef_core/__init__.py +3 -0
  2. somef_core/__main__.py +293 -0
  3. somef_core/configuration.py +177 -0
  4. somef_core/export/__init__.py +0 -0
  5. somef_core/export/google_codemeta_export.py +171 -0
  6. somef_core/export/json_export.py +1197 -0
  7. somef_core/extract_software_type.py +408 -0
  8. somef_core/extract_workflows.py +74 -0
  9. somef_core/header_analysis.py +712 -0
  10. somef_core/parser/__init__.py +0 -0
  11. somef_core/parser/authors_parser.py +121 -0
  12. somef_core/parser/bower_parser.py +185 -0
  13. somef_core/parser/cabal_parser.py +222 -0
  14. somef_core/parser/codemeta_parser.py +926 -0
  15. somef_core/parser/codeowners_parser.py +198 -0
  16. somef_core/parser/composer_parser.py +218 -0
  17. somef_core/parser/conda_environment_parser.py +91 -0
  18. somef_core/parser/create_excerpts.py +71 -0
  19. somef_core/parser/description_parser.py +228 -0
  20. somef_core/parser/dockerfile_parser.py +227 -0
  21. somef_core/parser/gemspec_parser.py +224 -0
  22. somef_core/parser/mardown_parser.py +536 -0
  23. somef_core/parser/package_json_parser.py +270 -0
  24. somef_core/parser/pom_xml_parser.py +405 -0
  25. somef_core/parser/publiccode_parser.py +385 -0
  26. somef_core/parser/python_parser.py +432 -0
  27. somef_core/parser/setupcfg_parser.py +192 -0
  28. somef_core/parser/toml_parser.py +717 -0
  29. somef_core/process_files.py +893 -0
  30. somef_core/process_repository.py +1793 -0
  31. somef_core/process_results.py +87 -0
  32. somef_core/regular_expressions.py +1333 -0
  33. somef_core/somef_cli.py +446 -0
  34. somef_core/test/__init__.py +1 -0
  35. somef_core/test/conftest.py +7 -0
  36. somef_core/test/test_JSON_export.py +1177 -0
  37. somef_core/test/test_bitbucket.py +137 -0
  38. somef_core/test/test_bower_parser.py +113 -0
  39. somef_core/test/test_cabal_parser.py +94 -0
  40. somef_core/test/test_cli.py +816 -0
  41. somef_core/test/test_codeberg.py +183 -0
  42. somef_core/test/test_codemeta_export.py +1135 -0
  43. somef_core/test/test_codemeta_gitlab_export.py +64 -0
  44. somef_core/test/test_codemeta_parser.py +167 -0
  45. somef_core/test/test_composer_parser.py +59 -0
  46. somef_core/test/test_conda_environment_parser.py +58 -0
  47. somef_core/test/test_configuration.py +40 -0
  48. somef_core/test/test_data/README-EUVpy.md +166 -0
  49. somef_core/test/test_data/README-FTR.md +50 -0
  50. somef_core/test/test_data/README-GENI.md +109 -0
  51. somef_core/test/test_data/README-MEL-TNNT.md +112 -0
  52. somef_core/test/test_data/README-OBA-SPARQL.md +87 -0
  53. somef_core/test/test_data/README-OWL-To-OAS.md +6 -0
  54. somef_core/test/test_data/README-PPool.md +26 -0
  55. somef_core/test/test_data/README-TINTO.md +213 -0
  56. somef_core/test/test_data/README-agora.md +511 -0
  57. somef_core/test/test_data/README-almost-empty.md +1 -0
  58. somef_core/test/test_data/README-base-images.md +1 -0
  59. somef_core/test/test_data/README-ci-sample-project.md +102 -0
  60. somef_core/test/test_data/README-citation-file-format.md +158 -0
  61. somef_core/test/test_data/README-componentinstaller.md +139 -0
  62. somef_core/test/test_data/README-csl-editor.md +57 -0
  63. somef_core/test/test_data/README-devilog.md +99 -0
  64. somef_core/test/test_data/README-easytv-annotator.md +74 -0
  65. somef_core/test/test_data/README-epw2rdf-contents.md +13 -0
  66. somef_core/test/test_data/README-epw2rdf.md +10 -0
  67. somef_core/test/test_data/README-fair-ontologies.md +53 -0
  68. somef_core/test/test_data/README-fastify.md +420 -0
  69. somef_core/test/test_data/README-gammalearn.md +193 -0
  70. somef_core/test/test_data/README-gammapy.rst +103 -0
  71. somef_core/test/test_data/README-kgtk-notebooks.md +81 -0
  72. somef_core/test/test_data/README-laueNN.md +123 -0
  73. somef_core/test/test_data/README-licensius.md +59 -0
  74. somef_core/test/test_data/README-lighttwin.md +234 -0
  75. somef_core/test/test_data/README-loupe-api.md +13 -0
  76. somef_core/test/test_data/README-manim.md +117 -0
  77. somef_core/test/test_data/README-mapeathor.md +45 -0
  78. somef_core/test/test_data/README-mapshaper.md +101 -0
  79. somef_core/test/test_data/README-menty.md +94 -0
  80. somef_core/test/test_data/README-os-platforms.md +196 -0
  81. somef_core/test/test_data/README-panda.md +16 -0
  82. somef_core/test/test_data/README-pylops.md +229 -0
  83. somef_core/test/test_data/README-pytorch.md +424 -0
  84. somef_core/test/test_data/README-raidologist.md +60 -0
  85. somef_core/test/test_data/README-reggae.md +95 -0
  86. somef_core/test/test_data/README-stract.md +66 -0
  87. somef_core/test/test_data/README-sunpy.rst +115 -0
  88. somef_core/test/test_data/README-tada-gam.md +130 -0
  89. somef_core/test/test_data/README-tensorflow-2.6.0.md +156 -0
  90. somef_core/test/test_data/README-tokei.md +85 -0
  91. somef_core/test/test_data/README-tpronk.md +75 -0
  92. somef_core/test/test_data/README-unboundedsystems.md +86 -0
  93. somef_core/test/test_data/README-uncbiag.md +91 -0
  94. somef_core/test/test_data/README-urllib3.md +110 -0
  95. somef_core/test/test_data/README-widoco-duplicate-dois.md +198 -0
  96. somef_core/test/test_data/README-widoco-swh.md +233 -0
  97. somef_core/test/test_data/README-widoco.md +187 -0
  98. somef_core/test/test_data/README-wothive.md +111 -0
  99. somef_core/test/test_data/README-ya2o.md +372 -0
  100. somef_core/test/test_data/SimulatedReads2Map.wdl +80 -0
  101. somef_core/test/test_data/api_responses/bitbucket/bitbucket_response.json +127 -0
  102. somef_core/test/test_data/api_responses/bitbucket/bitbucket_tags.json +85 -0
  103. somef_core/test/test_data/api_responses/codeberg/codeberg_forgejo.json +104 -0
  104. somef_core/test/test_data/api_responses/codeberg/codeberg_forgejo_languages.json +16 -0
  105. somef_core/test/test_data/api_responses/codeberg/codeberg_forgejo_releases.json +261 -0
  106. somef_core/test/test_data/api_responses/somef-demo.json +648 -0
  107. somef_core/test/test_data/api_responses/widoco_api_response.json +2779 -0
  108. somef_core/test/test_data/authors_files/docker_AUTHORS.txt +718 -0
  109. somef_core/test/test_data/authors_files/ireeOrg_AUTHORS.txt +13 -0
  110. somef_core/test/test_data/authors_files/jetBrain_AUTHORS.txt +10 -0
  111. somef_core/test/test_data/authors_files/pluginsGLPI_AUTHORS.txt +9 -0
  112. somef_core/test/test_data/authors_files/tensorFlow_AUTHORS.txt +11 -0
  113. somef_core/test/test_data/expected/Widoco.yaml +29 -0
  114. somef_core/test/test_data/expected/aladin-lite.yaml +22 -0
  115. somef_core/test/test_data/expected/gammapy.yaml +31 -0
  116. somef_core/test/test_data/expected/r3broot2.yaml +44 -0
  117. somef_core/test/test_data/expected/runtime_platform_repo.yaml +9 -0
  118. somef_core/test/test_data/export_test.json +142 -0
  119. somef_core/test/test_data/extract_bash_code.txt +37 -0
  120. somef_core/test/test_data/input-test.txt +2 -0
  121. somef_core/test/test_data/package_neors.json +29 -0
  122. somef_core/test/test_data/package_npm.json +258 -0
  123. somef_core/test/test_data/pyansys-README.rst +222 -0
  124. somef_core/test/test_data/rasterio-README.md +360 -0
  125. somef_core/test/test_data/repositories/A-Dynamic-E-Commerce-Website-master/README.md +12 -0
  126. somef_core/test/test_data/repositories/A-Dynamic-E-Commerce-Website-master/admin.php +75 -0
  127. somef_core/test/test_data/repositories/A-Dynamic-E-Commerce-Website-master/admin_orders.php +53 -0
  128. somef_core/test/test_data/repositories/A-Dynamic-E-Commerce-Website-master/delete_from_cart.php +20 -0
  129. somef_core/test/test_data/repositories/A-Dynamic-E-Commerce-Website-master/mangola.sql +327 -0
  130. somef_core/test/test_data/repositories/Clamp/README.md +222 -0
  131. somef_core/test/test_data/repositories/Fairwinds/Dockerfile +14 -0
  132. somef_core/test/test_data/repositories/Fermi/README.md +164 -0
  133. somef_core/test/test_data/repositories/Flux.jl/Project.toml +71 -0
  134. somef_core/test/test_data/repositories/JAFFA-master/README +9 -0
  135. somef_core/test/test_data/repositories/JAFFA-master/README.md +4 -0
  136. somef_core/test/test_data/repositories/JAFFA-master/cwl/converter.cwl +33 -0
  137. somef_core/test/test_data/repositories/JAFFA-master/cwl/smcFusion-jaffa-workflow.cwl +62 -0
  138. somef_core/test/test_data/repositories/JAFFA-master/cwl/tar.cwl +23 -0
  139. somef_core/test/test_data/repositories/JAFFA-master/cwl/twoBitToFa.cwl +37 -0
  140. somef_core/test/test_data/repositories/OWL-To-OAS-Specification-master/README.md +6 -0
  141. somef_core/test/test_data/repositories/OWL-To-OAS-Specification-master/mkdocs.yml +13 -0
  142. somef_core/test/test_data/repositories/OWL-To-OAS-Specification-master/requirements.txt +1 -0
  143. somef_core/test/test_data/repositories/Pluto.jl/Project.toml +84 -0
  144. somef_core/test/test_data/repositories/Prometeus/Dockerfile +31 -0
  145. somef_core/test/test_data/repositories/RDFChess/.gitignore +1 -0
  146. somef_core/test/test_data/repositories/RDFChess/README.md +20 -0
  147. somef_core/test/test_data/repositories/RDFChess/pom.xml +202 -0
  148. somef_core/test/test_data/repositories/RDFChess/rdfchess.config +0 -0
  149. somef_core/test/test_data/repositories/RDFChess/run.bat +1 -0
  150. somef_core/test/test_data/repositories/Widoco/CITATION.cff +20 -0
  151. somef_core/test/test_data/repositories/Widoco/CONTRIBUTING.md +2 -0
  152. somef_core/test/test_data/repositories/Widoco/Dockerfile +18 -0
  153. somef_core/test/test_data/repositories/Widoco/LICENSE +202 -0
  154. somef_core/test/test_data/repositories/Widoco/README.md +187 -0
  155. somef_core/test/test_data/repositories/Widoco/codemeta.json +62 -0
  156. somef_core/test/test_data/repositories/Widoco/docs/pom.sml +167 -0
  157. somef_core/test/test_data/repositories/Widoco/docs/pom.xml +167 -0
  158. somef_core/test/test_data/repositories/Widoco/example_onto/CITATION.cff +20 -0
  159. somef_core/test/test_data/repositories/Widoco/example_onto/README.md +188 -0
  160. somef_core/test/test_data/repositories/Widoco/example_onto/license.md +14 -0
  161. somef_core/test/test_data/repositories/Widoco/example_onto/ontology.nt +153 -0
  162. somef_core/test/test_data/repositories/Widoco/example_onto/ontology.ttl +159 -0
  163. somef_core/test/test_data/repositories/Widoco/example_onto/test.ttl +9 -0
  164. somef_core/test/test_data/repositories/Widoco/pom.xml +172 -0
  165. somef_core/test/test_data/repositories/Widoco/target/maven-archiver/pom.properties +3 -0
  166. somef_core/test/test_data/repositories/Widoco/test/CITATION.cff +20 -0
  167. somef_core/test/test_data/repositories/Widoco/test/test/CITATION.cff +20 -0
  168. somef_core/test/test_data/repositories/Widoco/test/test/test/CITATION.cff +20 -0
  169. somef_core/test/test_data/repositories/ack/ACKNOWLEDGMENT.md +1 -0
  170. somef_core/test/test_data/repositories/ack/README.md +4 -0
  171. somef_core/test/test_data/repositories/aladin-lite/codemeta.json +83 -0
  172. somef_core/test/test_data/repositories/auroral-ontology-core/ontology.rdf +2306 -0
  173. somef_core/test/test_data/repositories/auroral-ontology-core/ontology.xml +981 -0
  174. somef_core/test/test_data/repositories/auroral-ontology-core/readme.md +34 -0
  175. somef_core/test/test_data/repositories/basis_functions_approach_to_GP-master/BF_1D_notebook.Rmd +1041 -0
  176. somef_core/test/test_data/repositories/basis_functions_approach_to_GP-master/Diagnosis_HSGP_v2.Rmd +938 -0
  177. somef_core/test/test_data/repositories/basis_functions_approach_to_GP-master/Rnb_case3D_diagnosis.Rmd +1082 -0
  178. somef_core/test/test_data/repositories/bimerr-epw/LICENSE +201 -0
  179. somef_core/test/test_data/repositories/bimerr-epw/README.md +23 -0
  180. somef_core/test/test_data/repositories/bootstrap-datepicker-rails/bootstrap-datepicker-rails-2.gemspec +22 -0
  181. somef_core/test/test_data/repositories/bootstrap-datepicker-rails/bootstrap-datepicker-rails.gemspec +22 -0
  182. somef_core/test/test_data/repositories/captum/.gitattributes +2 -0
  183. somef_core/test/test_data/repositories/captum/.gitignore +117 -0
  184. somef_core/test/test_data/repositories/captum/AWESOME_LIST.md +28 -0
  185. somef_core/test/test_data/repositories/captum/CITATION +8 -0
  186. somef_core/test/test_data/repositories/captum/CODE_OF_CONDUCT.md +76 -0
  187. somef_core/test/test_data/repositories/captum/CONTRIBUTING.md +134 -0
  188. somef_core/test/test_data/repositories/captum/LICENSE +29 -0
  189. somef_core/test/test_data/repositories/captum/README.md +494 -0
  190. somef_core/test/test_data/repositories/captum/environment.yml +6 -0
  191. somef_core/test/test_data/repositories/captum/pyproject.toml +2 -0
  192. somef_core/test/test_data/repositories/captum/setup.cfg +12 -0
  193. somef_core/test/test_data/repositories/captum/setup.py +171 -0
  194. somef_core/test/test_data/repositories/chosen/bower.json +42 -0
  195. somef_core/test/test_data/repositories/codecov/composer.json +25 -0
  196. somef_core/test/test_data/repositories/codemeta_repo/codemeta.json +214 -0
  197. somef_core/test/test_data/repositories/componentInstaller/CITATION.cff +23 -0
  198. somef_core/test/test_data/repositories/componentInstaller/LICENSE +674 -0
  199. somef_core/test/test_data/repositories/componentInstaller/README.md +138 -0
  200. somef_core/test/test_data/repositories/composer/composer.json +110 -0
  201. somef_core/test/test_data/repositories/corpuser/README.md +38 -0
  202. somef_core/test/test_data/repositories/cropwater/DESCRIPTION +61 -0
  203. somef_core/test/test_data/repositories/cropwater/LICENSE.md +21 -0
  204. somef_core/test/test_data/repositories/cropwater/README.md +649 -0
  205. somef_core/test/test_data/repositories/cropwater/codemeta.json +159 -0
  206. somef_core/test/test_data/repositories/fair-ontologies/README.md +117 -0
  207. somef_core/test/test_data/repositories/fair-ontologies/citation.cff +23 -0
  208. somef_core/test/test_data/repositories/fair-ontologies/codemeta.json +133 -0
  209. somef_core/test/test_data/repositories/fair-ontologies/pom.xml +186 -0
  210. somef_core/test/test_data/repositories/fuji/AUTHORS +19 -0
  211. somef_core/test/test_data/repositories/fuji/CITATION.cff +35 -0
  212. somef_core/test/test_data/repositories/fuji/README.md +265 -0
  213. somef_core/test/test_data/repositories/fuji/pyproject.toml +169 -0
  214. somef_core/test/test_data/repositories/gammalearn/pyproject.toml +79 -0
  215. somef_core/test/test_data/repositories/gammapy/codemeta.json +376 -0
  216. somef_core/test/test_data/repositories/ggplot2/DESCRIPTION +132 -0
  217. somef_core/test/test_data/repositories/ggstatsplot/DESCRIPTION +86 -0
  218. somef_core/test/test_data/repositories/ggstatsplot/codemeta.json +638 -0
  219. somef_core/test/test_data/repositories/haskell/cabal.cabal +419 -0
  220. somef_core/test/test_data/repositories/inspect4py/CITATION.cff +25 -0
  221. somef_core/test/test_data/repositories/inspect4py/README.md +255 -0
  222. somef_core/test/test_data/repositories/inspect4py/docs/requirements.txt +1 -0
  223. somef_core/test/test_data/repositories/inspect4py/pyproject.toml +15 -0
  224. somef_core/test/test_data/repositories/inspect4py/requirements.txt +12 -0
  225. somef_core/test/test_data/repositories/inspect4py/setup.py +51 -0
  226. somef_core/test/test_data/repositories/ipynb-master/__init__.py +29 -0
  227. somef_core/test/test_data/repositories/ipynb-master/bogus_ipynb.ipynb +2 -0
  228. somef_core/test/test_data/repositories/ipynb-master/r_notebook.ipynb +40 -0
  229. somef_core/test/test_data/repositories/js-template/bower.json +26 -0
  230. somef_core/test/test_data/repositories/js-template/package.json +25 -0
  231. somef_core/test/test_data/repositories/kgtk-notebooks/LICENSE +21 -0
  232. somef_core/test/test_data/repositories/kgtk-notebooks/README.md +81 -0
  233. somef_core/test/test_data/repositories/kgtk-notebooks/tutorial/01-kgtk-introduction.ipynb +2455 -0
  234. somef_core/test/test_data/repositories/kgtk-notebooks/tutorial/02-kg-profiling.ipynb +4924 -0
  235. somef_core/test/test_data/repositories/kgtk-notebooks/tutorial/03-kg-graph-embeddings.ipynb +819 -0
  236. somef_core/test/test_data/repositories/kgtk-notebooks/tutorial/04-kg-enrichment-with-csv.ipynb +3570 -0
  237. somef_core/test/test_data/repositories/kgtk-notebooks/tutorial/05-kg-enrichment-with-lod.ipynb +3297 -0
  238. somef_core/test/test_data/repositories/kgtk-notebooks/tutorial/06-kg-network-analysis.ipynb +2363 -0
  239. somef_core/test/test_data/repositories/kgtk-notebooks/tutorial/07-kg-constraint-validation.ipynb +1423 -0
  240. somef_core/test/test_data/repositories/kgtk-notebooks/tutorial/kgtk-browser.ipynb +308 -0
  241. somef_core/test/test_data/repositories/kgtk-notebooks/use-cases/Visualize-force-graph-example.ipynb +937 -0
  242. somef_core/test/test_data/repositories/kgtk-notebooks/use-cases/browser-profiling.ipynb +1583 -0
  243. somef_core/test/test_data/repositories/kgtk-notebooks/use-cases/class-visualization.ipynb +7509 -0
  244. somef_core/test/test_data/repositories/kgtk-notebooks/use-cases/ontology-examples.ipynb +2002 -0
  245. somef_core/test/test_data/repositories/kgtk-notebooks/use-cases/properties-for-this-type.ipynb +1474 -0
  246. somef_core/test/test_data/repositories/maven/pom.xml +1275 -0
  247. somef_core/test/test_data/repositories/maykinMedia/publiccode.yaml +112 -0
  248. somef_core/test/test_data/repositories/mir_eval/LICENSE.txt +21 -0
  249. somef_core/test/test_data/repositories/mir_eval/README.rst +25 -0
  250. somef_core/test/test_data/repositories/mir_eval/setup.py +37 -0
  251. somef_core/test/test_data/repositories/mira/LICENSE +201 -0
  252. somef_core/test/test_data/repositories/mira/README.md +124 -0
  253. somef_core/test/test_data/repositories/nemo/CITATION.cff +23 -0
  254. somef_core/test/test_data/repositories/nemo/README.md +185 -0
  255. somef_core/test/test_data/repositories/pcake/README.md +4 -0
  256. somef_core/test/test_data/repositories/pcake/setup.py +28 -0
  257. somef_core/test/test_data/repositories/probot-12.1.1/.editorconfig +9 -0
  258. somef_core/test/test_data/repositories/probot-12.1.1/.gitignore +12 -0
  259. somef_core/test/test_data/repositories/probot-12.1.1/CODE_OF_CONDUCT.md +132 -0
  260. somef_core/test/test_data/repositories/probot-12.1.1/CONTRIBUTING.md +63 -0
  261. somef_core/test/test_data/repositories/probot-12.1.1/LICENSE +15 -0
  262. somef_core/test/test_data/repositories/probot-12.1.1/README.md +51 -0
  263. somef_core/test/test_data/repositories/probot-12.1.1/README.pt-br.md +40 -0
  264. somef_core/test/test_data/repositories/probot-12.1.1/codecov.yml +11 -0
  265. somef_core/test/test_data/repositories/publicCode-Example/publiccode.yml +131 -0
  266. somef_core/test/test_data/repositories/r3broot2/codemeta.json +437 -0
  267. somef_core/test/test_data/repositories/rdflib-6.0.2/CHANGELOG.md +2255 -0
  268. somef_core/test/test_data/repositories/rdflib-6.0.2/CONTRIBUTORS +57 -0
  269. somef_core/test/test_data/repositories/rdflib-6.0.2/LICENSE +29 -0
  270. somef_core/test/test_data/repositories/rdflib-6.0.2/MANIFEST.in +13 -0
  271. somef_core/test/test_data/repositories/rdflib-6.0.2/Makefile +20 -0
  272. somef_core/test/test_data/repositories/rdflib-6.0.2/README.md +226 -0
  273. somef_core/test/test_data/repositories/rdflib-6.0.2/black.toml +31 -0
  274. somef_core/test/test_data/repositories/rdflib-6.0.2/docker-compose.tests.yml +24 -0
  275. somef_core/test/test_data/repositories/rdflib-6.0.2/pull_request_template.md +7 -0
  276. somef_core/test/test_data/repositories/rdflib-6.0.2/requirements.dev.txt +12 -0
  277. somef_core/test/test_data/repositories/rdflib-6.0.2/requirements.txt +3 -0
  278. somef_core/test/test_data/repositories/rdflib-6.0.2/run_tests.py +92 -0
  279. somef_core/test/test_data/repositories/rdflib-6.0.2/run_tests.sh +9 -0
  280. somef_core/test/test_data/repositories/rdflib-6.0.2/run_tests_with_coverage_report.sh +9 -0
  281. somef_core/test/test_data/repositories/rdflib-6.0.2/setup.cfg +38 -0
  282. somef_core/test/test_data/repositories/rdflib-6.0.2/setup.py +90 -0
  283. somef_core/test/test_data/repositories/rdflib-6.0.2/skiptests.list +59 -0
  284. somef_core/test/test_data/repositories/rdflib-6.0.2/tox.ini +35 -0
  285. somef_core/test/test_data/repositories/repos_oeg/bimerr-core/README.md +1 -0
  286. somef_core/test/test_data/repositories/resolver_deco/LICENSE.txt +12 -0
  287. somef_core/test/test_data/repositories/resolver_deco/README.txt +56 -0
  288. somef_core/test/test_data/repositories/resolver_deco/resolver_deco.py +49 -0
  289. somef_core/test/test_data/repositories/resolver_deco/setup.py +20 -0
  290. somef_core/test/test_data/repositories/runtime_platform_repo/codemeta.json +6 -0
  291. somef_core/test/test_data/repositories/rustdesk/Cargo.toml +223 -0
  292. somef_core/test/test_data/repositories/soca/README.md +301 -0
  293. somef_core/test/test_data/repositories/soca/codemeta.json +75 -0
  294. somef_core/test/test_data/repositories/soca/pyproject.toml +6 -0
  295. somef_core/test/test_data/repositories/soca/setup.cfg +45 -0
  296. somef_core/test/test_data/repositories/software_catalog/LICENSE +201 -0
  297. somef_core/test/test_data/repositories/software_catalog/README.md +21 -0
  298. somef_core/test/test_data/repositories/software_catalog/repo_icons/acknowledgement.png +0 -0
  299. somef_core/test/test_data/repositories/software_catalog/repo_icons/citation.png +0 -0
  300. somef_core/test/test_data/repositories/somef_repo/CITATION.cff +43 -0
  301. somef_core/test/test_data/repositories/somef_repo/README.md +361 -0
  302. somef_core/test/test_data/repositories/somef_repo/lib/python/pip-24.2.dist-info/AUTHORS.txt +796 -0
  303. somef_core/test/test_data/repositories/somef_repo/pyproject.toml +55 -0
  304. somef_core/test/test_data/repositories/sprint-main/netex_accounting_version.xsd.rdf +425 -0
  305. somef_core/test/test_data/repositories/sprint-main/setup.sh +2 -0
  306. somef_core/test/test_data/repositories/stable-diffusion/README.md +214 -0
  307. somef_core/test/test_data/repositories/stable-diffusion/environment.yaml +31 -0
  308. somef_core/test/test_data/repositories/sunpy/CITATION.cff +79 -0
  309. somef_core/test/test_data/repositories/sunpy/pyproject.toml +256 -0
  310. somef_core/test/test_data/repositories/sunpy_short_desc/README.rst +82 -0
  311. somef_core/test/test_data/repositories/sunpy_short_desc/pyproject.toml +256 -0
  312. somef_core/test/test_data/repositories/tensorflow/CODEOWNERS +18 -0
  313. somef_core/test/test_data/repositories/tensorflow/README.md +173 -0
  314. somef_core/test/test_data/repositories/termlex-main/README.md +14 -0
  315. somef_core/test/test_data/repositories/termlex-main/ontology/termlex.ttl +2150 -0
  316. somef_core/test/test_data/repositories/termlex-main/termlex_core.png +0 -0
  317. somef_core/test/test_data/repositories/tidyverse/DESCRIPTION +63 -0
  318. somef_core/test/test_data/repositories/unused/unused.cabal +127 -0
  319. somef_core/test/test_data/repositories/wav2letter/CMakeLists.txt +168 -0
  320. somef_core/test/test_data/repositories/wav2letter/CODE_OF_CONDUCT.md +5 -0
  321. somef_core/test/test_data/repositories/wav2letter/CONTRIBUTING.md +27 -0
  322. somef_core/test/test_data/repositories/wav2letter/Decode.cpp +440 -0
  323. somef_core/test/test_data/repositories/wav2letter/LICENSE +30 -0
  324. somef_core/test/test_data/repositories/wav2letter/README.md +39 -0
  325. somef_core/test/test_data/repositories/wav2letter/Test.cpp +184 -0
  326. somef_core/test/test_data/repositories/wav2letter/Train.cpp +564 -0
  327. somef_core/test/test_data/repositories/wav2letter/docs/DecoderSketch.png +0 -0
  328. somef_core/test/test_data/repositories/wav2letter/docs/arch.md +68 -0
  329. somef_core/test/test_data/repositories/wav2letter/docs/decoder.md +80 -0
  330. somef_core/test/test_data/repositories/wav2letter/docs/installation.md +50 -0
  331. somef_core/test/test_data/repositories/wav2letter/docs/train.md +86 -0
  332. somef_core/test/test_data/repositories/wav2letter/scripts/OptimizeSortParams.cpp +156 -0
  333. somef_core/test/test_data/repositories/wav2letter/scripts/Seq2SeqAnalysis.cpp +215 -0
  334. somef_core/test/test_data/repositories/wav2letter/scripts/arrayfire_parser.py +65 -0
  335. somef_core/test/test_data/repositories/wav2letter/scripts/fair_cluster/w2l_fair_cluster_build.sh +37 -0
  336. somef_core/test/test_data/repositories/wav2letter/scripts/fair_cluster/w2l_fair_cluster_decode.sh +34 -0
  337. somef_core/test/test_data/repositories/wav2letter/scripts/fair_cluster/w2l_fair_cluster_distributed_training_benchmarks.sh +59 -0
  338. somef_core/test/test_data/repositories/wav2letter/scripts/fair_cluster/w2l_fair_cluster_train.sh +48 -0
  339. somef_core/test/test_data/repositories/wav2letter/scripts/imgcat +118 -0
  340. somef_core/test/test_data/repositories/wav2letter/scripts/launch_distributed_chronos.sh +60 -0
  341. somef_core/test/test_data/repositories/wav2letter/scripts/tensorboard.py +206 -0
  342. somef_core/test/test_data/repositories/wav2letter/scripts/wavdiff +53 -0
  343. somef_core/test/test_data/repositories/wav2letter/scripts/wavextract +180 -0
  344. somef_core/test/test_data/repositories/wav2letter/scripts/wavplot +170 -0
  345. somef_core/test/test_data/repositories/wav2letter/scripts/wavstatus +94 -0
  346. somef_core/test/test_data/repositories/website-static-master/fonts.css +15 -0
  347. somef_core/test/test_data/repositories/website-static-master/header.js +32 -0
  348. somef_core/test/test_data/repositories/website-static-master/image.js +32 -0
  349. somef_core/test/test_data/repositories/website-static-master/index.html +1 -0
  350. somef_core/test/test_data/repositories/website-static-master/index.js +126 -0
  351. somef_core/test/test_data/repositories/website-static-master/layout.js +102 -0
  352. somef_core/test/test_data/repositories/website-static-master/privacy.js +47 -0
  353. somef_core/test/test_data/repositories/website-static-master/terms.js +119 -0
  354. somef_core/test/test_data/repositories/workloopr/LICENSE +674 -0
  355. somef_core/test/test_data/repositories/workloopr/README.md +172 -0
  356. somef_core/test/test_data/repositories/workloopr/codemeta.json +144 -0
  357. somef_core/test/test_data/repositories/wot-hive/README.md +111 -0
  358. somef_core/test/test_data/repositories/wot-hive/docker/auroral-hive/Dockerfile +15 -0
  359. somef_core/test/test_data/repositories/wot-hive/docker/docker-compose.yml +26 -0
  360. somef_core/test/test_data/repositories/wot-hive/docker/fuseki/Dockerfile +9 -0
  361. somef_core/test/test_data/repostatus-README-2.md +50 -0
  362. somef_core/test/test_data/repostatus-README.md +50 -0
  363. somef_core/test/test_data/test_465.md +388 -0
  364. somef_core/test/test_data/test_arxiv_links.txt +95 -0
  365. somef_core/test/test_data/test_extract_bibtex.txt +17 -0
  366. somef_core/test/test_data/test_extract_binder_links.txt +4 -0
  367. somef_core/test/test_data/test_extract_dois.txt +8 -0
  368. somef_core/test/test_data/test_extract_gitter_chat.txt +2 -0
  369. somef_core/test/test_data/test_extract_header_content_hash.txt +13 -0
  370. somef_core/test/test_data/test_extract_header_content_hash_complex.txt +11 -0
  371. somef_core/test/test_data/test_extract_header_content_underline.txt +18 -0
  372. somef_core/test/test_data/test_extract_readthedocs_1.txt +2 -0
  373. somef_core/test/test_data/test_extract_readthedocs_2.txt +6 -0
  374. somef_core/test/test_data/test_extract_readthedocs_3.txt +6 -0
  375. somef_core/test/test_data/test_extract_title_hash.txt +6 -0
  376. somef_core/test/test_data/test_extract_title_underline.txt +6 -0
  377. somef_core/test/test_data/test_extract_title_with_md.txt +2 -0
  378. somef_core/test/test_data/test_images.txt +68 -0
  379. somef_core/test/test_data/test_issue_181.txt +16 -0
  380. somef_core/test/test_data/test_issue_181_2.txt +135 -0
  381. somef_core/test/test_data/test_issue_181_3.txt +110 -0
  382. somef_core/test/test_data/test_issue_270.txt +15 -0
  383. somef_core/test/test_data/test_issue_images.txt +9 -0
  384. somef_core/test/test_data/test_logo.txt +5 -0
  385. somef_core/test/test_data/test_logo2.txt +5 -0
  386. somef_core/test/test_data/test_logo_tensorflow.txt +157 -0
  387. somef_core/test/test_data/test_logo_uscisii2.txt +95 -0
  388. somef_core/test/test_data/test_pypi.md +26 -0
  389. somef_core/test/test_data/test_repo_status.txt +10 -0
  390. somef_core/test/test_data/test_wiki.md +46 -0
  391. somef_core/test/test_data/widoco_readme.md +152 -0
  392. somef_core/test/test_description_parser.py +116 -0
  393. somef_core/test/test_dockerfile_parser.py +190 -0
  394. somef_core/test/test_enrichment.py +169 -0
  395. somef_core/test/test_extract_authors.py +71 -0
  396. somef_core/test/test_extract_workflows.py +18 -0
  397. somef_core/test/test_gemspec_parser.py +77 -0
  398. somef_core/test/test_gitlab_selfhosted.py +26 -0
  399. somef_core/test/test_gitlab_token_handling.py +118 -0
  400. somef_core/test/test_google_compliant_export.py +50 -0
  401. somef_core/test/test_header_analysis.py +284 -0
  402. somef_core/test/test_package_json_parser.py +124 -0
  403. somef_core/test/test_parser_somef.py +95 -0
  404. somef_core/test/test_pom_parser.py +118 -0
  405. somef_core/test/test_process_repository.py +1350 -0
  406. somef_core/test/test_process_results.py +23 -0
  407. somef_core/test/test_publiccode_parser.py +144 -0
  408. somef_core/test/test_python_parser.py +56 -0
  409. somef_core/test/test_regular_expressions.py +677 -0
  410. somef_core/test/test_setupcfg_parser.py +53 -0
  411. somef_core/test/test_software_type.py +139 -0
  412. somef_core/test/test_toml_parser.py +375 -0
  413. somef_core/test/test_verify_token.py +62 -0
  414. somef_core/test/test_wiki.py +69 -0
  415. somef_core/utils/__init__.py +0 -0
  416. somef_core/utils/constants.py +794 -0
  417. somef_core/utils/enrichment.py +223 -0
  418. somef_core/utils/markdown_utils.py +79 -0
  419. somef_core-0.0.1.dist-info/METADATA +545 -0
  420. somef_core-0.0.1.dist-info/RECORD +423 -0
  421. somef_core-0.0.1.dist-info/WHEEL +4 -0
  422. somef_core-0.0.1.dist-info/entry_points.txt +3 -0
  423. 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()