1 | /*!
|
2 | Kettle Utilities.
|
3 |
|
4 | Copyright 2012-2013 OCAD University
|
5 | Copyright 2012 Antranig Basman
|
6 |
|
7 | Licensed under the New BSD license. You may not use this file except in
|
8 | compliance with this License.
|
9 |
|
10 | You may obtain a copy of the License at
|
11 | https://github.com/fluid-project/kettle/blob/master/LICENSE.txt
|
12 | */
|
13 |
|
14 | ;
|
15 |
|
16 | var fluid = require("infusion"),
|
17 | os = require("os"),
|
18 | fs = require("fs"),
|
19 | path = require("path");
|
20 |
|
21 | var kettle = fluid.registerNamespace("kettle"),
|
22 | $ = fluid.registerNamespace("jQuery");
|
23 |
|
24 | fluid.extend = $.extend; // passthrough definitions, compatible with upcoming Infusion
|
25 | fluid.trim = $.trim;
|
26 |
|
27 | // Debugging definition - node.js's default is only 10!
|
28 | fluid.Error.stackTraceLimit = 100;
|
29 | Error.stackTraceLimit = 100;
|
30 |
|
31 | kettle.requestUncaughtExceptionHandler = function (err) {
|
32 | var request = kettle.getCurrentRequest();
|
33 | // If there is no active request
|
34 | if (!request) {
|
35 | return;
|
36 | }
|
37 | // If there is an active request, ensure that it fails with this diagnostic
|
38 | request.events.onError.fire({
|
39 | isError: true,
|
40 | message: err.message
|
41 | });
|
42 | };
|
43 |
|
44 | fluid.onUncaughtException.addListener(kettle.requestUncaughtExceptionHandler, "fail",
|
45 | fluid.handlerPriorities.uncaughtException.fail);
|
46 |
|
47 |
|
48 | // In case of a fluid.fail - abort any current request, and then throw an exception
|
49 | kettle.failureHandler = function (args, activity) {
|
50 | var request = kettle.getCurrentRequest();
|
51 | if (request && !request.handlerPromise.disposition) {
|
52 | request.handlerPromise.reject({
|
53 | isError: true,
|
54 | message: args[0]
|
55 | });
|
56 | }
|
57 | fluid.builtinFail(args, activity);
|
58 | };
|
59 |
|
60 | // This is a default handler for fluid.fail. The handler will
|
61 | // fetch a request object from the environment and fire its
|
62 | // onError event.
|
63 | fluid.pushSoftFailure(kettle.failureHandler);
|
64 |
|
65 | // There seems to be no other way to determine whether signals are supported
|
66 | // than direct OS detection. Signals are currently completely unsupported on
|
67 | // Windows - https://github.com/joyent/node/issues/1553
|
68 | // The purpose of this code is to avoid hung or detached processes if node
|
69 | // is "killed" with CTRL-C etc.
|
70 | /* istanbul ignore next */
|
71 | if (os.type().indexOf("Windows") === -1) {
|
72 | process.on("SIGTERM", function handler() {
|
73 | process.exit(0);
|
74 | });
|
75 | }
|
76 |
|
77 | /** Upgrades a promise rejection payload (or Error) by suffixing an additional "while" reason into its "message" field
|
78 | * @param originError {Object|Error} A rejection payload. This should (at least) have the member `isError: true` set, as well as a String `message` holding a rejection reason.
|
79 | * @param whileMsg {String} A message describing the activity which led to this error
|
80 | * @return {Object} The rejected payload formed by shallow cloning the supplied argument (if it is not an `Error`) and suffixing its `message` member
|
81 | */
|
82 | kettle.upgradeError = function (originError, whileMsg) {
|
83 | var error = originError instanceof Error ? originError : fluid.extend({}, originError);
|
84 | error.message = originError.message + whileMsg;
|
85 | return error;
|
86 | };
|
87 |
|
88 | /** Clones an Error object into a JSON equivalent so that it can be checked in a test fixture using deep equality
|
89 | * @param originError {Object|Error} The error or rejection payload to be cloned
|
90 | */
|
91 | kettle.cloneError = function (originError) {
|
92 | var togo = fluid.extend({}, originError);
|
93 | togo.message = originError.message;
|
94 | return togo;
|
95 | };
|
96 |
|
97 | /** After express 4.15.0 of 2017-03-01 error messages are packaged as HTML readable
|
98 | * in this stereotypical form.
|
99 | * @param received {String} The body of an HTTP response from express which has
|
100 | * completed with an error
|
101 | * @return {String} The inner error encoded within the HTML body of the response,
|
102 | * if it could be decoded, or else the original value of `received`.
|
103 | */
|
104 |
|
105 | kettle.extractHtmlError = function (received) {
|
106 | var matches = /<pre>(.*)<\/pre>/gm.exec(received);
|
107 | console.log(matches);
|
108 | return matches ? matches[1] : received;
|
109 | };
|
110 |
|
111 | /** Returns a synchronously resolving promise for the contents of the supplied filename
|
112 | * @param fileName {String} the name of the file to be loaded (with `fs.readFileSync`)
|
113 | * @param message {String} A string describing the activity requiring the file, to appear in any rejection message
|
114 | * @return {Promise} A promise for the file's contents decoded as UTF-8
|
115 | */
|
116 | kettle.syncFilePromise = function (fileName, message) {
|
117 | var togo = fluid.promise();
|
118 | try {
|
119 | var string = fs.readFileSync(fileName, "utf8");
|
120 | togo.resolve(string);
|
121 | } catch (e) {
|
122 | togo.reject({
|
123 | message: "Error " + message + ": " + e,
|
124 | error: e
|
125 | });
|
126 | }
|
127 | togo.accumulateRejectionReason = function (originError) {
|
128 | return kettle.upgradeError(originError, " while " + message);
|
129 | };
|
130 | return togo;
|
131 | };
|
132 |
|
133 | /** @param fileNames {Array of String} An array of filenames to be tested
|
134 | * @return {String} The first member of the array which exists, or `null` if none do
|
135 | */
|
136 | kettle.firstExistingFile = function (fileNames) {
|
137 | return fluid.find_if(fileNames, function (fileName) {
|
138 | try {
|
139 | return fs.statSync(fileName).isFile();
|
140 | } catch (e) {
|
141 | return false;
|
142 | }
|
143 | }, null);
|
144 | };
|
145 |
|
146 | fluid.registerNamespace("kettle.JSON");
|
147 |
|
148 | /**
|
149 | * Parses a config file by chosing the appropriate decoder based on the extension of the file
|
150 | * Currently json and json5 are supported
|
151 | * @param fileName {String} The name of the file required to be loaded
|
152 | * @return {Function:String -> Promise} The parser to use (defaulting to a JSON parser as implemented by `kettle.dataSource.parseJSON`)
|
153 | */
|
154 | kettle.JSON.getSuitableParser = function (fileName) {
|
155 | return (path.extname(fileName).toLowerCase() === ".json5") ?
|
156 | kettle.dataSource.parseJSON5 : kettle.dataSource.parseJSON;
|
157 | };
|
158 |
|
159 | kettle.JSON.readFileSync = function (fileName, message) {
|
160 | message = message || "reading file " + fileName;
|
161 | var sequence = [{
|
162 | listener: function () {
|
163 | return kettle.syncFilePromise(fileName, message);
|
164 | }
|
165 | }, {
|
166 | listener: kettle.JSON.getSuitableParser(fileName)
|
167 | }];
|
168 | return fluid.promise.makeTransformer(sequence).promise;
|
169 | };
|
170 |
|
171 | // Direct replacement for JSON.parse which instead throws a helpful diagnostic (currently using jsonlint)
|
172 | kettle.JSON.parse = function (string) {
|
173 | if (typeof(string) !== "string") {
|
174 | fluid.fail("kettle.JSON.parse called on non-string object ", string);
|
175 | }
|
176 | var togo;
|
177 | kettle.dataSource.parseJSON(string).then(function (parsed) {
|
178 | togo = parsed;
|
179 | }, function (err) {
|
180 | throw err;
|
181 | });
|
182 | return togo;
|
183 | };
|
184 |
|
185 | // Distribute commonly used terms in dataSource resolution in development environments
|
186 |
|
187 | fluid.defaults("kettle.dataSource.distributeDevTerms", {
|
188 | gradeNames: ["fluid.component"],
|
189 | distributeOptions: {
|
190 | moduleTerms: {
|
191 | record: "kettle.dataSource.file.moduleTerms",
|
192 | target: "{that kettle.dataSource.file}.options.gradeNames"
|
193 | }
|
194 | }
|
195 | });
|