Skip to content

Commit f6517a3

Browse files
authored
Merge pull request #385 from ocaisa/schema
Add `ld+json` metadata to software detail pages
2 parents d576d2c + 37cbeea commit f6517a3

File tree

10 files changed

+139
-4
lines changed

10 files changed

+139
-4
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
site/
2+
venv*
3+

mkdocs-ldjson-plugin/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .inject_ld_json import InjectLDJsonPlugin
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import json
2+
from mkdocs.plugins import BasePlugin
3+
from bs4 import BeautifulSoup
4+
5+
class InjectLDJsonPlugin(BasePlugin):
6+
def on_post_page(self, output, page, config):
7+
"""Inject JSON-LD metadata into the <head> section of the rendered page."""
8+
9+
# Get JSON-LD data from page metadata
10+
json_ld = page.meta.get("json_ld")
11+
if not json_ld:
12+
return output # No JSON-LD to inject, return page as is
13+
14+
# Convert dictionary to JSON string
15+
json_ld_str = json.dumps(json_ld, indent=2)
16+
17+
# Parse the HTML output
18+
soup = BeautifulSoup(output, "html.parser")
19+
20+
# Create <script> tag for JSON-LD
21+
script_tag = soup.new_tag("script", type="application/ld+json")
22+
script_tag.string = json_ld_str
23+
24+
# Find the <head> and insert the script
25+
if soup.head:
26+
soup.head.append(script_tag)
27+
28+
return str(soup)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
mkdocs
2+
beautifulsoup4

mkdocs-ldjson-plugin/setup.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from setuptools import setup, find_packages
2+
3+
# Read requirements from requirements.txt
4+
def read_requirements():
5+
with open("requirements.txt", encoding="utf-8") as f:
6+
return f.read().splitlines()
7+
8+
setup(
9+
name="inject_ld_json",
10+
version="0.1.0",
11+
py_modules=["inject_ld_json"],
12+
packages=find_packages(),
13+
install_requires=read_requirements(), # Use requirements.txt
14+
include_package_data=True,
15+
entry_points={
16+
"mkdocs.plugins": [
17+
"inject_ld_json = inject_ld_json:InjectLDJsonPlugin",
18+
]
19+
},
20+
)

mkdocs.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ plugins:
100100
software_layer/adding_software.md: adding_software/overview.md
101101
pilot.md: repositories/pilot.md
102102
gpu.md: site_specific_config/gpu.md
103+
# Enable our custom plugin for json-ld metadata
104+
- inject_ld_json
103105
markdown_extensions:
104106
# enable adding HTML attributes and CSS classes
105107
- attr_list

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ mkdocs-redirects
44
mkdocs-git-revision-date-localized-plugin
55
mkdocs-toc-sidebar-plugin
66
mkdocs-macros-plugin
7+
./mkdocs-ldjson-plugin

scripts/available_software/available_software.py

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,16 @@
1111
@author: Michiel Lachaert (Ghent University)
1212
@author: Lara Peeters (Ghent University)
1313
"""
14+
import copy
1415
import json
1516
import os
1617
import re
1718
import subprocess
1819
import sys
1920
import time
21+
import yaml
2022
from typing import Union, Tuple
23+
from string import Template
2124
import numpy as np
2225
from mdutils.mdutils import MdUtils
2326
from natsort import natsorted
@@ -339,6 +342,41 @@ def generate_software_table_data(software_data: dict, targets: list) -> list:
339342
return table_data
340343

341344

345+
# LD+JSON Template with placeholders
346+
ldjson_template = Template("""
347+
{
348+
"json_ld": {
349+
"@context": "https://schema.org",
350+
"@type": "SoftwareApplication",
351+
"name": "$name",
352+
"url": "$homepage",
353+
"softwareVersion": "$version",
354+
"description": "$description",
355+
"operatingSystem": "LINUX",
356+
"applicationCategory": "DeveloperApplication",
357+
"softwareRequirements": "See https://www.eessi.io/docs/ for how to make EESSI available on your system",
358+
"license": "Not confirmed",
359+
"review": {
360+
"@type": "Review",
361+
"reviewRating": {
362+
"@type": "Rating",
363+
"ratingValue": 5
364+
},
365+
"author": {
366+
"@type": "Organization",
367+
"name": "EESSI"
368+
},
369+
"reviewBody": "Application has been successfully made available on all architectures supported by EESSI"
370+
},
371+
"offers": {
372+
"@type": "Offer",
373+
"price": 0
374+
}
375+
}
376+
}
377+
""")
378+
379+
342380
def generate_software_detail_page(
343381
software_name: str,
344382
software_data: dict,
@@ -357,15 +395,20 @@ def generate_software_detail_page(
357395
"""
358396
sorted_versions = dict_sort(software_data["versions"])
359397
newest_version = list(sorted_versions.keys())[-1]
398+
ldjson_software_data = copy.deepcopy(software_data)
360399

