UNPKG

7 kBJavaScriptView Raw
1/*!
2Kettle Utilities.
3
4Copyright 2012-2013 OCAD University
5Copyright 2012 Antranig Basman
6
7Licensed under the New BSD license. You may not use this file except in
8compliance with this License.
9
10You may obtain a copy of the License at
11https://github.com/fluid-project/kettle/blob/master/LICENSE.txt
12*/
13
14"use strict";
15
16var fluid = require("infusion"),
17 os = require("os"),
18 fs = require("fs"),
19 path = require("path");
20
21var kettle = fluid.registerNamespace("kettle"),
22 $ = fluid.registerNamespace("jQuery");
23
24fluid.extend = $.extend; // passthrough definitions, compatible with upcoming Infusion
25fluid.trim = $.trim;
26
27// Debugging definition - node.js's default is only 10!
28fluid.Error.stackTraceLimit = 100;
29Error.stackTraceLimit = 100;
30
31kettle.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
44fluid.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
49kettle.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.
63fluid.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 */
71if (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 */
82kettle.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 */
91kettle.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
105kettle.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 */
116kettle.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 */
136kettle.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
146fluid.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 */
154kettle.JSON.getSuitableParser = function (fileName) {
155 return (path.extname(fileName).toLowerCase() === ".json5") ?
156 kettle.dataSource.parseJSON5 : kettle.dataSource.parseJSON;
157};
158
159kettle.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)
172kettle.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
187fluid.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});