author | Quentin Pradet <quentin.pradet@gmail.com> |
Tue, 21 May 2013 10:18:55 +0100 | |
changeset 132422 | 3b90dbff58096d79c8c32343e8c0b1df8b0fe250 |
parent 132421 | 231636fc32319c1f4edc9c9d247d3af9212e38e8 |
child 132423 | 99a40b3cf2ec96c18d6d01b010f67bc3002cc24b |
push id | 24702 |
push user | ryanvm@gmail.com |
push date | Tue, 21 May 2013 12:15:30 +0000 |
treeherder | mozilla-central@957f5f047a94 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | jwalker, harth |
bugs | 773271 |
milestone | 24.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/browser/devtools/commandline/test/browser_gcli_cli.js +++ b/browser/devtools/commandline/test/browser_gcli_cli.js @@ -305,17 +305,17 @@ exports.testTsv = function(options) { input: 'tsv option ', hints: '<optionValue>', markup: 'VVVVEEEEEEV', cursor: 11, current: 'optionValue', status: 'ERROR', predictions: [ ], unassigned: [ ], - tooltipState: 'true:isError', + tooltipState: 'false:default', args: { command: { name: 'tsv' }, optionType: { value: undefined, arg: ' option ', status: 'ERROR', message: 'Can\'t use \'option\'.' },
new file mode 100644 --- /dev/null +++ b/browser/devtools/commandline/test/browser_gcli_date.js @@ -0,0 +1,247 @@ +/* + * Copyright 2009-2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE.txt or: + * http://opensource.org/licenses/BSD-3-Clause + */ + +// define(function(require, exports, module) { + +// <INJECTED SOURCE:START> + +// THIS FILE IS GENERATED FROM SOURCE IN THE GCLI PROJECT +// DO NOT EDIT IT DIRECTLY + +var exports = {}; + +const TEST_URI = "data:text/html;charset=utf-8,<p id='gcli-input'>gcli-testDate.js</p>"; + +function test() { + helpers.addTabWithToolbar(TEST_URI, function(options) { + return helpers.runTests(options, exports); + }).then(finish); +} + +// <INJECTED SOURCE:END> + +'use strict'; + +// var assert = require('test/assert'); + +var types = require('gcli/types'); +var Argument = require('gcli/argument').Argument; +var Status = require('gcli/types').Status; + +// var helpers = require('gclitest/helpers'); +// var mockCommands = require('gclitest/mockCommands'); + +exports.setup = function(options) { + mockCommands.setup(); +}; + +exports.shutdown = function(options) { + mockCommands.shutdown(); +}; + + +exports.testParse = function(options) { + var date = types.createType('date'); + return date.parse(new Argument('now')).then(function(conversion) { + // Date comparison - these 2 dates may not be the same, but how close is + // close enough? If this test takes more than 30secs to run the it will + // probably time out, so we'll assume that these 2 values must be within + // 1 min of each other + var gap = new Date().getTime() - conversion.value.getTime(); + assert.ok(gap < 60000, 'now is less than a minute away'); + + assert.is(conversion.getStatus(), Status.VALID, 'now parse'); + }); +}; + +exports.testMaxMin = function(options) { + var max = new Date(); + var min = new Date(); + var date = types.createType({ name: 'date', max: max, min: min }); + assert.is(date.getMax(), max, 'max setup'); + + var incremented = date.increment(min); + assert.is(incremented, max, 'incremented'); +}; + +exports.testIncrement = function(options) { + var date = types.createType('date'); + return date.parse(new Argument('now')).then(function(conversion) { + var plusOne = date.increment(conversion.value); + var minusOne = date.decrement(plusOne); + + // See comments in testParse + var gap = new Date().getTime() - minusOne.getTime(); + assert.ok(gap < 60000, 'now is less than a minute away'); + }); +}; + +exports.testInput = function(options) { + helpers.audit(options, [ + { + setup: 'tsdate 2001-01-01 1980-01-03', + check: { + input: 'tsdate 2001-01-01 1980-01-03', + hints: '', + markup: 'VVVVVVVVVVVVVVVVVVVVVVVVVVVV', + status: 'VALID', + message: '', + args: { + command: { name: 'tsdate' }, + d1: { + value: function(d1) { + assert.is(d1.getFullYear(), 2001, 'd1 year'); + assert.is(d1.getMonth(), 0, 'd1 month'); + assert.is(d1.getDate(), 1, 'd1 date'); + assert.is(d1.getHours(), 0, 'd1 hours'); + assert.is(d1.getMinutes(), 0, 'd1 minutes'); + assert.is(d1.getSeconds(), 0, 'd1 seconds'); + assert.is(d1.getMilliseconds(), 0, 'd1 millis'); + }, + arg: ' 2001-01-01', + status: 'VALID', + message: '' + }, + d2: { + value: function(d2) { + assert.is(d2.getFullYear(), 1980, 'd1 year'); + assert.is(d2.getMonth(), 0, 'd1 month'); + assert.is(d2.getDate(), 3, 'd1 date'); + assert.is(d2.getHours(), 0, 'd1 hours'); + assert.is(d2.getMinutes(), 0, 'd1 minutes'); + assert.is(d2.getSeconds(), 0, 'd1 seconds'); + assert.is(d2.getMilliseconds(), 0, 'd1 millis'); + }, + arg: ' 1980-01-03', + status: 'VALID', + message: '' + }, + } + }, + exec: { + output: [ /^Exec: tsdate/, /2001/, /1980/ ], + completed: true, + type: 'string', + error: false + } + } + ]); +}; + +exports.testIncrDecr = function(options) { + helpers.audit(options, [ + { + setup: 'tsdate 2001-01-01<UP>', + check: { + input: 'tsdate 2001-01-02', + hints: ' <d2>', + markup: 'VVVVVVVVVVVVVVVVV', + status: 'ERROR', + message: '', + args: { + command: { name: 'tsdate' }, + d1: { + value: function(d1) { + assert.is(d1.getFullYear(), 2001, 'd1 year'); + assert.is(d1.getMonth(), 0, 'd1 month'); + assert.is(d1.getDate(), 2, 'd1 date'); + assert.is(d1.getHours(), 0, 'd1 hours'); + assert.is(d1.getMinutes(), 0, 'd1 minutes'); + assert.is(d1.getSeconds(), 0, 'd1 seconds'); + assert.is(d1.getMilliseconds(), 0, 'd1 millis'); + }, + arg: ' 2001-01-02', + status: 'VALID', + message: '' + }, + d2: { + value: undefined, + status: 'INCOMPLETE', + message: '' + }, + } + } + }, + { + // Check wrapping on decrement + setup: 'tsdate 2001-02-01<DOWN>', + check: { + input: 'tsdate 2001-01-31', + hints: ' <d2>', + markup: 'VVVVVVVVVVVVVVVVV', + status: 'ERROR', + message: '', + args: { + command: { name: 'tsdate' }, + d1: { + value: function(d1) { + assert.is(d1.getFullYear(), 2001, 'd1 year'); + assert.is(d1.getMonth(), 0, 'd1 month'); + assert.is(d1.getDate(), 31, 'd1 date'); + assert.is(d1.getHours(), 0, 'd1 hours'); + assert.is(d1.getMinutes(), 0, 'd1 minutes'); + assert.is(d1.getSeconds(), 0, 'd1 seconds'); + assert.is(d1.getMilliseconds(), 0, 'd1 millis'); + }, + arg: ' 2001-01-31', + status: 'VALID', + message: '' + }, + d2: { + value: undefined, + status: 'INCOMPLETE', + message: '' + }, + } + } + }, + { + // Check 'max' value capping on increment + setup: 'tsdate 2001-02-01 "27 feb 2000"<UP>', + check: { + input: 'tsdate 2001-02-01 "2000-02-28"', + hints: '', + markup: 'VVVVVVVVVVVVVVVVVVVVVVVVVVVVVV', + status: 'VALID', + message: '', + args: { + command: { name: 'tsdate' }, + d1: { + value: function(d1) { + assert.is(d1.getFullYear(), 2001, 'd1 year'); + assert.is(d1.getMonth(), 1, 'd1 month'); + assert.is(d1.getDate(), 1, 'd1 date'); + assert.is(d1.getHours(), 0, 'd1 hours'); + assert.is(d1.getMinutes(), 0, 'd1 minutes'); + assert.is(d1.getSeconds(), 0, 'd1 seconds'); + assert.is(d1.getMilliseconds(), 0, 'd1 millis'); + }, + arg: ' 2001-02-01', + status: 'VALID', + message: '' + }, + d2: { + value: function(d1) { + assert.is(d1.getFullYear(), 2000, 'd1 year'); + assert.is(d1.getMonth(), 1, 'd1 month'); + assert.is(d1.getDate(), 28, 'd1 date'); + assert.is(d1.getHours(), 0, 'd1 hours'); + assert.is(d1.getMinutes(), 0, 'd1 minutes'); + assert.is(d1.getSeconds(), 0, 'd1 seconds'); + assert.is(d1.getMilliseconds(), 0, 'd1 millis'); + }, + arg: ' "2000-02-28"', + status: 'VALID', + message: '' + }, + } + } + } + ]); +}; + + +// });
--- a/browser/devtools/commandline/test/mockCommands.js +++ b/browser/devtools/commandline/test/mockCommands.js @@ -400,16 +400,37 @@ var tslong = { defaultValue: "collapse" } ] } ], exec: createExec('tslong') }; +var tsdate = { + name: 'tsdate', + description: 'long param tests to catch problems with the jsb command', + params: [ + { + name: 'd1', + type: 'date', + }, + { + name: 'd2', + type: { + name: 'date', + min: '1 jan 2000', + max: '28 feb 2000', + step: 2 + } + }, + ], + exec: createExec('tsdate') +}; + var tsfail = { name: 'tsfail', description: 'test errors', params: [ { name: 'method', type: { name: 'selection', @@ -505,16 +526,17 @@ mockCommands.setup = function(opts) { mockCommands.commands.tsnDeepDownNested = canon.addCommand(tsnDeepDownNested); mockCommands.commands.tsnDeepDownNestedCmd = canon.addCommand(tsnDeepDownNestedCmd); mockCommands.commands.tselarr = canon.addCommand(tselarr); mockCommands.commands.tsm = canon.addCommand(tsm); mockCommands.commands.tsg = canon.addCommand(tsg); mockCommands.commands.tshidden = canon.addCommand(tshidden); mockCommands.commands.tscook = canon.addCommand(tscook); mockCommands.commands.tslong = canon.addCommand(tslong); + mockCommands.commands.tsdate = canon.addCommand(tsdate); mockCommands.commands.tsfail = canon.addCommand(tsfail); }; mockCommands.shutdown = function(opts) { canon.removeCommand(tsv); canon.removeCommand(tsr); canon.removeCommand(tsrsrsr); canon.removeCommand(tso); @@ -535,16 +557,17 @@ mockCommands.shutdown = function(opts) { canon.removeCommand(tsnDeepDownNested); canon.removeCommand(tsnDeepDownNestedCmd); canon.removeCommand(tselarr); canon.removeCommand(tsm); canon.removeCommand(tsg); canon.removeCommand(tshidden); canon.removeCommand(tscook); canon.removeCommand(tslong); + canon.removeCommand(tsdate); canon.removeCommand(tsfail); types.removeType(mockCommands.optionType); types.removeType(mockCommands.optionValue); mockCommands.commands = {}; };
--- a/browser/locales/en-US/chrome/browser/devtools/gcli.properties +++ b/browser/locales/en-US/chrome/browser/devtools/gcli.properties @@ -109,16 +109,31 @@ typesNumberMax=%1$S is greater than maxi # number, but the number is lower than the smallest allowed number, this error # message is displayed. typesNumberMin=%1$S is smaller than minimum allowed: %2$S. # LOCALIZATION NOTE (typesNumberNotInt2): When the command line is passed a # number, but the number has a decimal part and floats are not allowed. typesNumberNotInt2=Can't convert "%S" to an integer. +# LOCALIZATION NOTE (typesDateNan): When the command line is passed a date, +# however the input string is not a valid date, this error message is +# displayed. +typesDateNan=Can't convert "%S" to a date. + +# LOCALIZATION NOTE (typesDateMax): When the command line is passed a date, +# but the number is later than the latest allowed date, this error message is +# displayed. +typesDateMax=%1$S is later than maximum allowed: %2$S. + +# LOCALIZATION NOTE (typesDateMin): When the command line is passed a date, +# but the date is earlier than the earliest allowed number, this error message +# is displayed. +typesDateMin=%1$S is earlier than minimum allowed: %2$S. + # LOCALIZATION NOTE (typesSelectionNomatch): When the command line is passed # an option with a limited number of correct values, but the passed value is # not one of them, this error message is displayed. typesSelectionNomatch=Can't use '%S'. # LOCALIZATION NOTE (nodeParseSyntax): When the command line is expecting a # CSS query string, however the passed string is not valid, this error message # is displayed.
--- a/toolkit/devtools/gcli/gcli.jsm +++ b/toolkit/devtools/gcli/gcli.jsm @@ -99,26 +99,27 @@ var mozl10n = {}; } catch (ex) { throw new Error("Failure in lookupFormat('" + name + "')"); } }; })(mozl10n); -define('gcli/index', ['require', 'exports', 'module' , 'gcli/types/basic', 'gcli/types/selection', 'gcli/types/command', 'gcli/types/javascript', 'gcli/types/node', 'gcli/types/resource', 'gcli/types/setting', 'gcli/settings', 'gcli/ui/intro', 'gcli/ui/focus', 'gcli/ui/fields/basic', 'gcli/ui/fields/javascript', 'gcli/ui/fields/selection', 'gcli/commands/connect', 'gcli/commands/context', 'gcli/commands/help', 'gcli/commands/pref', 'gcli/canon', 'gcli/converters', 'gcli/ui/ffdisplay'], function(require, exports, module) { +define('gcli/index', ['require', 'exports', 'module' , 'gcli/types/basic', 'gcli/types/selection', 'gcli/types/command', 'gcli/types/date', 'gcli/types/javascript', 'gcli/types/node', 'gcli/types/resource', 'gcli/types/setting', 'gcli/settings', 'gcli/ui/intro', 'gcli/ui/focus', 'gcli/ui/fields/basic', 'gcli/ui/fields/javascript', 'gcli/ui/fields/selection', 'gcli/commands/connect', 'gcli/commands/context', 'gcli/commands/help', 'gcli/commands/pref', 'gcli/canon', 'gcli/converters', 'gcli/ui/ffdisplay'], function(require, exports, module) { 'use strict'; // Internal startup process. Not exported // The basic/selection are depended on by others so they must come first require('gcli/types/basic').startup(); require('gcli/types/selection').startup(); require('gcli/types/command').startup(); + require('gcli/types/date').startup(); require('gcli/types/javascript').startup(); require('gcli/types/node').startup(); require('gcli/types/resource').startup(); require('gcli/types/setting').startup(); require('gcli/settings').startup(); require('gcli/ui/intro').startup(); require('gcli/ui/focus').startup(); @@ -3937,16 +3938,247 @@ function CommandOutputManager() { this.onOutput = util.createEvent('CommandOutputManager.onOutput'); } exports.CommandOutputManager = CommandOutputManager; }); /* + * Copyright 2009-2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE.txt or: + * http://opensource.org/licenses/BSD-3-Clause + */ + +define('gcli/types/date', ['require', 'exports', 'module' , 'util/promise', 'util/l10n', 'gcli/types'], function(require, exports, module) { + +'use strict'; + +var Promise = require('util/promise'); +var l10n = require('util/l10n'); + +var types = require('gcli/types'); +var Type = require('gcli/types').Type; +var Status = require('gcli/types').Status; +var Conversion = require('gcli/types').Conversion; + + +function DateType(typeSpec) { + // ECMA 5.1 ยง15.9.1.1 + // @see http://stackoverflow.com/questions/11526504/minimum-and-maximum-date + typeSpec = typeSpec || {}; + + this._step = typeSpec.step || 1; + this._min = new Date(-8640000000000000); + this._max = new Date(8640000000000000); + + if (typeSpec.min != null) { + if (typeof typeSpec.min === 'string') { + this._min = toDate(typeSpec.min); + } + else if (isDate(typeSpec.min) || typeof typeSpec.min === 'function') { + this._min = typeSpec.min; + } + else { + throw new Error('date min value must be a string a date or a function'); + } + } + + if (typeSpec.max != null) { + if (typeof typeSpec.max === 'string') { + this._max = toDate(typeSpec.max); + } + else if (isDate(typeSpec.max) || typeof typeSpec.max === 'function') { + this._max = typeSpec.max; + } + else { + throw new Error('date max value must be a string a date or a function'); + } + } +} + +DateType.prototype = Object.create(Type.prototype); + +/** + * Helper for stringify() to left pad a single digit number with a single '0' + * so 1 -> '01', 42 -> '42', etc. + */ +function pad(number) { + var r = String(number); + return r.length === 1 ? '0' + r : r; +} + +DateType.prototype.stringify = function(value) { + if (!isDate(value)) { + return ''; + } + + var str = pad(value.getFullYear()) + '-' + + pad(value.getMonth() + 1) + '-' + + pad(value.getDate()); + + // Only add in the time if it's not midnight + if (value.getHours() !== 0 || value.getMinutes() !== 0 || + value.getSeconds() !== 0 || value.getMilliseconds() !== 0) { + + // What string should we use to separate the date from the time? + // There are 3 options: + // 'T': This is the standard from ISO8601. i.e. 2013-05-20T11:05 + // The good news - it's a standard. The bad news - it's weird and + // alien to many if not most users + // ' ': This looks nicest, but needs escaping (which GCLI will do + // automatically) so it would look like: '2013-05-20 11:05' + // Good news: looks best, bad news: on completion we place the cursor + // after the final ', so repeated increment/decrement doesn't work + // '\ ': It's possible that we could find a way to use a \ to escape the + // space, so the output would look like: 2013-05-20\ 11:05 + // This would involve changes to a number of parts, and is probably + // too complex a solution for this problem for now + // In the short term I'm going for ' ', and raising the priority of cursor + // positioning on actions like increment/decrement/tab. + + str += ' ' + pad(value.getHours()); + str += ':' + pad(value.getMinutes()); + + // Only add in seconds/milliseconds if there is anything to report + if (value.getSeconds() !== 0 || value.getMilliseconds() !== 0) { + str += ':' + pad(value.getSeconds()); + if (value.getMilliseconds() !== 0) { + str += '.' + String((value.getUTCMilliseconds()/1000).toFixed(3)).slice(2, 5); + } + } + } + + return str; +}; + +DateType.prototype.getMin = function(context) { + if (typeof this._min === 'function') { + return this._min(context); + } + if (isDate(this._min)) { + return this._min; + } + return undefined; +}; + +DateType.prototype.getMax = function(context) { + if (typeof this._max === 'function') { + return this._max(context); + } + if (isDate(this._max)) { + return this._max; + } + return undefined; +}; + +DateType.prototype.parse = function(arg, context) { + var value; + + if (arg.text.replace(/\s/g, '').length === 0) { + return Promise.resolve(new Conversion(undefined, arg, Status.INCOMPLETE, '')); + } + + // Lots of room for improvement here: 1h ago, in two days, etc. + // Should "1h ago" dynamically update the step? + if (arg.text === 'now') { + value = new Date(); + } + else if (arg.text === 'yesterday') { + value = new Date().setDate(new Date().getDate() - 1); + } + else if (arg.text === 'tomorrow') { + value = new Date().setDate(new Date().getDate() + 1); + } + else { + var millis = Date.parse(arg.text); + + if (isNaN(millis)) { + var msg = l10n.lookupFormat('typesDateNan', [ arg.text ]); + return Promise.resolve(new Conversion(undefined, arg, Status.ERROR, msg)); + } + + value = new Date(millis); + } + + return Promise.resolve(new Conversion(value, arg)); +}; + +DateType.prototype.decrement = function(value, context) { + if (!isDate(value)) { + return new Date(); + } + + var newValue = new Date(value); + newValue.setDate(value.getDate() - this._step); + + if (newValue >= this.getMin(context)) { + return newValue; + } + else { + return this.getMin(context); + } +}; + +DateType.prototype.increment = function(value, context) { + if (!isDate(value)) { + return new Date(); + } + + var newValue = new Date(value); + newValue.setDate(value.getDate() + this._step); + + if (newValue <= this.getMax(context)) { + return newValue; + } + else { + return this.getMax(); + } +}; + +DateType.prototype.name = 'date'; + + +/** + * Utility to convert a string to a date, throwing if the date can't be + * parsed rather than having an invalid date + */ +function toDate(str) { + var millis = Date.parse(str); + if (isNaN(millis)) { + throw new Error(l10n.lookupFormat('typesDateNan', [ str ])); + } + return new Date(millis); +} + +/** + * Is |thing| a valid date? + * @see http://stackoverflow.com/questions/1353684/detecting-an-invalid-date-date-instance-in-javascript + */ +function isDate(thing) { + return Object.prototype.toString.call(thing) === '[object Date]' + && !isNaN(thing.getTime()); +}; + + +/** + * Registration and de-registration. + */ +exports.startup = function() { + types.addType(DateType); +}; + +exports.shutdown = function() { + types.removeType(DateType); +}; + + + +}); +/* * Copyright 2012, Mozilla Foundation and contributors * * 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 * @@ -10973,16 +11205,17 @@ Inputter.prototype._checkAssignment = fu * This function updates the data model. It sets the caret to the end of the * input. It does not make any similarity checks so calling this function with * it's current value resets the cursor position. * It does not execute the input or affect the history. * This function should not be called internally, by Inputter and never as a * result of a keyboard event on this.element or bug 676520 could be triggered. */ Inputter.prototype.setInput = function(str) { + this._caretChange = Caret.TO_END; return this.requisition.update(str); }; /** * Counterpart to |setInput| for moving the cursor. * @param cursor An object shaped like { start: x, end: y } */ Inputter.prototype.setCursor = function(cursor) {