Skip to content

Commit 2745382

Browse files
authored
Merge pull request #21 from thegreenwebfoundation/tc-schema-docs-generation
Automatically generate html schema docs
2 parents cbfd385 + a82db64 commit 2745382

7 files changed

Lines changed: 207 additions & 3 deletions

File tree

.github/workflows/jekyll.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ jobs:
2828
ruby-version: '3.3'
2929
bundler-cache: true
3030

31+
- run: npm run schema-to-data
32+
3133
- run: bundle exec jekyll build
3234

3335
- uses: actions/upload-pages-artifact@v4

_data/generated/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
*
2+
!.gitignore

_includes/schema-table.html

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
{% assign s = include.schema %}
2+
3+
<section class="schema-doc">
4+
{% if s.title %}<h2>{{ s.title }}</h2>{% endif %}
5+
{% if s.description %}<p>{{ s.description }}</p>{% endif %}
6+
7+
<table>
8+
<thead>
9+
<tr>
10+
<th>Property</th><th>Type</th><th>Required</th><th>Default</th><th>Description</th>
11+
</tr>
12+
</thead>
13+
<tbody>
14+
{% for prop in s.properties %}
15+
<tr>
16+
<td><code>{{ prop.name }}</code></td>
17+
<td>
18+
<code>{{ prop.type }}</code>
19+
{% if prop.type == 'array' and prop.items %}
20+
of <code>{{ prop.items.type }}</code>
21+
{% endif %}
22+
</td>
23+
<td>{% if prop.required %}✓{% endif %}</td>
24+
<td>{% if prop.default != nil %}<code>{{ prop.default }}</code>{% endif %}</td>
25+
<td>
26+
{{ prop.description }}
27+
28+
{% if prop.enum %}
29+
<br>One of:
30+
{% for v in prop.enum %}<code>{{ v }}</code>{% unless forloop.last %}, {% endunless %}{% endfor %}
31+
{% endif %}
32+
33+
{% if prop.type == 'object' and prop.properties %}
34+
{% include schema-table.html schema=prop %}
35+
{% endif %}
36+
37+
{% if prop.type == 'array' and prop.items.properties %}
38+
Items:
39+
{% include schema-table.html schema=prop.items %}
40+
{% endif %}
41+
42+
{% if prop.variants %}
43+
<em>{{ prop.type }}:</em>
44+
{% for variant in prop.variants %}
45+
<strong>{{ variant.name }}</strong>
46+
{% if variant.description %} — {{ variant.description }}{% endif %}
47+
{% if variant.properties %}
48+
{% include schema-table.html schema=variant %}
49+
{% endif %}
50+
{% endfor %}
51+
{% endif %}
52+
</td>
53+
</tr>
54+
{% endfor %}
55+
</tbody>
56+
</table>
57+
</section>

package-lock.json

