/** * Copyright 2014 IBM Corp. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. **/ var util = require("util"); var when = require("when"); var whenNode = require('when/node'); var fs = require("fs"); var path = require("path"); var cheerio = require("cheerio"); var UglifyJS = require("uglify-js"); var events = require("../events"); var Node; var settings; var node_types = {}; var node_configs = []; var node_scripts = []; function loadTemplate(templateFilename) { return when.promise(function(resolve,reject) { whenNode.call(fs.readFile,templateFilename,'utf8').done(function(content) { registerConfig(content); resolve(); //console.log(templateFilename); }, function(err) { reject("missing template file"); }); }); } // Return a promise that resolves to: // success: {fn,path} // failure: {fn.path,err} function loadNode(nodeDir, nodeFn, nodeLabel) { return when.promise(function(resolve,reject) { if (settings.nodesExcludes) { for (var i=0;i<settings.nodesExcludes.length;i++) { if (settings.nodesExcludes[i] == nodeFn) { //resolve({fn:nodeFn,path:nodeFilename,err:"nodesExcludes"}); resolve(); return; } } } var nodeFilename = path.join(nodeDir,nodeFn); try { var r = require(nodeFilename); if (typeof r === "function") { var promise = r(require('../red')); if (promise != null && typeof promise.then === "function") { promise.then(function() { resolve({fn:nodeLabel||nodeFn,path:nodeFilename}); },function(err) { resolve({fn:nodeLabel||nodeFn,path:nodeFilename,err:err}); }); } else { resolve({fn:nodeLabel||nodeFn,path:nodeFilename}); } } else { resolve({fn:nodeLabel||nodeFn,path:nodeFilename}); } } catch(err) { resolve({fn:nodeLabel||nodeFn,path:nodeFilename,err:err}); } }); } function loadNodesFromModule(moduleDir,pkg) { var nodes = pkg['node-red'].nodes||{}; var promises = []; for (var n in nodes) { promises.push(loadNode(moduleDir,nodes[n],pkg.name+":"+n)); } return promises; } function scanForNodes(dir) { var pm = path.join(dir,"node_modules"); return when.promise(function(resolve,reject) { whenNode.call(fs.readdir,pm).then(function(files) { var promises = []; files.forEach(function(fn) { var pkgfn = path.join(pm,fn,"package.json"); try { var pkg = require(pkgfn); if (pkg['node-red']) { var moduleDir = path.join(pm,fn); promises = promises.concat(loadNodesFromModule(moduleDir,pkg)); } } catch(err) { if (err.code != "MODULE_NOT_FOUND") { // TODO: handle unexpected error } } }); when.settle(promises).then(function(results) { var promises = []; results.forEach(function(result) { promises = promises.concat(result.value); }); resolve(promises); }); },function(err) { resolve([]); }); }); } function scanTreeForNodes(dir) { return when.promise(function(resolve) { var promises = []; var up = path.resolve(path.join(dir,"..")); while (up !== dir) { promises.push(scanForNodes(dir)); dir = up; up = path.resolve(path.join(dir,"..")); } when.settle(promises).then(function(results) { var promises = []; results.forEach(function(result) { promises = promises.concat(result.value); }); resolve(promises); }); }); } // Returns a promise that resolves to an array of node results function loadNodes(dir) { return when.promise(function(resolve,reject) { var promises = []; whenNode.call(fs.readdir,dir).done(function(files) { files = files.sort(); files.forEach(function(fn) { var stats = fs.statSync(path.join(dir,fn)); if (stats.isFile()) { if (/\.js$/.test(fn)) { promises.push(loadNode(dir,fn)); } } else if (stats.isDirectory()) { // Ignore /.dirs/, /lib/ /node_modules/ if (!/^(\..*|lib|icons|node_modules|test)$/.test(fn)) { promises.push(loadNodes(path.join(dir,fn))); } else if (fn === "icons") { events.emit("node-icon-dir",path.join(dir,fn)); } } }); when.settle(promises).then(function(results) { var errors = []; results.forEach(function(result) { if (result.state == 'fulfilled' && result.value) { errors = errors.concat(result.value); } }); resolve(errors); }); }, function(err) { resolve([]); // non-existant dir }); }); } function init(_settings) { settings = _settings; } function load() { Node = require("./Node"); return when.promise(function(resolve,reject) { var RED = require("../red.js"); loadNodes(__dirname+"/../../nodes").then(function(loadedNodes) { var promises = []; promises.push(scanTreeForNodes(__dirname+"/../../nodes")); if (settings.nodesDir) { var dir = settings.nodesDir; if (typeof settings.nodesDir == "string") { dir = [dir]; } for (var i=0;i<dir.length;i++) { promises.push(loadNodes(dir[i])); } } when.settle(promises).then(function(results) { results.forEach(function(result) { if (result.state == 'fulfilled' && result.value) { loadedNodes = loadedNodes.concat(result.value); } }); var promises = []; loadedNodes.forEach(function(v) { if (v.err == null) { var templateFilename = v.path.replace(/\.js$/,".html"); promises.push(loadTemplate(templateFilename)); } else { promises.push(when()); } }); when.settle(promises).then(function(results) { for (var i=0;i<loadedNodes.length;i++) { if (results[i].state == "rejected") { loadedNodes[i].err = results[i].reason; } } var errors = loadedNodes.filter(function(v) { return v.err != null;}); resolve(errors); }); }); }); }); } function registerConfig(config) { $ = cheerio.load(config); var template = ""; $("*").each(function(i,el) { if (el.type == "script" && el.attribs.type == "text/javascript") { var content = el.children[0].data; el.children[0].data = UglifyJS.minify(content, {fromString: true}).code; node_scripts.push($(this).text()); } else if (el.name == "script" || el.name == "style") { var openTag = "<"+el.name; var closeTag = "</"+el.name+">"; if (el.attribs) { for (var i in el.attribs) { openTag += " "+i+'="'+el.attribs[i]+'"'; } } openTag += ">"; template += openTag+$(el).text()+closeTag; } }); node_configs.push(template); } var typeRegistry = module.exports = { init:init, load:load, registerType: function(type,node) { util.inherits(node,Node); node_types[type] = node; events.emit("type-registered",type); }, get: function(type) { return node_types[type]; }, getNodeConfigs: function() { var result = ""; for (var i=0;i<node_configs.length;i++) { result += node_configs[i]; } result += '<script type="text/javascript">'; for (var i=0;i<node_scripts.length;i++) { result += node_scripts[i]; } result += '</script>'; return result; } }