Skip to content

Commit

Permalink
Add series titles to all specs
Browse files Browse the repository at this point in the history
This adds a `title` and a `shortTitle` property to `series` that contain
version-less versions of the spec's title and short title. The version-less
version is retrieved from the W3C API for /TR specs, and computed by dropping
the level from the spec's title otherwise.

When a `shortTitle` is set explicitly for the spec in `specs.json`, that
title is used to compute the series' short title. This makes it possible to
compute short titles such as "WebRTC" or "ECMAScript" instead of more verbose
ones.

Close #359.
  • Loading branch information
tidoust authored and dontcallmedom committed Jan 3, 2022
1 parent a5c1316 commit 1b17bb1
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 32 deletions.
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ cross-references, WebIDL, quality, etc.
- [`series`](#series)
- [`series.shortname`](#seriesshortname)
- [`series.currentSpecification`](#seriescurrentspecification)
- [`series.title`](#seriestitle)
- [`series.shortTitle`](#seriesshorttitle)
- [`series.releaseUrl`](#seriesreleaseurl)
- [`series.nightlyUrl`](#seriesnightlyurl)
- [`seriesVersion`](#seriesversion)
Expand Down Expand Up @@ -90,6 +92,8 @@ Each specification in the list comes with the following properties:
"series": {
"shortname": "css-color",
"currentSpecification": "css-color-4",
"title": "CSS Color",
"shortTitle": "CSS Color",
"releaseUrl": "https://www.w3.org/TR/css-color/",
"nightlyUrl": "https://drafts.csswg.org/css-color/"
},
Expand Down Expand Up @@ -202,6 +206,28 @@ version in the series that is a "full" spec (see
The `currentSpecification` property is always set.


#### `series.title`


The version-less version of the title of the spec which can be used to refer to
all specs in the series. The title is either retrieved from the
[W3C API](https://w3c.github.io/w3c-api/) for W3C specs, or derived from the
spec's [`title`](#title).

The `title` property is always set.


#### `series.shortTitle`


The short title of the series title. In most cases, the short title is generated
from [`series.title`](#seriestitle) by dropping terms such as "Module", "Level",
or "Standard". In some cases, the short title is set manually.

The `shortTitle` property is always set. When there is no meaningful short
title, the property is set to the actual (possibly long) series title.


#### `series.releaseUrl`

The URL of the latest published snapshot for the spec series. For leveled specs
Expand Down
2 changes: 2 additions & 0 deletions schema/definitions.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
"type": "string",
"pattern": "^[\\w\\-]+$"
},
"title": { "$ref": "#/$defs/title" },
"shortTitle": { "$ref": "#/$defs/title" },
"currentSpecification": { "$ref": "#/$defs/shortname" },
"releaseUrl": { "$ref": "#/$defs/url" },
"nightlyUrl": { "$ref": "#/$defs/url" }
Expand Down
42 changes: 32 additions & 10 deletions src/build-index.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,10 @@ console.log(`Prepare initial list of specs... done with ${specs.length} specs`);


// Fetch additional spec info from external sources and complete the list
console.log(`Fetch organization/groups info...`);
fetchGroups(specs, { githubToken, w3cApiKey })
Promise.resolve()

.then(dolog(`Fetch organization/groups info...`))
.then(_ => fetchGroups(specs, { githubToken, w3cApiKey }))
.then(dolog(`Fetch organization/groups info... done`))

.then(dolog(`Fetch other spec info from external sources...`))
Expand All @@ -133,19 +135,30 @@ fetchGroups(specs, { githubToken, w3cApiKey })
}
});

// Set the series title based on the info returned by the W3C API if
// we have it, or compute the series title ourselves
const seriesInfo = specInfo.__series[spec.series.shortname];
if (seriesInfo?.title && !res.series.title) {
res.series.title = seriesInfo.title;
}
else {
res.series.title = res.title
.replace(/ \d+(\.\d+)?$/, '') // Drop level number
.replace(/( -)? Level$/, '') // Drop "Level"
.replace(/ Module$/, ''); // Drop "Module"
}

// Update the current specification based on the info returned by the
// W3C API, unless specs.json imposed a specific level.
// Note: the current specification returned by the W3C API may not be in the
// list, since we tend not to include previous levels for IDL specs (even
// if they are still "current"), in which case we'll just ignore the info
// returned from the W3C API.
const currentSpecification = specInfo.__current ?
specInfo.__current[spec.series.shortname] : null;
if (currentSpecification &&
!spec.series.forceCurrent &&
(currentSpecification !== spec.series.currentSpecification) &&
specs.find(s => s.shortname === currentSpecification)) {
res.series.currentSpecification = currentSpecification;
if (seriesInfo?.currentSpecification &&
!res.series.forceCurrent &&
(seriesInfo.currentSpecification !== res.series.currentSpecification) &&
specs.find(s => s.shortname === seriesInfo.currentSpecification)) {
res.series.currentSpecification = seriesInfo.currentSpecification;
}
delete res.series.forceCurrent;
return res;
Expand All @@ -154,7 +167,16 @@ fetchGroups(specs, { githubToken, w3cApiKey })

.then(dolog(`Compute short titles...`))
.then(index => index.map(spec => {
spec.shortTitle = spec.shortTitle || computeShortTitle(spec.title);
if (spec.shortTitle) {
// Use short title explicitly set in specs.json
// and compute the series short title from it
spec.series.shortTitle = spec.series.shortTitle ?? computeShortTitle(spec.shortTitle);
}
else {
// Compute short title from title otherwise
spec.shortTitle = computeShortTitle(spec.title);
spec.series.shortTitle = spec.series.shortTitle ?? computeShortTitle(spec.series.title);
}
return spec;
}))
.then(dolog(`Compute short titles... done`))
Expand Down
25 changes: 13 additions & 12 deletions src/fetch-info.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ async function fetchInfoFromW3CApi(specs, options) {
});
}));

const seriesShortnames = new Set();
const results = {};
specs.forEach((spec, idx) => {
if (info[idx]) {
Expand All @@ -116,23 +117,20 @@ async function fetchInfoFromW3CApi(specs, options) {
info[idx]["editor-draft"].replace(/^http:/, 'https:') :
null;

const seriesUrl = info[idx]._links.series.href;
const seriesShortname = seriesUrl.substring(seriesUrl.lastIndexOf('/') + 1);

results[spec.shortname] = {
series: { shortname: seriesShortname },
release: { url: release },
nightly: { url: nightly },
title: info[idx].title
};

if (spec.series?.shortname) {
seriesShortnames.add(spec.series.shortname);
}
}
});

// Fetch info on the series
const seriesShortnames = [...new Set(
Object.values(results).map(spec => spec.series.shortname)
)];
const seriesInfo = await Promise.all(seriesShortnames.map(async shortname => {
const seriesInfo = await Promise.all([...seriesShortnames].map(async shortname => {
const url = `https://api.w3.org/specification-series/${shortname}`;
return new Promise((resolve, reject) => {
const request = https.get(url, options, res => {
Expand Down Expand Up @@ -160,11 +158,14 @@ async function fetchInfoFromW3CApi(specs, options) {
});
}));

results.__current = {};
results.__series = {};
seriesInfo.forEach(info => {
const currSpecUrl = info._links["current-specification"].href;
const currSpec = currSpecUrl.substring(currSpecUrl.lastIndexOf('/') + 1);
results.__current[info.shortname] = currSpec;
results.__series[info.shortname] = {
title: info.name,
currentSpecification: currSpec
};
});

return results;
Expand Down Expand Up @@ -342,8 +343,8 @@ async function fetchInfo(specs, options) {
(specrefInfo[name] ? Object.assign(specrefInfo[name], { source: "specref" }) : null) ||
(specInfo[name] ? Object.assign(specInfo[name], { source: "spec" }) : null));

// Add current specification info from W3C API
results.__current = w3cInfo.__current;
// Add series info from W3C API
results.__series = w3cInfo.__series;

return results;
}
Expand Down
28 changes: 18 additions & 10 deletions test/fetch-info-w3c.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,12 @@ describe("fetch-info module (with W3C API key)", function () {
this.slow(5000);
this.timeout(30000);

function getW3CSpec(shortname) {
return { shortname, url: `https://www.w3.org/TR/${shortname}/` };
function getW3CSpec(shortname, series) {
const spec = { shortname, url: `https://www.w3.org/TR/${shortname}/` };
if (series) {
spec.series = { shortname: series };
}
return spec;
}

describe("W3C API key", () => {
Expand All @@ -37,21 +41,23 @@ describe("fetch-info module (with W3C API key)", function () {

describe("fetch from W3C API", () => {
it("works on a TR spec", async () => {
const spec = getW3CSpec("hr-time-2");
const spec = getW3CSpec("hr-time-2", "hr-time");
const info = await fetchInfo([spec], { w3cApiKey });
assert.ok(info[spec.shortname]);
assert.equal(info[spec.shortname].source, "w3c");
assert.equal(info[spec.shortname].release.url, spec.url);
assert.equal(info[spec.shortname].nightly.url, "https://w3c.github.io/hr-time/");
assert.equal(info[spec.shortname].title, "High Resolution Time Level 2");

assert.ok(info.__current);
assert.equal(info.__current["hr-time"], "hr-time-3");
assert.ok(info.__series);
assert.ok(info.__series["hr-time"]);
assert.equal(info.__series["hr-time"].currentSpecification, "hr-time-3");
assert.equal(info.__series["hr-time"].title, "High Resolution Time");
});

it("can operate on multiple specs at once", async () => {
const spec = getW3CSpec("hr-time-2");
const other = getW3CSpec("presentation-api");
const spec = getW3CSpec("hr-time-2", "hr-time");
const other = getW3CSpec("presentation-api", "presentation-api");
const info = await fetchInfo([spec, other], { w3cApiKey });
assert.ok(info[spec.shortname]);
assert.equal(info[spec.shortname].source, "w3c");
Expand All @@ -65,9 +71,11 @@ describe("fetch-info module (with W3C API key)", function () {
assert.equal(info[other.shortname].nightly.url, "https://w3c.github.io/presentation-api/");
assert.equal(info[other.shortname].title, "Presentation API");

assert.ok(info.__current);
assert.equal(info.__current["hr-time"], "hr-time-3");
assert.equal(info.__current["presentation-api"], "presentation-api");
assert.ok(info.__series);
assert.ok(info.__series["hr-time"]);
assert.ok(info.__series["presentation-api"]);
assert.equal(info.__series["hr-time"].currentSpecification, "hr-time-3");
assert.equal(info.__series["presentation-api"].currentSpecification, "presentation-api");
});

it("throws when W3C API key is invalid", async () => {
Expand Down
22 changes: 22 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,28 @@ describe("List of specs", () => {
});
});

it("has series titles for all specs", () => {
const wrong = specs.filter(s => !s.series?.title);
assert.deepStrictEqual(wrong, []);
});

it("has series titles that look consistent with spec titles", () => {
// Note the WebRTC spec follows a slightly different pattern
// TEMP (2021-12-23): 2 temp exceptions to the rule: the W3C API returns an
// outdated title for fingerprinting-guidance (should get fixed soon),
// and published version of CSS Images Level 4 has an obscure title à la
// "CSS Image Values..." (should get fixed next time the spec gets published
// to /TR)
const wrong = specs.filter(s => !s.title.includes(s.series.title))
.filter(s => !["webrtc", "fingerprinting-guidance", "css-images-4"].includes(s.shortname));
assert.deepStrictEqual(wrong, []);
});

it("has series short titles for all specs", () => {
const wrong = specs.filter(s => !s.series?.shortTitle);
assert.deepStrictEqual(wrong, []);
});

it("contains nightly URLs for all specs", () => {
const wrong = specs.filter(s => !s.nightly.url);
assert.deepStrictEqual(wrong, []);
Expand Down

0 comments on commit 1b17bb1

Please sign in to comment.