Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ld+json metadata to software detail pages #385

Merged
merged 6 commits into from
Feb 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
site/
venv*

1 change: 1 addition & 0 deletions mkdocs-ldjson-plugin/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .inject_ld_json import InjectLDJsonPlugin
28 changes: 28 additions & 0 deletions mkdocs-ldjson-plugin/inject_ld_json.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import json
from mkdocs.plugins import BasePlugin
from bs4 import BeautifulSoup

class InjectLDJsonPlugin(BasePlugin):
def on_post_page(self, output, page, config):
"""Inject JSON-LD metadata into the <head> section of the rendered page."""

# Get JSON-LD data from page metadata
json_ld = page.meta.get("json_ld")
if not json_ld:
return output # No JSON-LD to inject, return page as is

# Convert dictionary to JSON string
json_ld_str = json.dumps(json_ld, indent=2)

# Parse the HTML output
soup = BeautifulSoup(output, "html.parser")

# Create <script> tag for JSON-LD
script_tag = soup.new_tag("script", type="application/ld+json")
script_tag.string = json_ld_str

# Find the <head> and insert the script
if soup.head:
soup.head.append(script_tag)

return str(soup)
2 changes: 2 additions & 0 deletions mkdocs-ldjson-plugin/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
mkdocs
beautifulsoup4
20 changes: 20 additions & 0 deletions mkdocs-ldjson-plugin/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from setuptools import setup, find_packages

# Read requirements from requirements.txt
def read_requirements():
with open("requirements.txt", encoding="utf-8") as f:
return f.read().splitlines()

setup(
name="inject_ld_json",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ocaisa Maybe it makes sense to throw this in a dedicated repo, and publish it on PyPI, so others can also use this plugin, but good enough to park it in our EESSI/docs repo for now

version="0.1.0",
py_modules=["inject_ld_json"],
packages=find_packages(),
install_requires=read_requirements(), # Use requirements.txt
include_package_data=True,
entry_points={
"mkdocs.plugins": [
"inject_ld_json = inject_ld_json:InjectLDJsonPlugin",
]
},
)
2 changes: 2 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ plugins:
software_layer/adding_software.md: adding_software/overview.md
pilot.md: repositories/pilot.md
gpu.md: site_specific_config/gpu.md
# Enable our custom plugin for json-ld metadata
- inject_ld_json
markdown_extensions:
# enable adding HTML attributes and CSS classes
- attr_list
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ mkdocs-redirects
mkdocs-git-revision-date-localized-plugin
mkdocs-toc-sidebar-plugin
mkdocs-macros-plugin
./mkdocs-ldjson-plugin
57 changes: 55 additions & 2 deletions scripts/available_software/available_software.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,16 @@
@author: Michiel Lachaert (Ghent University)
@author: Lara Peeters (Ghent University)
"""
import copy
import json
import os
import re
import subprocess
import sys
import time
import yaml
from typing import Union, Tuple
from string import Template
import numpy as np
from mdutils.mdutils import MdUtils
from natsort import natsorted
Expand Down Expand Up @@ -339,6 +342,41 @@ def generate_software_table_data(software_data: dict, targets: list) -> list:
return table_data


# LD+JSON Template with placeholders
ldjson_template = Template("""
{
"json_ld": {
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"name": "$name",
"url": "$homepage",
"softwareVersion": "$version",
"description": "$description",
"operatingSystem": "LINUX",
"applicationCategory": "DeveloperApplication",
"softwareRequirements": "See https://www.eessi.io/docs/ for how to make EESSI available on your system",
"license": "Not confirmed",
"review": {
"@type": "Review",
"reviewRating": {
"@type": "Rating",
"ratingValue": 5
},
"author": {
"@type": "Organization",
"name": "EESSI"
},
"reviewBody": "Application has been successfully made available on all architectures supported by EESSI"
},
"offers": {
"@type": "Offer",
"price": 0
}
}
}
""")


def generate_software_detail_page(
software_name: str,
software_data: dict,
Expand All @@ -357,15 +395,20 @@ def generate_software_detail_page(
"""
sorted_versions = dict_sort(software_data["versions"])
newest_version = list(sorted_versions.keys())[-1]
ldjson_software_data = copy.deepcopy(software_data)

filename = f"{path}/{software_name}.md"
md_file = MdUtils(file_name=filename, title=f"{software_name}")
if 'description' in software_data.keys():
description = software_data['description']
md_file.new_paragraph(f"{description}")
else:
ldjson_software_data['description'] = ''
if 'homepage' in software_data.keys():
homepage = software_data['homepage']
md_file.new_paragraph(f"{homepage}")
else:
ldjson_software_data["homepage"] = ''

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

Expand All @@ -392,11 +435,21 @@ def generate_software_detail_page(

md_file.create_md_file()

# Remove the TOC
with open(filename) as f:
read_data = f.read()
with open(filename, 'w') as f:
f.write("---\nhide:\n - toc\n---\n" + read_data)
# Add the software name
ldjson_software_data['name'] = software_name
# Just output the supported versions (with toolchains)
ldjson_software_data["version"] = list(sorted_versions.keys())
# Make the description safe for json (and remove surrounding quotes)
ldjson_software_data['description'] = json.dumps(ldjson_software_data['description'])[1:-1]
json_str = ldjson_template.substitute(ldjson_software_data) # Replace placeholders
json_topmatter = json.loads(json_str)
# Remove the TOC
json_topmatter["hide"] = ["toc"]
yaml_topmatter = yaml.dump(json_topmatter)
f.write("---\n" + yaml_topmatter + "---\n" + read_data)


def generate_detail_pages(json_path, dest_path) -> None:
Expand Down
3 changes: 2 additions & 1 deletion scripts/available_software/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mdutils
numpy
natsort
natsort
pyyaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,31 @@
---
hide:
- toc
- toc
json_ld:
'@context': https://schema.org
'@type': SoftwareApplication
applicationCategory: DeveloperApplication
description: ''
license: Not confirmed
name: science
offers:
'@type': Offer
price: 0
operatingSystem: LINUX
review:
'@type': Review
author:
'@type': Organization
name: EESSI
reviewBody: Application has been successfully made available on all architectures
supported by EESSI
reviewRating:
'@type': Rating
ratingValue: 5
softwareRequirements: See https://www.eessi.io/docs/ for how to make EESSI available
on your system
softwareVersion: '[''science/5.3.0'', ''science/7.2.0'']'
url: ''
---

science
Expand Down
Loading