361400
filename = f"{path}/{software_name}.md"
362401
md_file = MdUtils(file_name=filename, title=f"{software_name}")
363402
if 'description' in software_data.keys():
364403
description = software_data['description']
365404
md_file.new_paragraph(f"{description}")
405+
else:
406+
ldjson_software_data['description'] = ''
366407
if 'homepage' in software_data.keys():
367408
homepage = software_data['homepage']
368409
md_file.new_paragraph(f"{homepage}")
410+
else:
411+
ldjson_software_data["homepage"] = ''
369412

370413
md_file.new_header(level=1, title="Available modules")
371414

@@ -392,11 +435,21 @@ def generate_software_detail_page(
392435

393436
md_file.create_md_file()
394437

395-
# Remove the TOC
396438
with open(filename) as f:
397439
read_data = f.read()
398440
with open(filename, 'w') as f:
399-
f.write("---\nhide:\n - toc\n---\n" + read_data)
441+
# Add the software name
442+
ldjson_software_data['name'] = software_name
443+
# Just output the supported versions (with toolchains)
444+
ldjson_software_data["version"] = list(sorted_versions.keys())
445+
# Make the description safe for json (and remove surrounding quotes)
446+
ldjson_software_data['description'] = json.dumps(ldjson_software_data['description'])[1:-1]
447+
json_str = ldjson_template.substitute(ldjson_software_data) # Replace placeholders
448+
json_topmatter = json.loads(json_str)
449+
# Remove the TOC
450+
json_topmatter["hide"] = ["toc"]
451+
yaml_topmatter = yaml.dump(json_topmatter)
452+
f.write("---\n" + yaml_topmatter + "---\n" + read_data)
400453

401454

402455
def generate_detail_pages(json_path, dest_path) -> None:
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
mdutils
22
numpy
3-
natsort
3+
natsort
4+
pyyaml

scripts/available_software/tests/data/test_md_template_detailed_science_sol.md

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,31 @@
11
---
22
hide:
3-
- toc
3+
- toc
4+
json_ld:
5+
'@context': https://schema.org
6+
'@type': SoftwareApplication
7+
applicationCategory: DeveloperApplication
8+
description: ''
9+
license: Not confirmed
10+
name: science
11+
offers:
12+
'@type': Offer
13+
price: 0
14+
operatingSystem: LINUX
15+
review:
16+
'@type': Review
17+
author:
18+
'@type': Organization
19+
name: EESSI
20+
reviewBody: Application has been successfully made available on all architectures
21+
supported by EESSI
22+
reviewRating:
23+
'@type': Rating
24+
ratingValue: 5
25+
softwareRequirements: See https://www.eessi.io/docs/ for how to make EESSI available
26+
on your system
27+
softwareVersion: '[''science/5.3.0'', ''science/7.2.0'']'
28+
url: ''
429
---
530

631
science

0 commit comments

Comments
 (0)