spec-up-t 1.0.74 → 1.0.75
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.
- package/index.spec-up.js +366 -0
- package/package.json +1 -1
- package/src/get-xrefs-data.js +6 -1
- package/src/prepare-tref.js +7 -0
package/index.spec-up.js
ADDED
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
|
|
2
|
+
module.exports = function (options = {}) {
|
|
3
|
+
|
|
4
|
+
const {
|
|
5
|
+
fetchExternalSpecs,
|
|
6
|
+
validateReferences,
|
|
7
|
+
findExternalSpecByKey
|
|
8
|
+
} = require('./references.js');
|
|
9
|
+
const gulp = require('gulp');
|
|
10
|
+
const fs = require('fs-extra');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const findPkgDir = require('find-pkg-dir');
|
|
13
|
+
const modulePath = findPkgDir(__dirname);
|
|
14
|
+
let config = fs.readJsonSync('./specs.json');
|
|
15
|
+
let assets = fs.readJsonSync(modulePath + '/src/asset-map.json');
|
|
16
|
+
let externalReferences;
|
|
17
|
+
let references = [];
|
|
18
|
+
let definitions = [];
|
|
19
|
+
|
|
20
|
+
const katexRules = ['math_block', 'math_inline']
|
|
21
|
+
const replacerRegex = /\[\[\s*([^\s\[\]:]+):?\s*([^\]\n]+)?\]\]/img;
|
|
22
|
+
const replacerArgsRegex = /\s*,+\s*/;
|
|
23
|
+
const replacers = [
|
|
24
|
+
{
|
|
25
|
+
test: 'insert',
|
|
26
|
+
transform: function (path) {
|
|
27
|
+
if (!path) return '';
|
|
28
|
+
return fs.readFileSync(path, 'utf8');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
function applyReplacers(doc) {
|
|
34
|
+
return doc.replace(replacerRegex, function (match, type, args) {
|
|
35
|
+
let replacer = replacers.find(r => type.trim().match(r.test));
|
|
36
|
+
return replacer ? replacer.transform(...args.trim().split(replacerArgsRegex)) : match;
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function normalizePath(path) {
|
|
41
|
+
return path.trim().replace(/\/$/g, '') + '/';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function renderRefGroup(type) {
|
|
45
|
+
let group = specGroups[type];
|
|
46
|
+
if (!group) return '';
|
|
47
|
+
let html = Object.keys(group).sort().reduce((html, name) => {
|
|
48
|
+
let ref = group[name];
|
|
49
|
+
return html += `
|
|
50
|
+
<dt id="ref:${name}">${name}</dt>
|
|
51
|
+
<dd>
|
|
52
|
+
<cite><a href="${ref.href}">${ref.title}</a></cite>.
|
|
53
|
+
${ref.authors.join('; ')}; ${ref.rawDate}. <span class="reference-status">Status: ${ref.status}</span>.
|
|
54
|
+
</dd>
|
|
55
|
+
`;
|
|
56
|
+
}, '<dl class="reference-list">')
|
|
57
|
+
return `\n${html}\n</dl>\n`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function findKatexDist() {
|
|
61
|
+
const relpath = "node_modules/katex/dist";
|
|
62
|
+
const paths = [
|
|
63
|
+
path.join(process.cwd(), relpath),
|
|
64
|
+
path.join(__dirname, relpath),
|
|
65
|
+
];
|
|
66
|
+
for (const abspath of paths) {
|
|
67
|
+
if (fs.existsSync(abspath)) {
|
|
68
|
+
return abspath
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
throw Error("katex distribution could not be located");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
|
|
76
|
+
var toc;
|
|
77
|
+
var specGroups = {};
|
|
78
|
+
const noticeTypes = {
|
|
79
|
+
note: 1,
|
|
80
|
+
issue: 1,
|
|
81
|
+
example: 1,
|
|
82
|
+
warning: 1,
|
|
83
|
+
todo: 1
|
|
84
|
+
};
|
|
85
|
+
const spaceRegex = /\s+/g;
|
|
86
|
+
const specNameRegex = /^spec$|^spec[-]*\w+$/i;
|
|
87
|
+
const terminologyRegex = /^def$|^ref$|^xref/i;
|
|
88
|
+
const specCorpus = fs.readJsonSync(modulePath + '/assets/compiled/refs.json');
|
|
89
|
+
const containers = require('markdown-it-container');
|
|
90
|
+
const md = require('markdown-it')({
|
|
91
|
+
html: true,
|
|
92
|
+
linkify: true,
|
|
93
|
+
typographer: true
|
|
94
|
+
})
|
|
95
|
+
.use(require('./src/markdown-it-extensions.js'), [
|
|
96
|
+
{
|
|
97
|
+
filter: type => type.match(terminologyRegex),
|
|
98
|
+
parse(token, type, primary) {
|
|
99
|
+
if (!primary) return;
|
|
100
|
+
if (type === 'def') {
|
|
101
|
+
definitions.push(token.info.args);
|
|
102
|
+
return token.info.args.reduce((acc, syn) => {
|
|
103
|
+
return `<span id="term:${syn.replace(spaceRegex, '-').toLowerCase()}">${acc}</span>`;
|
|
104
|
+
}, primary);
|
|
105
|
+
}
|
|
106
|
+
else if (type === 'xref') {
|
|
107
|
+
const url = findExternalSpecByKey(config, token.info.args[0]);
|
|
108
|
+
const term = token.info.args[1].replace(spaceRegex, '-').toLowerCase();
|
|
109
|
+
return `<a class="term-reference" data-local-href="#term:${token.info.args[0]}:${term}"
|
|
110
|
+
href="${url}#term:${term}">${token.info.args[1]}</a>`;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
references.push(primary);
|
|
114
|
+
return `<a class="term-reference" href="#term:${primary.replace(spaceRegex, '-').toLowerCase()}">${primary}</a>`;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
filter: type => type.match(specNameRegex),
|
|
120
|
+
parse(token, type, name) {
|
|
121
|
+
if (name) {
|
|
122
|
+
let _name = name.replace(spaceRegex, '-').toUpperCase();
|
|
123
|
+
let spec = specCorpus[_name] ||
|
|
124
|
+
specCorpus[_name.toLowerCase()] ||
|
|
125
|
+
specCorpus[name.toLowerCase()] ||
|
|
126
|
+
specCorpus[name];
|
|
127
|
+
if (spec) {
|
|
128
|
+
spec._name = _name;
|
|
129
|
+
let group = specGroups[type] = specGroups[type] || {};
|
|
130
|
+
token.info.spec = group[_name] = spec;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
render(token, type, name) {
|
|
135
|
+
if (name) {
|
|
136
|
+
let spec = token.info.spec;
|
|
137
|
+
if (spec) return `[<a class="spec-reference" href="#ref:${spec._name}">${spec._name}</a>]`;
|
|
138
|
+
}
|
|
139
|
+
else return renderRefGroup(type);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
])
|
|
143
|
+
.use(require('markdown-it-attrs'))
|
|
144
|
+
.use(require('markdown-it-chart').default)
|
|
145
|
+
.use(require('markdown-it-deflist'))
|
|
146
|
+
.use(require('markdown-it-references'))
|
|
147
|
+
.use(require('markdown-it-icons').default, 'font-awesome')
|
|
148
|
+
.use(require('markdown-it-ins'))
|
|
149
|
+
.use(require('markdown-it-mark'))
|
|
150
|
+
.use(require('markdown-it-textual-uml'))
|
|
151
|
+
.use(require('markdown-it-sub'))
|
|
152
|
+
.use(require('markdown-it-sup'))
|
|
153
|
+
.use(require('markdown-it-task-lists'))
|
|
154
|
+
.use(require('markdown-it-multimd-table'), {
|
|
155
|
+
multiline: true,
|
|
156
|
+
rowspan: true,
|
|
157
|
+
headerless: true
|
|
158
|
+
})
|
|
159
|
+
.use(containers, 'notice', {
|
|
160
|
+
validate: function (params) {
|
|
161
|
+
let matches = params.match(/(\w+)\s?(.*)?/);
|
|
162
|
+
return matches && noticeTypes[matches[1]];
|
|
163
|
+
},
|
|
164
|
+
render: function (tokens, idx) {
|
|
165
|
+
let matches = tokens[idx].info.match(/(\w+)\s?(.*)?/);
|
|
166
|
+
if (matches && tokens[idx].nesting === 1) {
|
|
167
|
+
let id;
|
|
168
|
+
let type = matches[1];
|
|
169
|
+
if (matches[2]) {
|
|
170
|
+
id = matches[2].trim().replace(/\s+/g, '-').toLowerCase();
|
|
171
|
+
if (noticeTitles[id]) id += '-' + noticeTitles[id]++;
|
|
172
|
+
else noticeTitles[id] = 1;
|
|
173
|
+
}
|
|
174
|
+
else id = type + '-' + noticeTypes[type]++;
|
|
175
|
+
return `<div id="${id}" class="notice ${type}"><a class="notice-link" href="#${id}">${type.toUpperCase()}</a>`;
|
|
176
|
+
}
|
|
177
|
+
else return '</div>\n';
|
|
178
|
+
}
|
|
179
|
+
})
|
|
180
|
+
.use(require('markdown-it-prism'))
|
|
181
|
+
.use(require('markdown-it-toc-and-anchor').default, {
|
|
182
|
+
tocClassName: 'toc',
|
|
183
|
+
tocFirstLevel: 2,
|
|
184
|
+
tocLastLevel: 4,
|
|
185
|
+
tocCallback: (_md, _tokens, html) => toc = html,
|
|
186
|
+
anchorLinkSymbol: '§',
|
|
187
|
+
anchorClassName: 'toc-anchor'
|
|
188
|
+
})
|
|
189
|
+
.use(require('@traptitech/markdown-it-katex'))
|
|
190
|
+
|
|
191
|
+
async function render(spec, assets) {
|
|
192
|
+
try {
|
|
193
|
+
noticeTitles = {};
|
|
194
|
+
specGroups = {};
|
|
195
|
+
console.log('Rendering: ' + spec.title);
|
|
196
|
+
return new Promise(async (resolve, reject) => {
|
|
197
|
+
Promise.all((spec.markdown_paths || ['spec.md']).map(_path => {
|
|
198
|
+
return fs.readFile(spec.spec_directory + _path, 'utf8').catch(e => reject(e))
|
|
199
|
+
})).then(async docs => {
|
|
200
|
+
const features = (({ source, logo }) => ({ source, logo }))(spec);
|
|
201
|
+
if (spec.external_specs && !externalReferences) {
|
|
202
|
+
externalReferences = await fetchExternalSpecs(spec);
|
|
203
|
+
}
|
|
204
|
+
let doc = docs.join("\n");
|
|
205
|
+
doc = applyReplacers(doc);
|
|
206
|
+
md[spec.katex ? "enable" : "disable"](katexRules);
|
|
207
|
+
const render = md.render(doc);
|
|
208
|
+
fs.writeFile(path.join(spec.destination, 'index.html'), `
|
|
209
|
+
<!DOCTYPE html>
|
|
210
|
+
<html lang="en">
|
|
211
|
+
<head>
|
|
212
|
+
<meta charset="utf-8">
|
|
213
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
214
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
|
215
|
+
|
|
216
|
+
<title>${spec.title}</title>
|
|
217
|
+
|
|
218
|
+
<link href="https://fonts.googleapis.com/css2?family=Heebo:wght@300;400&display=swap" rel="stylesheet">
|
|
219
|
+
|
|
220
|
+
${assets.head}
|
|
221
|
+
</head>
|
|
222
|
+
<body features="${Object.keys(features).join(' ')}">
|
|
223
|
+
|
|
224
|
+
${assets.svg}
|
|
225
|
+
|
|
226
|
+
<main>
|
|
227
|
+
|
|
228
|
+
<header id="header" class="panel-header">
|
|
229
|
+
<span id="toc_toggle" panel-toggle="toc">
|
|
230
|
+
<svg icon><use xlink:href="#svg-nested-list"></use></svg>
|
|
231
|
+
</span>
|
|
232
|
+
<a id="logo" href="${spec.logo_link ? spec.logo_link : '#_'}">
|
|
233
|
+
<img src="${spec.logo}" />
|
|
234
|
+
</a>
|
|
235
|
+
<span issue-count animate panel-toggle="repo_issues">
|
|
236
|
+
<svg icon><use xlink:href="#svg-github"></use></svg>
|
|
237
|
+
</span>
|
|
238
|
+
</header>
|
|
239
|
+
|
|
240
|
+
<article id="content">
|
|
241
|
+
${render}
|
|
242
|
+
</article>
|
|
243
|
+
|
|
244
|
+
</main>
|
|
245
|
+
|
|
246
|
+
<slide-panels id="slidepanels">
|
|
247
|
+
<slide-panel id="repo_issues" options="right">
|
|
248
|
+
<header class="panel-header">
|
|
249
|
+
<span>
|
|
250
|
+
<svg icon><use xlink:href="#svg-github"></use></svg>
|
|
251
|
+
<span issue-count></span>
|
|
252
|
+
</span>
|
|
253
|
+
<span class="repo-issue-toggle" panel-toggle="repo_issues">✕</span>
|
|
254
|
+
</header>
|
|
255
|
+
<ul id="repo_issue_list"></ul>
|
|
256
|
+
</slide-panel>
|
|
257
|
+
|
|
258
|
+
<slide-panel id="toc">
|
|
259
|
+
<header class="panel-header">
|
|
260
|
+
<span>Table of Contents</span>
|
|
261
|
+
<span panel-toggle="toc">✕</span>
|
|
262
|
+
</header>
|
|
263
|
+
<div id="toc_list">
|
|
264
|
+
${toc}
|
|
265
|
+
</div>
|
|
266
|
+
</slide-panel>
|
|
267
|
+
|
|
268
|
+
</slide-panels>
|
|
269
|
+
<div style="display: none;">
|
|
270
|
+
${externalReferences}
|
|
271
|
+
</div>
|
|
272
|
+
</body>
|
|
273
|
+
<script>window.specConfig = ${JSON.stringify(spec)}</script>
|
|
274
|
+
${assets.body}
|
|
275
|
+
</html>
|
|
276
|
+
`, function (err, data) {
|
|
277
|
+
if (err) {
|
|
278
|
+
reject(err);
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
resolve();
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
validateReferences(references, definitions, render);
|
|
285
|
+
references = [];
|
|
286
|
+
definitions = [];
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
catch (e) {
|
|
291
|
+
console.error(e);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
config.specs.forEach(spec => {
|
|
296
|
+
spec.spec_directory = normalizePath(spec.spec_directory);
|
|
297
|
+
spec.destination = normalizePath(spec.output_path || spec.spec_directory);
|
|
298
|
+
|
|
299
|
+
fs.ensureDirSync(spec.destination);
|
|
300
|
+
|
|
301
|
+
let assetTags = {
|
|
302
|
+
svg: fs.readFileSync(modulePath + '/assets/icons.svg', 'utf8') || ''
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
let customAssets = (spec.assets || []).reduce((assets, asset) => {
|
|
306
|
+
let ext = asset.path.split('.').pop();
|
|
307
|
+
if (ext === 'css') {
|
|
308
|
+
assets.css += `<link href="${asset.path}" rel="stylesheet"/>`;
|
|
309
|
+
}
|
|
310
|
+
if (ext === 'js') {
|
|
311
|
+
assets.js[asset.inject || 'body'] += `<script src="${asset.path}" ${asset.module ? 'type="module"' : ''} ></script>`;
|
|
312
|
+
}
|
|
313
|
+
return assets;
|
|
314
|
+
}, {
|
|
315
|
+
css: '',
|
|
316
|
+
js: { head: '', body: '' }
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
if (options.dev) {
|
|
320
|
+
assetTags.head = assets.head.css.map(_path => `<link href="${_path}" rel="stylesheet"/>`).join('') +
|
|
321
|
+
customAssets.css +
|
|
322
|
+
assets.head.js.map(_path => `<script src="${_path}"></script>`).join('') +
|
|
323
|
+
customAssets.js.head;
|
|
324
|
+
assetTags.body = assets.body.js.map(_path => `<script src="${_path}" data-manual></script>`).join('') +
|
|
325
|
+
customAssets.js.body;
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
assetTags.head = `
|
|
329
|
+
<style>${fs.readFileSync(modulePath + '/assets/compiled/head.css', 'utf8')}</style>
|
|
330
|
+
${customAssets.css}
|
|
331
|
+
<script>${fs.readFileSync(modulePath + '/assets/compiled/head.js', 'utf8')}</script>
|
|
332
|
+
${customAssets.js.head}
|
|
333
|
+
`;
|
|
334
|
+
assetTags.body = `<script>${fs.readFileSync(modulePath + '/assets/compiled/body.js', 'utf8')}</script>
|
|
335
|
+
${customAssets.js.body}`;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (spec.katex) {
|
|
339
|
+
const katexDist = findKatexDist();
|
|
340
|
+
assetTags.body += `<script>/* katex */${fs.readFileSync(path.join(katexDist, 'katex.min.js'),
|
|
341
|
+
'utf8')}</script>`;
|
|
342
|
+
assetTags.body += `<style>/* katex */${fs.readFileSync(path.join(katexDist, 'katex.min.css'),
|
|
343
|
+
'utf8')}</style>`;
|
|
344
|
+
|
|
345
|
+
fs.copySync(path.join(katexDist, 'fonts'), path.join(spec.destination, 'fonts'));
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (!options.nowatch) {
|
|
349
|
+
gulp.watch(
|
|
350
|
+
[spec.spec_directory + '**/*', '!' + path.join(spec.destination, 'index.html')],
|
|
351
|
+
render.bind(null, spec, assetTags)
|
|
352
|
+
)
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
render(spec, assetTags).then(() => {
|
|
356
|
+
if (options.nowatch) process.exit(0)
|
|
357
|
+
}).catch(() => process.exit(1));
|
|
358
|
+
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
}
|
|
362
|
+
catch (e) {
|
|
363
|
+
console.error(e);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spec-up-t",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.75",
|
|
4
4
|
"description": "Technical specification drafting tool that generates rich specification documents from markdown. Forked from https://github.com/decentralized-identity/spec-up by Daniel Buchner (https://github.com/csuwildcat)",
|
|
5
5
|
"main": "./index",
|
|
6
6
|
"repository": {
|
package/src/get-xrefs-data.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @file This
|
|
2
|
+
* @file This script is responsible for fetching the latest commit hash of term files from the GitHub API and generating both a JavaScript file and a JSON file containing the data for the cross-references (xrefs).
|
|
3
|
+
*
|
|
4
|
+
* The generated JavaScript file is included in the HTML output of the specification, serving as a data source for the JavaScript code embedded in the HTML file.
|
|
5
|
+
*
|
|
6
|
+
* Additionally, the data is written to a JSON file for further processing or usage. This ensures that the xref data is available in both JavaScript and JSON formats, providing flexibility for different use cases.
|
|
7
|
+
*
|
|
3
8
|
* @author Kor Dwarshuis
|
|
4
9
|
* @version 1.0.0
|
|
5
10
|
* @since 2024-06-09
|