reffy 20.0.13 → 20.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +151 -151
  3. package/index.js +29 -29
  4. package/package.json +3 -3
  5. package/reffy.js +324 -324
  6. package/schemas/browserlib/extract-algorithms.json +52 -52
  7. package/schemas/browserlib/extract-cssdfn.json +108 -108
  8. package/schemas/browserlib/extract-dfns.json +90 -90
  9. package/schemas/browserlib/extract-elements.json +17 -17
  10. package/schemas/browserlib/extract-events.json +31 -31
  11. package/schemas/browserlib/extract-headings.json +19 -19
  12. package/schemas/browserlib/extract-ids.json +7 -7
  13. package/schemas/browserlib/extract-links.json +12 -12
  14. package/schemas/browserlib/extract-refs.json +12 -12
  15. package/schemas/common.json +876 -876
  16. package/schemas/files/extracts/algorithms.json +12 -12
  17. package/schemas/files/extracts/css.json +16 -16
  18. package/schemas/files/extracts/dfns.json +12 -12
  19. package/schemas/files/extracts/elements.json +12 -12
  20. package/schemas/files/extracts/events.json +12 -12
  21. package/schemas/files/extracts/headings.json +12 -12
  22. package/schemas/files/extracts/ids.json +12 -12
  23. package/schemas/files/extracts/links.json +12 -12
  24. package/schemas/files/extracts/refs.json +12 -12
  25. package/schemas/files/index.json +59 -59
  26. package/schemas/postprocessing/events.json +50 -50
  27. package/schemas/postprocessing/idlnames-parsed.json +27 -27
  28. package/schemas/postprocessing/idlnames.json +17 -17
  29. package/schemas/postprocessing/idlparsed.json +67 -67
  30. package/src/browserlib/clone-and-clean.mjs +24 -24
  31. package/src/browserlib/create-outline.mjs +353 -353
  32. package/src/browserlib/extract-algorithms.mjs +723 -723
  33. package/src/browserlib/extract-cddl.mjs +125 -125
  34. package/src/browserlib/extract-dfns.mjs +1093 -1093
  35. package/src/browserlib/extract-headings.mjs +76 -76
  36. package/src/browserlib/extract-ids.mjs +28 -28
  37. package/src/browserlib/extract-links.mjs +45 -45
  38. package/src/browserlib/extract-references.mjs +308 -308
  39. package/src/browserlib/extract-webidl.mjs +89 -89
  40. package/src/browserlib/get-absolute-url.mjs +29 -29
  41. package/src/browserlib/get-code-elements.mjs +20 -20
  42. package/src/browserlib/get-generator.mjs +26 -26
  43. package/src/browserlib/get-lastmodified-date.mjs +13 -13
  44. package/src/browserlib/get-revision.mjs +12 -12
  45. package/src/browserlib/get-title.mjs +14 -14
  46. package/src/browserlib/informative-selector.mjs +24 -24
  47. package/src/browserlib/map-ids-to-headings.mjs +173 -173
  48. package/src/browserlib/reffy.json +85 -85
  49. package/src/browserlib/trim-spaces.mjs +35 -35
  50. package/src/cli/check-missing-dfns.js +587 -587
  51. package/src/cli/merge-crawl-results.js +132 -132
  52. package/src/cli/parse-webidl.js +447 -447
  53. package/src/lib/css-grammar-parse-tree.schema.json +109 -109
  54. package/src/lib/css-grammar-parser.js +440 -440
  55. package/src/lib/fetch.js +51 -51
  56. package/src/lib/markdown-report.js +360 -360
  57. package/src/lib/mock-server.js +218 -218
  58. package/src/lib/post-processor.js +322 -322
  59. package/src/lib/throttled-queue.js +129 -129
  60. package/src/postprocessing/annotate-links.js +41 -41
  61. package/src/postprocessing/csscomplete.js +48 -48
  62. package/src/postprocessing/idlnames.js +391 -391
  63. package/src/postprocessing/idlparsed.js +179 -179
  64. package/src/postprocessing/patch-dfns.js +51 -51
  65. package/src/specs/missing-css-rules.json +197 -197
  66. package/src/specs/spec-equivalents.json +149 -149
  67. package/src/browserlib/extract-editors.mjs~ +0 -14
  68. package/src/browserlib/extract-events.mjs~ +0 -3
  69. package/src/browserlib/generate-es-dfn-report.sh~ +0 -4
  70. package/src/browserlib/get-revision.mjs~ +0 -7
  71. package/src/cli/csstree-grammar-check.js +0 -28
  72. package/src/cli/csstree-grammar-check.js~ +0 -10
  73. package/src/cli/csstree-grammar-parser.js +0 -11
  74. package/src/cli/csstree-grammar-parser.js~ +0 -1
  75. package/src/cli/extract-editors.js~ +0 -38
  76. package/src/cli/process-specs.js~ +0 -28
  77. package/src/postprocessing/annotate-links.js~ +0 -8
  78. package/src/postprocessing/events.js~ +0 -245
