From 855cf1e8a7572f104e2bc0558a182c184c16d189 Mon Sep 17 00:00:00 2001 From: Joe Walker Date: Wed, 7 Mar 2012 16:58:17 +0000 Subject: [PATCH] Bug 733794 (buildslice): Cut up build file. (Non-FF) This code doesn't go into Firefox. --- lib/gcli/ui/view.js | 21 ++ lib/server/build.js | 326 +++++++++++++++++++++++ lib/server/commands.js | 74 ++++++ lib/server/help_list.txt | 15 ++ lib/server/help_man.txt | 22 ++ lib/server/index.js | 123 +++++++++ node-main.js | 560 +++------------------------------------ 7 files changed, 619 insertions(+), 522 deletions(-) create mode 100644 lib/server/build.js create mode 100644 lib/server/commands.js create mode 100644 lib/server/help_list.txt create mode 100644 lib/server/help_man.txt create mode 100644 lib/server/index.js diff --git a/lib/gcli/ui/view.js b/lib/gcli/ui/view.js index d77a7905..9a4fc085 100644 --- a/lib/gcli/ui/view.js +++ b/lib/gcli/ui/view.js @@ -106,5 +106,26 @@ exports.populateWithOutputData = function(outputData, element) { element.appendChild(node); }; +/** + * Convert an outputData object to a string so GCLI can be used in traditional + * character based terminals. + */ +exports.toString = function(outputData, document) { + var output = outputData.output; + if (output == null) { + return ''; + } + + if (typeof HTMLElement !== 'undefined' && output instanceof HTMLElement) { + return output.textContent; + } + + if (output.isView) { + return output.toDom(document).textContent; + } + + return output.toString(); +}; + }); diff --git a/lib/server/build.js b/lib/server/build.js new file mode 100644 index 00000000..b0a34937 --- /dev/null +++ b/lib/server/build.js @@ -0,0 +1,326 @@ +/* + * Copyright 2009-2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE.txt or: + * http://opensource.org/licenses/BSD-3-Clause + */ + +var copy = require('dryice').copy; +var path = require('path'); +var fs = require('fs'); +var main = require('../../node-main'); + +/** + * There are 2 important ways to build GCLI. + * The first is for use within a normal web page. + * It has compressed and uncompressed versions of the output script file. + */ +exports.buildStandard = function() { + var log = 'Building standard outputs to built/gcli[-uncompressed].js\n'; + + copy.mkdirSync(main.gcliHome + '/built', 0755); + var project = copy.createCommonJsProject({ + roots: [ main.gcliHome + '/lib' ] + }); + var sources = copy.createDataObject(); + + copy({ + source: { + project: project, + // This list of dependencies should be the same as in index.html + require: [ 'gcli/index', 'demo/index', 'gclitest/index' ] + }, + filter: copy.filter.moduleDefines, + dest: sources + }); + copy({ + source: { root: project, include: /.*\.png$|.*\.gif$/ }, + filter: copy.filter.base64, + dest: sources + }); + log += project.report() + '\n'; + + // Create a GraphML dependency report. Directions: + // - Install yEd (http://www.yworks.com/en/products_yed_about.htm) + // - Load gcli/built/gcli.graphml + // - Resize the nodes (Tools->Fit Node to Label) + // - Apply a layout (Layout->Hierarchical) + log += 'Outputting dependency graph to built/gcli.graphml\n'; + if (project.getDependencyGraphML) { + copy({ + source: { value:project.getDependencyGraphML() }, + dest: 'built/gcli.graphml' + }); + } + + // Create the output scripts, compressed and uncompressed + copy({ source: 'index.html', filter: tweakIndex, dest: 'built/index.html' }); + copy({ source: 'scripts/es5-shim.js', dest: 'built/es5-shim.js' }); + copy({ + source: [ copy.getMiniRequire(), sources ], + dest: 'built/gcli-uncompressed.js' + }); + try { + copy({ + source: [ copy.getMiniRequire(), sources ], + filter: copy.filter.uglifyjs, + dest: 'built/gcli.js' + }); + } + catch (ex) { + log += 'ERROR: Uglify compression fails on windows/linux. ' + + 'Skipping creation of built/gcli.js\n'; + } + return log; +}; + +/** + * Build the Javascript JSM files for Firefox + * It consists of 1 output file: gcli.jsm + */ +exports.buildFirefox = function(destDir) { + if (!destDir && process.env.FIREFOX_HOME) { + destDir = process.env.FIREFOX_HOME; + } + log = 'Building Firefox outputs to ' + (destDir || 'built/ff') + '.\n\n'; + + if (!destDir) { + copy.mkdirSync(main.gcliHome + '/built/ff', 0755); + } + + var jsmDir = '/browser/devtools/webconsole'; + var winCssDir = '/browser/themes/winstripe/devtools'; + var pinCssDir = '/browser/themes/pinstripe/devtools'; + var gnomeCssDir = '/browser/themes/gnomestripe/devtools'; + var propsDir = '/browser/locales/en-US/chrome/browser/devtools'; + var testDir = '/browser/devtools/webconsole/test'; + + if (destDir) { + var fail = false; + if (!path.existsSync(destDir + jsmDir)) { + log += 'Missing path for JSM: ' + destDir + jsmDir + '\n'; + fail = true; + } + if (!path.existsSync(destDir + winCssDir)) { + log += 'Missing path for Windows CSS: ' + destDir + winCssDir + '\n'; + fail = true; + } + if (!path.existsSync(destDir + pinCssDir)) { + log += 'Missing path for Mac CSS: ' + destDir + pinCssDir + '\n'; + fail = true; + } + if (!path.existsSync(destDir + gnomeCssDir)) { + log += 'Missing path for Gnome CSS: ' + destDir + gnomeCssDir + '\n'; + fail = true; + } + if (!path.existsSync(destDir + propsDir)) { + log += 'Missing path for l10n string: ' + destDir + propsDir + '\n'; + fail = true; + } + if (fail) { + process.exit(1); + } + } + + var project = copy.createCommonJsProject({ + roots: [ main.gcliHome + '/mozilla', main.gcliHome + '/lib' ] + }); + + // Package the JavaScript + copy({ + source: [ + 'mozilla/build/prefix-gcli.jsm', + 'mozilla/build/console.js', + copy.getMiniRequire(), + // This list of dependencies should be the same as in suffix-gcli.jsm + { project: project, require: [ 'gcli/index' ] }, + 'mozilla/build/suffix-gcli.jsm' + ], + filter: [ createIgnoreFilter(), copy.filter.moduleDefines ], + dest: (destDir ? destDir + jsmDir : 'built/ff') + '/gcli.jsm' + }); + + // Package the gclichrome.jsm + project.assumeAllFilesLoaded(); + copy({ + source: [ + 'mozilla/build/prefix-gclichrome.jsm', + 'mozilla/build/console.js', + copy.source.commonjs({ + project: project, + // This list should be the same as suffix-gclichrome.jsm + require: [ 'gcli/gclichrome' ] + }), + 'mozilla/build/suffix-gclichrome.jsm' + ], + filter: copy.filter.moduleDefines, + dest: (destDir ? destDir + jsmDir : 'built/ff') + '/gclichrome.jsm' + }); + + // Package the test files + project.assumeAllFilesLoaded(); + var sources = copy.createDataObject(); + copy({ + // This list of dependencies should be the same as in suffix-test.js + source: { project: project, require: [ 'gclitest/index' ] }, + filter: copy.filter.moduleDefines, + dest: sources + }); + // This has to be done in 2 steps because createUndefineFunction uses + // project.currentFiles which is populated by using the project as a source + copy({ + source: [ + 'mozilla/build/prefix-test.js', + sources, + createUndefineFunction(project), + 'mozilla/build/suffix-test.js' + ], + dest: (destDir ? destDir + testDir : 'built/ff') + '/browser_gcli_web.js' + }); + + // Package the i18n strings + copy({ + source: [ 'lib/gcli/nls/strings.js', 'mozilla/gcli/commands/strings.js' ], + filter: tweakI18nStrings, + dest: (destDir ? destDir + propsDir : 'built/ff') + '/gcli.properties' + }); + + return log; +}; + +/** + * Sometimes we want to exclude CSS modules from the output. + * This replaces the contents of any file named '*.css' with an empty string. + */ +function createIgnoreFilter() { + var filter = function(data, location) { + return location != null && /\.css$/.test(location.path) ? '' : data; + }; + filter.onRead = true; + return filter; +} + +/** + * Filter index.html to: + * - Make links relative, we flatten out the scripts directory + * - Replace require.js with the built GCLI script file + * - Remove the RequireJS configuration + */ +function tweakIndex(data) { + return data + .replace(/scripts\/es5-shim.js/, 'es5-shim.js') + .replace(/scripts\/require.js/, 'gcli-uncompressed.js') + .replace(/\s*require\([^;]*;\n/, ''); +} + +/** + * Regular expression that removes the header/footer from a nls strings file. + * If/when we revert to RequireJS formatted strings files, we'll need to update + * this. + * See lib/gcli/nls/strings.js for an example + */ +var outline = /root: {([^}]*)}/g; + +/** + * Regex to match a set of single line comments followed by a name:value + * We run this to fund the list of strings once we've used 'outline' to get the + * main body. + * See lib/gcli/nls/strings.js for an example + */ +var singleString = /((\s*\/\/.*\n)+)\s*([A-z.]+):\s*'(.*)',?\n/g; + +/** + * Filter to turn GCLIs l18n script file into a Firefox l10n strings file + */ +function tweakI18nStrings(data) { + // Rip off the CommonJS header/footer + var output = ''; + data.replace(outline, function(m, inner) { + // Remove the trailing spaces + output += inner.replace(/ *$/, ''); + }); + + if (output === '') { + throw new Error('Mismatch in lib/gcli/nls/strings.js'); + } + + // Convert each of the string definitions + output = output.replace(singleString, function(m, note, x, name, value) { + note = note.replace(/\n? *\/\/ */g, ' ') + .replace(/^ /, '') + .replace(/\n$/, ''); + note = 'LOCALIZATION NOTE (' + name + '): ' + note; + var lines = '# ' + wordWrap(note, 77).join('\n# ') + '\n'; + // Unescape JavaScript strings so they're property values + value = value.replace(/\\\\/g, '\\') + .replace(/\\'/g, '\''); + return lines + name + '=' + value + '\n\n'; + }); + + return '' + + '# LOCALIZATION NOTE These strings are used inside the Web Console\n' + + '# command line which is available from the Web Developer sub-menu\n' + + '# -> \'Web Console\'.\n' + + '# The correct localization of this file might be to keep it in\n' + + '# English, or another language commonly spoken among web developers.\n' + + '# You want to make that choice consistent across the developer tools.\n' + + '# A good criteria is the language in which you\'d find the best\n' + + '# documentation on web development on the web.\n' + + '\n' + output; +} + +/** + * Return an input string split into lines of a given length + */ +function wordWrap(input, length) { + // LOOK! Over there! Is it an airplane? + var wrapper = new RegExp('.{0,' + (length - 1) + '}([ $|\\s$]|$)', 'g'); + return input.match(wrapper).slice(0, -1).map(function(s) { + return s.replace(/ $/, ''); + }); +} + +/** + * A function to create an undefine function that undoes the effect of defining + * all the modules in the given project. This is useful in firefox tests that + * need to undo all the changes they make to avoid memleak detection tests + * @param project The commonjs the we're aggregating together. + */ +function createUndefineFunction(project) { + // This is slightly evil because it digs into the guts of a project + var modules = Object.keys(project.currentModules); + var undefine = '\nfunction undefine() {\n'; + modules.forEach(function(module) { + undefine += ' delete define.modules[\'' + module + '\'];\n'; + }); + undefine += '\n'; + modules.forEach(function(module) { + undefine += ' delete define.globalDomain.modules[\'' + module + '\'];\n'; + }); + undefine += '}\n'; + return { value: undefine }; +} + +/** + * + */ +exports.buildMain = function() { + var firefoxHome = main.gcliHome + '/../devtools'; + [ + firefoxHome + '/obj/browser/themes', + firefoxHome + '/obj/browser/devtools', + firefoxHome + '/obj/browser/app', + firefoxHome + '/obj/browser/base' + ].forEach(function(dir) { + var cmd = 'python'; + var options = { cwd: firefoxHome }; + var args = [ '-OO', 'build/pymake/make.py', '-C', dir ]; + childProcess.execFile(cmd, args, options, function(error, stdout, stderr) { + var status = error ? 500 : 200; + response.writeHead(status, { + 'Content-Length': stdout.toString().length, + 'Content-Type': 'text/plain' + }); + response.end(stdout); + }); + }); +}; diff --git a/lib/server/commands.js b/lib/server/commands.js new file mode 100644 index 00000000..0a94b1ec --- /dev/null +++ b/lib/server/commands.js @@ -0,0 +1,74 @@ +/* + * Copyright 2009-2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE.txt or: + * http://opensource.org/licenses/BSD-3-Clause + */ + +var main = require('../../node-main'); +var build = require('./build'); + +var fs = require('fs'); +var jsdom = require('jsdom').jsdom; + +var canon = main.requirejs('gcli/canon'); +var l10n = main.requirejs('gcli/l10n'); + +/** + * Registration and de-registration. + */ +exports.startup = function() { + canon.addCommand({ + name: 'standard', + description: 'Build the basic GCLI web target', + exec: function(args, context) { + return build.buildStandard(); + } + }); + + canon.addCommand({ + name: 'firefox', + description: 'Build the firefox-GCLI target', + params: [ + { + name: 'location', + type: 'string', + defaultValue: '../devtools', + description: 'The location of the mozilla-central checkout' + } + ], + exec: function(args, context) { + return build.buildFirefox(args.location); + } + }); + + canon.addCommand({ + name: 'incrbuild', + description: 'Incremental firefox build/run', + exec: function(args, context) { + return build.buildMain(); + } + }); + + canon.addCommand({ + name: 'test', + description: 'Run GCLI unit tests', + exec: function(args, context) { + jsdom.env({ + html: fs.readFileSync(main.gcliHome + '/index.html').toString(), + src: [ + fs.readFileSync(main.gcliHome + '/scripts/html5-shim.js').toString() + ], + features: { + QuerySelector: true + }, + done: main.requirejs('gclitest/nodeIndex').run + }); + } + }); + + //canon.addCommand(); +}; + +exports.shutdown = function() { + console.warn('server/commands does not unregister commands'); +}; diff --git a/lib/server/help_list.txt b/lib/server/help_list.txt new file mode 100644 index 00000000..2127ca67 --- /dev/null +++ b/lib/server/help_list.txt @@ -0,0 +1,15 @@ +
+## Welcome to GCLI
+
+GCLI is an experiment to create a highly usable JavaScript command line for developers.
+
+Useful links:
+- Source (BSD): https://github.com/joewalker/gcli
+- Documentation: https://github.com/joewalker/gcli/blob/master/docs/index.md
+
+
+# ${getHeading()}
+
+${command.name} → ${command.description}
+
+
diff --git a/lib/server/help_man.txt b/lib/server/help_man.txt new file mode 100644 index 00000000..927422f6 --- /dev/null +++ b/lib/server/help_man.txt @@ -0,0 +1,22 @@ + +
## ${command.name} + +# ${l10n.helpManSynopsis}: ${command.name} ${param.defaultValue !== undefined ? '[' + param.name + ']' : param.name} + +# ${l10n.helpManDescription}: + +${command.manual || command.description} + +# ${l10n.helpManParameters}: + +${l10n.helpManNone}* ${param.name}: ${getTypeDescription(param)} + ${param.manual || param.description} + + + +# ${l10n.subCommands}: + +${l10n.subcommandsNone} +* ${subcommand.name}: ${subcommand.description} + +
diff --git a/lib/server/index.js b/lib/server/index.js new file mode 100644 index 00000000..43c698cb --- /dev/null +++ b/lib/server/index.js @@ -0,0 +1,123 @@ +/* + * Copyright 2009-2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE.txt or: + * http://opensource.org/licenses/BSD-3-Clause + */ + +var express = require('express'); +var util = require('util'); +var childProcess = require('child_process'); +var io = require('socket.io'); +var repl = require('repl'); +var jsdom = require('jsdom').jsdom; + +var build = require('./build'); +var main = require('../../node-main'); + +var requisition = new (main.requirejs('gcli/cli').Requisition)(); +var view = new main.requirejs('gcli/ui/view'); + +/** + * Serve '.' to http://localhost:9999/ + * And support a local console + */ +exports.serve = function() { + var server = express.createServer(); + server.use(express.logger('dev')); + server.use(express.bodyParser()); + // server.use(express.cookieParser('secret')); + // server.use(express.session({secret: 'secret', key: 'express.sid'})); + server.use('/exec/', execApp); + server.use('/func/', execFunction); + server.use(express.static(main.gcliHome, { maxAge: 0 })); + + io.listen(server).sockets.on('connection', function(socket) { + socket.emit('news', { hello: 'world' }); + socket.on('my other event', function (data) { + console.log(data); + }); + }); + + console.log('Serving GCLI to http://localhost:9999/'); + server.listen(9999, 'localhost'); + + function gcliEval(command, scope, file, callback) { + // Why does node wrap the command in '(...)\n'? + command = command.replace(/^\((.*)\n\)$/, function(all, part) { + return part; + }); + + if (command.length === 0) { + callback(); + return; + } + + var reply = exports.exec(command); + console.log(reply); + + callback(); + } + + console.log('This is also a limited GCLI prompt. ' + + 'Type \'help\' for a list of commands, CTRL+C twice to exit:'); + repl.start('\u00bb', process, gcliEval, false, true); +}; + +/** + * Execute a single command + */ +exports.exec = function(command) { + var reply; + try { + var outputData = requisition.exec(command); + reply = view.toString(outputData, jsdom()); + } + catch (ex) { + reply = ex.message; + console.error(ex); + } + return reply; +}; + +/** + * Express middleware to execute an OS level command + */ +function execApp(request, response, next) { + var cmd = request.body.cmd; + var args = request.body.args; + var options = { cwd: request.body.cwd, env: request.body.env }; + childProcess.execFile(cmd, args, options, function(error, stdout, stderr) { + var status = error ? 500 : 200; + var output = error ? error.message : stdout.toString(); + response.writeHead(status, { + 'Content-Length': output.length, + 'Content-Type': 'text/plain' + }); + response.end(output); + }); +} + +/** + * Express middleware to execute a JS function + */ +function execFunction(request, response, next) { + var func = request.body.func; + var args = request.body.args; + try { + var reply = exported[func].apply(exported, args); + var data = reply == null ? '' : JSON.stringify(reply); + response.writeHead(200, { + 'Content-Length': data.length, + 'Content-Type': 'text/plain' + }); + response.end(data); + } + catch (ex) { + var reply = JSON.stringify(ex); + response.writeHead(500, { + 'Content-Length': reply.length, + 'Content-Type': 'text/plain' + }); + response.end(reply); + } +} diff --git a/node-main.js b/node-main.js index f7819cda..1b7b2de7 100644 --- a/node-main.js +++ b/node-main.js @@ -4,528 +4,44 @@ * http://opensource.org/licenses/BSD-3-Clause */ -var copy = require('dryice').copy; -var path = require('path'); -var fs = require('fs'); - -var gcliHome = __dirname; - -/** - * The main() function is called at the bottom of this file to ensure all the - * globals are setup properly. - */ -function main() { - var args = process.argv; - if (args[2] === 'standard') { - buildStandard(); - } - else if (args[2] === 'firefox') { - buildFirefox(args[3]); - } - else if (args[2] === 'test') { - test(); - } - else if (args[2] === 'serve') { - serve(); - } - else { - console.log('Targets:'); - console.log('> node node-main.js standard'); - console.log(' # Builds GCLI for the web to ./built'); - console.log('> node node-main.js firefox [directory]'); - console.log(' # Builds GCLI for firefox to ./built/mozilla or [directory]'); - console.log('> node node-main.js test'); - console.log(' # Run GCLI tests using jsdom'); - console.log('> node node-main.js serve'); - console.log(' # Serve . to http://localhost:9999 for chrome'); - process.exit(1); - } -} - -/** - * There are 2 important ways to build GCLI. - * The first is for use within a normal web page. - * It has compressed and uncompressed versions of the output script file. - */ -function buildStandard() { - console.log('Building standard outputs to built/gcli[-uncompressed].js'); - - copy.mkdirSync(gcliHome + '/built', 0755); - var project = copy.createCommonJsProject({ - roots: [ gcliHome + '/lib' ] - }); - var sources = copy.createDataObject(); - - copy({ - source: { - project: project, - // This list of dependencies should be the same as in index.html - require: [ 'gcli/index', 'demo/index', 'gclitest/index' ] - }, - filter: copy.filter.moduleDefines, - dest: sources - }); - copy({ - source: { root: project, include: /.*\.png$|.*\.gif$/ }, - filter: copy.filter.base64, - dest: sources - }); - console.log(project.report()); - - // Create a GraphML dependency report. Directions: - // - Install yEd (http://www.yworks.com/en/products_yed_about.htm) - // - Load gcli/built/gcli.graphml - // - Resize the nodes (Tools->Fit Node to Label) - // - Apply a layout (Layout->Hierarchical) - console.log('Outputting dependency graph to built/gcli.graphml\n'); - if (project.getDependencyGraphML) { - copy({ - source: { value:project.getDependencyGraphML() }, - dest: 'built/gcli.graphml' - }); - } - - // Create the output scripts, compressed and uncompressed - copy({ source: 'index.html', filter: tweakIndex, dest: 'built/index.html' }); - copy({ source: 'scripts/es5-shim.js', dest: 'built/es5-shim.js' }); - copy({ - source: [ copy.getMiniRequire(), sources ], - dest: 'built/gcli-uncompressed.js' - }); - try { - copy({ - source: [ copy.getMiniRequire(), sources ], - filter: copy.filter.uglifyjs, - dest: 'built/gcli.js' - }); - } - catch (ex) { - console.log('ERROR: Uglify compression fails on windows/linux. ' + - 'Skipping creation of built/gcli.js\n'); - } -} - -/** - * Build the Javascript JSM files for Firefox - * It consists of 1 output file: gcli.jsm - */ -function buildFirefox(destDir) { - if (!destDir && process.env.FIREFOX_HOME) { - destDir = process.env.FIREFOX_HOME; - } - console.log('Building Firefox outputs to ' + (destDir || 'built/ff') + '.\n'); - - if (!destDir) { - copy.mkdirSync(gcliHome + '/built/ff', 0755); - } - - var jsmDir = '/browser/devtools/webconsole'; - var winCssDir = '/browser/themes/winstripe/devtools'; - var pinCssDir = '/browser/themes/pinstripe/devtools'; - var gnomeCssDir = '/browser/themes/gnomestripe/devtools'; - var propsDir = '/browser/locales/en-US/chrome/browser/devtools'; - var testDir = '/browser/devtools/webconsole/test'; - - if (destDir) { - var fail = false; - if (!path.existsSync(destDir + jsmDir)) { - console.error('Missing path for JSM: ' + destDir + jsmDir); - fail = true; - } - if (!path.existsSync(destDir + winCssDir)) { - console.error('Missing path for Windows CSS: ' + destDir + winCssDir); - fail = true; - } - if (!path.existsSync(destDir + pinCssDir)) { - console.error('Missing path for Mac CSS: ' + destDir + pinCssDir); - fail = true; - } - if (!path.existsSync(destDir + gnomeCssDir)) { - console.error('Missing path for Gnome CSS: ' + destDir + gnomeCssDir); - fail = true; - } - if (!path.existsSync(destDir + propsDir)) { - console.error('Missing path for l10n string: ' + destDir + propsDir); - fail = true; - } - if (fail) { - process.exit(1); - } - } - - var project = copy.createCommonJsProject({ - roots: [ gcliHome + '/mozilla', gcliHome + '/lib' ] - }); - - // Package the JavaScript - copy({ - source: [ - 'mozilla/build/prefix-gcli.jsm', - 'mozilla/build/console.js', - copy.getMiniRequire(), - // This list of dependencies should be the same as in suffix-gcli.jsm - { project: project, require: [ 'gcli/index' ] }, - 'mozilla/build/suffix-gcli.jsm' - ], - filter: [ createIgnoreFilter(), copy.filter.moduleDefines ], - dest: (destDir ? destDir + jsmDir : 'built/ff') + '/gcli.jsm' - }); - - // Package the gclichrome.jsm - project.assumeAllFilesLoaded(); - copy({ - source: [ - 'mozilla/build/prefix-gclichrome.jsm', - 'mozilla/build/console.js', - copy.source.commonjs({ - project: project, - // This list should be the same as suffix-gclichrome.jsm - require: [ 'gcli/gclichrome' ] - }), - 'mozilla/build/suffix-gclichrome.jsm' - ], - filter: copy.filter.moduleDefines, - dest: (destDir ? destDir + jsmDir : 'built/ff') + '/gclichrome.jsm' - }); - - // Package the test files - project.assumeAllFilesLoaded(); - var sources = copy.createDataObject(); - copy({ - // This list of dependencies should be the same as in suffix-test.js - source: { project: project, require: [ 'gclitest/index' ] }, - filter: copy.filter.moduleDefines, - dest: sources - }); - // This has to be done in 2 steps because createUndefineFunction uses - // project.currentFiles which is populated by using the project as a source - copy({ - source: [ - 'mozilla/build/prefix-test.js', - sources, - createUndefineFunction(project), - 'mozilla/build/suffix-test.js' - ], - dest: (destDir ? destDir + testDir : 'built/ff') + '/browser_gcli_web.js' - }); - - // Package the i18n strings - copy({ - source: [ 'lib/gcli/nls/strings.js', 'mozilla/gcli/commands/strings.js' ], - filter: tweakI18nStrings, - dest: (destDir ? destDir + propsDir : 'built/ff') + '/gcli.properties' - }); -} - -/** - * Sometimes we want to exclude CSS modules from the output. - * This replaces the contents of any file named '*.css' with an empty string. - */ -function createIgnoreFilter() { - var filter = function(data, location) { - return location != null && /\.css$/.test(location.path) ? '' : data; - }; - filter.onRead = true; - return filter; -} - -/** - * Filter index.html to: - * - Make links relative, we flatten out the scripts directory - * - Replace require.js with the built GCLI script file - * - Remove the RequireJS configuration - */ -function tweakIndex(data) { - return data - .replace(/scripts\/es5-shim.js/, 'es5-shim.js') - .replace(/scripts\/require.js/, 'gcli-uncompressed.js') - .replace(/\s*require\([^;]*;\n/, ''); -} - -/** - * Regular expression that removes the header/footer from a nls strings file. - * If/when we revert to RequireJS formatted strings files, we'll need to update - * this. - * See lib/gcli/nls/strings.js for an example - */ -var outline = /root: {([^}]*)}/g; - -/** - * Regex to match a set of single line comments followed by a name:value - * We run this to fund the list of strings once we've used 'outline' to get the - * main body. - * See lib/gcli/nls/strings.js for an example - */ -var singleString = /((\s*\/\/.*\n)+)\s*([A-z.]+):\s*'(.*)',?\n/g; +exports.gcliHome = __dirname; -/** - * Filter to turn GCLIs l18n script file into a Firefox l10n strings file - */ -function tweakI18nStrings(data) { - // Rip off the CommonJS header/footer - var output = ''; - data.replace(outline, function(m, inner) { - // Remove the trailing spaces - output += inner.replace(/ *$/, ''); - }); - - if (output === '') { - console.error('Mismatch in lib/gcli/nls/strings.js'); - process.exit(1); - } - - // Convert each of the string definitions - output = output.replace(singleString, function(m, note, x, name, value) { - note = note.replace(/\n? *\/\/ */g, ' ') - .replace(/^ /, '') - .replace(/\n$/, ''); - note = 'LOCALIZATION NOTE (' + name + '): ' + note; - var lines = '# ' + wordWrap(note, 77).join('\n# ') + '\n'; - // Unescape JavaScript strings so they're property values - value = value.replace(/\\\\/g, '\\') - .replace(/\\'/g, '\''); - return lines + name + '=' + value + '\n\n'; - }); - - return '' + - '# LOCALIZATION NOTE These strings are used inside the Web Console\n' + - '# command line which is available from the Web Developer sub-menu\n' + - '# -> \'Web Console\'.\n' + - '# The correct localization of this file might be to keep it in\n' + - '# English, or another language commonly spoken among web developers.\n' + - '# You want to make that choice consistent across the developer tools.\n' + - '# A good criteria is the language in which you\'d find the best\n' + - '# documentation on web development on the web.\n' + - '\n' + output; -} - -/** - * Return an input string split into lines of a given length - */ -function wordWrap(input, length) { - // LOOK! Over there! Is it an airplane? - var wrapper = new RegExp('.{0,' + (length - 1) + '}([ $|\\s$]|$)', 'g'); - return input.match(wrapper).slice(0, -1).map(function(s) { - return s.replace(/ $/, ''); - }); -} - -/** - * A function to create an undefine function that undoes the effect of defining - * all the modules in the given project. This is useful in firefox tests that - * need to undo all the changes they make to avoid memleak detection tests - * @param project The commonjs the we're aggregating together. - */ -function createUndefineFunction(project) { - // This is slightly evil because it digs into the guts of a project - var modules = Object.keys(project.currentModules); - var undefine = '\nfunction undefine() {\n'; - modules.forEach(function(module) { - undefine += ' delete define.modules[\'' + module + '\'];\n'; - }); - undefine += '\n'; - modules.forEach(function(module) { - undefine += ' delete define.globalDomain.modules[\'' + module + '\'];\n'; - }); - undefine += '}\n'; - return { value: undefine }; -} - -/** - * Run the test suite inside node - */ -function test() { - // It's tempting to use RequireJS from npm, however that would break - // running GCLI in Firefox just by opening index.html - var requirejs = require('./scripts/r.js'); - - requirejs.config({ - nodeRequire: require, - paths: { 'text': 'scripts/text', 'i18n': 'scripts/i18n' }, - packagePaths: { - 'lib': [ - { name: 'gcli', main: 'index', lib: '.' }, - { name: 'test', main: 'index', lib: '.' }, - { name: 'gclitest', main: 'index', lib: '.' }, - { name: 'demo', main: 'index', lib: '.' } - ] - } - }); - - require('jsdom').jsdom.env({ - html: fs.readFileSync(gcliHome + '/index.html').toString(), - src: [ - fs.readFileSync(gcliHome + '/scripts/html5-shim.js').toString() - ], - features: { - QuerySelector: true - }, - done: requirejs('gclitest/nodeIndex').run - }); -} - -var express = require('express'); -var util = require('util'); -var childProcess = require('child_process'); -var io = require('socket.io'); - -/** - * Serve '.' to http://localhost:9999/ - */ -function serve() { - var server = express.createServer(); - server.use(express.logger('dev')); - server.use(express.bodyParser()); - // server.use(express.cookieParser('secret')); - // server.use(express.session({secret: 'secret', key: 'express.sid'})); - server.use('/exec/', execApp); - server.use('/func/', execFunction); - server.use(express.static(gcliHome, { maxAge: 0 })); - - io.listen(server).sockets.on('connection', function (socket) { - socket.emit('news', { hello: 'world' }); - socket.on('my other event', function (data) { - console.log(data); - }); - }); - - console.log('Serving GCLI to http://localhost:9999/'); - server.listen(9999, 'localhost'); -} - -/** - * Express middleware to execute an OS level command - */ -function execApp(request, response, next) { - var cmd = request.body.cmd; - var args = request.body.args; - var options = { cwd: request.body.cwd, env: request.body.env }; - childProcess.execFile(cmd, args, options, function(error, stdout, stderr) { - var status = error ? 500 : 200; - var output = error ? error.message : stdout.toString(); - response.writeHead(status, { - 'Content-Length': output.length, - 'Content-Type': 'text/plain' - }); - response.end(output); - }); -} - -/** - * Express middleware to execute a JS function - */ -function execFunction(request, response, next) { - var func = request.body.func; - var args = request.body.args; - try { - var reply = exported[func].apply(exported, args); - var data = reply == null ? '' : JSON.stringify(reply); - response.writeHead(200, { - 'Content-Length': data.length, - 'Content-Type': 'text/plain' - }); - response.end(data); - } - catch (ex) { - var reply = JSON.stringify(ex); - response.writeHead(500, { - 'Content-Length': reply.length, - 'Content-Type': 'text/plain' - }); - response.end(reply); - } -} - -/** - * A list of functions that can be called by execFunction - */ -var exported = { - build: function(type) { - if (type === 'standard') { - return redirectConsole(buildStandard); - } - else if (type === 'firefox') { - return redirectConsole(buildFirefox); - } - else if (type === 'main') { - var firefoxHome = gcliHome + '/../devtools'; - [ - firefoxHome + '/obj/browser/themes', - firefoxHome + '/obj/browser/devtools', - firefoxHome + '/obj/browser/app', - firefoxHome + '/obj/browser/base' - ].forEach(function(dir) { - var cmd = 'python'; - var options = { cwd: firefoxHome }; - var args = [ '-OO', 'build/pymake/make.py', '-C', dir ]; - childProcess.execFile(cmd, args, options, function(error, stdout, stderr) { - var status = error ? 500 : 200; - response.writeHead(status, { - 'Content-Length': stdout.toString().length, - 'Content-Type': 'text/plain' - }); - response.end(stdout); - }); - }); - } - } -}; - -/** - * A way to redirect output sent to the console - * @param action A command to execute. The return value from this function will - * be discarded. Any scope/params should be used via bind(). - * @return An array of console messages, each as an object containing the - * following properties: - * - dest: One of 'log', 'info', 'warn', 'error' - * - args: The arguments to the call, to be logged - * Any exception will be added as the final entry, containing the following: - * - dest: 'exception' - * - args.message: The exception error message - * - args.type: constructor name of the exception - */ -function redirectConsole(action) { - var messages = []; - redirectConsole.start(messages); - try { - action(); - redirectConsole.end(); - } - catch (ex) { - redirectConsole.end(); - messages.push({ - dest: 'exception', - args: { message: ex.message, type: ex.constructor.name } - }); - console.error(ex); - } +var fs = require('fs'); - return messages.map(function(message) { - return message.args.join(','); - }).join('\n'); +// It's tempting to use RequireJS from npm, however that would break +// running GCLI in Firefox just by opening index.html +exports.requirejs = require('./scripts/r.js'); +exports.requirejs.config({ + nodeRequire: require, + paths: { 'text': 'scripts/text', 'i18n': 'scripts/i18n' }, + packagePaths: { + 'lib': [ + { name: 'gcli', main: 'index', lib: '.' }, + { name: 'test', main: 'index', lib: '.' }, + { name: 'gclitest', main: 'index', lib: '.' }, + { name: 'demo', main: 'index', lib: '.' }, + { name: 'server', main: 'index', lib: '.' } + ] + } +}); + +exports.requirejs('gcli/index'); +var help = exports.requirejs('gcli/commands/help'); +help.startup(); +exports.requirejs('gcli/commands/pref').startup(); +exports.requirejs('test/commands').startup(); +require('./lib/server/commands').startup(); + +help.helpManHtml = fs.readFileSync(exports.gcliHome + '/lib/server/help_man.txt', 'utf8'); +help.helpListHtml = fs.readFileSync(exports.gcliHome + '/lib/server/help_list.txt', 'utf8'); + +var server = require('./lib/server/index'); + +if (process.argv.length < 3) { + server.serve(); +} +else { + var command = process.argv.slice(2).join(' '); + var reply = server.exec(command); + console.log(reply); } - -/** - * Mutate 'console' for redirectConsole() - */ -redirectConsole.start = function(messages) { - [ 'log', 'info', 'warn', 'error' ].forEach(function(name) { - console['_' + name] = console[name]; - console[name] = function() { - messages.push({ - dest: name, - args: Array.prototype.slice.call(arguments) - }); - }; - }); -}; - -/** - * Restore 'console' for redirectConsole() - */ -redirectConsole.end = function() { - console.log = console.oldlog; -}; - -// Now everything is defined properly, start working -main();