Lines changed: 51 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
{
2+
"scripts": {
3+
"schema-to-data": "node scripts/schema-to-data.js schema/dist/dist-v0.0.1.json _data/generated/schema.json"
4+
},
25
"dependencies": {
6+
"@apidevtools/json-schema-ref-parser": "^15.4.0",
37
"@tailwindcss/aspect-ratio": "^0.4.2",
48
"@tailwindcss/forms": "^0.5.11",
59
"@tailwindcss/line-clamp": "^0.4.4",

schema.markdown

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,11 @@ The schema is metrics-focused and aligns with the [DIST taxonomy](/taxonomy). Mo
3434

3535
### This IS NOT:
3636

37-
* A prescriptive methodology - the schema defines what to report, not how to calculate it; recommended methodologies will be available through the [DIST methodology map](/methodologies) but are not required.
37+
* A prescriptive methodology - the schema defines what to report, not how to calculate it; recommended methodologies will be available through the [DIST methodology map](/methodologies) but are not required.
3838

3939
* A certification or compliance framework - though it should be useful for those who need to comply with regulations like CSRD.
4040

41-
* A commercial product - it is open source, dual-licensed for open adoption and ISO standardisation but it is commercial product friendly - it can be used in commercial products and services.
41+
* A commercial product - it is open source, dual-licensed for open adoption and ISO standardisation but it is commercial product friendly - it can be used in commercial products and services.
4242

4343
## Use cases: what will people actually do with this?
4444

@@ -58,4 +58,8 @@ Benchmark and compare. Because the schema enforces a consistent structure, repor
5858
**Campaign and hold to account.** An NGO, journalist, or activist uses published schema data to identify organisations with poor performance on specific dimensions (e.g. e-waste, societal harms) and make evidence-based cases for improvement. Machine-readable data is far harder to hide behind than a glossy sustainability report.
5959

6060
**Build tools and services.** Developers build calculators, dashboards, benchmarking platforms, and analysis tools on top of the schema. Because the data structure is open and consistent, tooling built for one organisation works for any organisation. The schema becomes infrastructure that an ecosystem of tools plugs into.
61-
Teach and learn. Educators, training providers, and professional bodies use the taxonomy as a teaching framework for technology sustainability that goes beyond carbon. The dimensions provide a structured syllabus for understanding the full breadth of technology's impacts.
61+
Teach and learn. Educators, training providers, and professional bodies use the taxonomy as a teaching framework for technology sustainability that goes beyond carbon. The dimensions provide a structured syllabus for understanding the full breadth of technology's impacts.
62+
63+
64+
{% include schema-table.html schema=site.data.generated.schema %}
65+

scripts/schema-to-data.js

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import $RefParser from '@apidevtools/json-schema-ref-parser';
2+
import { writeFileSync } from 'fs';
3+
import { basename, extname } from 'path';
4+
5+
const inputFile = process.argv[2];
6+
const outputFile = process.argv[3]
7+
8+
if (!inputFile || !outputFile) {
9+
console.error('Usage: node scripts/schema-to-data.js <inputPath> <outputPath>');
10+
process.exit(1);
11+
}
12+
13+
function processProperty(name, prop, required = []) {
14+
const base = {
15+
name,
16+
required: required.includes(name),
17+
description: prop.description || '',
18+
default: prop.default ?? null,
19+
enum: prop.enum || null,
20+
properties: null,
21+
variants: null,
22+
};
23+
24+
if (prop.type && prop.type !== 'object') {
25+
return { ...base, type: prop.type };
26+
}
27+
28+
if (prop.type === 'object' || prop.properties) {
29+
return {
30+
...base,
31+
type: 'object',
32+
properties: processSchema(prop),
33+
};
34+
}
35+
36+
if (prop.type === 'array' && prop.items) {
37+
const items = prop.items;
38+
return {
39+
...base,
40+
type: 'array',
41+
items: items.properties
42+
? { type: 'object', properties: processSchema(items) }
43+
: { type: items.type || 'any' },
44+
};
45+
}
46+
47+
const combiner = ['anyOf', 'oneOf', 'allOf'].find(k => prop[k]);
48+
if (combiner) {
49+
return {
50+
...base,
51+
type: combiner,
52+
variants: prop[combiner].map((variant, i) => ({
53+
name: variant.title || `Option ${i + 1}`,
54+
description: variant.description || '',
55+
type: variant.type || 'object',
56+
properties: variant.properties ? processSchema(variant) : null,
57+
})),
58+
};
59+
}
60+
61+
return { ...base, type: 'any' };
62+
}
63+
64+
function processSchema(schema) {
65+
return Object.entries(schema.properties || {}).map(([name, prop]) =>
66+
processProperty(name, prop, schema.required || [])
67+
);
68+
}
69+
70+
try {
71+
const schema = await $RefParser.dereference(inputFile);
72+
73+
const data = {
74+
title: schema.title || '',
75+
description: schema.description || '',
76+
properties: processSchema(schema),
77+
};
78+
79+
writeFileSync(outputFile, JSON.stringify(data, null, 2));
80+
console.log(`Written to ${outputFile}`);
81+
} catch (err) {
82+
console.error('Failed to process schema:', err.message);
83+
process.exit(1);
84+
}

0 commit comments

Comments
 (0)