define(function(require, exports, module) { 'use strict'; var l10n = require('util/l10n'); var intro = require('gcli/ui/intro'); exports.items = [ { item: 'converter', from: 'intro', to: 'view', exec: intro.createView }, { item: 'command', name: 'intro', description: l10n.lookup('introDesc'), manual: l10n.lookup('introManual'), returnType: 'intro', exec: function(args, context) { // The intro command is pure formatting - no data } } ]; });
define(function(require, exports, module) { 'use strict'; var l10n = require('util/l10n'); /** * 'context' command */ var context = { item: 'command', name: 'context', description: l10n.lookup('contextDesc'), manual: l10n.lookup('contextManual'), params: [ { name: 'prefix', type: 'command', description: l10n.lookup('contextPrefixDesc'), defaultValue: null } ], returnType: 'string', noRemote: true, exec: function echo(args, context) { // Do not copy this code var requisition = context.__dlhjshfw; if (args.prefix == null) { requisition.prefix = null; return l10n.lookup('contextEmptyReply'); } if (args.prefix.exec != null) { throw new Error(l10n.lookupFormat('contextNotParentError', [ args.prefix.name ])); } requisition.prefix = args.prefix.name; return l10n.lookupFormat('contextReply', [ args.prefix.name ]); } }; exports.items = [ context ]; });
define(function(require, exports, module) { 'use strict'; var l10n = require('util/l10n'); var canon = require('gcli/canon'); var converters = require('gcli/converters'); var intro = require('gcli/ui/intro'); var introConverterSpec = { from: 'intro', to: 'view', exec: function(ignore, converterContext) { return intro.createView(converterContext); } }; /** * 'intro' command */ var introCommandSpec = { name: 'intro', description: l10n.lookup('introDesc'), manual: l10n.lookup('introManual'), returnType: 'intro', exec: function(args, context) { // The intro command is pure formatting - no data } }; /** * Registration and de-registration. */ exports.startup = function() { canon.addCommand(introCommandSpec); converters.addConverter(introConverterSpec); }; exports.shutdown = function() { canon.removeCommand(introCommandSpec); converters.removeConverter(introConverterSpec); }; });
/** * Implement the localization algorithm for any documentation objects (i.e. * description and manual) in a command. * @param data The data assigned to a description or manual property * @param onUndefined If data == null, should we return the data untouched or * lookup a 'we don't know' key in it's place. */ function lookup(data, onUndefined) { if (data == null) { if (onUndefined) { return l10n.lookup(onUndefined); } return data; } if (typeof data === 'string') { return data; } if (typeof data === 'object') { if (data.key) { return l10n.lookup(data.key); } var locales = l10n.getPreferredLocales(); var translated; locales.some(function(locale) { translated = data[locale]; return translated != null; }); if (translated != null) { return translated; } console.error('Can\'t find locale in descriptions: ' + 'locales=' + JSON.stringify(locales) + ', ' + 'description=' + JSON.stringify(data)); return '(No description)'; } return l10n.lookup(onUndefined); }
ParamType.prototype.parse = function(arg, requisition) { //Orion-20140210 if (this.isIncompleteName) { return SelectionType.prototype.parse.call(this, arg); } else { var message = l10n.lookup('cliUnusedArg'); if (requisition) { //Orion-20140210 var conversion = requisition.commandAssignment.conversion; //Orion-20140210 if (conversion.getStatus() === Status.ERROR && conversion.message) { //Orion-20140210 message = conversion.message; //Orion-20140210 } //Orion-20140210 } //Orion-20140210 return Promise.resolve(new Conversion(undefined, arg, Status.ERROR, message)); } };
exec: function echo(args, context) { // Do not copy this code var requisition = context.__dlhjshfw; if (args.prefix == null) { requisition.prefix = null; return l10n.lookup('contextEmptyReply'); } if (args.prefix.exec != null) { throw new Error(l10n.lookupFormat('contextNotParentError', [ args.prefix.name ])); } requisition.prefix = args.prefix.name; return l10n.lookupFormat('contextReply', [ args.prefix.name ]); }
var predictions = matches.map(function(match) { var description; var incomplete = true; if (this._isSafeProperty(scope, match.prop)) { description = '(property getter)'; } else { try { var value = scope[match.prop]; if (typeof value === 'function') { description = '(function)'; } else if (typeof value === 'boolean' || typeof value === 'number') { description = '= ' + value; incomplete = false; } else if (typeof value === 'string') { if (value.length > 40) { value = value.substring(0, 37) + '…'; } description = '= \'' + value + '\''; incomplete = false; } else { description = '(' + typeof value + ')'; } } catch (ex) { description = '(' + l10n.lookup('jstypeParseError') + ')'; } } return { name: prefix + match.prop, value: { name: prefix + match.prop, description: description }, description: description, incomplete: incomplete }; }, this);
define(function(require, exports, module) { 'use strict'; var cli = require('gcli/cli'); var host = require('util/host'); var l10n = require('util/l10n'); /** * 'cd' command */ var cd = { item: 'command', name: 'cd', description: l10n.lookup('cdDesc'), manual: l10n.lookup('cdManual'), params: [ { name: 'directory', type: { name: 'file', filetype: 'directory', existing: 'yes' }, description: l10n.lookup('cdDirectoryDesc'), } ], returnType: 'string', exec: function(args, context) { context.shell.cwd = args.directory; return 'Working directory is now ' + context.shell.cwd; } }; /** * 'exec' command */ var exec = { item: 'command', name: 'exec', description: l10n.lookup('execDesc'), manual: l10n.lookup('execManual'), params: [ { name: 'command', type: 'string', description: l10n.lookup('execCommandDesc'), } ], returnType: 'output', exec: function(args, context) { var cmdArgs = cli.tokenize(args.command).map(function(arg) { return arg.text; }); var cmd = cmdArgs.shift(); var execSpec = { cmd: cmd, args: cmdArgs, env: context.shell.env, cwd: context.shell.cwd }; return host.exec(execSpec).then(function(output) { if (output.code === 0) { return output; } throw output.data; }, function(output) { throw output.data; }); } }; /** * How we display the output of a generic exec command: we have to assume that * it is a string to be displayed on a terminal - i.e. in a monospaced font */ var outputToView = { item: 'converter', from: 'output', to: 'view', exec: function(output, context) { return context.createView({ html: '<pre>${output.data}</pre>', data: { output: output } }); } }; /** * How we display the output of a generic exec command: we have to assume that * it is a string to be displayed on a terminal - i.e. in a monospaced font */ var outputToString = { item: 'converter', from: 'output', to: 'string', exec: function(output, context) { return output.data; } }; exports.items = [ outputToView, outputToString, cd, exec ]; });
define(function(require, exports, module) { 'use strict'; var l10n = require('util/l10n'); var canon = require('gcli/canon'); var connector = require('util/connect/connector'); /** * A lookup of the current connection */ var connections = {}; /** * 'connection' type */ var connection = { item: 'type', name: 'connection', parent: 'selection', lookup: function() { return Object.keys(connections).map(function(prefix) { return { name: prefix, value: connections[prefix] }; }); } }; /** * 'connect' command */ var connect = { item: 'command', name: 'connect', description: l10n.lookup('connectDesc'), manual: l10n.lookup('connectManual'), params: [ { name: 'prefix', type: 'string', description: l10n.lookup('connectPrefixDesc') }, { name: 'host', short: 'h', type: 'string', description: l10n.lookup('connectHostDesc'), defaultValue: 'localhost', option: true }, { name: 'port', short: 'p', type: { name: 'number', max: 65536, min: 0 }, description: l10n.lookup('connectPortDesc'), defaultValue: connector.defaultPort, option: true } ], returnType: 'string', exec: function(args, context) { if (connections[args.prefix] != null) { throw new Error(l10n.lookupFormat('connectDupReply', [ args.prefix ])); } var cxp = connector.connect(args.prefix, args.host, args.port); return cxp.then(function(connection) { connections[args.prefix] = connection; return connection.getCommandSpecs().then(function(commandSpecs) { var remoter = this.createRemoter(args.prefix, connection); canon.addProxyCommands(args.prefix, commandSpecs, remoter); // commandSpecs doesn't include the parent command that we added return l10n.lookupFormat('connectReply', [ Object.keys(commandSpecs).length + 1 ]); }.bind(this)); }.bind(this)); }, /** * When we register a set of remote commands, we need to provide the canon * with a proxy executor. This is that executor. */ createRemoter: function(prefix, connection) { return function(cmdArgs, context) { var typed = context.typed; // If we've been called using a 'context' then there will be no prefix // otherwise we need to remove it if (typed.indexOf(prefix) === 0) { typed = typed.substring(prefix.length).replace(/^ */, ''); } return connection.execute(typed, cmdArgs).then(function(reply) { var typedData = context.typedData(reply.type, reply.data); if (!reply.error) { return typedData; } else { throw typedData; } }); }.bind(this); } }; /** * 'disconnect' command */ var disconnect = { item: 'command', name: 'disconnect', description: l10n.lookup('disconnectDesc2'), manual: l10n.lookup('disconnectManual2'), params: [ { name: 'prefix', type: 'connection', description: l10n.lookup('disconnectPrefixDesc'), }, { name: 'force', type: 'boolean', description: l10n.lookup('disconnectForceDesc'), hidden: connector.disconnectSupportsForce, option: true } ], returnType: 'string', exec: function(args, context) { return args.prefix.disconnect(args.force).then(function() { var removed = canon.removeProxyCommands(args.prefix.prefix); delete connections[args.prefix.prefix]; return l10n.lookupFormat('disconnectReply', [ removed.length ]); }); } }; exports.items = [ connection, connect, disconnect ]; });
JavascriptType.prototype.parse = function(arg, context) { var typed = arg.text; var scope = globalObject; // No input is undefined if (typed === '') { return Promise.resolve(new Conversion(undefined, arg, Status.INCOMPLETE)); } // Just accept numbers if (!isNaN(parseFloat(typed)) && isFinite(typed)) { return Promise.resolve(new Conversion(typed, arg)); } // Just accept constants like true/false/null/etc if (typed.trim().match(/(null|undefined|NaN|Infinity|true|false)/)) { return Promise.resolve(new Conversion(typed, arg)); } // Analyze the input text and find the beginning of the last part that // should be completed. var beginning = this._findCompletionBeginning(typed); // There was an error analyzing the string. if (beginning.err) { return Promise.resolve(new Conversion(typed, arg, Status.ERROR, beginning.err)); } // If the current state is ParseState.COMPLEX, then we can't do completion. // so bail out now if (beginning.state === ParseState.COMPLEX) { return Promise.resolve(new Conversion(typed, arg)); } // If the current state is not ParseState.NORMAL, then we are inside of a // string which means that no completion is possible. if (beginning.state !== ParseState.NORMAL) { return Promise.resolve(new Conversion(typed, arg, Status.INCOMPLETE, '')); } var completionPart = typed.substring(beginning.startPos); var properties = completionPart.split('.'); var matchProp; var prop = undefined; if (properties.length > 1) { matchProp = properties.pop().trimLeft(); for (var i = 0; i < properties.length; i++) { prop = properties[i].trim(); // We can't complete on null.foo, so bail out if (scope == null) { return Promise.resolve(new Conversion(typed, arg, Status.ERROR, l10n.lookup('jstypeParseScope'))); } if (prop === '') { return Promise.resolve(new Conversion(typed, arg, Status.INCOMPLETE, '')); } // Check if prop is a getter function on 'scope'. Functions can change // other stuff so we can't execute them to get the next object. Stop here. if (this._isSafeProperty(scope, prop)) { return Promise.resolve(new Conversion(typed, arg)); } try { scope = scope[prop]; } catch (ex) { // It would be nice to be able to report this error in some way but // as it can happen just when someone types '{sessionStorage.', it // almost doesn't really count as an error, so we ignore it return Promise.resolve(new Conversion(typed, arg, Status.VALID, '')); } } } else { matchProp = properties[0].trimLeft(); } // If the reason we just stopped adjusting the scope was a non-simple string, // then we're not sure if the input is valid or invalid, so accept it if (prop && !prop.match(/^[0-9A-Za-z]*$/)) { return Promise.resolve(new Conversion(typed, arg)); } // However if the prop was a simple string, it is an error if (scope == null) { var message = l10n.lookupFormat('jstypeParseMissing', [ prop ]); return Promise.resolve(new Conversion(typed, arg, Status.ERROR, message)); } // If the thing we're looking for isn't a simple string, then we're not going // to find it, but we're not sure if it's valid or invalid, so accept it if (!matchProp.match(/^[0-9A-Za-z]*$/)) { return Promise.resolve(new Conversion(typed, arg)); } // Skip Iterators and Generators. if (this._isIteratorOrGenerator(scope)) { return Promise.resolve(new Conversion(typed, arg)); } var matchLen = matchProp.length; var prefix = matchLen === 0 ? typed : typed.slice(0, -matchLen); var status = Status.INCOMPLETE; var message = ''; // We really want an array of matches (for sorting) but it's easier to // detect existing members if we're using a map initially var matches = {}; // We only display a maximum of MAX_COMPLETION_MATCHES, so there is no point // in digging up the prototype chain for matches that we're never going to // use. Initially look for matches directly on the object itself and then // look up the chain to find more var distUpPrototypeChain = 0; var root = scope; try { while (root != null && Object.keys(matches).length < JavascriptType.MAX_COMPLETION_MATCHES) { Object.keys(root).forEach(function(property) { // Only add matching properties. Also, as we're walking up the // prototype chain, properties on 'higher' prototypes don't override // similarly named properties lower down if (property.indexOf(matchProp) === 0 && !(property in matches)) { matches[property] = { prop: property, distUpPrototypeChain: distUpPrototypeChain }; } }); distUpPrototypeChain++; root = Object.getPrototypeOf(root); } } catch (ex) { return Promise.resolve(new Conversion(typed, arg, Status.INCOMPLETE, '')); } // Convert to an array for sorting, and while we're at it, note if we got // an exact match so we know that this input is valid matches = Object.keys(matches).map(function(property) { if (property === matchProp) { status = Status.VALID; } return matches[property]; }); // The sort keys are: // - Being on the object itself, not in the prototype chain // - The lack of existence of a vendor prefix // - The name matches.sort(function(m1, m2) { if (m1.distUpPrototypeChain !== m2.distUpPrototypeChain) { return m1.distUpPrototypeChain - m2.distUpPrototypeChain; } // Push all vendor prefixes to the bottom of the list return isVendorPrefixed(m1.prop) ? (isVendorPrefixed(m2.prop) ? m1.prop.localeCompare(m2.prop) : 1) : (isVendorPrefixed(m2.prop) ? -1 : m1.prop.localeCompare(m2.prop)); }); // Trim to size. There is a bug for doing a better job of finding matches // (bug 682694), but in the mean time there is a performance problem // associated with creating a large number of DOM nodes that few people will // ever read, so trim ... if (matches.length > JavascriptType.MAX_COMPLETION_MATCHES) { matches = matches.slice(0, JavascriptType.MAX_COMPLETION_MATCHES - 1); } // Decorate the matches with: // - a description // - a value (for the menu) and, // - an incomplete flag which reports if we should assume that the user isn't // going to carry on the JS expression with this input so far var predictions = matches.map(function(match) { var description; var incomplete = true; if (this._isSafeProperty(scope, match.prop)) { description = '(property getter)'; } else { try { var value = scope[match.prop]; if (typeof value === 'function') { description = '(function)'; } else if (typeof value === 'boolean' || typeof value === 'number') { description = '= ' + value; incomplete = false; } else if (typeof value === 'string') { if (value.length > 40) { value = value.substring(0, 37) + '…'; } description = '= \'' + value + '\''; incomplete = false; } else { description = '(' + typeof value + ')'; } } catch (ex) { description = '(' + l10n.lookup('jstypeParseError') + ')'; } } return { name: prefix + match.prop, value: { name: prefix + match.prop, description: description }, description: description, incomplete: incomplete }; }, this); if (predictions.length === 0) { status = Status.ERROR; message = l10n.lookupFormat('jstypeParseMissing', [ matchProp ]); } // If the match is the only one possible, and its VALID, predict nothing if (predictions.length === 1 && status === Status.VALID) { predictions = []; } return Promise.resolve(new Conversion(typed, arg, status, message, Promise.resolve(predictions))); };
JavascriptType.prototype._findCompletionBeginning = function(text) { var bodyStack = []; var state = ParseState.NORMAL; var start = 0; var c; var complex = false; for (var i = 0; i < text.length; i++) { c = text[i]; if (!simpleChars.test(c)) { complex = true; } switch (state) { // Normal JS state. case ParseState.NORMAL: if (c === '"') { state = ParseState.DQUOTE; } else if (c === '\'') { state = ParseState.QUOTE; } else if (c === ';') { start = i + 1; } else if (c === ' ') { start = i + 1; } else if (OPEN_BODY.indexOf(c) != -1) { bodyStack.push({ token: c, start: start }); start = i + 1; } else if (CLOSE_BODY.indexOf(c) != -1) { var last = bodyStack.pop(); if (!last || OPEN_CLOSE_BODY[last.token] != c) { return { err: l10n.lookup('jstypeBeginSyntax') }; } if (c === '}') { start = i + 1; } else { start = last.start; } } break; // Double quote state > " < case ParseState.DQUOTE: if (c === '\\') { i ++; } else if (c === '\n') { return { err: l10n.lookup('jstypeBeginUnterm') }; } else if (c === '"') { state = ParseState.NORMAL; } break; // Single quote state > ' < case ParseState.QUOTE: if (c === '\\') { i ++; } else if (c === '\n') { return { err: l10n.lookup('jstypeBeginUnterm') }; } else if (c === '\'') { state = ParseState.NORMAL; } break; } } if (state === ParseState.NORMAL && complex) { state = ParseState.COMPLEX; } return { state: state, startPos: start }; };
define(function(require, exports, module) { 'use strict'; var l10n = require('util/l10n'); var types = require('gcli/types'); var canon = require('gcli/canon'); var connector = require('util/connect/connector'); /** * A lookup of the current connection */ var connections = {}; /** * 'connect' command */ var connect = { name: 'connect', description: l10n.lookup('connectDesc'), manual: l10n.lookup('connectManual'), params: [ { name: 'prefix', type: 'string', description: l10n.lookup('connectPrefixDesc') }, { name: 'host', type: 'string', description: l10n.lookup('connectHostDesc'), defaultValue: 'localhost', option: true }, { name: 'port', type: { name: 'number', max: 65536, min: 0 }, description: l10n.lookup('connectPortDesc'), defaultValue: connector.defaultPort, option: true } ], returnType: 'string', exec: function(args, context) { if (connections[args.prefix] != null) { throw new Error(l10n.lookupFormat('connectDupReply', [ args.prefix ])); } var cxp = connector.connect(args.prefix, args.host, args.port); return cxp.then(function(connection) { connections[args.prefix] = connection; return connection.getCommandSpecs().then(function(commandSpecs) { var remoter = this.createRemoter(args.prefix, connection); canon.addProxyCommands(args.prefix, commandSpecs, remoter); // commandSpecs doesn't include the parent command that we added return l10n.lookupFormat('connectReply', [ Object.keys(commandSpecs).length + 1 ]); }.bind(this)); }.bind(this)); }, /** * When we register a set of remote commands, we need to provide the canon * with a proxy executor. This is that executor. */ createRemoter: function(prefix, connection) { return function(cmdArgs, context) { var typed = context.typed; if (typed.indexOf(prefix) !== 0) { throw new Error("Missing prefix"); } typed = typed.substring(prefix.length).replace(/^ */, ""); return connection.execute(typed, cmdArgs).then(function(reply) { var typedData = context.typedData(reply.type, reply.data); if (!reply.error) { return typedData; } else { throw typedData; } }); }.bind(this); } }; /** * 'connection' type */ var connection = { name: 'connection', parent: 'selection', lookup: function() { return Object.keys(connections).map(function(prefix) { return { name: prefix, value: connections[prefix] }; }); } }; /** * 'disconnect' command */ var disconnect = { name: 'disconnect', description: l10n.lookup('disconnectDesc'), manual: l10n.lookup('disconnectManual'), params: [ { name: 'prefix', type: 'connection', description: l10n.lookup('disconnectPrefixDesc'), } ], returnType: 'string', exec: function(args, context) { return args.prefix.disconnect().then(function() { var removed = canon.removeProxyCommands(args.prefix.prefix); delete connections[args.prefix.prefix]; return l10n.lookupFormat('disconnectReply', [ removed.length ]); }); } }; /** * Registration and de-registration. */ exports.startup = function() { types.addType(connection); canon.addCommand(connect); canon.addCommand(disconnect); }; exports.shutdown = function() { canon.removeCommand(connect); canon.removeCommand(disconnect); types.removeType(connection); }; });
define(function(require, exports, module) { 'use strict'; var l10n = require('util/l10n'); var settings = require('gcli/settings'); var view = require('gcli/ui/view'); var Output = require('gcli/cli').Output; /** * Record if the user has clicked on 'Got It!' */ exports.items = [ { item: 'setting', name: 'hideIntro', type: 'boolean', description: l10n.lookup('hideIntroDesc'), defaultValue: false } ]; /** * Called when the UI is ready to add a welcome message to the output */ exports.maybeShowIntro = function(commandOutputManager, conversionContext) { var hideIntro = settings.getSetting('hideIntro'); if (hideIntro.value) { return; } var output = new Output(); output.type = 'view'; commandOutputManager.onOutput({ output: output }); var viewData = this.createView(null, conversionContext, output); output.complete({ isTypedData: true, type: 'view', data: viewData }); }; /** * Called when the UI is ready to add a welcome message to the output */ exports.createView = function(ignore, conversionContext, output) { return view.createView({ html: require('text!gcli/ui/intro.html'), options: { stack: 'intro.html' }, data: { l10n: l10n.propertyLookup, onclick: conversionContext.update, ondblclick: conversionContext.updateExec, showHideButton: (output != null), onGotIt: function(ev) { var hideIntro = settings.getSetting('hideIntro'); hideIntro.value = true; output.onClose(); } } }); }; });