toloframework
Version:
Javascript/HTML/CSS compiler for Firefox OS or nodewebkit apps using modules in the nodejs style.
238 lines (228 loc) • 8.6 kB
JavaScript
var FS = require("fs");
var Path = require("path");
var Tree = require("./htmltree");
var Util = require("./util");
var Fatal = require("./fatal");
var Source = require("./source");
var Template = require("./template");
var ParserHTML = require("./tlk-htmlparser");
var CompilerCOM = require("./compiler-com");
var CompilerHTML = require("./compiler-html2");
module.exports = function(source, components, scopes, output) {
var ID = 0;
function setVar(key, val) {
scopes[scopes.length - 1][key] = val;
}
function getVar(key) {
var k, v;
for (k = scopes.length - 1; k >= 0; k--) {
v = scopes[k][key];
if (typeof v !== 'undefined') {
return v;
}
}
return '';
}
// Comnpilation options are set by the function `libs.compile(root, options)`.
var compilationOptions = {};
var prj = source.prj();
/**
* @return
* The path of the current HTML file, relative to the `src` path.
*/
function dirname() {
var absoluteDirname = Path.dirname(source.getAbsoluteFilePath());
var absoluteSrcPath = source.prj().srcPath();
return absoluteDirname.substr(absoluteSrcPath.length);
}
var libs = {
Tree: Tree,
Template: Template,
// An HTML file can ask to compile another HTML file with this function.
compileHTML: function(src) {
CompilerHTML.compile(src, compilationOptions);
output.include[src] = 1;
},
/**
* The depth of `path` is the number of subfolders it defines. For
* example, `foo.js' defined no subfolder and it is of depth 0. But
* `foo/bar/file.html` has two levels of subfolders hence it is of depth
* 2.
*/
getBackToRoot: function (path) {
// Counter for '/'.
var standardFolderSepCount = 0;
// Counter for '\' (windows folder separator).
var windowsFolderSepCount = 0;
// Loops index used for parsing chars of `path`and to add `../` to the result.
var i;
// Current char read from `path`.
var c;
// Counting folders' separators.
for (i = 0; i < path.length; i++) {
c = path.charAt(i);
if (c == '/') standardFolderSepCount++;
if (c == '\\') windowsFolderSepCount++;
}
var folderSepCount = Math.max(standardFolderSepCount, windowsFolderSepCount);
if (folderSepCount == 0) {
// There is no subfolder.
return '';
}
var result = '';
var folderSep = '/'; // windowsFolderSepCount > standardFolderSepCount ? '\\' : '/';
for (i = 0; i < folderSepCount; i++) {
result += '..' + folderSep;
}
return result;
},
fatal: function(msg, tree) {
if (typeof tree !== 'undefined' && typeof tree.pos === 'number') {
msg += "\n\n" + Fatal.extractCodeAtPos(source.read(), tree.pos);
}
Fatal.fire(msg, "Component");
},
warning: function(msg, filename, content, pos) {
filename = filename || source.name();
console.log("------------------------------------------------------------".yellow);
console.log("Warning in file ".bold.yellow + filename.cyan.bold);
console.log(msg.bold.yellow);
console.log();
if (typeof content === 'string') {
// There is code to show.
console.log(Fatal.extractCodeAtPos(content, pos).yellow);
}
},
nextID: function() {
ID++;
return 'x-' + ID;
},
setVar: setVar,
getVar: getVar,
fileExists: function(relPath) {
var name = Path.join( Path.dirname( source.name() ), relPath );
var srcPath = source.prj().srcOrLibPath( name );
return srcPath;
},
filePath: function(relPath) {
var name = Path.join( Path.dirname( source.name() ), relPath );
var srcPath = source.prj().srcOrLibPath( name );
return srcPath;
},
/**
* @return
* The path of the current HTML file, relative to the `src` path.
*/
dirname: dirname,
/**
* Return the absolute path of a path relative to the HTML file.
* @param path {string} - Path relative to the HTML file.
*/
htmPath: function(path) {
return source.prj().srcPath(Path.join(dirname(), path));
},
readFileContent: function(relPath) {
try {
var name = Path.join( Path.dirname( source.name() ), relPath );
var srcPath = source.prj().srcOrLibPath( name );
if( !srcPath ) return "";
return FS.readFileSync( srcPath ).toString();
}
catch (ex) {
Fatal.bubble(ex, "readFileContent(\"" + relPath + "\")", "");
}
},
addDynamicModule: function( name, code ) {
output.dynamicModules['mod/' + name] =
"require('" + name + "', function(exports, module) {\n"
+ code + "\});\n";
},
addDependency: function(dependency) {
output.dependencies[dependency] = 1;
},
addInnerCSS: function(contentCSS) {
output.innerCSS[contentCSS] = 1;
},
addInitJS: function(code) {
output.initJS[code] = 1;
},
addPostInitJS: function(code) {
output.postInitJS[code] = 1;
},
addInclude: function(src) {
output.include[src] = 1;
},
addResourceText: function(name, dst, txt) {
output.resource[name] = {dst: dst, txt: txt};
},
addResourceFile: function(name, dst, src) {
output.resource[name] = {dst: dst, src: src};
},
require: function(moduleName) {
var prefix = moduleName.substr(0, 4);
if (prefix != 'mod/' && prefix != 'cls/') {
// Add directory if missing.
moduleName = 'mod/' + moduleName;
}
output.require[moduleName] = 1;
},
parseHTML: ParserHTML.parse
};
// Project configuration.
var cfg = prj._config;
setVar('$filename', source.name());
setVar('$title', cfg.name);
setVar('$author', cfg.author);
setVar('$version', cfg.version);
function compileChildren(root) {
if (Array.isArray(root.children)) {
// We create a new children array to avoid nodes of type VOID.
var result = [];
root.children.forEach(function (child) {
libs.compile(child);
if (child.type == Tree.VOID || typeof child.type === 'undefined') {
result.push.apply( result, child.children );
} else {
result.push( child );
}
});
root.children = result;
}
}
libs.compileChildren = compileChildren;
libs.compile = function(root, options) {
compilationOptions = options;
if (root.type !== Tree.TAG) {
compileChildren(root);
return;
} else {
var component = CompilerCOM.getCompilerForTag(root.name);
if (component) {
if (component.$.css) {
libs.addInnerCSS(component.$.css);
}
if (component.$.res) {
// There is a resource folder.
var dstPath = source.name();
libs.addResourceFile(
Path.join('com', component.$.id),
Path.join('css', Path.dirname(dstPath), Path.join('com', component.$.id)),
component.$.res
);
}
try {
scopes.push({});
component.compile(root, libs);
scopes.pop();
}
catch (ex) {
Fatal.bubble(ex, "Tag: " + root.name + ", HTML file: " + source.name());
}
} else {
// This is a standard TAG. Let's loop on children.
compileChildren(root);
}
}
};
return libs;
};