UNPKG

toloframework

Version:

Javascript/HTML/CSS compiler for Firefox OS or nodewebkit apps using modules in the nodejs style.

936 lines (853 loc) 32.7 kB
/* When compiling the file `main.html`, we gather all the CSS and JS files needed. If `no-zip` option has not been set, all the CSS files are combined in `css/@main.html` and all the JS files are combined in `js/@main.html`. # Modules Modules are stored in folder `mod/`. * `mymodule.js` * `mymodule.css` * `mymodule/` * `mymodule.dep` */ var FS = require("fs"); var Path = require("path"); var CompilerINI = require("./compiler-ini"); var CompilerCOM = require("./compiler-com"); var ParserHTML = require("./tlk-htmlparser"); var SourceMap = require("./source-map"); var PathUtils = require("./pathutils"); var MinifyJS = require("./minifyJS"); var Template = require("./template"); var DepFind = require("./dependencies-finder"); var Chrono = require("./chrono"); var Source = require("./source"); var Fatal = require("./fatal"); var Tree = require("./htmltree"); var Libs = require("./compiler-com-libs"); var less = require("less"); var Tpl = require("./template"); var Project, Components, Scopes; exports.initialize = function(prj) { Project = prj; Components = {}; Scopes = [{}]; CompilerCOM.loadComponents(prj); }; exports.terminate = function() { }; /** * @param {string} file Full path of the HTML file to compile. */ exports.compile = function(file, options) { if (typeof options === 'undefined') options = {}; var sourceHTML = new Source(Project, file), // Output of the main file. output, // Output of a page file. outputPage, // In case of multi-pages, the first page has the same name as the // `file`. So it's output must become the output of the `file`. outputOfFirstPage, // Page filename relative to the sourceHTML. pageFilename, // Array of the sources we must link. sourcesToLink = []; // Check if the file and all its components are uptodate. if (!isUptodate(sourceHTML)) { Scopes[0].$filename = sourceHTML.name(); console.log("Compile HTML: " + sourceHTML.name().cyan); var root = ParserHTML.parse(sourceHTML.read()); output = compileRoot(root, sourceHTML, options); if (output) { sourceHTML.tag('output', output); while (typeof root.type === 'undefined' && root.children && root.children.length == 1) { root = root.children[0]; } if (root.type == Tree.PAGES) { outputPage = JSON.parse(JSON.stringify(output)); root.children.forEach(function (child, idx) { console.log((" Page " + (idx + 1)).cyan); var src = sourceHTML; pageFilename = src.name(); if (idx != 0) { pageFilename = file.substr(0, file.length - 4) + idx + '.html'; src = new Source(Project, pageFilename); } outputPage = compileRoot(child, src, options, JSON.parse(JSON.stringify(output))); outputPage.filename = pageFilename; src.tag("output", outputPage); src.save(); sourcesToLink.push(pageFilename); if (idx == 0) { // This is the first page. outputOfFirstPage = outputPage; } }); sourceHTML.tag('pages', sourcesToLink); sourceHTML.tag('output', outputOfFirstPage); } sourceHTML.save(); } } // Linking. console.log("Link: " + sourceHTML.name().yellow); sourcesToLink = sourceHTML.tag('pages'); if (sourcesToLink) { // Multi-pages. sourcesToLink.forEach(function (pageFilename) { var src = new Source(Project, pageFilename); link(src, options); }); } else { // Single page. link(sourceHTML, options); } output = sourceHTML.tag('output'); return sourceHTML; }; function compileRoot(root, sourceHTML, options, output) { // Stuff to create HTML file. if (typeof output === 'undefined') output = { // Code CSS. innerCSS: {}, // CSS files. outerCSS: {}, // Code Javascript to embed in `js/@index.js` file. innerJS: {}, // Javascript modules directly required. require: {}, // All the Javascript modules needed to build this page. modules: [], // Javascript code to insert in a `DOMContentLoaded` event. initJS: {}, // Javascript code to insert in a `DOMContentLoaded` event after all the code of `initJS`. postInitJS: {}, // Files needed to build this file. If a include change, we must recompile. include: {}, // A resource is a file to create in the output folder when this HTML is linked. // the key is the resource name, and the value is an objet depending on the type of resource: // * {dst: "img/plus.png", src: "../gfx/icon-plus.png"} // * {dst: "img/face.svg", txt: "<svg xmlns:svg=..."} resource: {}, // Modules that have no real file in `mod` directory, but // which are created dynamically in Components. dynamicModules: {} }; var libs = Libs(sourceHTML, Components, Scopes, output); libs.compile(root, options); Tree.trim(root); output.root = root; return output; } /** * A source needs to be rebuild if it is not uptodate. * Here are the reasons for a source not to be uptodate: * * Source code more recent than the tags (`Source.isUptodate()`). * * Includes source codes more recent than this source. */ function isUptodate(sourceHTML) { if (!sourceHTML.isUptodate()) return false; var output = sourceHTML.tag('output'); if (!output || !output.include) return true; // File name relative to `sourceHTML`. var includeFilename; // Source object of the `sourceFilename`. var includeSource; // Modification time for the current HTML file. var currentFileModificationTime = sourceHTML.modificationTime(); // Stats of an include file. var stats; for (includeFilename in output.include) { includeSource = new Source( Project, sourceHTML.getPathRelativeToSource(includeFilename) ); stats = FS.statSync(includeSource.getAbsoluteFilePath()); if (stats.mtime > currentFileModificationTime) { // An included file if more recent. We must recompile. return false; } } return true; } /** * Take a HTML file `filename.html` and combine all the styles in * `css/@filename.css` and all the javascripts in `js/@filename.js`. * For each module, check if there is a folder with the same name. If * yes, copy that resource in `css` dir. For example, it you have * `mod/foobar.js` and a folder `mod/foobar/`, copy it to * `www/css/foobar/`. */ function link(src, options) { var pathWWW = Project.wwwPath(); var pathJS = Path.join(pathWWW, "js"); var pathCSS = Path.join(pathWWW, "css"); Project.mkdir(pathJS); Project.mkdir(pathCSS); var output; output = linkForRelease(src, pathJS, pathCSS, options); PathUtils.file( Project.wwwPath(src.name()), '<!DOCTYPE html>' + Tree.toString(output.root).trim() ); // Writing resources if any. writeResources(output); } function linkForRelease(src, pathJS, pathCSS, options) { Project.mkdir(Path.join(pathJS, "map")); Project.mkdir(Path.join(pathCSS, "map")); var key, val; var prj = src.prj(); var nameWithoutExt = src.name().substr(0, src.name().length - 5); // If `nameWithoutExt` is in a subfolder, `backToRoot` must containt // as many `../` as there are subfolders in `nameWithoutExt`. var backToRoot = getBackToRoot(nameWithoutExt); var output = src.tag("output") || {}; var root = output.root; if (!root) { Fatal.fire( "The cache seems to be corrupted. Try `tfw clean` to clean it up. " + "And try building again.", "Please cleanup the cache!" ); } output.filename = src.name(); var head = findHead(root); var innerJS = Tpl.file("require.js").out + concatDicValues(output.innerJS); innerJS += getInitJS(output); var innerCSS = concatDicValues(output.innerCSS); // If there is a CSS file with the same name as the HTML file, embed it. if (FS.existsSync(Project.srcOrLibPath(nameWithoutExt + '.css'))) { console.log("Found: " + (nameWithoutExt + ".css").bold); innerCSS += PathUtils.file(Project.srcOrLibPath(nameWithoutExt + '.css')); } function addInnerJS() { // Adding innerJS. prj.flushContent( "js/" + addFilePrefix(nameWithoutExt) + ".js", innerJS ); head.children.push( {type: Tree.TAG, name: 'script', attribs: { src: backToRoot + "js/" + addFilePrefix(nameWithoutExt) + ".js" }} ); } var combination = combineRequires(output, options); var externalDeps = lookForExternalDependencies( combination.js ); externalDeps.js.forEach(function ( code ) { innerJS += code; }); for( key in externalDeps.res ) { val = externalDeps.res[key]; try { Project.copyFile( key, val ); } catch( ex ) { throw Error( "Unable to copy external dependency `" + key + "` into `" + val + "`!\n" + JSON.stringify( externalDeps, null, ' ' ) ); } } // Used to loop over CSS and JS files. if ( options.dev ) { addInnerJS(); // DEBUG. Do not combine. for (key in combination.css) { val = combination.css[key]; if (key.substr(0, 4) == 'mod/') { key = key.substr(4); } head.children.push( {type: Tree.TAG, name: 'link', void: true, attribs: { rel: "stylesheet", type: "text/css", href: backToRoot + "css/" + key + ".css" }} ); prj.flushContent( "css/" + key + ".css", val.src ); } for (key in combination.js) { val = combination.js[key]; if (key.substr(0, 4) == 'mod/') { key = key.substr(4); } head.children.push( {type: Tree.TAG, name: 'script', attribs: { src: backToRoot + "js/" + key + ".js" }} ); prj.flushContent( "js/" + key + ".js", val.src ); } } else { // RELEASE. for (key in combination.css) { val = combination.css[key]; innerCSS += val.zip; } for (key in combination.js) { val = combination.js[key]; innerJS += val.zip + "\n"; } addInnerJS(); } // Adding innerCSS. prj.flushContent( "css/" + addFilePrefix(nameWithoutExt) + ".css", innerCSS ); head.children.push( {type: Tree.TAG, name: 'link', void: true, attribs: { rel: "stylesheet", type: "text/css", href: backToRoot + "css/" + addFilePrefix(nameWithoutExt) + ".css" }} ); return output; } function lookForExternalDependencies( jsFiles ) { var jsFileName, jsFile; var depFileName, depFile; var dependencies; // Module private variables which string's value is read from a text file. var variables = {}; var dep, srcDep, dstDep; var javascriptSources = []; var resources = {}; // JSON string content of a dependency file. var json; for( jsFileName in jsFiles ) { jsFile = jsFiles[jsFileName]; depFileName = jsFileName + ".dep"; if( Project.srcOrLibPath( depFileName ) ) { depFile = new Source( Project, depFileName ); try { json = depFile.read(); dependencies = JSON.parse( json ); // Looking for Javascript dependencies. if( dependencies.js ) { if( !Array.isArray( dependencies.js ) ) { dependencies.js = ["" + dependencies.js]; } dependencies.js.forEach(function ( js ) { var filename = "mod/" + js; var src = new Source( Project, filename ); var code = src.read(); pushUnique( javascriptSources, code ); }); } // Looking for other dependencies. if( dependencies.res ) { for( dep in dependencies.res ) { if( dependencies.res[dep] === "" ) { // `res: { "bob/foo.png": "" }` is equivalent to // `res: { "bob/foo.png": "bob/foo.png" }` dependencies.res[dep] = dep; } srcDep = Project.srcOrLibPath( 'mod/' + dep ); if( !srcDep ) { srcDep = Project.srcOrLibPath( dep ); } if( !srcDep ) { Fatal.fire( "Unable to find dependency file `" + dep + "` nor `mod/" + dep + "`!", depFile.getAbsoluteFilePath() ); } dstDep = Project.wwwPath( dependencies.res[dep] ); resources[srcDep] = dstDep; } } } catch( ex ) { Fatal.fire( "Unable to parse JSON!\n" + ex, depFile.getAbsoluteFilePath() ); } } } return { js: javascriptSources, res: resources }; } /** * Push `item` into `arr` if it is not already in. */ function pushUnique( arr, item ) { if( arr.indexOf( item ) > -1 ) return false; arr.push( item ); return true; } function writeResources(output) { // Name of the resource. var resourceName; // Data of the resource. var resourceData; // Destination path (in `www`folder). var dstPath; // Source path (in `src` folder). var srcPath; // Resource content. var content; for (resourceName in output.resource) { resourceData = output.resource[resourceName]; dstPath = Project.wwwPath(resourceData.dst); if (PathUtils.isDirectory(dstPath)) { // We must copy a whole directory. } else { // Create folders if needed. Project.mkdir(Path.dirname(dstPath)); if (resourceData.src) { srcPath = Project.srcOrLibPath(resourceData.src); Project.copyFile(srcPath, dstPath); } else { content = resourceData.txt; PathUtils.file(dstPath, content); } } } // Copy modules' resources if any. var moduleName; // Path of the folder containing the resourses of the module (if any). var resourcePath; output.modules.forEach(function (moduleName) { resourcePath = Project.srcOrLibPath(moduleName); if (resourcePath) { // Ok, this folder exists. console.info("Copy resource: " + (moduleName + "/").cyan); var dst = Path.join(Path.dirname(output.filename), moduleName.substr(4)); dst = dst.replace(/\\/g, '/'); Project.copyFile(resourcePath, Project.wwwPath('css/' + dst)); } }); } function concatDicValues(map) { if (!map) return ''; var key, out = ''; for (key in map) { if (out != '') out += "\n"; out += key; } return out; } function findHead(root) { if (!root) return null; var head = Tree.getElementByName(root, "head"); if (!head) { // There is no <head> tag. Create it! var html = Tree.getElementByName(root, "html"); if (!html) { html = {type: Tree.TAG, name: "html", children: []}; root.children.push(html); } head = {type: Tree.TAG, name: "head", children: []}; html.children.push(head); } return head; } function getInitJS(output) { var js = ''; var dynamicModule, code; for( dynamicModule in output.dynamicModules ) { code = output.dynamicModules[dynamicModule]; js += code; } js += concatDicValues(output.initJS) + "\n" + concatDicValues(output.postInitJS); if (js.length > 0) { return Tpl.file("init.js", {INIT_JS: js}).out; } return js; } function writeInnerCSS(innerCSS, pathCSS, nameWithoutExt, head, sourcemap) { if (innerCSS.length > 0) { // Add inner CSS file. writeCSS('@' + nameWithoutExt + ".css", innerCSS); head.children.push( {type: Tree.TAG, name: 'link', void: true, attribs: { rel: "stylesheet", type: "text/css", href: "css/@" + nameWithoutExt + ".css" }} ); } } function writeInnerJS(innerJS, pathJS, nameWithoutExt, head, sourcemap) { if (innerJS.length > 0) { // Add inner JS file. writeJS('@' + nameWithoutExt + ".js", innerJS); head.children.push( {type: Tree.TAG, name: 'script', attribs: { src: "js/@" + nameWithoutExt + ".js" }} ); } } /** * @param {object} output - Results of the HTML's compilation. * * @return {object} two attributes: * * __js__: map of Javascript sources. * * __css__: map of stylesheet sources. */ function combineRequires(output, options) { // The `cache` is used to prevent dependencies cycling. When a // module has been processed, we add its name in the `cache`. Next // time we find a module already in `cache` we will not process it. var cache = {}, // dictionary of directly needed modules. The key is the module's name, the value is always `1`. modules = output.require || {}, // List of modules' names we have to process. fringe = [], // Name of the current module. moduleName, // Style Sheet combined content. css = '', // Source file of the JS or CSS for the current module. src, // Dependencies of the current module's javascript. dependencies, // Map of Javascript sources. No compression. jsFiles = {}, // Map of Stylesheet sources. No compression. cssFiles = {}, // Iterator used for comments visual improvements. i; if (!Array.isArray(output.modules)) output.modules = []; // Fill the cache with all dynamic modules. for( moduleName in output.dynamicModules ) { cache[moduleName] = 1; } // Always include the module `$` which was generated automatically. modules['mod/$'] = 1; // Fill the fringe with `modules`. for (moduleName in modules) { fringe.push(moduleName); } // Process all required modules by popping the next module's name from the `fringe`. while (fringe.length > 0) { moduleName = fringe.pop(); // Pop the current module from the `fringe`. cache[moduleName] = 1; // Don't process this module more than once. if (moduleName.substr(0, 4) == 'cls/') { // We have to include `tfw3.js` for backward compatibility. output.innerJS[Template.file('tfw3.js').out] = 1; } else if (moduleName.substr(0, 4) == 'mod/') { // Remember all the modules used in this HTML page. if (output.modules.indexOf(moduleName) < 0) { output.modules.push(moduleName); } } //============ // Javascript //------------ // Compile (if not uptodate) the JS of the current module and // return the source file. src = compileJS(moduleName + ".js", options, output); if (!jsFiles[moduleName]) { jsFiles[moduleName] = { src: src.tag('src'), zip: src.tag('zip') }; } // Adding dependencies to the `fringe`. dependencies = src.tag("dependencies"); if (Array.isArray(dependencies)) { dependencies.forEach(function (dep) { if (!cache[dep]) { fringe.push(dep); } }); } //============== // Style Sheets //-------------- src = compileCSS(moduleName + ".css", options); if (src) { if (!cssFiles[moduleName]) { cssFiles[moduleName] = { src: src.tag('src'), zip: src.tag('zip') }; } } } return { js: jsFiles, css: cssFiles }; } function writeJS(name, sourceZip, sourceMap) { if (name.substr(-3) == '.js') { name = name.substr(0, name.length - 3); } var path = Path.join(Project.wwwPath("js"), name + ".js"); FS.writeFileSync(path, sourceZip); if (sourceMap) { path = Path.join(Project.wwwPath("js"), name + ".js.map"); FS.writeFileSync(path, sourceMap); } // Look for resources. var src = Project.srcOrLibPath(name); if (FS.existsSync(src)) { var dst = Path.join(Project.wwwPath("css"), name); Project.copyFile(src, dst); } } function writeCSS(name, content, sourceMap) { if (name.substr(-4) == '.css') { name = name.substr(0, name.length - 4); } var path = Path.join(Project.wwwPath("css"), name + ".css"); FS.writeFileSync(path, content); if (sourceMap) { path = Path.join(Project.wwwPath("css"), name + ".css.map"); FS.writeFileSync(path, sourceMap); } } function moduleExists(requiredModule) { var path = Project.srcOrLibPath(requiredModule + ".js"); if (path) return true; return false; } /** * @param {string} path Source path relative to the `src` folder. * @return {Source} * Tags: * * __src__: debug content. * * __zip__: release content. * * __dependencies__: array of dependent modules. */ function compileJS(path, options, output) { var src = new Source(Project, path), // Tout le code qu'on ajoute en début de module. // Par exemple les variables privées issues de fichiers. head = ' ', // Tout le code qu'on ajoute en fin de module. foot = ' ', // Fichier de dépendances. Pour `mod/module.js`, il s'agit de `mod/module.dep`. depFile, depFilename, // JSON parsing of the dependency file. depJSON, // Nom d'une la variable globale. varName, // Nom du fichier texte définissant le contenu d'une variable globale. varFilename, // Fichier texte définissant le contenu d'une variable globale. srcVar, // Permet de gérer les virgules qui séparent des items. firstItem, code, moduleName = src.name(), moduleShortName, iniName, iniPath, compilation, mode, requiredModule, dependencies, minification, minifiedCode, sourceMap; if (!src.getAbsoluteFilePath()) { // This file does not exist! Fatal.fire( 'Javascript file not found: "' + Project.srcPath(path) + '"!', path ); } if (!src.isUptodate()) { var watch = []; moduleShortName = moduleName.substr(4); moduleShortName = moduleShortName.substr(0, moduleShortName.length - 3); console.log("Compile JS module: " + moduleShortName.cyan + " " + src.getAbsoluteFilePath().substr( 0, src.getAbsoluteFilePath().length - moduleShortName.length - 3).grey); // Dependencies. depFilename = 'mod/' + moduleShortName + ".dep"; if( Project.srcOrLibPath( depFilename ) ) { depFile = new Source( Project, depFilename ); try { depJSON = JSON.parse( depFile.read() ); head = ''; if( typeof depJSON.var !== 'undefined' ) { head = 'var GLOBAL = {'; firstItem = true; for ( varName in depJSON.var ) { varFilename = depJSON.var[varName]; srcVar = Project.srcOrLibPath( 'mod/' + varFilename ); if( !srcVar ) { srcVar = Project.srcOrLibPath( varFilename ); } if( !srcVar ) { Fatal.fire( "Unable to find varFilenameendency file `" + varFilename + "` nor `mod/" + varFilename + "`!", depFile.getAbsoluteFilePath() ); } pushUnique( watch, "mod/" + varFilename ); if (firstItem) { firstItem = false; } else { head += ','; } srcVar = new Source( Project, srcVar ); head += '\n ' + JSON.stringify( varName ) + ": " + JSON.stringify( srcVar.read() ); } head += "};\n"; } } catch ( ex ) { Fatal.fire( "Unable to parse JSON dependency file!\n" + ex, depFile.getAbsoluteFilePath() ); } // List of files to watch. If one of those files is newer // that the JS file, we have to recompile. src.tag('watch', watch); } // Intl. if (path == 'mod/$.js') { // Internationalization for all modules except the special one: '$'. src.tag("intl", ""); } else { iniName = src.name().substr(0, src.name().length - 2) + "ini"; iniPath = Project.srcOrLibPath(iniName); if (iniPath) { src.tag("intl", CompilerINI.parse(iniPath)); } else { src.tag("intl", "var _=function(){return ''};"); } } var isModule = (moduleName.substr(0, 4) == 'mod/'); if( isModule ) { code = Tpl.file( "module.js", { name: moduleShortName, code: src.read(), intl: src.tag('intl'), head: head + " ", foot: foot + " " } ).out; } else { code = src.read(); } // Export internationalization. if (path != 'mod/$.js') { code += "module.exports._ = _;\n"; } minification = MinifyJS({ name: moduleShortName + ".js", content: code + (isModule ? "});" : "") }); var info = DepFind(code); if (info.requires.length > 0) { console.log(moduleShortName.cyan.bold + " depends on: " + info.requires.join(', ').bold); } dependencies = info.requires.map(function(itm) { return "mod/" + itm; }); if( dependencies.length > 0 ) { code += "/**\n" + " * @module " + moduleShortName + "\n"; dependencies.forEach(function (itm) { if( itm.substr(0, 4) == 'mod/' ) { code += " * @see module:" + itm.substr(4) + "\n"; } }); code += "\n */\n"; } src.tag('src', code + (isModule ? "});" : "")); src.tag('zip', minification.zip); src.tag('map', minification.map); src.tag('dependencies', dependencies); src.save(); } return src; } function minifyCSS(name, code, options) { var result = null; if (!code) return null; if (code.trim().length == 0) { // Empty CSS content. console.log(" Warning! ".yellowBG.black + name.bold + " is EMPTY!"); return null; // {src: "", zip: ""}; } less.render(code, {sourceMap: {}, compress: options.dev ? false : true}, function (e, output) { try { var map = JSON.parse(output.map); map.sourcesContent = [code]; map.sources = [name]; result = { src: code, zip: output.css, map: map }; } catch (ex) { throw Error("Unable to minify CSS \"" + name + "\":\n" + ex + "\n\nCSS content was:\n" + code.substr(0, 256) + (code.length > 256 ? '\n[...]' : '')); } }); return result; } /** * @param {string} path Source path relative to the `src` folder. */ function compileCSS(path, options) { var absPath = Project.srcOrLibPath(path); if (!absPath) return null; var src = new Source(Project, path); if (!src.exists()) return null; if (!src.isUptodate()) { console.log("Compile CSS: " + path.cyan); var cssCode = src.read(); var minify = minifyCSS(src.name(), cssCode, options); src.tag('src', cssCode); src.tag('zip', minify.zip); src.tag('map', minify.map); src.save(); } return src; } /** * Add a prefix to a filename. This is not as simple as prepending the * `prefix` to the string `path`, because `path` can contain folders' * separators. The prefix must be prepended to the real file name and * not to the whole path. * Examples with `prefix` == "@": * * `foobar.html`: `@foobar.html` * * `myfolder/myfile.js`: `myfolder/@myfile.js` */ function addFilePrefix(path, prefix) { if (typeof prefix === 'undefined') prefix = '@'; var separatorPosition = path.lastIndexOf('/'); if (separatorPosition < 0) { // Let's try with Windows separators. separatorPosition = path.lastIndexOf('\\'); } var filenameStart = separatorPosition > -1 ? separatorPosition + 1 : 0; var result = path.substr(0, filenameStart) + prefix + path.substr(filenameStart); return result.replace(/\\/g, '/'); } /** * 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. */ function getBackToRoot(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; }