This repository has been archived by the owner on Jun 7, 2024. It is now read-only.
forked from speced/respec
-
Notifications
You must be signed in to change notification settings - Fork 0
/
respecDocWriter.js
189 lines (188 loc) · 6.15 KB
/
respecDocWriter.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
/**
* Exports fetchAndWrite() method, allowing programmatic control of the
* spec generator.
*
* For usage, see example a https://github.com/w3c/respec/pull/692
*/
/*jshint node: true, browser: false*/
"use strict";
const async = require("marcosc-async");
const os = require("os");
const Nightmare = require("nightmare");
const colors = require("colors");
const fsp = require("fs-promise");
const fs = require("fs");
const path = require("path");
const parseURL = require("url").parse;
colors.setTheme({
debug: "cyan",
error: "red",
warn: "yellow",
});
const tasks = {
/**
* Writes "data" to a particular outPath as UTF-8.
* @private
* @param {String} outPath The relative or absolute path to write to.
* @param {String} data The data to write.
* @return {Promise} Resolves when writing is done.
*/
writeTo(outPath, data) {
return async.task(function* () {
let newFilePath = "";
if (path.isAbsolute(outPath)) {
newFilePath = outPath;
} else {
newFilePath = path.resolve(process.cwd(), outPath);
}
try {
yield fsp.writeFile(newFilePath, data, "utf-8");
} catch (err) {
console.error(err, err.stack);
process.exit(1);
}
});
},
/**
* Makes a temporary directory.
*
* @private
* @param {String} prefix The prefix to use, to distinguish it from other tmp
* directories.
* @return {Promise} Resolves if dir is created; rejects otherwise.
*/
makeTempDir(prefix) {
return new Promise((resolve, reject) => {
fs.mkdtemp(prefix, (err, folder) => {
return (err) ? reject(err) : resolve(folder);
});
});
},
/**
* Fetches a ReSpec "src" URL, processes via NightmareJS and writes it to an
* "out" path within a given "timeout".
*
* @public
* @param {String} src A URL that is the ReSpec source.
* @param {String|null|""} out A path to write to. If null, goes to stdout.
* If "", then don't write, just return value.
* @param {Object} whenToHalt Object with two bool props (haltOnWarn,
* haltOnError), allowing execution to stop
* if either occurs.
* @param {Number} timeout Optional. Milliseconds before NightmareJS
* should timeout.
* @return {Promise} Resolves with HTML when done writing.
* Rejects on errors.
*/
fetchAndWrite(src, out, whenToHalt, timeout) {
return async.task(function* () {
const userData = yield this.makeTempDir(os.tmpDir() + "/respec2html-");
const nightmare = new Nightmare({
show: false,
timeout,
webPreferences: {
"images": false,
"defaultEncoding": "utf-8",
userData,
}
});
nightmare.useragent("respec2html");
const url = parseURL(src).href;
const handleConsoleMessages = makeConsoleMsgHandler(nightmare);
handleConsoleMessages(whenToHalt);
const response = yield nightmare
.goto(url);
if (response.code !== 200) {
const warn = colors.warn(`📡 HTTP Error ${response.code}:`);
const msg = `${warn} ${colors.debug(url)}`;
throw new Error(msg);
}
const isRespecDoc = yield nightmare
.wait(function(){
return document.readyState === "complete";
})
.evaluate(function(){
if(document.hasOwnProperty("respecIsReady")){
return true;
}
// does it try to load ReSpec locally or remotely
const remoteScriptQuery = "script[src='https://www.w3.org/Tools/respec/respec-w3c-common']";
const query = `script[data-main*=profile-w3c-common], ${remoteScriptQuery}`;
return (document.querySelector(query)) ? true : false;
});
if(!isRespecDoc){
const msg = `${colors.warn("💣 Not a ReSpec source document?")} ${colors.debug(url)}`;
throw new Error(msg);
}
const html = yield nightmare
.wait(function () {
return document.respecDone;
})
.wait("#respec-modal-save-snapshot")
.click("#respec-modal-save-snapshot")
.wait(100)
.evaluate(function () {
var encodedText = document.querySelector("#respec-save-as-html").href;
var decodedText = decodeURIComponent(encodedText);
var cleanedUpText = decodedText.replace(/^data:text\/html;charset=utf-8,/, "");
return cleanedUpText;
})
.end();
switch (out) {
case null:
process.stdout.write(html);
break;
case "":
break;
default:
try {
yield this.writeTo(out, html);
} catch (err) {
throw err;
}
}
return html;
}, this);
}
};
/**
* Handles messages from the browser's Console API.
*
* @param {Nightmare} nightmare Instance of Nightmare to listen on.
* @return {Function}
*/
function makeConsoleMsgHandler(nightmare) {
/**
* Specifies what to do when the browser emits "error" and "warn" console
* messages.
*
* @param {Object} whenToHalt Object with two bool props (haltOnWarn,
* haltOnError), allowing execution to stop
* if either occurs.
* @return {Void}
*/
return function handleConsoleMessages(whenToHalt) {
nightmare.on("console", (type, message) => {
const abortOnWarning = whenToHalt.haltOnWarn && type === "warn";
const abortOnError = whenToHalt.haltOnError && type === "error";
const output = `ReSpec ${type}: ${colors.debug(message)}`;
switch (type) {
case "error":
console.error(colors.error(`😱 ${output}`));
break;
case "warn":
// Ignore Nightmare's poling of respecDone
if (/document\.respecDone/.test(message)) {
return;
}
console.error(colors.warn(`😳 ${output}`));
break;
}
if (abortOnError || abortOnWarning) {
nightmare.proc.kill();
process.exit(1);
}
});
};
}
exports.fetchAndWrite = tasks.fetchAndWrite.bind(tasks);