UNPKG

14.8 kBJavaScriptView Raw
1/*
2 * Licensed under the Apache License, Version 2.0 (the "License");
3 * you may not use this file except in compliance with the License.
4 * You may obtain a copy of the License at
5 *
6 * http://www.apache.org/licenses/LICENSE-2.0
7 *
8 * Unless required by applicable law or agreed to in writing, software
9 * distributed under the License is distributed on an "AS IS" BASIS,
10 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 * See the License for the specific language governing permissions and
12 * limitations under the License.
13 */
14'use strict';
15
16function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
17
18function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
19
20const fs = require('fs');
21
22const fsPath = require('path');
23
24const JSZip = require('jszip');
25
26const xregexp = require('xregexp');
27
28const languageTagRegex = require('ietf-language-tag-regex');
29
30const DefaultArchiveLoader = require('./loaders/defaultarchiveloader');
31
32const Logger = require('@accordproject/ergo-compiler').Logger;
33
34const promisify = require('util').promisify;
35
36const readdir = fs.readdir ? promisify(fs.readdir) : undefined;
37const stat = fs.stat ? promisify(fs.stat) : undefined;
38const ENCODING = 'utf8'; // Matches 'sample.txt' or 'sample_TAG.txt' where TAG is an IETF language tag (BCP 47)
39
40const IETF_REGEXP = languageTagRegex({
41 exact: false
42}).toString().slice(1, -2);
43const SAMPLE_FILE_REGEXP = xregexp('sample(_(' + IETF_REGEXP + '))?.txt$');
44/**
45 * A utility class to create templates from data sources.
46 * @class
47 * @interal
48 * @abstract
49 * @memberof module:cicero-core
50 */
51
52class TemplateLoader {
53 /**
54 * Loads a required file from the zip, displaying an error if missing
55 * @internal
56 * @param {*} zip the JSZip instance
57 * @param {string} path the file path within the zip
58 * @param {boolean} json if true the file is converted to a JS Object using JSON.parse
59 * @param {boolean} required whether the file is required
60 * @return {Promise<string>} a promise to the contents of the zip file or null if it does not exist and
61 * required is false
62 */
63 static loadZipFileContents(zip, path) {
64 var _arguments = arguments;
65 return _asyncToGenerator(function* () {
66 let json = _arguments.length > 2 && _arguments[2] !== undefined ? _arguments[2] : false;
67 let required = _arguments.length > 3 && _arguments[3] !== undefined ? _arguments[3] : false;
68 Logger.debug('loadZipFileContents', 'Loading ' + path);
69 let zipFile = zip.file(path);
70
71 if (!zipFile && required) {
72 throw new Error("Failed to find ".concat(path, " in archive file."));
73 }
74
75 if (zipFile) {
76 const content = yield zipFile.async('string');
77
78 if (json && content) {
79 return JSON.parse(content);
80 } else {
81 return content;
82 }
83 }
84
85 return null;
86 })();
87 }
88 /**
89 * Loads the contents of all files in the zip that match a regex
90 * @internal
91 * @param {*} zip the JSZip instance
92 * @param {RegExp} regex the regex to use to match files
93 * @return {Promise<object[]>} a promise to an array of objects with the name and contents of the zip files
94 */
95
96
97 static loadZipFilesContents(zip, regex) {
98 return _asyncToGenerator(function* () {
99 const results = [];
100 let matchedFiles = zip.file(regex); // do not use forEach, because we need to call an async function!
101
102 for (let n = 0; n < matchedFiles.length; n++) {
103 const file = matchedFiles[n];
104 const result = {
105 name: file.name
106 };
107 result.contents = yield TemplateLoader.loadZipFileContents(zip, file.name, false, true);
108 results.push(result);
109 }
110
111 return results;
112 })();
113 }
114 /**
115 * Loads a required file from a directory, displaying an error if missing
116 * @internal
117 * @param {*} path the root path
118 * @param {string} fileName the relative file name
119 * @param {boolean} json if true the file is converted to a JS Object using JSON.parse
120 * @param {boolean} required whether the file is required
121 * @return {Promise<string>} a promise to the contents of the file or null if it does not exist and
122 * required is false
123 */
124
125
126 static loadFileContents(path, fileName) {
127 var _arguments2 = arguments;
128 return _asyncToGenerator(function* () {
129 let json = _arguments2.length > 2 && _arguments2[2] !== undefined ? _arguments2[2] : false;
130 let required = _arguments2.length > 3 && _arguments2[3] !== undefined ? _arguments2[3] : false;
131 Logger.debug('loadFileContents', 'Loading ' + fileName);
132 const filePath = fsPath.resolve(path, fileName);
133
134 if (fs.existsSync(filePath)) {
135 const contents = fs.readFileSync(filePath, ENCODING);
136
137 if (json && contents) {
138 return JSON.parse(contents);
139 } else {
140 return contents;
141 }
142 } else {
143 if (required) {
144 throw new Error("Failed to find ".concat(fileName, " in directory."));
145 }
146 }
147
148 return null;
149 })();
150 }
151 /**
152 * Loads the contents of all files under a path that match a regex
153 * Note that any directories called node_modules are ignored.
154 * @internal
155 * @param {*} path the file path
156 * @param {RegExp} regex the regex to match files
157 * @return {Promise<object[]>} a promise to an array of objects with the name and contents of the files
158 */
159
160
161 static loadFilesContents(path, regex) {
162 return _asyncToGenerator(function* () {
163 Logger.debug('loadFilesContents', 'Loading ' + path);
164 const subdirs = yield readdir(path);
165 const result = yield Promise.all(subdirs.map(
166 /*#__PURE__*/
167 function () {
168 var _ref = _asyncToGenerator(function* (subdir) {
169 const res = fsPath.resolve(path, subdir);
170
171 if ((yield stat(res)).isDirectory()) {
172 if (/.*node_modules$/.test(res) === false) {
173 return TemplateLoader.loadFilesContents(res, regex);
174 } else {
175 return null;
176 }
177 } else {
178 if (regex.test(res)) {
179 return {
180 name: res,
181 contents: yield TemplateLoader.loadFileContents(path, res, false, true)
182 };
183 } else {
184 return null;
185 }
186 }
187 });
188
189 return function (_x) {
190 return _ref.apply(this, arguments);
191 };
192 }()));
193 return result.reduce((a, f) => a.concat(f), []).filter(f => f !== null);
194 })();
195 }
196 /**
197 * Create a template from an archive.
198 * @param {*} Template - the type to construct
199 * @param {Buffer} buffer - the buffer to a Cicero Template Archive (cta) file
200 * @return {Promise<Template>} a Promise to the template
201 */
202
203
204 static fromArchive(Template, buffer) {
205 return _asyncToGenerator(function* () {
206 const method = 'fromArchive';
207 const zip = yield JSZip.loadAsync(buffer); // const allFiles = await TemplateLoader.loadZipFilesContents(zip, /.*/);
208 // console.log(allFiles);
209
210 const ctoModelFiles = [];
211 const ctoModelFileNames = [];
212 const sampleTextFiles = {};
213 const readmeContents = yield TemplateLoader.loadZipFileContents(zip, 'README.md');
214 let sampleFiles = yield TemplateLoader.loadZipFilesContents(zip, SAMPLE_FILE_REGEXP);
215 sampleFiles.forEach(
216 /*#__PURE__*/
217 function () {
218 var _ref2 = _asyncToGenerator(function* (sampleFile) {
219 let matches = sampleFile.name.match(SAMPLE_FILE_REGEXP);
220 let locale = 'default'; // Locale match found
221
222 if (matches !== null && matches[2]) {
223 locale = matches[2];
224 }
225
226 sampleTextFiles[locale] = sampleFile.contents;
227 });
228
229 return function (_x2) {
230 return _ref2.apply(this, arguments);
231 };
232 }());
233 const requestContents = yield TemplateLoader.loadZipFileContents(zip, 'request.json', true);
234 const packageJsonObject = yield TemplateLoader.loadZipFileContents(zip, 'package.json', true, true);
235 const templatizedGrammar = yield TemplateLoader.loadZipFileContents(zip, 'grammar/template.tem', false, true);
236 Logger.debug(method, 'Looking for model files');
237 let ctoFiles = yield TemplateLoader.loadZipFilesContents(zip, /models\/.*\.cto$/);
238 ctoFiles.forEach(
239 /*#__PURE__*/
240 function () {
241 var _ref3 = _asyncToGenerator(function* (file) {
242 ctoModelFileNames.push(file.name);
243 ctoModelFiles.push(file.contents);
244 });
245
246 return function (_x3) {
247 return _ref3.apply(this, arguments);
248 };
249 }()); // create the template
250
251 const template = new (Function.prototype.bind.call(Template, null, packageJsonObject, readmeContents, sampleTextFiles, requestContents))(); // add model files
252
253 Logger.debug(method, 'Adding model files to model manager');
254 template.getModelManager().addModelFiles(ctoModelFiles, ctoModelFileNames, true); // validation is disabled
255 // load and add the ergo files
256
257 if (template.getMetadata().getErgoVersion()) {
258 Logger.debug(method, 'Adding Ergo files to script manager');
259 const scriptFiles = yield TemplateLoader.loadZipFilesContents(zip, /lib\/.*\.ergo$/);
260 scriptFiles.forEach(function (obj) {
261 template.getTemplateLogic().addLogicFile(obj.contents, obj.name);
262 });
263 } // load and add compiled JS files - we assume all runtimes are JS based (review!)
264
265
266 if (template.getMetadata().getRuntime()) {
267 Logger.debug(method, 'Adding JS files to script manager');
268 const scriptFiles = yield TemplateLoader.loadZipFilesContents(zip, /lib\/.*\.js$/);
269 scriptFiles.forEach(function (obj) {
270 template.getTemplateLogic().addLogicFile(obj.contents, obj.name);
271 });
272 } // check the integrity of the model and logic of the template
273
274
275 template.validate();
276 Logger.debug(method, 'Setting grammar');
277 template.parserManager.buildGrammar(templatizedGrammar);
278 return template; // Returns template
279 })();
280 }
281 /**
282 * Create a template from an URL.
283 * @param {*} Template - the type to construct
284 * @param {String} url - the URL to a Cicero Template Archive (cta) file
285 * @param {object} options - additional options
286 * @return {Promise} a Promise to the template
287 */
288
289
290 static fromUrl(Template, url, options) {
291 return _asyncToGenerator(function* () {
292 const loader = new DefaultArchiveLoader();
293 const buffer = yield loader.load(url, options);
294 return TemplateLoader.fromArchive(Template, buffer);
295 })();
296 }
297 /**
298 * Builds a Template from the contents of a directory.
299 * The directory must include a package.json in the root (used to specify
300 * the name, version and description of the template).
301 *
302 * @param {*} Template - the type to construct
303 * @param {String} path to a local directory
304 * @param {Object} [options] - an optional set of options to configure the instance.
305 * @return {Promise<Template>} a Promise to the instantiated template
306 */
307
308
309 static fromDirectory(Template, path, options) {
310 return _asyncToGenerator(function* () {
311 if (!options) {
312 options = {};
313 }
314
315 const method = 'fromDirectory'; // grab the README.md
316
317 const readmeContents = yield TemplateLoader.loadFileContents(path, 'README.md'); // grab the request.json
318
319 const requestJsonObject = yield TemplateLoader.loadFileContents(path, 'request.json', true); // grab the package.json
320
321 const packageJsonObject = yield TemplateLoader.loadFileContents(path, 'package.json', true, true); // grab the sample files
322
323 Logger.debug(method, 'Looking for sample files');
324 const sampleFiles = yield TemplateLoader.loadFilesContents(path, SAMPLE_FILE_REGEXP);
325 const sampleTextFiles = {};
326 sampleFiles.forEach(file => {
327 const matches = file.name.match(SAMPLE_FILE_REGEXP);
328 let locale = 'default'; // Match found
329
330 if (matches !== null && matches[2]) {
331 locale = matches[2];
332 }
333
334 Logger.debug(method, 'Using sample file locale', locale);
335 sampleTextFiles[locale] = file.contents;
336 }); // create the template
337
338 const template = new (Function.prototype.bind.call(Template, null, packageJsonObject, readmeContents, sampleTextFiles, requestJsonObject))();
339 const modelFiles = [];
340 const modelFileNames = [];
341 const ctoFiles = yield TemplateLoader.loadFilesContents(path, /models\/.*\.cto$/);
342 ctoFiles.forEach(file => {
343 modelFileNames.push(file.name);
344 modelFiles.push(file.contents);
345 });
346 template.getModelManager().addModelFiles(modelFiles, modelFileNames, true);
347 yield template.getModelManager().updateExternalModels();
348 Logger.debug(method, 'Added model files', modelFiles.length); // load and add the ergo files
349
350 if (template.getMetadata().getErgoVersion()) {
351 const ergoFiles = yield TemplateLoader.loadFilesContents(path, /lib\/.*\.ergo$/);
352 ergoFiles.forEach(file => {
353 const resolvedPath = fsPath.resolve(path);
354 const resolvedFilePath = fsPath.resolve(file.name);
355 const truncatedPath = resolvedFilePath.replace(resolvedPath + '/', '');
356 template.getTemplateLogic().addLogicFile(file.contents, truncatedPath);
357 });
358 } // load and add compiled JS files - we assume all runtimes are JS based (review!)
359
360
361 if (template.getMetadata().getRuntime()) {
362 const jsFiles = yield TemplateLoader.loadFilesContents(path, /lib\/.*\.js$/);
363 jsFiles.forEach(file => {
364 const resolvedPath = fsPath.resolve(path);
365 const resolvedFilePath = fsPath.resolve(file.name);
366 const truncatedPath = resolvedFilePath.replace(resolvedPath + '/', '');
367 template.getTemplateLogic().addLogicFile(file.contents, truncatedPath);
368 });
369 } // check the template
370
371
372 template.validate();
373 let template_txt = yield TemplateLoader.loadFileContents(path, 'grammar/template.tem', false, true);
374 template.parserManager.buildGrammar(template_txt);
375 Logger.debug(method, 'Loaded template.tem', template_txt);
376 return template;
377 })();
378 }
379
380}
381
382module.exports = TemplateLoader;
\No newline at end of file