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 new composition level: "fork" #528

Merged
merged 6 commits into from
Feb 28, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 3 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ properties:
- `url`: same as the [`url`](README.md#url) property in `index.json`.
- `shortname`: same as the [`shortname`](README.md#shortname) property in
`index.json`.
- `forkOf`: same as the [`forkOf`](README.md#forkof) property in `index.json`.
No need to set `seriesComposition` to `"fork"` when this property is set, the
build logic will take care of that automatically.
dontcallmedom marked this conversation as resolved.
Show resolved Hide resolved
- `series`: same as the [`series`](README.md#series) property in `index.json`,
but note the `currentSpecification` property will be ignored.
- `seriesVersion`: same as the [`seriesVersion`](README.md#seriesversion)
Expand Down
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ cross-references, WebIDL, quality, etc.
- [`seriesComposition`](#seriescomposition)
- [`seriesPrevious`](#seriesprevious)
- [`seriesNext`](#seriesnext)
- [`forkOf`](#forkof)
- [`forks`](#forks)
- [`organization`](#organization)
- [`groups`](#groups)
- [`release`](#release)
Expand Down Expand Up @@ -306,6 +308,28 @@ The `shortname` of the next spec in the series.
The `seriesNext` property is only set where there is a next level or version.


### `forkOf`

The shortname of the spec that this spec is a fork of.

The `forkOf` property is only set when the spec is a fork of another one. The
[`seriesComposition`](#seriescomposition) property is always `"fork"` when the
`forkOf` property is set.

A forked specs is supposed to be temporary by nature. It will be removed from
the list as soon as it gets merged into the main spec, or as soon as it gets
abandoned.


### `forks`

An array that lists shortnames of known forks of the spec in the list.

The `forks` property is only set when there exists at least one fork of the
spec in the list, meaning when there is an entry in the list that has a
[`forkOf`](#forkof) property set to the spec's shortname.


### `organization`

The name of the standardization organization that owns the spec such as `W3C`,
Expand Down
5 changes: 5 additions & 0 deletions schema/definitions.json
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,11 @@
"minItems": 1
}
]
},

"forks": {
"type": "array",
"items": { "$ref": "#/$defs/shortname" }
}
}
}
2 changes: 2 additions & 0 deletions schema/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
"properties": {
"url": { "$ref": "definitions.json#/$defs/url" },
"shortname": { "$ref": "definitions.json#/$defs/shortname" },
"forkOf": { "$ref": "definitions.json#/$defs/shortname" },
"forks": { "$ref": "definitions.json#/$defs/forks" },
"series": { "$ref": "definitions.json#/$defs/series" },
"seriesVersion": { "$ref": "definitions.json#/$defs/seriesVersion" },
"seriesComposition": { "$ref": "definitions.json#/$defs/seriesComposition" },
Expand Down
3 changes: 2 additions & 1 deletion schema/specs.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@
"oneOf": [
{
"type": "string",
"pattern": "^https://[^\\s]+(\\s(delta|fork|current|multipage))?$"
"pattern": "^https://[^\\s]+(\\s(delta|current|multipage))?$"
},
{
"type": "object",
"properties": {
"url": { "$ref": "definitions.json#/$defs/url" },
"shortname": { "$ref": "definitions.json#/$defs/shortname" },
"forkOf": { "$ref": "definitions.json#/$defs/shortname" },
"series": { "$ref": "definitions.json#/$defs/series" },
"seriesVersion": { "$ref": "definitions.json#/$defs/seriesVersion" },
"seriesComposition": { "$ref": "definitions.json#/$defs/seriesComposition" },
Expand Down
3 changes: 1 addition & 2 deletions specs.json
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,7 @@
"https://w3c.github.io/webrtc-ice/",
{
"url": "https://webassembly.github.io/exception-handling/js-api/",
"shortname": "wasm-js-api-1",
"seriesComposition": "fork"
"forkOf": "wasm-js-api-1"
},
"https://webbluetoothcg.github.io/web-bluetooth/",
"https://webidl.spec.whatwg.org/",
Expand Down
23 changes: 16 additions & 7 deletions src/build-index.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,17 +64,14 @@ async function generateIndex(specs, { previousIndex = null, log = console.log }
log("Prepare initial list of specs...");
specs = specs
// Turn all specs into objects
// (and handle syntactic sugar notation for delta/current/fork flags)
// (and handle syntactic sugar notation for delta/current flags)
.map(spec => {
if (typeof spec === "string") {
const parts = spec.split(" ");
const res = { url: parts[0] };
if (parts[1] === "delta") {
res.seriesComposition = "delta";
}
else if (parts[1] === "fork") {
res.seriesComposition = "fork";
}
else if (parts[1] === "current") {
res.forceCurrent = true;
}
Expand All @@ -96,9 +93,11 @@ async function generateIndex(specs, { previousIndex = null, log = console.log }
delete spec.series;

// Complete information
const seriesComposition = spec.seriesComposition ??
(spec.forkOf ? "fork" : "full");
const res = Object.assign(
{ url: spec.url, seriesComposition: spec.seriesComposition || "full" },
computeShortname(spec.shortname || spec.url),
{ url: spec.url, seriesComposition },
computeShortname(spec.shortname ?? spec.url, spec.forkOf),
spec);

// Restore series info explicitly set in initial spec object
Expand All @@ -117,7 +116,17 @@ async function generateIndex(specs, { previousIndex = null, log = console.log }
.map(spec => { delete spec.forceCurrent; return spec; })

// Complete information with previous/next level links
.map((spec, _, list) => Object.assign(spec, computePrevNext(spec, list)));
.map((spec, _, list) => Object.assign(spec, computePrevNext(spec, list)))

// Complete information with forks
.map((spec, _, list) => {
const forks = list.filter(s => s.series.shortname === spec.series.shortname && s.seriesComposition === "fork")
.map(s => s.shortname);
if (forks.length > 0) {
spec.forks = forks;
}
return spec;
});
log(`Prepare initial list of specs... found ${specs.length} specs`);

// Fetch additional spec info from external sources and complete the list
Expand Down
2 changes: 1 addition & 1 deletion src/compute-prevnext.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ module.exports = function (spec, list) {
const level = spec.seriesVersion || "0";

return list
.filter(s => s.series.shortname === spec.series.shortname)
.filter(s => s.series.shortname === spec.series.shortname && s.seriesComposition !== "fork")
.sort((a, b) => (a.seriesVersion || "0").localeCompare(b.seriesVersion || "0"))
.reduce((res, s) => {
if ((s.seriesVersion || "0") < level) {
Expand Down
27 changes: 15 additions & 12 deletions src/compute-shortname.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ function computeShortname(url) {
/**
* Compute the shortname and level from the spec name, if possible.
*/
function completeWithSeriesAndLevel(shortname, url) {
function completeWithSeriesAndLevel(shortname, url, forkOf) {
// Use latest convention for CSS specs
function modernizeShortname(name) {
if (name.startsWith("css3-")) {
Expand All @@ -142,42 +142,45 @@ function completeWithSeriesAndLevel(shortname, url) {
}
}

const seriesBasename = forkOf ?? shortname;
const specShortname = forkOf ? `${forkOf}-fork-${shortname}` : shortname;
dontcallmedom marked this conversation as resolved.
Show resolved Hide resolved

// Shortnames of WebGL extensions sometimes end up with digits which are *not*
// to be interpreted as level numbers. Similarly, shortnames of ECMA specs
// typically have the form "ecma-ddd", and "ddd" is *not* a level number.
if (shortname.match(/^ecma-/) || url.match(/^https:\/\/www\.khronos\.org\/registry\/webgl\/extensions\//)) {
if (seriesBasename.match(/^ecma-/) || url.match(/^https:\/\/www\.khronos\.org\/registry\/webgl\/extensions\//)) {
return {
shortname,
series: { shortname }
shortname: specShortname,
series: { shortname: seriesBasename }
};
}

// Extract X and X.Y levels, with form "name-X" or "name-X.Y".
// (e.g. 5 for "mediaqueries-5", 1.2 for "wai-aria-1.2")
let match = shortname.match(/^(.*?)-(\d+)(.\d+)?$/);
let match = seriesBasename.match(/^(.*?)-(\d+)(.\d+)?$/);
if (match) {
return {
shortname,
shortname: specShortname,
series: { shortname: modernizeShortname(match[1]) },
seriesVersion: match[3] ? match[2] + match[3] : match[2]
};
}

// Extract X and X.Y levels with form "nameX" or "nameXY" (but not "nameXXY")
// (e.g. 2.1 for "CSS21", 1.1 for "SVG11", 4 for "selectors4")
match = shortname.match(/^(.*?)(?<!\d)(\d)(\d?)$/);
match = seriesBasename.match(/^(.*?)(?<!\d)(\d)(\d?)$/);
if (match) {
return {
shortname,
shortname: specShortname,
series: { shortname: modernizeShortname(match[1]) },
seriesVersion: match[3] ? match[2] + "." + match[3] : match[2]
};
}

// No level found
return {
shortname,
series: { shortname: modernizeShortname(shortname) }
shortname: specShortname,
series: { shortname: modernizeShortname(seriesBasename) }
};
}

Expand All @@ -186,9 +189,9 @@ function completeWithSeriesAndLevel(shortname, url) {
* Exports main function that takes a URL (or a spec name) and returns an
* object with a name, a shortname and a level (if needed).
*/
module.exports = function (url) {
module.exports = function (url, forkOf) {
if (!url) {
throw "No URL passed as parameter";
}
return completeWithSeriesAndLevel(computeShortname(url), url);
return completeWithSeriesAndLevel(computeShortname(url), url, forkOf);
}
10 changes: 9 additions & 1 deletion test/compute-currentlevel.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ describe("compute-currentlevel module", () => {
function getSpec(options) {
options = options || {};
const res = {
shortname: (options.seriesVersion ? `spec-${options.seriesVersion}` : "spec"),
shortname: options.shortname ?? (options.seriesVersion ? `spec-${options.seriesVersion}` : "spec"),
series: { shortname: "spec" },
};
for (const property of Object.keys(options)) {
Expand Down Expand Up @@ -97,4 +97,12 @@ describe("compute-currentlevel module", () => {
getCurrentName(spec, [spec, other]),
spec.shortname);
});

it("does not take forks into account", () => {
const spec = getSpec({ shortname: "spec-1-fork-1", seriesVersion: "1", seriesComposition: "fork" });
const base = getSpec({ seriesVersion: "1" });
assert.equal(
getCurrentName(spec, [spec, base]),
base.shortname);
});
});
15 changes: 15 additions & 0 deletions test/compute-shortname.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ describe("compute-shortname module", () => {
() => computeInfo("https://w3c.github.io/spec4.2/"),
/^Specification name contains unexpected characters/);
});

it("handles forks", () => {
const url = "https://www.w3.org/TR/extension/";
assert.equal(computeInfo(url, "source-2").shortname, "source-2-fork-extension");
});
});


Expand Down Expand Up @@ -152,6 +157,11 @@ describe("compute-shortname module", () => {
it("preserves digits at the end of WebGL extension names", () => {
assertSeries("https://www.khronos.org/registry/webgl/extensions/EXT_wow32/", "EXT_wow32");
});

it("handles forks", () => {
const url = "https://www.w3.org/TR/the-ext/";
assert.equal(computeInfo(url, "source-2").series.shortname, "source");
});
});


Expand Down Expand Up @@ -203,5 +213,10 @@ describe("compute-shortname module", () => {
it("does not confuse digits at the end of a WebGL extension spec with a series version", () => {
assertNoSeriesVersion("https://www.khronos.org/registry/webgl/extensions/EXT_wow32/");
});

it("handles forks", () => {
const url = "https://www.w3.org/TR/the-ext/";
assert.equal(computeInfo(url, "source-2").seriesVersion, "2");
});
});
});
37 changes: 31 additions & 6 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,6 @@ describe("List of specs", () => {
assert.deepStrictEqual(wrong, []);
});

it("has previous links for all fork specs", () => {
const wrong = specs.filter(s =>
s.seriesComposition === "fork" && !s.seriesPrevious);
assert.deepStrictEqual(wrong, []);
});

it("has previous links that can be resolved to a spec", () => {
const wrong = specs.filter(s =>
s.seriesPrevious && !specs.find(p => p.shortname === s.seriesPrevious));
Expand Down Expand Up @@ -96,6 +90,18 @@ describe("List of specs", () => {
assert.deepStrictEqual(wrong, []);
});

it("does not have previous links for fork specs", () => {
const wrong = specs.filter(s =>
s.seriesComposition === "fork" && s.seriesPrevious);
assert.deepStrictEqual(wrong, []);
});

it("does not have next links for fork specs", () => {
const wrong = specs.filter(s =>
s.seriesComposition === "fork" && s.seriesNext);
assert.deepStrictEqual(wrong, []);
});

it("has consistent series info", () => {
const wrong = specs.filter(s => {
if (!s.seriesPrevious) {
Expand Down Expand Up @@ -156,4 +162,23 @@ describe("List of specs", () => {
const wrong = specs.filter(s => s.release && !s.release.filename);
assert.deepStrictEqual(wrong, []);
});

it("has a forkOf property for all fork specs", () => {
const wrong = specs.filter(s => s.seriesComposition === "fork" && !s.forkOf);
assert.deepStrictEqual(wrong, []);
});

it("only has forks of existing specs", () => {
const wrong = specs.filter(s => s.forkOf && !specs.find(spec => spec.shortname === s.forkOf));
assert.deepStrictEqual(wrong, []);
});

it("has consistent forks properties", () => {
const wrong = specs.filter(s => !!s.forks &&
s.forks.find(shortname => !specs.find(spec =>
spec.shortname === shortname &&
spec.seriesComposition === "fork" &&
spec.forkOf === s.shortname)));
assert.deepStrictEqual(wrong, []);
});
});
19 changes: 0 additions & 19 deletions test/lint.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,6 @@ describe("Linter", () => {
assert.equal(lintStr(toStr(specs)), null);
});

it("passes if specs contains a URL with a fork spec", () => {
const specs = [
"https://www.w3.org/TR/spec-1/",
"https://www.w3.org/TR/spec-2/ fork"
];
assert.equal(lintStr(toStr(specs)), null);
});

it("passes if specs contains a URL with a spec flagged as current", () => {
const specs = [
"https://www.w3.org/TR/spec-1/ current",
Expand Down Expand Up @@ -133,17 +125,6 @@ describe("Linter", () => {
]));
});

it("lints an object with a useless current flag (fork version)", () => {
const specs = [
"https://www.w3.org/TR/spec-1/ current",
"https://www.w3.org/TR/spec-2/ fork"
];
assert.equal(lintStr(toStr(specs)), toStr([
"https://www.w3.org/TR/spec-1/",
"https://www.w3.org/TR/spec-2/ fork",
]));
});

it("lints an object with a 'full' flag", () => {
const specs = [
{ "url": "https://www.w3.org/TR/spec/", "seriesComposition": "full" }
Expand Down
Loading