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 |
|
15 | ;
|
16 |
|
17 | const fs = require('fs');
|
18 | const fsPath = require('path');
|
19 | const Logger = require('@accordproject/concerto-util').Logger;
|
20 | const promisify = require('util').promisify;
|
21 | const readdir = fs.readdir ? promisify(fs.readdir) : undefined;
|
22 | const stat = fs.stat ? promisify(fs.stat) : undefined;
|
23 |
|
24 | const ENCODING = 'utf8';
|
25 |
|
26 | /**
|
27 | * A utility class to load files from zip or directories
|
28 | * @class
|
29 | * @private
|
30 | * @abstract
|
31 | */
|
32 | class FileLoader {
|
33 | /**
|
34 | * Loads a required file from the zip, displaying an error if missing
|
35 | * @internal
|
36 | * @param {*} zip the JSZip instance
|
37 | * @param {string} path the file path within the zip
|
38 | * @param {boolean} json if true the file is converted to a JS Object using JSON.parse
|
39 | * @param {boolean} required whether the file is required
|
40 | * @return {Promise<string>} a promise to the contents of the zip file or null if it does not exist and
|
41 | * required is false
|
42 | */
|
43 | static async loadZipFileContents(zip, path, json=false, required=false) {
|
44 | Logger.debug('loadZipFileContents', 'Loading ' + path);
|
45 | let zipFile = zip.file(path);
|
46 | if(!zipFile && required) {
|
47 | throw new Error(`Failed to find ${path} in archive file.`);
|
48 | }
|
49 |
|
50 | if(zipFile) {
|
51 | const content = await zipFile.async('string');
|
52 |
|
53 | if(json && content) {
|
54 | return JSON.parse(content);
|
55 | }
|
56 | else {
|
57 | return FileLoader.normalizeNLs(content);
|
58 | }
|
59 | }
|
60 |
|
61 | return null;
|
62 | }
|
63 |
|
64 | /**
|
65 | * Loads the contents of all files in the zip that match a regex
|
66 | * @internal
|
67 | * @param {*} zip the JSZip instance
|
68 | * @param {RegExp} regex the regex to use to match files
|
69 | * @return {Promise<object[]>} a promise to an array of objects with the name and contents of the zip files
|
70 | */
|
71 | static async loadZipFilesContents(zip, regex) {
|
72 | const results = [];
|
73 | let matchedFiles = zip.file(regex);
|
74 |
|
75 | // do not use forEach, because we need to call an async function!
|
76 | for(let n=0; n < matchedFiles.length; n++) {
|
77 | const file = matchedFiles[n];
|
78 | const result = {name: file.name};
|
79 | result.contents = await FileLoader.loadZipFileContents(zip, file.name, false, true);
|
80 | results.push(result);
|
81 | }
|
82 |
|
83 | return results;
|
84 | }
|
85 |
|
86 | /**
|
87 | * Loads a required buffer of a file from the zip, displaying an error if missing
|
88 | * @internal
|
89 | * @param {*} zip the JSZip instance
|
90 | * @param {string} path the file path within the zip
|
91 | * @param {boolean} required whether the file is required
|
92 | * @return {Promise<Buffer>} a promise to the Buffer of the zip file or null if it does not exist and
|
93 | * required is false
|
94 | */
|
95 | static async loadZipFileBuffer(zip, path, required=false) {
|
96 | Logger.debug('loadZipFileBuffer', 'Loading ' + path);
|
97 | let zipFile = zip.file(path);
|
98 | if(!zipFile && required) {
|
99 | throw new Error(`Failed to find ${path} in archive file.`);
|
100 | }
|
101 |
|
102 | else if(zipFile) {
|
103 | return zipFile.async('nodebuffer');
|
104 | }
|
105 |
|
106 | return null;
|
107 | }
|
108 |
|
109 | /**
|
110 | * Loads a required file from a directory, displaying an error if missing
|
111 | * @internal
|
112 | * @param {*} path the root path
|
113 | * @param {string} fileName the relative file name
|
114 | * @param {boolean} json if true the file is converted to a JS Object using JSON.parse
|
115 | * @param {boolean} required whether the file is required
|
116 | * @return {Promise<string>} a promise to the contents of the file or null if it does not exist and
|
117 | * required is false
|
118 | */
|
119 | static async loadFileContents(path, fileName, json=false, required=false) {
|
120 |
|
121 | Logger.debug('loadFileContents', 'Loading ' + fileName);
|
122 | const filePath = fsPath.resolve(path, fileName);
|
123 |
|
124 | if (fs.existsSync(filePath)) {
|
125 | const contents = fs.readFileSync(filePath, ENCODING);
|
126 | if(json && contents) {
|
127 | return JSON.parse(contents);
|
128 | }
|
129 | else {
|
130 | return FileLoader.normalizeNLs(contents);
|
131 | }
|
132 | }
|
133 | else {
|
134 | if(required) {
|
135 | throw new Error(`Failed to find ${fileName} in directory.`);
|
136 | }
|
137 | }
|
138 |
|
139 | return null;
|
140 | }
|
141 |
|
142 | /**
|
143 | * Loads a file as buffer from a directory, displaying an error if missing
|
144 | * @internal
|
145 | * @param {*} path the root path
|
146 | * @param {string} fileName the relative file name
|
147 | * @param {boolean} required whether the file is required
|
148 | * @return {Promise<Buffer>} a promise to the buffer of the file or null if
|
149 | * it does not exist and required is false
|
150 | */
|
151 | static async loadFileBuffer(path, fileName, required=false) {
|
152 |
|
153 | Logger.debug('loadFileBuffer', 'Loading ' + fileName);
|
154 | const filePath = fsPath.resolve(path, fileName);
|
155 |
|
156 | if (fs.existsSync(filePath)) {
|
157 | return fs.readFileSync(filePath);
|
158 | }
|
159 | else {
|
160 | if(required) {
|
161 | throw new Error(`Failed to find ${fileName} in directory`);
|
162 | }
|
163 | }
|
164 | return null;
|
165 | }
|
166 |
|
167 | /**
|
168 | * Loads the contents of all files under a path that match a regex
|
169 | * Note that any directories called node_modules are ignored.
|
170 | * @internal
|
171 | * @param {*} path the file path
|
172 | * @param {RegExp} regex the regex to match files
|
173 | * @return {Promise<object[]>} a promise to an array of objects with the name and contents of the files
|
174 | */
|
175 | static async loadFilesContents(path, regex) {
|
176 |
|
177 | Logger.debug('loadFilesContents', 'Loading ' + path);
|
178 | const subdirs = await readdir(path);
|
179 | const result = await Promise.all(subdirs.map(async (subdir) => {
|
180 | const res = fsPath.resolve(path, subdir);
|
181 |
|
182 | if((await stat(res)).isDirectory()) {
|
183 | if( /.*node_modules$/.test(res) === false) {
|
184 | return FileLoader.loadFilesContents(res, regex);
|
185 | }
|
186 | else {
|
187 | return null;
|
188 | }
|
189 | }
|
190 | else {
|
191 | if(regex.test(res)) {
|
192 | return {
|
193 | name: res,
|
194 | contents: await FileLoader.loadFileContents(path, res, false, true)
|
195 | };
|
196 | }
|
197 | else {
|
198 | return null;
|
199 | }
|
200 | }
|
201 | }));
|
202 | return result.reduce((a, f) => a.concat(f), []).filter((f) => f !== null);
|
203 | }
|
204 |
|
205 | /**
|
206 | * Prepare the text for parsing (normalizes new lines, etc)
|
207 | * @param {string} input - the text for the clause
|
208 | * @return {string} - the normalized text for the clause
|
209 | */
|
210 | static normalizeNLs(input) {
|
211 | // we replace all \r and \n with \n
|
212 | let text = input.replace(/\r/gm,'');
|
213 | return text;
|
214 | }
|
215 |
|
216 | }
|
217 |
|
218 | module.exports = FileLoader; |
\ | No newline at end of file |