@@ -1,323 +1,323 @@
1
- #!/usr/bin/env node
2
- /**
3
- * The post-processor runs post-processing modules against crawl results.
4
- *
5
- * There are two types of post-processing modules:
6
- * 1. Modules that run against the result of crawling an individual spec. Such
7
- * modules take the spec crawl result as input and typically update it in place
8
- * 2. Modules that run against an entire crawl result. Such modules take the
9
- * entire crawl result as input and return whatever structure they would like
10
- * to return.
11
- *
12
- * The post-processor exposes two main functions:
13
- * - run() to run a post-processing module against crawl results or against a
14
- * spec crawl result (depending on the module)
15
- * - save() to save processing results to files
16
- *
17
- * A post-processing module needs to expose the following properties and
18
- * functions:
19
- * - dependsOn: list of crawl result info that the module depends on. Values
20
- * include "css", "dfns", "idl", as well as info that other post-processing
21
- * modules may generate such as "idlparsed".
22
- * - input: either "crawl" or "spec". Default is "spec". Tells whether the
23
- * module operates on a spec crawl result or on the entire crawl result
24
- * - property: When "input" is "spec", gives the name of the property that
25
- * will be set in the spec crawl result when the post-processing module runs
26
- * and of the folder that will contain the spec extracts (unless module has its
27
- * "save" logic). For modules that run at the crawl level, gives the name of
28
- * the final extract file that gets created (unless module has its own "save"
29
- * logic).
30
- * - run: Async function to call to apply the post-processing module. The
31
- * function is called with either a spec crawl result of the entire crawl result
32
- * depending on "input". Second parameter is the crawl options object. The
33
- * function should return the created structure when "input" is "crawl" and
34
- * the updated spec crawl result when "input" is "spec". Note the function
35
- * may update the spec crawl result in place.
36
- * - save: Function to call to save the results of the post-processing module.
37
- * The function is called with the returned result of running the
38
- * post-processing module. Second parameter is the crawl options object. The
39
- * function is only needed if "save" needs to do specific things that the
40
- * post-processor cannot do on its own. Function must return the relative path
41
- * to the file that was saved
42
- * - extractsPerSeries: A boolean flag that tells the crawler that it should
43
- * clean up extract afterwards to produce extracts per series instead of
44
- * extracts per spec. The flag is only meaningful if module runs at the spec
45
- * level and if "property" is set.
46
- *
47
- * @module
48
- */
49
-
50
- import fs from 'node:fs';
51
- import path from 'node:path';
52
- import { pathToFileURL } from 'node:url';
53
- import { createFolderIfNeeded, shouldSaveToFile } from './util.js';
54
- import csscomplete from '../postprocessing/csscomplete.js';
55
- import cssmerge from '../postprocessing/cssmerge.js';
56
- import events from '../postprocessing/events.js';
57
- import idlnames from '../postprocessing/idlnames.js';
58
- import idlparsed from '../postprocessing/idlparsed.js';
59
- import annotatelinks from '../postprocessing/annotate-links.js';
60
- import patchdfns from '../postprocessing/patch-dfns.js';
61
-
62
-
63
- /**
64
- * Core post-processing modules
65
- */
66
- const modules = {
67
- csscomplete,
68
- cssmerge,
69
- events,
70
- idlnames,
71
- idlparsed,
72
- annotatelinks,
73
- patchdfns
74
- };
75
-
76
-
77
- /**
78
- * Custom post-processing modules
79
- */
80
- const customModules = {};
81
-
82
-
83
- /**
84
- * Loads post-processing modules once and for all
85
- */
86
- async function loadModules(mods) {
87
- for (const mod of mods) {
88
- if (typeof mod === 'string') {
89
- if (modules[mod]) {
90
- // Core post-processing module, already loaded
91
- continue;
92
- }
93
- else {
94
- try {
95
- customModules[mod] = await import(pathToFileURL(mod));
96
- }
97
- catch (err) {
98
- throw new Error(`Unknown post-processing module "${mod}"`);
99
- }
100
- if (!isModuleValid(customModules[mod])) {
101
- throw new Error(`"${mod}" is not a valid post-processing module`);
102
- }
103
- }
104
- }
105
- else {
106
- // Given post-processing moduel should already be a good one
107
- if (!isModuleValid(customModules[mod])) {
108
- throw new Error(`Post-processing module given as parameter does not have a "run" function`);
109
- }
110
- }
111
- }
112
- }
113
-
114
-
115
- /**
116
- * Returns the post-processing module that match the requested name, or the
117
- * given parameter if it is a post-processing module already
118
- *
119
- * @function
120
- * @param {String|Object} mod Module name of known post-processing module, or
121
- * actual post-processing module.
122
- * @return {Object} Post-processing module
123
- */
124
- function getModule(mod) {
125
- if (typeof mod === 'string') {
126
- if (modules[mod]) {
127
- return Object.assign({ name: mod }, modules[mod]);
128
- }
129
- else if (customModules[mod]) {
130
- return Object.assign({ name: mod }, customModules[mod]);
131
- }
132
- else {
133
- throw new Error(`Unknown post-processing module "${mod}"`);
134
- }
135
- }
136
- return mod;
137
- }
138
-
139
-
140
- /**
141
- * Returns true if given module object looks like a valid module, false
142
- * otherwise.
143
- *
144
- * @function
145
- * @param {Object} mod Post-processing module object
146
- * @return {boolean} True when module looks valid, false otherwise
147
- */
148
- function isModuleValid(mod) {
149
- return !!mod && mod.run && (typeof mod.run === 'function');
150
- }
151
-
152
-
153
- /**
154
- * Run a post-processing module against some crawl result
155
- *
156
- * @function
157
- * @param {String|Object} mod Module name for known module or the actual
158
- * module implementation.
159
- * @param {Object} crawlResult The entire crawl results if module runs at the
160
- * "crawl" input level, the result of crawling a spec if module runs at the
161
- * "spec" input level.
162
- * @param {Object} options Crawl options. See spec crawler for details.
163
- * @return {Object} Post-processing structure
164
- */
165
- async function run(mod, crawlResult, options) {
166
- mod = getModule(mod);
167
-
168
- if (mod.input === 'crawl') {
169
- if (crawlResult.crawled || !crawlResult.results) {
170
- // Post-processing module runs at the crawl level and we received
171
- // a spec crawl result
172
- return;
173
- }
174
-
175
- // TODO: make sure that there is at least one spec for which properties
176
- // listed in "dependsOn" are set. If not, the module cannot run, which
177
- // typically signals that the crawler was called with incompatible settings.
178
- }
179
- else {
180
- if (!crawlResult.crawled) {
181
- // Post-processing module runs at the spec level and we received
182
- // a full crawl result
183
- return;
184
- }
185
-
186
- // TODO: check properties listed in "dependsOn". If none is set, no need to
187
- // run the module (but not an error per se, it may just be that this
188
- // particular spec does not define relevant info)
189
- }
190
-
191
- return await mod.run(crawlResult, options);
192
- }
193
-
194
-
195
- /**
196
- * Save post-processing results
197
- *
198
- * @function
199
- * @param {String|Object} mod Module name for known module or the actual
200
- * module implementation.
201
- * @param {Object} processResult The post-processing results
202
- * @param {Object} options Crawl options. See spec crawler for details.
203
- * @return {String} Relative path to the file created
204
- */
205
- async function save(mod, processResult, options) {
206
- mod = getModule(mod);
207
- processResult = processResult || {};
208
- options = options || {};
209
-
210
- if (mod.input === 'crawl') {
211
- if (processResult.shortname) {
212
- // Post-processing module runs at the crawl level and we received
213
- // a spec crawl result
214
- return;
215
- }
216
- }
217
- else {
218
- if (!processResult.shortname) {
219
- // Post-processing module runs at the spec level and we received
220
- // a full crawl result
221
- return;
222
- }
223
- }
224
-
225
- if (!shouldSaveToFile(options)) {
226
- // Nothing to do if no output folder was given
227
- return;
228
- }
229
-
230
- if (mod.save) {
231
- // For post-processing modules that have some save logic, we'll just let
232
- // them do whatever they want
233
- return mod.save(processResult, options);
234
- }
235
- else if (!mod.property) {
236
- // For post-processing modules that don't touch any single property, default
237
- // save operation is to do nothing.
238
- return;
239
- }
240
- else if (mod.input === 'crawl') {
241
- // For post-processing modules that apply at the crawl level, default save
242
- // operation is to create a JSON file in the output folder named after the
243
- // post-processing module
244
- const filename = path.join(options.output, `${mod.property}.json`);
245
- await createFolderIfNeeded(options.output);
246
- await fs.promises.writeFile(filename, JSON.stringify(processResult, null, 2), 'utf8');
247
- return `${mod.property}.json`;
248
- }
249
- else {
250
- // For post-processing modules that apply at the spec level, default save
251
- // operation is to create a JSON extract file named after the spec's
252
- // shortname under a subfolder named after the post-processing module in the
253
- // output folder. Contents of the extract are the contents of the property
254
- // that has the same name as the module (or the name of the module's
255
- // "property" parameter if defined) in the post-processing result.
256
- if (!processResult[mod.property]) {
257
- return;
258
- }
259
- const folder = path.join(options.output, mod.property);
260
- const filename = path.join(folder, `${processResult.shortname}.json`);
261
- const contents = {
262
- spec: {
263
- title: processResult.title,
264
- url: processResult.crawled
265
- }
266
- };
267
- contents[mod.property] = processResult[mod.property];
268
- await createFolderIfNeeded(folder);
269
- await fs.promises.writeFile(filename, JSON.stringify(contents, null, 2), 'utf8');
270
- processResult[mod.property] = `${mod.property}/${processResult.shortname}.json`;
271
- return processResult[mod.property];
272
- }
273
- }
274
-
275
-
276
- /**
277
- * Return true if post-processing module generates extracts per spec series
278
- */
279
- function extractsPerSeries(mod) {
280
- mod = getModule(mod);
281
- return (mod.input !== 'crawl') && !!mod.property && !!mod.extractsPerSeries;
282
- }
283
-
284
-
285
- /**
286
- * Return true if post-processing module generates extracts per spec series
287
- */
288
- function dependsOn(mod) {
289
- mod = getModule(mod);
290
- return mod.dependsOn;
291
- }
292
-
293
- /**
294
- * Return the name of the property that will be set in the spec crawl result
295
- * when the post-processing module runs, if any
296
- */
297
- function getProperty(mod) {
298
- mod = getModule(mod);
299
- return mod.property ?? mod.name;
300
- }
301
-
302
- function appliesAtLevel(mod, level) {
303
- mod = getModule(mod);
304
- const crawlLevel = mod.input === 'crawl';
305
- return level === 'crawl' ? crawlLevel : !crawlLevel;
306
- }
307
-
308
-
309
-
310
- /**************************************************
311
- Export post-processing functions
312
- **************************************************/
313
- const postProcessor = {
314
- modules: Object.keys(modules),
315
- loadModules,
316
- run, save,
317
- extractsPerSeries,
318
- dependsOn,
319
- getProperty,
320
- appliesAtLevel
321
- }
322
-
1
+ #!/usr/bin/env node
2
+ /**
3
+ * The post-processor runs post-processing modules against crawl results.
4
+ *
5
+ * There are two types of post-processing modules:
6
+ * 1. Modules that run against the result of crawling an individual spec. Such
7
+ * modules take the spec crawl result as input and typically update it in place
8
+ * 2. Modules that run against an entire crawl result. Such modules take the
9
+ * entire crawl result as input and return whatever structure they would like
10
+ * to return.
11
+ *
12
+ * The post-processor exposes two main functions:
13
+ * - run() to run a post-processing module against crawl results or against a
14
+ * spec crawl result (depending on the module)
15
+ * - save() to save processing results to files
16
+ *
17
+ * A post-processing module needs to expose the following properties and
18
+ * functions:
19
+ * - dependsOn: list of crawl result info that the module depends on. Values
20
+ * include "css", "dfns", "idl", as well as info that other post-processing
21
+ * modules may generate such as "idlparsed".
22
+ * - input: either "crawl" or "spec". Default is "spec". Tells whether the
23
+ * module operates on a spec crawl result or on the entire crawl result
24
+ * - property: When "input" is "spec", gives the name of the property that
25
+ * will be set in the spec crawl result when the post-processing module runs
26
+ * and of the folder that will contain the spec extracts (unless module has its
27
+ * "save" logic). For modules that run at the crawl level, gives the name of
28
+ * the final extract file that gets created (unless module has its own "save"
29
+ * logic).
30
+ * - run: Async function to call to apply the post-processing module. The
31
+ * function is called with either a spec crawl result of the entire crawl result
32
+ * depending on "input". Second parameter is the crawl options object. The
33
+ * function should return the created structure when "input" is "crawl" and
34
+ * the updated spec crawl result when "input" is "spec". Note the function
35
+ * may update the spec crawl result in place.
36
+ * - save: Function to call to save the results of the post-processing module.
37
+ * The function is called with the returned result of running the
38
+ * post-processing module. Second parameter is the crawl options object. The
39
+ * function is only needed if "save" needs to do specific things that the
40
+ * post-processor cannot do on its own. Function must return the relative path
41
+ * to the file that was saved
42
+ * - extractsPerSeries: A boolean flag that tells the crawler that it should
43
+ * clean up extract afterwards to produce extracts per series instead of
44
+ * extracts per spec. The flag is only meaningful if module runs at the spec
45
+ * level and if "property" is set.
46
+ *
47
+ * @module
48
+ */
49
+
50
+ import fs from 'node:fs';
51
+ import path from 'node:path';
52
+ import { pathToFileURL } from 'node:url';
53
+ import { createFolderIfNeeded, shouldSaveToFile } from './util.js';
54
+ import csscomplete from '../postprocessing/csscomplete.js';
55
+ import cssmerge from '../postprocessing/cssmerge.js';
56
+ import events from '../postprocessing/events.js';
57
+ import idlnames from '../postprocessing/idlnames.js';
58
+ import idlparsed from '../postprocessing/idlparsed.js';
59
+ import annotatelinks from '../postprocessing/annotate-links.js';
60
+ import patchdfns from '../postprocessing/patch-dfns.js';
61
+
62
+
63
+ /**
64
+ * Core post-processing modules
65
+ */
66
+ const modules = {
67
+ csscomplete,
68
+ cssmerge,
69
+ events,
70
+ idlnames,
71
+ idlparsed,
72
+ annotatelinks,
73
+ patchdfns
74
+ };
75
+
76
+
77
+ /**
78
+ * Custom post-processing modules
79
+ */
80
+ const customModules = {};
81
+
82
+
83
+ /**
84
+ * Loads post-processing modules once and for all
85
+ */
86
+ async function loadModules(mods) {
87
+ for (const mod of mods) {
88
+ if (typeof mod === 'string') {
89
+ if (modules[mod]) {
90
+ // Core post-processing module, already loaded
91
+ continue;
92
+ }
93
+ else {
94
+ try {
95
+ customModules[mod] = await import(pathToFileURL(mod));
96
+ }
97
+ catch (err) {
98
+ throw new Error(`Unknown post-processing module "${mod}"`);
99
+ }
100
+ if (!isModuleValid(customModules[mod])) {
101
+ throw new Error(`"${mod}" is not a valid post-processing module`);
102
+ }
103
+ }
104
+ }
105
+ else {
106
+ // Given post-processing moduel should already be a good one
107
+ if (!isModuleValid(customModules[mod])) {
108
+ throw new Error(`Post-processing module given as parameter does not have a "run" function`);
109
+ }
110
+ }
111
+ }
112
+ }
113
+
114
+
115
+ /**
116
+ * Returns the post-processing module that match the requested name, or the
117
+ * given parameter if it is a post-processing module already
118
+ *
119
+ * @function
120
+ * @param {String|Object} mod Module name of known post-processing module, or
121
+ * actual post-processing module.
122
+ * @return {Object} Post-processing module
123
+ */
124
+ function getModule(mod) {
125
+ if (typeof mod === 'string') {
126
+ if (modules[mod]) {
127
+ return Object.assign({ name: mod }, modules[mod]);
128
+ }
129
+ else if (customModules[mod]) {
130
+ return Object.assign({ name: mod }, customModules[mod]);
131
+ }
132
+ else {
133
+ throw new Error(`Unknown post-processing module "${mod}"`);
134
+ }
135
+ }
136
+ return mod;
137
+ }
138
+
139
+
140
+ /**
141
+ * Returns true if given module object looks like a valid module, false
142
+ * otherwise.
143
+ *
144
+ * @function
145
+ * @param {Object} mod Post-processing module object
146
+ * @return {boolean} True when module looks valid, false otherwise
147
+ */
148
+ function isModuleValid(mod) {
149
+ return !!mod && mod.run && (typeof mod.run === 'function');
150
+ }
151
+
152
+
153
+ /**
154
+ * Run a post-processing module against some crawl result
155
+ *
156
+ * @function
157
+ * @param {String|Object} mod Module name for known module or the actual
158
+ * module implementation.
159
+ * @param {Object} crawlResult The entire crawl results if module runs at the
160
+ * "crawl" input level, the result of crawling a spec if module runs at the
161
+ * "spec" input level.
162
+ * @param {Object} options Crawl options. See spec crawler for details.
163
+ * @return {Object} Post-processing structure
164
+ */
165
+ async function run(mod, crawlResult, options) {
166
+ mod = getModule(mod);
167
+
168
+ if (mod.input === 'crawl') {
169
+ if (crawlResult.crawled || !crawlResult.results) {
170
+ // Post-processing module runs at the crawl level and we received
171
+ // a spec crawl result
172
+ return;
173
+ }
174
+
175
+ // TODO: make sure that there is at least one spec for which properties
176
+ // listed in "dependsOn" are set. If not, the module cannot run, which
177
+ // typically signals that the crawler was called with incompatible settings.
178
+ }
179
+ else {
180
+ if (!crawlResult.crawled) {
181
+ // Post-processing module runs at the spec level and we received
182
+ // a full crawl result
183
+ return;
184
+ }
185
+
186
+ // TODO: check properties listed in "dependsOn". If none is set, no need to
187
+ // run the module (but not an error per se, it may just be that this
188
+ // particular spec does not define relevant info)
189
+ }
190
+
191
+ return await mod.run(crawlResult, options);
192
+ }
193
+
194
+
195
+ /**
196
+ * Save post-processing results
197
+ *
198
+ * @function
199
+ * @param {String|Object} mod Module name for known module or the actual
200
+ * module implementation.
201
+ * @param {Object} processResult The post-processing results
202
+ * @param {Object} options Crawl options. See spec crawler for details.
203
+ * @return {String} Relative path to the file created
204
+ */
205
+ async function save(mod, processResult, options) {
206
+ mod = getModule(mod);
207
+ processResult = processResult || {};
208
+ options = options || {};
209
+
210
+ if (mod.input === 'crawl') {
211
+ if (processResult.shortname) {
212
+ // Post-processing module runs at the crawl level and we received
213
+ // a spec crawl result
214
+ return;
215
+ }
216
+ }
217
+ else {
218
+ if (!processResult.shortname) {
219
+ // Post-processing module runs at the spec level and we received
220
+ // a full crawl result
221
+ return;
222
+ }
223
+ }
224
+
225
+ if (!shouldSaveToFile(options)) {
226
+ // Nothing to do if no output folder was given
227
+ return;
228
+ }
229
+
230
+ if (mod.save) {
231
+ // For post-processing modules that have some save logic, we'll just let
232
+ // them do whatever they want
233
+ return mod.save(processResult, options);
234
+ }
235
+ else if (!mod.property) {
236
+ // For post-processing modules that don't touch any single property, default
237
+ // save operation is to do nothing.
238
+ return;
239
+ }
240
+ else if (mod.input === 'crawl') {
241
+ // For post-processing modules that apply at the crawl level, default save
242
+ // operation is to create a JSON file in the output folder named after the
243
+ // post-processing module
244
+ const filename = path.join(options.output, `${mod.property}.json`);
245
+ await createFolderIfNeeded(options.output);
246
+ await fs.promises.writeFile(filename, JSON.stringify(processResult, null, 2), 'utf8');
247
+ return `${mod.property}.json`;
248
+ }
249
+ else {
250
+ // For post-processing modules that apply at the spec level, default save
251
+ // operation is to create a JSON extract file named after the spec's
252
+ // shortname under a subfolder named after the post-processing module in the
253
+ // output folder. Contents of the extract are the contents of the property
254
+ // that has the same name as the module (or the name of the module's
255
+ // "property" parameter if defined) in the post-processing result.
256
+ if (!processResult[mod.property]) {
257
+ return;
258
+ }
259
+ const folder = path.join(options.output, mod.property);
260
+ const filename = path.join(folder, `${processResult.shortname}.json`);
261
+ const contents = {
262
+ spec: {
263
+ title: processResult.title,
264
+ url: processResult.crawled
265
+ }
266
+ };
267
+ contents[mod.property] = processResult[mod.property];
268
+ await createFolderIfNeeded(folder);
269
+ await fs.promises.writeFile(filename, JSON.stringify(contents, null, 2), 'utf8');
270
+ processResult[mod.property] = `${mod.property}/${processResult.shortname}.json`;
271
+ return processResult[mod.property];
272
+ }
273
+ }
274
+
275
+
276
+ /**
277
+ * Return true if post-processing module generates extracts per spec series
278
+ */
279
+ function extractsPerSeries(mod) {
280
+ mod = getModule(mod);
281
+ return (mod.input !== 'crawl') && !!mod.property && !!mod.extractsPerSeries;
282
+ }
283
+
284
+
285
+ /**
286
+ * Return true if post-processing module generates extracts per spec series
287
+ */
288
+ function dependsOn(mod) {
289
+ mod = getModule(mod);
290
+ return mod.dependsOn;
291
+ }
292
+
293
+ /**
294
+ * Return the name of the property that will be set in the spec crawl result
295
+ * when the post-processing module runs, if any
296
+ */
297
+ function getProperty(mod) {
298
+ mod = getModule(mod);
299
+ return mod.property ?? mod.name;
300
+ }
301
+
302
+ function appliesAtLevel(mod, level) {
303
+ mod = getModule(mod);
304
+ const crawlLevel = mod.input === 'crawl';
305
+ return level === 'crawl' ? crawlLevel : !crawlLevel;
306
+ }
307
+
308
+
309
+
310
+ /**************************************************
311
+ Export post-processing functions
312
+ **************************************************/
313
+ const postProcessor = {
314
+ modules: Object.keys(modules),
315
+ loadModules,
316
+ run, save,
317
+ extractsPerSeries,
318
+ dependsOn,
319
+ getProperty,
320
+ appliesAtLevel
321
+ }
322
+
323
323
  export default postProcessor;