From 66483d114baff67364ad45dba53d62de9f26cae8 Mon Sep 17 00:00:00 2001 From: Romain Courteaud <romain@nexedi.com> Date: Fri, 24 Jun 2016 07:28:58 +0000 Subject: [PATCH] Update jio.js to 3.11.0 --- .../web_page_module/rjs_jio_js.js | 5514 ++++-- .../web_page_module/rjs_jio_js.xml | 4 +- .../portal_skins/erp5_core/jio.js.js | 15776 +++++++++------- 3 files changed, 12593 insertions(+), 8701 deletions(-) diff --git a/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_jio_js.js b/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_jio_js.js index 1d8f142f5b..de2f614c12 100644 --- a/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_jio_js.js +++ b/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_jio_js.js @@ -1527,422 +1527,243 @@ if (typeof define === 'function' && define.amd) { module.exports = LZString } ;//! moment.js -//! version : 2.5.0 +//! version : 2.13.0 //! authors : Tim Wood, Iskren Chernev, Moment.js contributors //! license : MIT //! momentjs.com -(function (undefined) { +;(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + global.moment = factory() +}(this, function () { 'use strict'; - /************************************ - Constants - ************************************/ + var hookCallback; - var moment, - VERSION = "2.5.0", - global = this, - round = Math.round, - i, - - YEAR = 0, - MONTH = 1, - DATE = 2, - HOUR = 3, - MINUTE = 4, - SECOND = 5, - MILLISECOND = 6, - - // internal storage for language config files - languages = {}, - - // check for nodeJS - hasModule = (typeof module !== 'undefined' && module.exports && typeof require !== 'undefined'), - - // ASP.NET json date format regex - aspNetJsonRegex = /^\/?Date\((\-?\d+)/i, - aspNetTimeSpanJsonRegex = /(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/, - - // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html - // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere - isoDurationRegex = /^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/, - - // format tokens - formattingTokens = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|X|zz?|ZZ?|.)/g, - localFormattingTokens = /(\[[^\[]*\])|(\\)?(LT|LL?L?L?|l{1,4})/g, - - // parsing token regexes - parseTokenOneOrTwoDigits = /\d\d?/, // 0 - 99 - parseTokenOneToThreeDigits = /\d{1,3}/, // 0 - 999 - parseTokenOneToFourDigits = /\d{1,4}/, // 0 - 9999 - parseTokenOneToSixDigits = /[+\-]?\d{1,6}/, // -999,999 - 999,999 - parseTokenDigits = /\d+/, // nonzero number of digits - parseTokenWord = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i, // any word (or two) characters or numbers including two/three word month in arabic. - parseTokenTimezone = /Z|[\+\-]\d\d:?\d\d/gi, // +00:00 -00:00 +0000 -0000 or Z - parseTokenT = /T/i, // T (ISO separator) - parseTokenTimestampMs = /[\+\-]?\d+(\.\d{1,3})?/, // 123456789 123456789.123 - - //strict parsing regexes - parseTokenOneDigit = /\d/, // 0 - 9 - parseTokenTwoDigits = /\d\d/, // 00 - 99 - parseTokenThreeDigits = /\d{3}/, // 000 - 999 - parseTokenFourDigits = /\d{4}/, // 0000 - 9999 - parseTokenSixDigits = /[+\-]?\d{6}/, // -999,999 - 999,999 - - // iso 8601 regex - // 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00) - isoRegex = /^\s*\d{4}-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/, - - isoFormat = 'YYYY-MM-DDTHH:mm:ssZ', - - isoDates = [ - 'YYYY-MM-DD', - 'GGGG-[W]WW', - 'GGGG-[W]WW-E', - 'YYYY-DDD' - ], - - // iso time formats and regexes - isoTimes = [ - ['HH:mm:ss.SSSS', /(T| )\d\d:\d\d:\d\d\.\d{1,3}/], - ['HH:mm:ss', /(T| )\d\d:\d\d:\d\d/], - ['HH:mm', /(T| )\d\d:\d\d/], - ['HH', /(T| )\d\d/] - ], - - // timezone chunker "+10:00" > ["10", "00"] or "-1530" > ["-15", "30"] - parseTimezoneChunker = /([\+\-]|\d\d)/gi, - - // getter and setter names - proxyGettersAndSetters = 'Date|Hours|Minutes|Seconds|Milliseconds'.split('|'), - unitMillisecondFactors = { - 'Milliseconds' : 1, - 'Seconds' : 1e3, - 'Minutes' : 6e4, - 'Hours' : 36e5, - 'Days' : 864e5, - 'Months' : 2592e6, - 'Years' : 31536e6 - }, - - unitAliases = { - ms : 'millisecond', - s : 'second', - m : 'minute', - h : 'hour', - d : 'day', - D : 'date', - w : 'week', - W : 'isoWeek', - M : 'month', - y : 'year', - DDD : 'dayOfYear', - e : 'weekday', - E : 'isoWeekday', - gg: 'weekYear', - GG: 'isoWeekYear' - }, - - camelFunctions = { - dayofyear : 'dayOfYear', - isoweekday : 'isoWeekday', - isoweek : 'isoWeek', - weekyear : 'weekYear', - isoweekyear : 'isoWeekYear' - }, - - // format function strings - formatFunctions = {}, - - // tokens to ordinalize and pad - ordinalizeTokens = 'DDD w W M D d'.split(' '), - paddedTokens = 'M D H h m s w W'.split(' '), - - formatTokenFunctions = { - M : function () { - return this.month() + 1; - }, - MMM : function (format) { - return this.lang().monthsShort(this, format); - }, - MMMM : function (format) { - return this.lang().months(this, format); - }, - D : function () { - return this.date(); - }, - DDD : function () { - return this.dayOfYear(); - }, - d : function () { - return this.day(); - }, - dd : function (format) { - return this.lang().weekdaysMin(this, format); - }, - ddd : function (format) { - return this.lang().weekdaysShort(this, format); - }, - dddd : function (format) { - return this.lang().weekdays(this, format); - }, - w : function () { - return this.week(); - }, - W : function () { - return this.isoWeek(); - }, - YY : function () { - return leftZeroFill(this.year() % 100, 2); - }, - YYYY : function () { - return leftZeroFill(this.year(), 4); - }, - YYYYY : function () { - return leftZeroFill(this.year(), 5); - }, - YYYYYY : function () { - var y = this.year(), sign = y >= 0 ? '+' : '-'; - return sign + leftZeroFill(Math.abs(y), 6); - }, - gg : function () { - return leftZeroFill(this.weekYear() % 100, 2); - }, - gggg : function () { - return this.weekYear(); - }, - ggggg : function () { - return leftZeroFill(this.weekYear(), 5); - }, - GG : function () { - return leftZeroFill(this.isoWeekYear() % 100, 2); - }, - GGGG : function () { - return this.isoWeekYear(); - }, - GGGGG : function () { - return leftZeroFill(this.isoWeekYear(), 5); - }, - e : function () { - return this.weekday(); - }, - E : function () { - return this.isoWeekday(); - }, - a : function () { - return this.lang().meridiem(this.hours(), this.minutes(), true); - }, - A : function () { - return this.lang().meridiem(this.hours(), this.minutes(), false); - }, - H : function () { - return this.hours(); - }, - h : function () { - return this.hours() % 12 || 12; - }, - m : function () { - return this.minutes(); - }, - s : function () { - return this.seconds(); - }, - S : function () { - return toInt(this.milliseconds() / 100); - }, - SS : function () { - return leftZeroFill(toInt(this.milliseconds() / 10), 2); - }, - SSS : function () { - return leftZeroFill(this.milliseconds(), 3); - }, - SSSS : function () { - return leftZeroFill(this.milliseconds(), 3); - }, - Z : function () { - var a = -this.zone(), - b = "+"; - if (a < 0) { - a = -a; - b = "-"; - } - return b + leftZeroFill(toInt(a / 60), 2) + ":" + leftZeroFill(toInt(a) % 60, 2); - }, - ZZ : function () { - var a = -this.zone(), - b = "+"; - if (a < 0) { - a = -a; - b = "-"; - } - return b + leftZeroFill(toInt(a / 60), 2) + leftZeroFill(toInt(a) % 60, 2); - }, - z : function () { - return this.zoneAbbr(); - }, - zz : function () { - return this.zoneName(); - }, - X : function () { - return this.unix(); - }, - Q : function () { - return this.quarter(); - } - }, - - lists = ['months', 'monthsShort', 'weekdays', 'weekdaysShort', 'weekdaysMin']; - - function padToken(func, count) { - return function (a) { - return leftZeroFill(func.call(this, a), count); - }; - } - function ordinalizeToken(func, period) { - return function (a) { - return this.lang().ordinal(func.call(this, a), period); - }; + function utils_hooks__hooks () { + return hookCallback.apply(null, arguments); } - while (ordinalizeTokens.length) { - i = ordinalizeTokens.pop(); - formatTokenFunctions[i + 'o'] = ordinalizeToken(formatTokenFunctions[i], i); - } - while (paddedTokens.length) { - i = paddedTokens.pop(); - formatTokenFunctions[i + i] = padToken(formatTokenFunctions[i], 2); + // This is done to register the method called with moment() + // without creating circular dependencies. + function setHookCallback (callback) { + hookCallback = callback; } - formatTokenFunctions.DDDD = padToken(formatTokenFunctions.DDD, 3); - - - /************************************ - Constructors - ************************************/ - - function Language() { + function isArray(input) { + return input instanceof Array || Object.prototype.toString.call(input) === '[object Array]'; } - // Moment prototype object - function Moment(config) { - checkOverflow(config); - extend(this, config); + function isDate(input) { + return input instanceof Date || Object.prototype.toString.call(input) === '[object Date]'; } - // Duration Constructor - function Duration(duration) { - var normalizedInput = normalizeObjectUnits(duration), - years = normalizedInput.year || 0, - months = normalizedInput.month || 0, - weeks = normalizedInput.week || 0, - days = normalizedInput.day || 0, - hours = normalizedInput.hour || 0, - minutes = normalizedInput.minute || 0, - seconds = normalizedInput.second || 0, - milliseconds = normalizedInput.millisecond || 0; - - // representation for dateAddRemove - this._milliseconds = +milliseconds + - seconds * 1e3 + // 1000 - minutes * 6e4 + // 1000 * 60 - hours * 36e5; // 1000 * 60 * 60 - // Because of dateAddRemove treats 24 hours as different from a - // day when working around DST, we need to store them separately - this._days = +days + - weeks * 7; - // It is impossible translate months into days without knowing - // which months you are are talking about, so we have to store - // it separately. - this._months = +months + - years * 12; - - this._data = {}; - - this._bubble(); + function map(arr, fn) { + var res = [], i; + for (i = 0; i < arr.length; ++i) { + res.push(fn(arr[i], i)); + } + return res; } - /************************************ - Helpers - ************************************/ - + function hasOwnProp(a, b) { + return Object.prototype.hasOwnProperty.call(a, b); + } function extend(a, b) { for (var i in b) { - if (b.hasOwnProperty(i)) { + if (hasOwnProp(b, i)) { a[i] = b[i]; } } - if (b.hasOwnProperty("toString")) { + if (hasOwnProp(b, 'toString')) { a.toString = b.toString; } - if (b.hasOwnProperty("valueOf")) { + if (hasOwnProp(b, 'valueOf')) { a.valueOf = b.valueOf; } return a; } - function absRound(number) { - if (number < 0) { - return Math.ceil(number); - } else { - return Math.floor(number); + function create_utc__createUTC (input, format, locale, strict) { + return createLocalOrUTC(input, format, locale, strict, true).utc(); + } + + function defaultParsingFlags() { + // We need to deep clone this object. + return { + empty : false, + unusedTokens : [], + unusedInput : [], + overflow : -2, + charsLeftOver : 0, + nullInput : false, + invalidMonth : null, + invalidFormat : false, + userInvalidated : false, + iso : false, + parsedDateParts : [], + meridiem : null + }; + } + + function getParsingFlags(m) { + if (m._pf == null) { + m._pf = defaultParsingFlags(); } + return m._pf; } - // left zero fill a number - // see http://jsperf.com/left-zero-filling for performance comparison - function leftZeroFill(number, targetLength, forceSign) { - var output = Math.abs(number) + '', - sign = number >= 0; + var some; + if (Array.prototype.some) { + some = Array.prototype.some; + } else { + some = function (fun) { + var t = Object(this); + var len = t.length >>> 0; + + for (var i = 0; i < len; i++) { + if (i in t && fun.call(this, t[i], i, t)) { + return true; + } + } + + return false; + }; + } + + function valid__isValid(m) { + if (m._isValid == null) { + var flags = getParsingFlags(m); + var parsedParts = some.call(flags.parsedDateParts, function (i) { + return i != null; + }); + m._isValid = !isNaN(m._d.getTime()) && + flags.overflow < 0 && + !flags.empty && + !flags.invalidMonth && + !flags.invalidWeekday && + !flags.nullInput && + !flags.invalidFormat && + !flags.userInvalidated && + (!flags.meridiem || (flags.meridiem && parsedParts)); - while (output.length < targetLength) { - output = '0' + output; + if (m._strict) { + m._isValid = m._isValid && + flags.charsLeftOver === 0 && + flags.unusedTokens.length === 0 && + flags.bigHour === undefined; + } } - return (sign ? (forceSign ? '+' : '') : '-') + output; + return m._isValid; } - // helper function for _.addTime and _.subtractTime - function addOrSubtractDurationFromMoment(mom, duration, isAdding, ignoreUpdateOffset) { - var milliseconds = duration._milliseconds, - days = duration._days, - months = duration._months, - minutes, - hours; + function valid__createInvalid (flags) { + var m = create_utc__createUTC(NaN); + if (flags != null) { + extend(getParsingFlags(m), flags); + } + else { + getParsingFlags(m).userInvalidated = true; + } - if (milliseconds) { - mom._d.setTime(+mom._d + milliseconds * isAdding); + return m; + } + + function isUndefined(input) { + return input === void 0; + } + + // Plugins that add properties should also add the key here (null value), + // so we can properly clone ourselves. + var momentProperties = utils_hooks__hooks.momentProperties = []; + + function copyConfig(to, from) { + var i, prop, val; + + if (!isUndefined(from._isAMomentObject)) { + to._isAMomentObject = from._isAMomentObject; } - // store the minutes and hours so we can restore them - if (days || months) { - minutes = mom.minute(); - hours = mom.hour(); + if (!isUndefined(from._i)) { + to._i = from._i; } - if (days) { - mom.date(mom.date() + days * isAdding); + if (!isUndefined(from._f)) { + to._f = from._f; } - if (months) { - mom.month(mom.month() + months * isAdding); + if (!isUndefined(from._l)) { + to._l = from._l; + } + if (!isUndefined(from._strict)) { + to._strict = from._strict; + } + if (!isUndefined(from._tzm)) { + to._tzm = from._tzm; + } + if (!isUndefined(from._isUTC)) { + to._isUTC = from._isUTC; + } + if (!isUndefined(from._offset)) { + to._offset = from._offset; + } + if (!isUndefined(from._pf)) { + to._pf = getParsingFlags(from); } - if (milliseconds && !ignoreUpdateOffset) { - moment.updateOffset(mom); + if (!isUndefined(from._locale)) { + to._locale = from._locale; } - // restore the minutes and hours after possibly changing dst - if (days || months) { - mom.minute(minutes); - mom.hour(hours); + + if (momentProperties.length > 0) { + for (i in momentProperties) { + prop = momentProperties[i]; + val = from[prop]; + if (!isUndefined(val)) { + to[prop] = val; + } + } } + + return to; } - // check if is an array - function isArray(input) { - return Object.prototype.toString.call(input) === '[object Array]'; + var updateInProgress = false; + + // Moment prototype object + function Moment(config) { + copyConfig(this, config); + this._d = new Date(config._d != null ? config._d.getTime() : NaN); + // Prevent infinite loop in case updateOffset creates new moment + // objects. + if (updateInProgress === false) { + updateInProgress = true; + utils_hooks__hooks.updateOffset(this); + updateInProgress = false; + } } - function isDate(input) { - return Object.prototype.toString.call(input) === '[object Date]' || - input instanceof Date; + function isMoment (obj) { + return obj instanceof Moment || (obj != null && obj._isAMomentObject != null); + } + + function absFloor (number) { + if (number < 0) { + return Math.ceil(number); + } else { + return Math.floor(number); + } + } + + function toInt(argumentForCoercion) { + var coercedNumber = +argumentForCoercion, + value = 0; + + if (coercedNumber !== 0 && isFinite(coercedNumber)) { + value = absFloor(coercedNumber); + } + + return value; } // compare two arrays, return the number of differences @@ -1960,432 +1781,379 @@ if (typeof define === 'function' && define.amd) { return diffs + lengthDiff; } - function normalizeUnits(units) { - if (units) { - var lowered = units.toLowerCase().replace(/(.)s$/, '$1'); - units = unitAliases[units] || camelFunctions[lowered] || lowered; + function warn(msg) { + if (utils_hooks__hooks.suppressDeprecationWarnings === false && + (typeof console !== 'undefined') && console.warn) { + console.warn('Deprecation warning: ' + msg); } - return units; } - function normalizeObjectUnits(inputObject) { - var normalizedInput = {}, - normalizedProp, - prop; + function deprecate(msg, fn) { + var firstTime = true; - for (prop in inputObject) { - if (inputObject.hasOwnProperty(prop)) { - normalizedProp = normalizeUnits(prop); - if (normalizedProp) { - normalizedInput[normalizedProp] = inputObject[prop]; - } + return extend(function () { + if (utils_hooks__hooks.deprecationHandler != null) { + utils_hooks__hooks.deprecationHandler(null, msg); } - } - - return normalizedInput; + if (firstTime) { + warn(msg + '\nArguments: ' + Array.prototype.slice.call(arguments).join(', ') + '\n' + (new Error()).stack); + firstTime = false; + } + return fn.apply(this, arguments); + }, fn); } - function makeList(field) { - var count, setter; + var deprecations = {}; - if (field.indexOf('week') === 0) { - count = 7; - setter = 'day'; - } - else if (field.indexOf('month') === 0) { - count = 12; - setter = 'month'; + function deprecateSimple(name, msg) { + if (utils_hooks__hooks.deprecationHandler != null) { + utils_hooks__hooks.deprecationHandler(name, msg); } - else { - return; + if (!deprecations[name]) { + warn(msg); + deprecations[name] = true; } + } - moment[field] = function (format, index) { - var i, getter, - method = moment.fn._lang[field], - results = []; - - if (typeof format === 'number') { - index = format; - format = undefined; - } - - getter = function (i) { - var m = moment().utc().set(setter, i); - return method.call(moment.fn._lang, m, format || ''); - }; + utils_hooks__hooks.suppressDeprecationWarnings = false; + utils_hooks__hooks.deprecationHandler = null; - if (index != null) { - return getter(index); - } - else { - for (i = 0; i < count; i++) { - results.push(getter(i)); - } - return results; - } - }; + function isFunction(input) { + return input instanceof Function || Object.prototype.toString.call(input) === '[object Function]'; } - function toInt(argumentForCoercion) { - var coercedNumber = +argumentForCoercion, - value = 0; + function isObject(input) { + return Object.prototype.toString.call(input) === '[object Object]'; + } - if (coercedNumber !== 0 && isFinite(coercedNumber)) { - if (coercedNumber >= 0) { - value = Math.floor(coercedNumber); + function locale_set__set (config) { + var prop, i; + for (i in config) { + prop = config[i]; + if (isFunction(prop)) { + this[i] = prop; } else { - value = Math.ceil(coercedNumber); + this['_' + i] = prop; } } - - return value; + this._config = config; + // Lenient ordinal parsing accepts just a number in addition to + // number + (possibly) stuff coming from _ordinalParseLenient. + this._ordinalParseLenient = new RegExp(this._ordinalParse.source + '|' + (/\d{1,2}/).source); } - function daysInMonth(year, month) { - return new Date(Date.UTC(year, month + 1, 0)).getUTCDate(); - } - - function daysInYear(year) { - return isLeapYear(year) ? 366 : 365; + function mergeConfigs(parentConfig, childConfig) { + var res = extend({}, parentConfig), prop; + for (prop in childConfig) { + if (hasOwnProp(childConfig, prop)) { + if (isObject(parentConfig[prop]) && isObject(childConfig[prop])) { + res[prop] = {}; + extend(res[prop], parentConfig[prop]); + extend(res[prop], childConfig[prop]); + } else if (childConfig[prop] != null) { + res[prop] = childConfig[prop]; + } else { + delete res[prop]; + } + } + } + return res; } - function isLeapYear(year) { - return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; + function Locale(config) { + if (config != null) { + this.set(config); + } } - function checkOverflow(m) { - var overflow; - if (m._a && m._pf.overflow === -2) { - overflow = - m._a[MONTH] < 0 || m._a[MONTH] > 11 ? MONTH : - m._a[DATE] < 1 || m._a[DATE] > daysInMonth(m._a[YEAR], m._a[MONTH]) ? DATE : - m._a[HOUR] < 0 || m._a[HOUR] > 23 ? HOUR : - m._a[MINUTE] < 0 || m._a[MINUTE] > 59 ? MINUTE : - m._a[SECOND] < 0 || m._a[SECOND] > 59 ? SECOND : - m._a[MILLISECOND] < 0 || m._a[MILLISECOND] > 999 ? MILLISECOND : - -1; + var keys; - if (m._pf._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) { - overflow = DATE; + if (Object.keys) { + keys = Object.keys; + } else { + keys = function (obj) { + var i, res = []; + for (i in obj) { + if (hasOwnProp(obj, i)) { + res.push(i); + } } - - m._pf.overflow = overflow; - } + return res; + }; } - function initializeParsingFlags(config) { - config._pf = { - empty : false, - unusedTokens : [], - unusedInput : [], - overflow : -2, - charsLeftOver : 0, - nullInput : false, - invalidMonth : null, - invalidFormat : false, - userInvalidated : false, - iso: false - }; + // internal storage for locale config files + var locales = {}; + var globalLocale; + + function normalizeLocale(key) { + return key ? key.toLowerCase().replace('_', '-') : key; } - function isValid(m) { - if (m._isValid == null) { - m._isValid = !isNaN(m._d.getTime()) && - m._pf.overflow < 0 && - !m._pf.empty && - !m._pf.invalidMonth && - !m._pf.nullInput && - !m._pf.invalidFormat && - !m._pf.userInvalidated; + // pick the locale from the array + // try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each + // substring from most specific to least, but move to the next array item if it's a more specific variant than the current root + function chooseLocale(names) { + var i = 0, j, next, locale, split; - if (m._strict) { - m._isValid = m._isValid && - m._pf.charsLeftOver === 0 && - m._pf.unusedTokens.length === 0; + while (i < names.length) { + split = normalizeLocale(names[i]).split('-'); + j = split.length; + next = normalizeLocale(names[i + 1]); + next = next ? next.split('-') : null; + while (j > 0) { + locale = loadLocale(split.slice(0, j).join('-')); + if (locale) { + return locale; + } + if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) { + //the next array item is better than a shallower substring of this one + break; + } + j--; } + i++; } - return m._isValid; - } - - function normalizeLanguage(key) { - return key ? key.toLowerCase().replace('_', '-') : key; + return null; } - // Return a moment from input, that is local/utc/zone equivalent to model. - function makeAs(input, model) { - return model._isUTC ? moment(input).zone(model._offset || 0) : - moment(input).local(); + function loadLocale(name) { + var oldLocale = null; + // TODO: Find a better way to register and load all the locales in Node + if (!locales[name] && (typeof module !== 'undefined') && + module && module.exports) { + try { + oldLocale = globalLocale._abbr; + require('./locale/' + name); + // because defineLocale currently also sets the global locale, we + // want to undo that for lazy loaded locales + locale_locales__getSetGlobalLocale(oldLocale); + } catch (e) { } + } + return locales[name]; } - /************************************ - Languages - ************************************/ + // This function will load locale and then set the global locale. If + // no arguments are passed in, it will simply return the current global + // locale key. + function locale_locales__getSetGlobalLocale (key, values) { + var data; + if (key) { + if (isUndefined(values)) { + data = locale_locales__getLocale(key); + } + else { + data = defineLocale(key, values); + } + if (data) { + // moment.duration._locale = moment._locale = data; + globalLocale = data; + } + } - extend(Language.prototype, { + return globalLocale._abbr; + } - set : function (config) { - var prop, i; - for (i in config) { - prop = config[i]; - if (typeof prop === 'function') { - this[i] = prop; + function defineLocale (name, config) { + if (config !== null) { + config.abbr = name; + if (locales[name] != null) { + deprecateSimple('defineLocaleOverride', + 'use moment.updateLocale(localeName, config) to change ' + + 'an existing locale. moment.defineLocale(localeName, ' + + 'config) should only be used for creating a new locale'); + config = mergeConfigs(locales[name]._config, config); + } else if (config.parentLocale != null) { + if (locales[config.parentLocale] != null) { + config = mergeConfigs(locales[config.parentLocale]._config, config); } else { - this['_' + i] = prop; + // treat as if there is no base config + deprecateSimple('parentLocaleUndefined', + 'specified parentLocale is not defined yet'); } } - }, - - _months : "January_February_March_April_May_June_July_August_September_October_November_December".split("_"), - months : function (m) { - return this._months[m.month()]; - }, + locales[name] = new Locale(config); - _monthsShort : "Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"), - monthsShort : function (m) { - return this._monthsShort[m.month()]; - }, + // backwards compat for now: also set the locale + locale_locales__getSetGlobalLocale(name); - monthsParse : function (monthName) { - var i, mom, regex; + return locales[name]; + } else { + // useful for testing + delete locales[name]; + return null; + } + } - if (!this._monthsParse) { - this._monthsParse = []; + function updateLocale(name, config) { + if (config != null) { + var locale; + if (locales[name] != null) { + config = mergeConfigs(locales[name]._config, config); } + locale = new Locale(config); + locale.parentLocale = locales[name]; + locales[name] = locale; - for (i = 0; i < 12; i++) { - // make the regex if we don't have it already - if (!this._monthsParse[i]) { - mom = moment.utc([2000, i]); - regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, ''); - this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i'); - } - // test the regex - if (this._monthsParse[i].test(monthName)) { - return i; + // backwards compat for now: also set the locale + locale_locales__getSetGlobalLocale(name); + } else { + // pass null for config to unupdate, useful for tests + if (locales[name] != null) { + if (locales[name].parentLocale != null) { + locales[name] = locales[name].parentLocale; + } else if (locales[name] != null) { + delete locales[name]; } } - }, - - _weekdays : "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"), - weekdays : function (m) { - return this._weekdays[m.day()]; - }, + } + return locales[name]; + } - _weekdaysShort : "Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"), - weekdaysShort : function (m) { - return this._weekdaysShort[m.day()]; - }, + // returns locale data + function locale_locales__getLocale (key) { + var locale; - _weekdaysMin : "Su_Mo_Tu_We_Th_Fr_Sa".split("_"), - weekdaysMin : function (m) { - return this._weekdaysMin[m.day()]; - }, + if (key && key._locale && key._locale._abbr) { + key = key._locale._abbr; + } - weekdaysParse : function (weekdayName) { - var i, mom, regex; + if (!key) { + return globalLocale; + } - if (!this._weekdaysParse) { - this._weekdaysParse = []; + if (!isArray(key)) { + //short-circuit everything else + locale = loadLocale(key); + if (locale) { + return locale; } + key = [key]; + } - for (i = 0; i < 7; i++) { - // make the regex if we don't have it already - if (!this._weekdaysParse[i]) { - mom = moment([2000, 1]).day(i); - regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, ''); - this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i'); - } - // test the regex - if (this._weekdaysParse[i].test(weekdayName)) { - return i; - } - } - }, + return chooseLocale(key); + } - _longDateFormat : { - LT : "h:mm A", - L : "MM/DD/YYYY", - LL : "MMMM D YYYY", - LLL : "MMMM D YYYY LT", - LLLL : "dddd, MMMM D YYYY LT" - }, - longDateFormat : function (key) { - var output = this._longDateFormat[key]; - if (!output && this._longDateFormat[key.toUpperCase()]) { - output = this._longDateFormat[key.toUpperCase()].replace(/MMMM|MM|DD|dddd/g, function (val) { - return val.slice(1); - }); - this._longDateFormat[key] = output; - } - return output; - }, - - isPM : function (input) { - // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays - // Using charAt should be more compatible. - return ((input + '').toLowerCase().charAt(0) === 'p'); - }, + function locale_locales__listLocales() { + return keys(locales); + } - _meridiemParse : /[ap]\.?m?\.?/i, - meridiem : function (hours, minutes, isLower) { - if (hours > 11) { - return isLower ? 'pm' : 'PM'; - } else { - return isLower ? 'am' : 'AM'; - } - }, + var aliases = {}; - _calendar : { - sameDay : '[Today at] LT', - nextDay : '[Tomorrow at] LT', - nextWeek : 'dddd [at] LT', - lastDay : '[Yesterday at] LT', - lastWeek : '[Last] dddd [at] LT', - sameElse : 'L' - }, - calendar : function (key, mom) { - var output = this._calendar[key]; - return typeof output === 'function' ? output.apply(mom) : output; - }, + function addUnitAlias (unit, shorthand) { + var lowerCase = unit.toLowerCase(); + aliases[lowerCase] = aliases[lowerCase + 's'] = aliases[shorthand] = unit; + } - _relativeTime : { - future : "in %s", - past : "%s ago", - s : "a few seconds", - m : "a minute", - mm : "%d minutes", - h : "an hour", - hh : "%d hours", - d : "a day", - dd : "%d days", - M : "a month", - MM : "%d months", - y : "a year", - yy : "%d years" - }, - relativeTime : function (number, withoutSuffix, string, isFuture) { - var output = this._relativeTime[string]; - return (typeof output === 'function') ? - output(number, withoutSuffix, string, isFuture) : - output.replace(/%d/i, number); - }, - pastFuture : function (diff, output) { - var format = this._relativeTime[diff > 0 ? 'future' : 'past']; - return typeof format === 'function' ? format(output) : format.replace(/%s/i, output); - }, + function normalizeUnits(units) { + return typeof units === 'string' ? aliases[units] || aliases[units.toLowerCase()] : undefined; + } - ordinal : function (number) { - return this._ordinal.replace("%d", number); - }, - _ordinal : "%d", + function normalizeObjectUnits(inputObject) { + var normalizedInput = {}, + normalizedProp, + prop; - preparse : function (string) { - return string; - }, + for (prop in inputObject) { + if (hasOwnProp(inputObject, prop)) { + normalizedProp = normalizeUnits(prop); + if (normalizedProp) { + normalizedInput[normalizedProp] = inputObject[prop]; + } + } + } - postformat : function (string) { - return string; - }, + return normalizedInput; + } - week : function (mom) { - return weekOfYear(mom, this._week.dow, this._week.doy).week; - }, + function makeGetSet (unit, keepTime) { + return function (value) { + if (value != null) { + get_set__set(this, unit, value); + utils_hooks__hooks.updateOffset(this, keepTime); + return this; + } else { + return get_set__get(this, unit); + } + }; + } - _week : { - dow : 0, // Sunday is the first day of the week. - doy : 6 // The week that contains Jan 1st is the first week of the year. - }, + function get_set__get (mom, unit) { + return mom.isValid() ? + mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]() : NaN; + } - _invalidDate: 'Invalid date', - invalidDate: function () { - return this._invalidDate; + function get_set__set (mom, unit, value) { + if (mom.isValid()) { + mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value); } - }); + } - // Loads a language definition into the `languages` cache. The function - // takes a key and optionally values. If not in the browser and no values - // are provided, it will load the language file module. As a convenience, - // this function also returns the language values. - function loadLang(key, values) { - values.abbr = key; - if (!languages[key]) { - languages[key] = new Language(); + // MOMENTS + + function getSet (units, value) { + var unit; + if (typeof units === 'object') { + for (unit in units) { + this.set(unit, units[unit]); + } + } else { + units = normalizeUnits(units); + if (isFunction(this[units])) { + return this[units](value); + } } - languages[key].set(values); - return languages[key]; + return this; } - // Remove a language from the `languages` cache. Mostly useful in tests. - function unloadLang(key) { - delete languages[key]; + function zeroFill(number, targetLength, forceSign) { + var absNumber = '' + Math.abs(number), + zerosToFill = targetLength - absNumber.length, + sign = number >= 0; + return (sign ? (forceSign ? '+' : '') : '-') + + Math.pow(10, Math.max(0, zerosToFill)).toString().substr(1) + absNumber; } - // Determines which language definition to use and returns it. - // - // With no parameters, it will return the global language. If you - // pass in a language key, such as 'en', it will return the - // definition for 'en', so long as 'en' has already been loaded using - // moment.lang. - function getLangDefinition(key) { - var i = 0, j, lang, next, split, - get = function (k) { - if (!languages[k] && hasModule) { - try { - require('./lang/' + k); - } catch (e) { } - } - return languages[k]; - }; + var formattingTokens = /(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g; - if (!key) { - return moment.fn._lang; - } + var localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g; - if (!isArray(key)) { - //short-circuit everything else - lang = get(key); - if (lang) { - return lang; - } - key = [key]; - } + var formatFunctions = {}; - //pick the language from the array - //try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each - //substring from most specific to least, but move to the next array item if it's a more specific variant than the current root - while (i < key.length) { - split = normalizeLanguage(key[i]).split('-'); - j = split.length; - next = normalizeLanguage(key[i + 1]); - next = next ? next.split('-') : null; - while (j > 0) { - lang = get(split.slice(0, j).join('-')); - if (lang) { - return lang; - } - if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) { - //the next array item is better than a shallower substring of this one - break; - } - j--; - } - i++; + var formatTokenFunctions = {}; + + // token: 'M' + // padded: ['MM', 2] + // ordinal: 'Mo' + // callback: function () { this.month() + 1 } + function addFormatToken (token, padded, ordinal, callback) { + var func = callback; + if (typeof callback === 'string') { + func = function () { + return this[callback](); + }; + } + if (token) { + formatTokenFunctions[token] = func; + } + if (padded) { + formatTokenFunctions[padded[0]] = function () { + return zeroFill(func.apply(this, arguments), padded[1], padded[2]); + }; + } + if (ordinal) { + formatTokenFunctions[ordinal] = function () { + return this.localeData().ordinal(func.apply(this, arguments), token); + }; } - return moment.fn._lang; } - /************************************ - Formatting - ************************************/ - - function removeFormattingTokens(input) { if (input.match(/\[[\s\S]/)) { - return input.replace(/^\[|\]$/g, ""); + return input.replace(/^\[|\]$/g, ''); } - return input.replace(/\\/g, ""); + return input.replace(/\\/g, ''); } function makeFormatFunction(format) { @@ -2400,7 +2168,7 @@ if (typeof define === 'function' && define.amd) { } return function (mom) { - var output = ""; + var output = '', i; for (i = 0; i < length; i++) { output += array[i] instanceof Function ? array[i].call(mom, format) : array[i]; } @@ -2410,25 +2178,21 @@ if (typeof define === 'function' && define.amd) { // format date using native date object function formatMoment(m, format) { - if (!m.isValid()) { - return m.lang().invalidDate(); + return m.localeData().invalidDate(); } - format = expandFormat(format, m.lang()); - - if (!formatFunctions[format]) { - formatFunctions[format] = makeFormatFunction(format); - } + format = expandFormat(format, m.localeData()); + formatFunctions[format] = formatFunctions[format] || makeFormatFunction(format); return formatFunctions[format](m); } - function expandFormat(format, lang) { + function expandFormat(format, locale) { var i = 5; function replaceLongDateFormatTokens(input) { - return lang.longDateFormat(input) || input; + return locale.longDateFormat(input) || input; } localFormattingTokens.lastIndex = 0; @@ -2441,1455 +2205,3367 @@ if (typeof define === 'function' && define.amd) { return format; } + var match1 = /\d/; // 0 - 9 + var match2 = /\d\d/; // 00 - 99 + var match3 = /\d{3}/; // 000 - 999 + var match4 = /\d{4}/; // 0000 - 9999 + var match6 = /[+-]?\d{6}/; // -999999 - 999999 + var match1to2 = /\d\d?/; // 0 - 99 + var match3to4 = /\d\d\d\d?/; // 999 - 9999 + var match5to6 = /\d\d\d\d\d\d?/; // 99999 - 999999 + var match1to3 = /\d{1,3}/; // 0 - 999 + var match1to4 = /\d{1,4}/; // 0 - 9999 + var match1to6 = /[+-]?\d{1,6}/; // -999999 - 999999 - /************************************ - Parsing - ************************************/ - - - // get the regex to find the next token - function getParseRegexForToken(token, config) { - var a, strict = config._strict; - switch (token) { - case 'DDDD': - return parseTokenThreeDigits; - case 'YYYY': - case 'GGGG': - case 'gggg': - return strict ? parseTokenFourDigits : parseTokenOneToFourDigits; - case 'YYYYYY': - case 'YYYYY': - case 'GGGGG': - case 'ggggg': - return strict ? parseTokenSixDigits : parseTokenOneToSixDigits; - case 'S': - if (strict) { return parseTokenOneDigit; } - /* falls through */ - case 'SS': - if (strict) { return parseTokenTwoDigits; } - /* falls through */ - case 'SSS': - case 'DDD': - return strict ? parseTokenThreeDigits : parseTokenOneToThreeDigits; - case 'MMM': - case 'MMMM': - case 'dd': - case 'ddd': - case 'dddd': - return parseTokenWord; - case 'a': - case 'A': - return getLangDefinition(config._l)._meridiemParse; - case 'X': - return parseTokenTimestampMs; - case 'Z': - case 'ZZ': - return parseTokenTimezone; - case 'T': - return parseTokenT; - case 'SSSS': - return parseTokenDigits; - case 'MM': - case 'DD': - case 'YY': - case 'GG': - case 'gg': - case 'HH': - case 'hh': - case 'mm': - case 'ss': - case 'ww': - case 'WW': - return strict ? parseTokenTwoDigits : parseTokenOneOrTwoDigits; - case 'M': - case 'D': - case 'd': - case 'H': - case 'h': - case 'm': - case 's': - case 'w': - case 'W': - case 'e': - case 'E': - return strict ? parseTokenOneDigit : parseTokenOneOrTwoDigits; - default : - a = new RegExp(regexpEscape(unescapeFormat(token.replace('\\', '')), "i")); - return a; - } + var matchUnsigned = /\d+/; // 0 - inf + var matchSigned = /[+-]?\d+/; // -inf - inf + + var matchOffset = /Z|[+-]\d\d:?\d\d/gi; // +00:00 -00:00 +0000 -0000 or Z + var matchShortOffset = /Z|[+-]\d\d(?::?\d\d)?/gi; // +00 -00 +00:00 -00:00 +0000 -0000 or Z + + var matchTimestamp = /[+-]?\d+(\.\d{1,3})?/; // 123456789 123456789.123 + + // any word (or two) characters or numbers including two/three word month in arabic. + // includes scottish gaelic two word and hyphenated months + var matchWord = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i; + + + var regexes = {}; + + function addRegexToken (token, regex, strictRegex) { + regexes[token] = isFunction(regex) ? regex : function (isStrict, localeData) { + return (isStrict && strictRegex) ? strictRegex : regex; + }; } - function timezoneMinutesFromString(string) { - string = string || ""; - var possibleTzMatches = (string.match(parseTokenTimezone) || []), - tzChunk = possibleTzMatches[possibleTzMatches.length - 1] || [], - parts = (tzChunk + '').match(parseTimezoneChunker) || ['-', 0, 0], - minutes = +(parts[1] * 60) + toInt(parts[2]); + function getParseRegexForToken (token, config) { + if (!hasOwnProp(regexes, token)) { + return new RegExp(unescapeFormat(token)); + } + + return regexes[token](config._strict, config._locale); + } - return parts[0] === '+' ? -minutes : minutes; + // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript + function unescapeFormat(s) { + return regexEscape(s.replace('\\', '').replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) { + return p1 || p2 || p3 || p4; + })); } - // function to convert string input to date - function addTimeToArrayFromToken(token, input, config) { - var a, datePartArray = config._a; + function regexEscape(s) { + return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); + } - switch (token) { - // MONTH - case 'M' : // fall through to MM - case 'MM' : - if (input != null) { - datePartArray[MONTH] = toInt(input) - 1; - } - break; - case 'MMM' : // fall through to MMMM - case 'MMMM' : - a = getLangDefinition(config._l).monthsParse(input); - // if we didn't find a month name, mark the date as invalid. - if (a != null) { - datePartArray[MONTH] = a; - } else { - config._pf.invalidMonth = input; - } - break; - // DAY OF MONTH - case 'D' : // fall through to DD - case 'DD' : - if (input != null) { - datePartArray[DATE] = toInt(input); - } - break; - // DAY OF YEAR - case 'DDD' : // fall through to DDDD - case 'DDDD' : - if (input != null) { - config._dayOfYear = toInt(input); - } + var tokens = {}; - break; - // YEAR - case 'YY' : - datePartArray[YEAR] = toInt(input) + (toInt(input) > 68 ? 1900 : 2000); - break; - case 'YYYY' : - case 'YYYYY' : - case 'YYYYYY' : - datePartArray[YEAR] = toInt(input); - break; - // AM / PM - case 'a' : // fall through to A - case 'A' : - config._isPm = getLangDefinition(config._l).isPM(input); - break; - // 24 HOUR - case 'H' : // fall through to hh - case 'HH' : // fall through to hh - case 'h' : // fall through to hh - case 'hh' : - datePartArray[HOUR] = toInt(input); - break; - // MINUTE - case 'm' : // fall through to mm - case 'mm' : - datePartArray[MINUTE] = toInt(input); - break; - // SECOND - case 's' : // fall through to ss - case 'ss' : - datePartArray[SECOND] = toInt(input); - break; - // MILLISECOND - case 'S' : - case 'SS' : - case 'SSS' : - case 'SSSS' : - datePartArray[MILLISECOND] = toInt(('0.' + input) * 1000); - break; - // UNIX TIMESTAMP WITH MS - case 'X': - config._d = new Date(parseFloat(input) * 1000); - break; - // TIMEZONE - case 'Z' : // fall through to ZZ - case 'ZZ' : - config._useUTC = true; - config._tzm = timezoneMinutesFromString(input); - break; - case 'w': - case 'ww': - case 'W': - case 'WW': - case 'd': - case 'dd': - case 'ddd': - case 'dddd': - case 'e': - case 'E': - token = token.substr(0, 1); - /* falls through */ - case 'gg': - case 'gggg': - case 'GG': - case 'GGGG': - case 'GGGGG': - token = token.substr(0, 2); - if (input) { - config._w = config._w || {}; - config._w[token] = input; - } - break; + function addParseToken (token, callback) { + var i, func = callback; + if (typeof token === 'string') { + token = [token]; + } + if (typeof callback === 'number') { + func = function (input, array) { + array[callback] = toInt(input); + }; + } + for (i = 0; i < token.length; i++) { + tokens[token[i]] = func; } } - // convert an array to a date. - // the array should mirror the parameters below - // note: all values past the year are optional and will default to the lowest possible value. - // [year, month, day , hour, minute, second, millisecond] - function dateFromConfig(config) { - var i, date, input = [], currentDate, - yearToUse, fixYear, w, temp, lang, weekday, week; + function addWeekParseToken (token, callback) { + addParseToken(token, function (input, array, config, token) { + config._w = config._w || {}; + callback(input, config._w, config, token); + }); + } - if (config._d) { - return; + function addTimeToArrayFromToken(token, input, config) { + if (input != null && hasOwnProp(tokens, token)) { + tokens[token](input, config._a, config, token); } + } - currentDate = currentDateArray(config); + var YEAR = 0; + var MONTH = 1; + var DATE = 2; + var HOUR = 3; + var MINUTE = 4; + var SECOND = 5; + var MILLISECOND = 6; + var WEEK = 7; + var WEEKDAY = 8; - //compute day of the year from weeks and weekdays - if (config._w && config._a[DATE] == null && config._a[MONTH] == null) { - fixYear = function (val) { - var int_val = parseInt(val, 10); - return val ? - (val.length < 3 ? (int_val > 68 ? 1900 + int_val : 2000 + int_val) : int_val) : - (config._a[YEAR] == null ? moment().weekYear() : config._a[YEAR]); - }; + var indexOf; - w = config._w; - if (w.GG != null || w.W != null || w.E != null) { - temp = dayOfYearFromWeeks(fixYear(w.GG), w.W || 1, w.E, 4, 1); + if (Array.prototype.indexOf) { + indexOf = Array.prototype.indexOf; + } else { + indexOf = function (o) { + // I know + var i; + for (i = 0; i < this.length; ++i) { + if (this[i] === o) { + return i; + } } - else { - lang = getLangDefinition(config._l); - weekday = w.d != null ? parseWeekday(w.d, lang) : - (w.e != null ? parseInt(w.e, 10) + lang._week.dow : 0); + return -1; + }; + } - week = parseInt(w.w, 10) || 1; + function daysInMonth(year, month) { + return new Date(Date.UTC(year, month + 1, 0)).getUTCDate(); + } - //if we're parsing 'd', then the low day numbers may be next week - if (w.d != null && weekday < lang._week.dow) { - week++; - } + // FORMATTING - temp = dayOfYearFromWeeks(fixYear(w.gg), week, weekday, lang._week.doy, lang._week.dow); - } + addFormatToken('M', ['MM', 2], 'Mo', function () { + return this.month() + 1; + }); - config._a[YEAR] = temp.year; - config._dayOfYear = temp.dayOfYear; - } + addFormatToken('MMM', 0, 0, function (format) { + return this.localeData().monthsShort(this, format); + }); - //if the day of the year is set, figure out what it is - if (config._dayOfYear) { - yearToUse = config._a[YEAR] == null ? currentDate[YEAR] : config._a[YEAR]; + addFormatToken('MMMM', 0, 0, function (format) { + return this.localeData().months(this, format); + }); - if (config._dayOfYear > daysInYear(yearToUse)) { - config._pf._overflowDayOfYear = true; - } + // ALIASES - date = makeUTCDate(yearToUse, 0, config._dayOfYear); - config._a[MONTH] = date.getUTCMonth(); - config._a[DATE] = date.getUTCDate(); - } + addUnitAlias('month', 'M'); - // Default to current date. - // * if no year, month, day of month are given, default to today - // * if day of month is given, default month and year - // * if month is given, default only year - // * if year is given, don't default anything - for (i = 0; i < 3 && config._a[i] == null; ++i) { - config._a[i] = input[i] = currentDate[i]; - } + // PARSING - // Zero out whatever was not defaulted, including time - for (; i < 7; i++) { - config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i]; + addRegexToken('M', match1to2); + addRegexToken('MM', match1to2, match2); + addRegexToken('MMM', function (isStrict, locale) { + return locale.monthsShortRegex(isStrict); + }); + addRegexToken('MMMM', function (isStrict, locale) { + return locale.monthsRegex(isStrict); + }); + + addParseToken(['M', 'MM'], function (input, array) { + array[MONTH] = toInt(input) - 1; + }); + + addParseToken(['MMM', 'MMMM'], function (input, array, config, token) { + var month = config._locale.monthsParse(input, token, config._strict); + // if we didn't find a month name, mark the date as invalid. + if (month != null) { + array[MONTH] = month; + } else { + getParsingFlags(config).invalidMonth = input; } + }); - // add the offsets to the time to be parsed so that we can have a clean array for checking isValid - input[HOUR] += toInt((config._tzm || 0) / 60); - input[MINUTE] += toInt((config._tzm || 0) % 60); + // LOCALES - config._d = (config._useUTC ? makeUTCDate : makeDate).apply(null, input); + var MONTHS_IN_FORMAT = /D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/; + var defaultLocaleMonths = 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'); + function localeMonths (m, format) { + return isArray(this._months) ? this._months[m.month()] : + this._months[MONTHS_IN_FORMAT.test(format) ? 'format' : 'standalone'][m.month()]; } - function dateFromObject(config) { - var normalizedInput; + var defaultLocaleMonthsShort = 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'); + function localeMonthsShort (m, format) { + return isArray(this._monthsShort) ? this._monthsShort[m.month()] : + this._monthsShort[MONTHS_IN_FORMAT.test(format) ? 'format' : 'standalone'][m.month()]; + } - if (config._d) { - return; + function units_month__handleStrictParse(monthName, format, strict) { + var i, ii, mom, llc = monthName.toLocaleLowerCase(); + if (!this._monthsParse) { + // this is not used + this._monthsParse = []; + this._longMonthsParse = []; + this._shortMonthsParse = []; + for (i = 0; i < 12; ++i) { + mom = create_utc__createUTC([2000, i]); + this._shortMonthsParse[i] = this.monthsShort(mom, '').toLocaleLowerCase(); + this._longMonthsParse[i] = this.months(mom, '').toLocaleLowerCase(); + } } - normalizedInput = normalizeObjectUnits(config._i); - config._a = [ - normalizedInput.year, - normalizedInput.month, - normalizedInput.day, - normalizedInput.hour, - normalizedInput.minute, - normalizedInput.second, - normalizedInput.millisecond - ]; - - dateFromConfig(config); - } - - function currentDateArray(config) { - var now = new Date(); - if (config._useUTC) { - return [ - now.getUTCFullYear(), - now.getUTCMonth(), - now.getUTCDate() - ]; + if (strict) { + if (format === 'MMM') { + ii = indexOf.call(this._shortMonthsParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf.call(this._longMonthsParse, llc); + return ii !== -1 ? ii : null; + } } else { - return [now.getFullYear(), now.getMonth(), now.getDate()]; + if (format === 'MMM') { + ii = indexOf.call(this._shortMonthsParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._longMonthsParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf.call(this._longMonthsParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._shortMonthsParse, llc); + return ii !== -1 ? ii : null; + } } } - // date from string and format string - function makeDateFromStringAndFormat(config) { - - config._a = []; - config._pf.empty = true; + function localeMonthsParse (monthName, format, strict) { + var i, mom, regex; - // This array is used to make a Date, either with `new Date` or `Date.UTC` - var lang = getLangDefinition(config._l), - string = '' + config._i, - i, parsedInput, tokens, token, skipped, - stringLength = string.length, - totalParsedInputLength = 0; + if (this._monthsParseExact) { + return units_month__handleStrictParse.call(this, monthName, format, strict); + } - tokens = expandFormat(config._f, lang).match(formattingTokens) || []; + if (!this._monthsParse) { + this._monthsParse = []; + this._longMonthsParse = []; + this._shortMonthsParse = []; + } - for (i = 0; i < tokens.length; i++) { - token = tokens[i]; - parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0]; - if (parsedInput) { - skipped = string.substr(0, string.indexOf(parsedInput)); - if (skipped.length > 0) { - config._pf.unusedInput.push(skipped); - } - string = string.slice(string.indexOf(parsedInput) + parsedInput.length); - totalParsedInputLength += parsedInput.length; + // TODO: add sorting + // Sorting makes sure if one month (or abbr) is a prefix of another + // see sorting in computeMonthsParse + for (i = 0; i < 12; i++) { + // make the regex if we don't have it already + mom = create_utc__createUTC([2000, i]); + if (strict && !this._longMonthsParse[i]) { + this._longMonthsParse[i] = new RegExp('^' + this.months(mom, '').replace('.', '') + '$', 'i'); + this._shortMonthsParse[i] = new RegExp('^' + this.monthsShort(mom, '').replace('.', '') + '$', 'i'); } - // don't parse if it's not a known token - if (formatTokenFunctions[token]) { - if (parsedInput) { - config._pf.empty = false; - } - else { - config._pf.unusedTokens.push(token); - } - addTimeToArrayFromToken(token, parsedInput, config); + if (!strict && !this._monthsParse[i]) { + regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, ''); + this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i'); } - else if (config._strict && !parsedInput) { - config._pf.unusedTokens.push(token); + // test the regex + if (strict && format === 'MMMM' && this._longMonthsParse[i].test(monthName)) { + return i; + } else if (strict && format === 'MMM' && this._shortMonthsParse[i].test(monthName)) { + return i; + } else if (!strict && this._monthsParse[i].test(monthName)) { + return i; } } + } - // add remaining unparsed input length to the string - config._pf.charsLeftOver = stringLength - totalParsedInputLength; - if (string.length > 0) { - config._pf.unusedInput.push(string); - } + // MOMENTS - // handle am pm - if (config._isPm && config._a[HOUR] < 12) { - config._a[HOUR] += 12; + function setMonth (mom, value) { + var dayOfMonth; + + if (!mom.isValid()) { + // No op + return mom; } - // if is 12 am, change hours to 0 - if (config._isPm === false && config._a[HOUR] === 12) { - config._a[HOUR] = 0; + + if (typeof value === 'string') { + if (/^\d+$/.test(value)) { + value = toInt(value); + } else { + value = mom.localeData().monthsParse(value); + // TODO: Another silent failure? + if (typeof value !== 'number') { + return mom; + } + } } - dateFromConfig(config); - checkOverflow(config); + dayOfMonth = Math.min(mom.date(), daysInMonth(mom.year(), value)); + mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth); + return mom; } - function unescapeFormat(s) { - return s.replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) { - return p1 || p2 || p3 || p4; - }); + function getSetMonth (value) { + if (value != null) { + setMonth(this, value); + utils_hooks__hooks.updateOffset(this, true); + return this; + } else { + return get_set__get(this, 'Month'); + } } - // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript - function regexpEscape(s) { - return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); + function getDaysInMonth () { + return daysInMonth(this.year(), this.month()); } - // date from string and array of format strings - function makeDateFromStringAndArray(config) { - var tempConfig, - bestMoment, + var defaultMonthsShortRegex = matchWord; + function monthsShortRegex (isStrict) { + if (this._monthsParseExact) { + if (!hasOwnProp(this, '_monthsRegex')) { + computeMonthsParse.call(this); + } + if (isStrict) { + return this._monthsShortStrictRegex; + } else { + return this._monthsShortRegex; + } + } else { + return this._monthsShortStrictRegex && isStrict ? + this._monthsShortStrictRegex : this._monthsShortRegex; + } + } - scoreToBeat, - i, - currentScore; - - if (config._f.length === 0) { - config._pf.invalidFormat = true; - config._d = new Date(NaN); - return; + var defaultMonthsRegex = matchWord; + function monthsRegex (isStrict) { + if (this._monthsParseExact) { + if (!hasOwnProp(this, '_monthsRegex')) { + computeMonthsParse.call(this); + } + if (isStrict) { + return this._monthsStrictRegex; + } else { + return this._monthsRegex; + } + } else { + return this._monthsStrictRegex && isStrict ? + this._monthsStrictRegex : this._monthsRegex; } + } - for (i = 0; i < config._f.length; i++) { - currentScore = 0; - tempConfig = extend({}, config); - initializeParsingFlags(tempConfig); - tempConfig._f = config._f[i]; - makeDateFromStringAndFormat(tempConfig); + function computeMonthsParse () { + function cmpLenRev(a, b) { + return b.length - a.length; + } - if (!isValid(tempConfig)) { - continue; - } + var shortPieces = [], longPieces = [], mixedPieces = [], + i, mom; + for (i = 0; i < 12; i++) { + // make the regex if we don't have it already + mom = create_utc__createUTC([2000, i]); + shortPieces.push(this.monthsShort(mom, '')); + longPieces.push(this.months(mom, '')); + mixedPieces.push(this.months(mom, '')); + mixedPieces.push(this.monthsShort(mom, '')); + } + // Sorting makes sure if one month (or abbr) is a prefix of another it + // will match the longer piece. + shortPieces.sort(cmpLenRev); + longPieces.sort(cmpLenRev); + mixedPieces.sort(cmpLenRev); + for (i = 0; i < 12; i++) { + shortPieces[i] = regexEscape(shortPieces[i]); + longPieces[i] = regexEscape(longPieces[i]); + mixedPieces[i] = regexEscape(mixedPieces[i]); + } - // if there is any input that was not parsed add a penalty for that format - currentScore += tempConfig._pf.charsLeftOver; + this._monthsRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i'); + this._monthsShortRegex = this._monthsRegex; + this._monthsStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i'); + this._monthsShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i'); + } - //or tokens - currentScore += tempConfig._pf.unusedTokens.length * 10; + function checkOverflow (m) { + var overflow; + var a = m._a; - tempConfig._pf.score = currentScore; + if (a && getParsingFlags(m).overflow === -2) { + overflow = + a[MONTH] < 0 || a[MONTH] > 11 ? MONTH : + a[DATE] < 1 || a[DATE] > daysInMonth(a[YEAR], a[MONTH]) ? DATE : + a[HOUR] < 0 || a[HOUR] > 24 || (a[HOUR] === 24 && (a[MINUTE] !== 0 || a[SECOND] !== 0 || a[MILLISECOND] !== 0)) ? HOUR : + a[MINUTE] < 0 || a[MINUTE] > 59 ? MINUTE : + a[SECOND] < 0 || a[SECOND] > 59 ? SECOND : + a[MILLISECOND] < 0 || a[MILLISECOND] > 999 ? MILLISECOND : + -1; - if (scoreToBeat == null || currentScore < scoreToBeat) { - scoreToBeat = currentScore; - bestMoment = tempConfig; + if (getParsingFlags(m)._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) { + overflow = DATE; } + if (getParsingFlags(m)._overflowWeeks && overflow === -1) { + overflow = WEEK; + } + if (getParsingFlags(m)._overflowWeekday && overflow === -1) { + overflow = WEEKDAY; + } + + getParsingFlags(m).overflow = overflow; } - extend(config, bestMoment || tempConfig); + return m; } + // iso 8601 regex + // 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00) + var extendedIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?/; + var basicIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?/; + + var tzRegex = /Z|[+-]\d\d(?::?\d\d)?/; + + var isoDates = [ + ['YYYYYY-MM-DD', /[+-]\d{6}-\d\d-\d\d/], + ['YYYY-MM-DD', /\d{4}-\d\d-\d\d/], + ['GGGG-[W]WW-E', /\d{4}-W\d\d-\d/], + ['GGGG-[W]WW', /\d{4}-W\d\d/, false], + ['YYYY-DDD', /\d{4}-\d{3}/], + ['YYYY-MM', /\d{4}-\d\d/, false], + ['YYYYYYMMDD', /[+-]\d{10}/], + ['YYYYMMDD', /\d{8}/], + // YYYYMM is NOT allowed by the standard + ['GGGG[W]WWE', /\d{4}W\d{3}/], + ['GGGG[W]WW', /\d{4}W\d{2}/, false], + ['YYYYDDD', /\d{7}/] + ]; + + // iso time formats and regexes + var isoTimes = [ + ['HH:mm:ss.SSSS', /\d\d:\d\d:\d\d\.\d+/], + ['HH:mm:ss,SSSS', /\d\d:\d\d:\d\d,\d+/], + ['HH:mm:ss', /\d\d:\d\d:\d\d/], + ['HH:mm', /\d\d:\d\d/], + ['HHmmss.SSSS', /\d\d\d\d\d\d\.\d+/], + ['HHmmss,SSSS', /\d\d\d\d\d\d,\d+/], + ['HHmmss', /\d\d\d\d\d\d/], + ['HHmm', /\d\d\d\d/], + ['HH', /\d\d/] + ]; + + var aspNetJsonRegex = /^\/?Date\((\-?\d+)/i; + // date from iso format - function makeDateFromString(config) { - var i, + function configFromISO(config) { + var i, l, string = config._i, - match = isoRegex.exec(string); + match = extendedIsoRegex.exec(string) || basicIsoRegex.exec(string), + allowTime, dateFormat, timeFormat, tzFormat; if (match) { - config._pf.iso = true; - for (i = 4; i > 0; i--) { - if (match[i]) { - // match[5] should be "T" or undefined - config._f = isoDates[i - 1] + (match[6] || " "); + getParsingFlags(config).iso = true; + + for (i = 0, l = isoDates.length; i < l; i++) { + if (isoDates[i][1].exec(match[1])) { + dateFormat = isoDates[i][0]; + allowTime = isoDates[i][2] !== false; break; } } - for (i = 0; i < 4; i++) { - if (isoTimes[i][1].exec(string)) { - config._f += isoTimes[i][0]; - break; + if (dateFormat == null) { + config._isValid = false; + return; + } + if (match[3]) { + for (i = 0, l = isoTimes.length; i < l; i++) { + if (isoTimes[i][1].exec(match[3])) { + // match[2] should be 'T' or space + timeFormat = (match[2] || ' ') + isoTimes[i][0]; + break; + } + } + if (timeFormat == null) { + config._isValid = false; + return; } } - if (string.match(parseTokenTimezone)) { - config._f += "Z"; + if (!allowTime && timeFormat != null) { + config._isValid = false; + return; } - makeDateFromStringAndFormat(config); - } - else { - config._d = new Date(string); + if (match[4]) { + if (tzRegex.exec(match[4])) { + tzFormat = 'Z'; + } else { + config._isValid = false; + return; + } + } + config._f = dateFormat + (timeFormat || '') + (tzFormat || ''); + configFromStringAndFormat(config); + } else { + config._isValid = false; } } - function makeDateFromInput(config) { - var input = config._i, - matched = aspNetJsonRegex.exec(input); + // date from iso format or fallback + function configFromString(config) { + var matched = aspNetJsonRegex.exec(config._i); - if (input === undefined) { - config._d = new Date(); - } else if (matched) { + if (matched !== null) { config._d = new Date(+matched[1]); - } else if (typeof input === 'string') { - makeDateFromString(config); - } else if (isArray(input)) { - config._a = input.slice(0); - dateFromConfig(config); - } else if (isDate(input)) { - config._d = new Date(+input); - } else if (typeof(input) === 'object') { - dateFromObject(config); - } else { - config._d = new Date(input); + return; + } + + configFromISO(config); + if (config._isValid === false) { + delete config._isValid; + utils_hooks__hooks.createFromInputFallback(config); } } - function makeDate(y, m, d, h, M, s, ms) { + utils_hooks__hooks.createFromInputFallback = deprecate( + 'moment construction falls back to js Date. This is ' + + 'discouraged and will be removed in upcoming major ' + + 'release. Please refer to ' + + 'https://github.com/moment/moment/issues/1407 for more info.', + function (config) { + config._d = new Date(config._i + (config._useUTC ? ' UTC' : '')); + } + ); + + function createDate (y, m, d, h, M, s, ms) { //can't just apply() to create a date: //http://stackoverflow.com/questions/181348/instantiating-a-javascript-object-by-calling-prototype-constructor-apply var date = new Date(y, m, d, h, M, s, ms); - //the date constructor doesn't accept years < 1970 - if (y < 1970) { + //the date constructor remaps years 0-99 to 1900-1999 + if (y < 100 && y >= 0 && isFinite(date.getFullYear())) { date.setFullYear(y); } return date; } - function makeUTCDate(y) { + function createUTCDate (y) { var date = new Date(Date.UTC.apply(null, arguments)); - if (y < 1970) { + + //the Date.UTC function remaps years 0-99 to 1900-1999 + if (y < 100 && y >= 0 && isFinite(date.getUTCFullYear())) { date.setUTCFullYear(y); } return date; } - function parseWeekday(input, language) { - if (typeof input === 'string') { - if (!isNaN(input)) { - input = parseInt(input, 10); - } - else { - input = language.weekdaysParse(input); - if (typeof input !== 'number') { - return null; - } - } - } - return input; - } + // FORMATTING + + addFormatToken('Y', 0, 0, function () { + var y = this.year(); + return y <= 9999 ? '' + y : '+' + y; + }); - /************************************ - Relative Time - ************************************/ + addFormatToken(0, ['YY', 2], 0, function () { + return this.year() % 100; + }); + addFormatToken(0, ['YYYY', 4], 0, 'year'); + addFormatToken(0, ['YYYYY', 5], 0, 'year'); + addFormatToken(0, ['YYYYYY', 6, true], 0, 'year'); - // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize - function substituteTimeAgo(string, number, withoutSuffix, isFuture, lang) { - return lang.relativeTime(number || 1, !!withoutSuffix, string, isFuture); + // ALIASES + + addUnitAlias('year', 'y'); + + // PARSING + + addRegexToken('Y', matchSigned); + addRegexToken('YY', match1to2, match2); + addRegexToken('YYYY', match1to4, match4); + addRegexToken('YYYYY', match1to6, match6); + addRegexToken('YYYYYY', match1to6, match6); + + addParseToken(['YYYYY', 'YYYYYY'], YEAR); + addParseToken('YYYY', function (input, array) { + array[YEAR] = input.length === 2 ? utils_hooks__hooks.parseTwoDigitYear(input) : toInt(input); + }); + addParseToken('YY', function (input, array) { + array[YEAR] = utils_hooks__hooks.parseTwoDigitYear(input); + }); + addParseToken('Y', function (input, array) { + array[YEAR] = parseInt(input, 10); + }); + + // HELPERS + + function daysInYear(year) { + return isLeapYear(year) ? 366 : 365; } - function relativeTime(milliseconds, withoutSuffix, lang) { - var seconds = round(Math.abs(milliseconds) / 1000), - minutes = round(seconds / 60), - hours = round(minutes / 60), - days = round(hours / 24), - years = round(days / 365), - args = seconds < 45 && ['s', seconds] || - minutes === 1 && ['m'] || - minutes < 45 && ['mm', minutes] || - hours === 1 && ['h'] || - hours < 22 && ['hh', hours] || - days === 1 && ['d'] || - days <= 25 && ['dd', days] || - days <= 45 && ['M'] || - days < 345 && ['MM', round(days / 30)] || - years === 1 && ['y'] || ['yy', years]; - args[2] = withoutSuffix; - args[3] = milliseconds > 0; - args[4] = lang; - return substituteTimeAgo.apply({}, args); + function isLeapYear(year) { + return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; } + // HOOKS + + utils_hooks__hooks.parseTwoDigitYear = function (input) { + return toInt(input) + (toInt(input) > 68 ? 1900 : 2000); + }; - /************************************ - Week of Year - ************************************/ + // MOMENTS + var getSetYear = makeGetSet('FullYear', true); - // firstDayOfWeek 0 = sun, 6 = sat - // the day of the week that starts the week - // (usually sunday or monday) - // firstDayOfWeekOfYear 0 = sun, 6 = sat - // the first week is the week that contains the first - // of this day of the week - // (eg. ISO weeks use thursday (4)) - function weekOfYear(mom, firstDayOfWeek, firstDayOfWeekOfYear) { - var end = firstDayOfWeekOfYear - firstDayOfWeek, - daysToDayOfWeek = firstDayOfWeekOfYear - mom.day(), - adjustedMoment; + function getIsLeapYear () { + return isLeapYear(this.year()); + } + // start-of-first-week - start-of-year + function firstWeekOffset(year, dow, doy) { + var // first-week day -- which january is always in the first week (4 for iso, 1 for other) + fwd = 7 + dow - doy, + // first-week day local weekday -- which local weekday is fwd + fwdlw = (7 + createUTCDate(year, 0, fwd).getUTCDay() - dow) % 7; - if (daysToDayOfWeek > end) { - daysToDayOfWeek -= 7; - } + return -fwdlw + fwd - 1; + } - if (daysToDayOfWeek < end - 7) { - daysToDayOfWeek += 7; + //http://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday + function dayOfYearFromWeeks(year, week, weekday, dow, doy) { + var localWeekday = (7 + weekday - dow) % 7, + weekOffset = firstWeekOffset(year, dow, doy), + dayOfYear = 1 + 7 * (week - 1) + localWeekday + weekOffset, + resYear, resDayOfYear; + + if (dayOfYear <= 0) { + resYear = year - 1; + resDayOfYear = daysInYear(resYear) + dayOfYear; + } else if (dayOfYear > daysInYear(year)) { + resYear = year + 1; + resDayOfYear = dayOfYear - daysInYear(year); + } else { + resYear = year; + resDayOfYear = dayOfYear; } - adjustedMoment = moment(mom).add('d', daysToDayOfWeek); return { - week: Math.ceil(adjustedMoment.dayOfYear() / 7), - year: adjustedMoment.year() + year: resYear, + dayOfYear: resDayOfYear }; } - //http://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday - function dayOfYearFromWeeks(year, week, weekday, firstDayOfWeekOfYear, firstDayOfWeek) { - // The only solid way to create an iso date from year is to use - // a string format (Date.UTC handles only years > 1900). Don't ask why - // it doesn't need Z at the end. - var d = new Date(leftZeroFill(year, 6, true) + '-01-01').getUTCDay(), - daysToAdd, dayOfYear; + function weekOfYear(mom, dow, doy) { + var weekOffset = firstWeekOffset(mom.year(), dow, doy), + week = Math.floor((mom.dayOfYear() - weekOffset - 1) / 7) + 1, + resWeek, resYear; - weekday = weekday != null ? weekday : firstDayOfWeek; - daysToAdd = firstDayOfWeek - d + (d > firstDayOfWeekOfYear ? 7 : 0); - dayOfYear = 7 * (week - 1) + (weekday - firstDayOfWeek) + daysToAdd + 1; + if (week < 1) { + resYear = mom.year() - 1; + resWeek = week + weeksInYear(resYear, dow, doy); + } else if (week > weeksInYear(mom.year(), dow, doy)) { + resWeek = week - weeksInYear(mom.year(), dow, doy); + resYear = mom.year() + 1; + } else { + resYear = mom.year(); + resWeek = week; + } return { - year: dayOfYear > 0 ? year : year - 1, - dayOfYear: dayOfYear > 0 ? dayOfYear : daysInYear(year - 1) + dayOfYear + week: resWeek, + year: resYear }; } - /************************************ - Top Level Functions - ************************************/ + function weeksInYear(year, dow, doy) { + var weekOffset = firstWeekOffset(year, dow, doy), + weekOffsetNext = firstWeekOffset(year + 1, dow, doy); + return (daysInYear(year) - weekOffset + weekOffsetNext) / 7; + } - function makeMoment(config) { - var input = config._i, - format = config._f; + // Pick the first defined of two or three arguments. + function defaults(a, b, c) { + if (a != null) { + return a; + } + if (b != null) { + return b; + } + return c; + } - if (typeof config._pf === 'undefined') { - initializeParsingFlags(config); + function currentDateArray(config) { + // hooks is actually the exported moment object + var nowValue = new Date(utils_hooks__hooks.now()); + if (config._useUTC) { + return [nowValue.getUTCFullYear(), nowValue.getUTCMonth(), nowValue.getUTCDate()]; } + return [nowValue.getFullYear(), nowValue.getMonth(), nowValue.getDate()]; + } + + // convert an array to a date. + // the array should mirror the parameters below + // note: all values past the year are optional and will default to the lowest possible value. + // [year, month, day , hour, minute, second, millisecond] + function configFromArray (config) { + var i, date, input = [], currentDate, yearToUse; - if (input === null) { - return moment.invalid({nullInput: true}); + if (config._d) { + return; } - if (typeof input === 'string') { - config._i = input = getLangDefinition().preparse(input); + currentDate = currentDateArray(config); + + //compute day of the year from weeks and weekdays + if (config._w && config._a[DATE] == null && config._a[MONTH] == null) { + dayOfYearFromWeekInfo(config); } - if (moment.isMoment(input)) { - config = extend({}, input); + //if the day of the year is set, figure out what it is + if (config._dayOfYear) { + yearToUse = defaults(config._a[YEAR], currentDate[YEAR]); - config._d = new Date(+input._d); - } else if (format) { - if (isArray(format)) { - makeDateFromStringAndArray(config); - } else { - makeDateFromStringAndFormat(config); + if (config._dayOfYear > daysInYear(yearToUse)) { + getParsingFlags(config)._overflowDayOfYear = true; } - } else { - makeDateFromInput(config); + + date = createUTCDate(yearToUse, 0, config._dayOfYear); + config._a[MONTH] = date.getUTCMonth(); + config._a[DATE] = date.getUTCDate(); } - return new Moment(config); - } + // Default to current date. + // * if no year, month, day of month are given, default to today + // * if day of month is given, default month and year + // * if month is given, default only year + // * if year is given, don't default anything + for (i = 0; i < 3 && config._a[i] == null; ++i) { + config._a[i] = input[i] = currentDate[i]; + } - moment = function (input, format, lang, strict) { - if (typeof(lang) === "boolean") { - strict = lang; - lang = undefined; + // Zero out whatever was not defaulted, including time + for (; i < 7; i++) { + config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i]; } - return makeMoment({ - _i : input, - _f : format, - _l : lang, - _strict : strict, - _isUTC : false - }); - }; - // creating with utc - moment.utc = function (input, format, lang, strict) { - var m; + // Check for 24:00:00.000 + if (config._a[HOUR] === 24 && + config._a[MINUTE] === 0 && + config._a[SECOND] === 0 && + config._a[MILLISECOND] === 0) { + config._nextDay = true; + config._a[HOUR] = 0; + } - if (typeof(lang) === "boolean") { - strict = lang; - lang = undefined; + config._d = (config._useUTC ? createUTCDate : createDate).apply(null, input); + // Apply timezone offset from input. The actual utcOffset can be changed + // with parseZone. + if (config._tzm != null) { + config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm); } - m = makeMoment({ - _useUTC : true, - _isUTC : true, - _l : lang, - _i : input, - _f : format, - _strict : strict - }).utc(); - return m; - }; + if (config._nextDay) { + config._a[HOUR] = 24; + } + } - // creating with unix timestamp (in seconds) - moment.unix = function (input) { - return moment(input * 1000); - }; + function dayOfYearFromWeekInfo(config) { + var w, weekYear, week, weekday, dow, doy, temp, weekdayOverflow; - // duration - moment.duration = function (input, key) { - var duration = input, - // matching against regexp is expensive, do it on demand - match = null, - sign, - ret, - parseIso; + w = config._w; + if (w.GG != null || w.W != null || w.E != null) { + dow = 1; + doy = 4; - if (moment.isDuration(input)) { - duration = { - ms: input._milliseconds, - d: input._days, - M: input._months - }; - } else if (typeof input === 'number') { - duration = {}; - if (key) { - duration[key] = input; + // TODO: We need to take the current isoWeekYear, but that depends on + // how we interpret now (local, utc, fixed offset). So create + // a now version of current config (take local/utc/offset flags, and + // create now). + weekYear = defaults(w.GG, config._a[YEAR], weekOfYear(local__createLocal(), 1, 4).year); + week = defaults(w.W, 1); + weekday = defaults(w.E, 1); + if (weekday < 1 || weekday > 7) { + weekdayOverflow = true; + } + } else { + dow = config._locale._week.dow; + doy = config._locale._week.doy; + + weekYear = defaults(w.gg, config._a[YEAR], weekOfYear(local__createLocal(), dow, doy).year); + week = defaults(w.w, 1); + + if (w.d != null) { + // weekday -- low day numbers are considered next week + weekday = w.d; + if (weekday < 0 || weekday > 6) { + weekdayOverflow = true; + } + } else if (w.e != null) { + // local weekday -- counting starts from begining of week + weekday = w.e + dow; + if (w.e < 0 || w.e > 6) { + weekdayOverflow = true; + } } else { - duration.milliseconds = input; + // default to begining of week + weekday = dow; } - } else if (!!(match = aspNetTimeSpanJsonRegex.exec(input))) { - sign = (match[1] === "-") ? -1 : 1; - duration = { - y: 0, - d: toInt(match[DATE]) * sign, - h: toInt(match[HOUR]) * sign, - m: toInt(match[MINUTE]) * sign, - s: toInt(match[SECOND]) * sign, - ms: toInt(match[MILLISECOND]) * sign - }; - } else if (!!(match = isoDurationRegex.exec(input))) { - sign = (match[1] === "-") ? -1 : 1; - parseIso = function (inp) { - // We'd normally use ~~inp for this, but unfortunately it also - // converts floats to ints. - // inp may be undefined, so careful calling replace on it. - var res = inp && parseFloat(inp.replace(',', '.')); - // apply sign while we're at it - return (isNaN(res) ? 0 : res) * sign; - }; - duration = { - y: parseIso(match[2]), - M: parseIso(match[3]), - d: parseIso(match[4]), - h: parseIso(match[5]), - m: parseIso(match[6]), - s: parseIso(match[7]), - w: parseIso(match[8]) - }; } + if (week < 1 || week > weeksInYear(weekYear, dow, doy)) { + getParsingFlags(config)._overflowWeeks = true; + } else if (weekdayOverflow != null) { + getParsingFlags(config)._overflowWeekday = true; + } else { + temp = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy); + config._a[YEAR] = temp.year; + config._dayOfYear = temp.dayOfYear; + } + } - ret = new Duration(duration); + // constant that refers to the ISO standard + utils_hooks__hooks.ISO_8601 = function () {}; - if (moment.isDuration(input) && input.hasOwnProperty('_lang')) { - ret._lang = input._lang; + // date from string and format string + function configFromStringAndFormat(config) { + // TODO: Move this to another part of the creation flow to prevent circular deps + if (config._f === utils_hooks__hooks.ISO_8601) { + configFromISO(config); + return; } - return ret; - }; + config._a = []; + getParsingFlags(config).empty = true; - // version number - moment.version = VERSION; + // This array is used to make a Date, either with `new Date` or `Date.UTC` + var string = '' + config._i, + i, parsedInput, tokens, token, skipped, + stringLength = string.length, + totalParsedInputLength = 0; - // default format - moment.defaultFormat = isoFormat; + tokens = expandFormat(config._f, config._locale).match(formattingTokens) || []; - // This function will be called whenever a moment is mutated. - // It is intended to keep the offset in sync with the timezone. - moment.updateOffset = function () {}; + for (i = 0; i < tokens.length; i++) { + token = tokens[i]; + parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0]; + // console.log('token', token, 'parsedInput', parsedInput, + // 'regex', getParseRegexForToken(token, config)); + if (parsedInput) { + skipped = string.substr(0, string.indexOf(parsedInput)); + if (skipped.length > 0) { + getParsingFlags(config).unusedInput.push(skipped); + } + string = string.slice(string.indexOf(parsedInput) + parsedInput.length); + totalParsedInputLength += parsedInput.length; + } + // don't parse if it's not a known token + if (formatTokenFunctions[token]) { + if (parsedInput) { + getParsingFlags(config).empty = false; + } + else { + getParsingFlags(config).unusedTokens.push(token); + } + addTimeToArrayFromToken(token, parsedInput, config); + } + else if (config._strict && !parsedInput) { + getParsingFlags(config).unusedTokens.push(token); + } + } - // This function will load languages and then set the global language. If - // no arguments are passed in, it will simply return the current global - // language key. - moment.lang = function (key, values) { - var r; - if (!key) { - return moment.fn._lang._abbr; - } - if (values) { - loadLang(normalizeLanguage(key), values); - } else if (values === null) { - unloadLang(key); - key = 'en'; - } else if (!languages[key]) { - getLangDefinition(key); - } - r = moment.duration.fn._lang = moment.fn._lang = getLangDefinition(key); - return r._abbr; - }; + // add remaining unparsed input length to the string + getParsingFlags(config).charsLeftOver = stringLength - totalParsedInputLength; + if (string.length > 0) { + getParsingFlags(config).unusedInput.push(string); + } - // returns language data - moment.langData = function (key) { - if (key && key._lang && key._lang._abbr) { - key = key._lang._abbr; + // clear _12h flag if hour is <= 12 + if (getParsingFlags(config).bigHour === true && + config._a[HOUR] <= 12 && + config._a[HOUR] > 0) { + getParsingFlags(config).bigHour = undefined; } - return getLangDefinition(key); - }; - // compare moment object - moment.isMoment = function (obj) { - return obj instanceof Moment; - }; + getParsingFlags(config).parsedDateParts = config._a.slice(0); + getParsingFlags(config).meridiem = config._meridiem; + // handle meridiem + config._a[HOUR] = meridiemFixWrap(config._locale, config._a[HOUR], config._meridiem); - // for typechecking Duration objects - moment.isDuration = function (obj) { - return obj instanceof Duration; - }; + configFromArray(config); + checkOverflow(config); + } + + + function meridiemFixWrap (locale, hour, meridiem) { + var isPm; + + if (meridiem == null) { + // nothing to do + return hour; + } + if (locale.meridiemHour != null) { + return locale.meridiemHour(hour, meridiem); + } else if (locale.isPM != null) { + // Fallback + isPm = locale.isPM(meridiem); + if (isPm && hour < 12) { + hour += 12; + } + if (!isPm && hour === 12) { + hour = 0; + } + return hour; + } else { + // this is not supposed to happen + return hour; + } + } + + // date from string and array of format strings + function configFromStringAndArray(config) { + var tempConfig, + bestMoment, + + scoreToBeat, + i, + currentScore; + + if (config._f.length === 0) { + getParsingFlags(config).invalidFormat = true; + config._d = new Date(NaN); + return; + } + + for (i = 0; i < config._f.length; i++) { + currentScore = 0; + tempConfig = copyConfig({}, config); + if (config._useUTC != null) { + tempConfig._useUTC = config._useUTC; + } + tempConfig._f = config._f[i]; + configFromStringAndFormat(tempConfig); + + if (!valid__isValid(tempConfig)) { + continue; + } + + // if there is any input that was not parsed add a penalty for that format + currentScore += getParsingFlags(tempConfig).charsLeftOver; + + //or tokens + currentScore += getParsingFlags(tempConfig).unusedTokens.length * 10; + + getParsingFlags(tempConfig).score = currentScore; + + if (scoreToBeat == null || currentScore < scoreToBeat) { + scoreToBeat = currentScore; + bestMoment = tempConfig; + } + } + + extend(config, bestMoment || tempConfig); + } + + function configFromObject(config) { + if (config._d) { + return; + } + + var i = normalizeObjectUnits(config._i); + config._a = map([i.year, i.month, i.day || i.date, i.hour, i.minute, i.second, i.millisecond], function (obj) { + return obj && parseInt(obj, 10); + }); + + configFromArray(config); + } + + function createFromConfig (config) { + var res = new Moment(checkOverflow(prepareConfig(config))); + if (res._nextDay) { + // Adding is smart enough around DST + res.add(1, 'd'); + res._nextDay = undefined; + } + + return res; + } + + function prepareConfig (config) { + var input = config._i, + format = config._f; + + config._locale = config._locale || locale_locales__getLocale(config._l); + + if (input === null || (format === undefined && input === '')) { + return valid__createInvalid({nullInput: true}); + } + + if (typeof input === 'string') { + config._i = input = config._locale.preparse(input); + } + + if (isMoment(input)) { + return new Moment(checkOverflow(input)); + } else if (isArray(format)) { + configFromStringAndArray(config); + } else if (format) { + configFromStringAndFormat(config); + } else if (isDate(input)) { + config._d = input; + } else { + configFromInput(config); + } + + if (!valid__isValid(config)) { + config._d = null; + } + + return config; + } + + function configFromInput(config) { + var input = config._i; + if (input === undefined) { + config._d = new Date(utils_hooks__hooks.now()); + } else if (isDate(input)) { + config._d = new Date(input.valueOf()); + } else if (typeof input === 'string') { + configFromString(config); + } else if (isArray(input)) { + config._a = map(input.slice(0), function (obj) { + return parseInt(obj, 10); + }); + configFromArray(config); + } else if (typeof(input) === 'object') { + configFromObject(config); + } else if (typeof(input) === 'number') { + // from milliseconds + config._d = new Date(input); + } else { + utils_hooks__hooks.createFromInputFallback(config); + } + } + + function createLocalOrUTC (input, format, locale, strict, isUTC) { + var c = {}; + + if (typeof(locale) === 'boolean') { + strict = locale; + locale = undefined; + } + // object construction must be done this way. + // https://github.com/moment/moment/issues/1423 + c._isAMomentObject = true; + c._useUTC = c._isUTC = isUTC; + c._l = locale; + c._i = input; + c._f = format; + c._strict = strict; + + return createFromConfig(c); + } + + function local__createLocal (input, format, locale, strict) { + return createLocalOrUTC(input, format, locale, strict, false); + } + + var prototypeMin = deprecate( + 'moment().min is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548', + function () { + var other = local__createLocal.apply(null, arguments); + if (this.isValid() && other.isValid()) { + return other < this ? this : other; + } else { + return valid__createInvalid(); + } + } + ); + + var prototypeMax = deprecate( + 'moment().max is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548', + function () { + var other = local__createLocal.apply(null, arguments); + if (this.isValid() && other.isValid()) { + return other > this ? this : other; + } else { + return valid__createInvalid(); + } + } + ); + + // Pick a moment m from moments so that m[fn](other) is true for all + // other. This relies on the function fn to be transitive. + // + // moments should either be an array of moment objects or an array, whose + // first element is an array of moment objects. + function pickBy(fn, moments) { + var res, i; + if (moments.length === 1 && isArray(moments[0])) { + moments = moments[0]; + } + if (!moments.length) { + return local__createLocal(); + } + res = moments[0]; + for (i = 1; i < moments.length; ++i) { + if (!moments[i].isValid() || moments[i][fn](res)) { + res = moments[i]; + } + } + return res; + } + + // TODO: Use [].sort instead? + function min () { + var args = [].slice.call(arguments, 0); + + return pickBy('isBefore', args); + } + + function max () { + var args = [].slice.call(arguments, 0); + + return pickBy('isAfter', args); + } + + var now = function () { + return Date.now ? Date.now() : +(new Date()); + }; + + function Duration (duration) { + var normalizedInput = normalizeObjectUnits(duration), + years = normalizedInput.year || 0, + quarters = normalizedInput.quarter || 0, + months = normalizedInput.month || 0, + weeks = normalizedInput.week || 0, + days = normalizedInput.day || 0, + hours = normalizedInput.hour || 0, + minutes = normalizedInput.minute || 0, + seconds = normalizedInput.second || 0, + milliseconds = normalizedInput.millisecond || 0; + + // representation for dateAddRemove + this._milliseconds = +milliseconds + + seconds * 1e3 + // 1000 + minutes * 6e4 + // 1000 * 60 + hours * 1000 * 60 * 60; //using 1000 * 60 * 60 instead of 36e5 to avoid floating point rounding errors https://github.com/moment/moment/issues/2978 + // Because of dateAddRemove treats 24 hours as different from a + // day when working around DST, we need to store them separately + this._days = +days + + weeks * 7; + // It is impossible translate months into days without knowing + // which months you are are talking about, so we have to store + // it separately. + this._months = +months + + quarters * 3 + + years * 12; + + this._data = {}; + + this._locale = locale_locales__getLocale(); + + this._bubble(); + } + + function isDuration (obj) { + return obj instanceof Duration; + } + + // FORMATTING + + function offset (token, separator) { + addFormatToken(token, 0, 0, function () { + var offset = this.utcOffset(); + var sign = '+'; + if (offset < 0) { + offset = -offset; + sign = '-'; + } + return sign + zeroFill(~~(offset / 60), 2) + separator + zeroFill(~~(offset) % 60, 2); + }); + } + + offset('Z', ':'); + offset('ZZ', ''); + + // PARSING + + addRegexToken('Z', matchShortOffset); + addRegexToken('ZZ', matchShortOffset); + addParseToken(['Z', 'ZZ'], function (input, array, config) { + config._useUTC = true; + config._tzm = offsetFromString(matchShortOffset, input); + }); + + // HELPERS + + // timezone chunker + // '+10:00' > ['10', '00'] + // '-1530' > ['-15', '30'] + var chunkOffset = /([\+\-]|\d\d)/gi; + + function offsetFromString(matcher, string) { + var matches = ((string || '').match(matcher) || []); + var chunk = matches[matches.length - 1] || []; + var parts = (chunk + '').match(chunkOffset) || ['-', 0, 0]; + var minutes = +(parts[1] * 60) + toInt(parts[2]); + + return parts[0] === '+' ? minutes : -minutes; + } + + // Return a moment from input, that is local/utc/zone equivalent to model. + function cloneWithOffset(input, model) { + var res, diff; + if (model._isUTC) { + res = model.clone(); + diff = (isMoment(input) || isDate(input) ? input.valueOf() : local__createLocal(input).valueOf()) - res.valueOf(); + // Use low-level api, because this fn is low-level api. + res._d.setTime(res._d.valueOf() + diff); + utils_hooks__hooks.updateOffset(res, false); + return res; + } else { + return local__createLocal(input).local(); + } + } + + function getDateOffset (m) { + // On Firefox.24 Date#getTimezoneOffset returns a floating point. + // https://github.com/moment/moment/pull/1871 + return -Math.round(m._d.getTimezoneOffset() / 15) * 15; + } + + // HOOKS + + // This function will be called whenever a moment is mutated. + // It is intended to keep the offset in sync with the timezone. + utils_hooks__hooks.updateOffset = function () {}; + + // MOMENTS + + // keepLocalTime = true means only change the timezone, without + // affecting the local hour. So 5:31:26 +0300 --[utcOffset(2, true)]--> + // 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset + // +0200, so we adjust the time as needed, to be valid. + // + // Keeping the time actually adds/subtracts (one hour) + // from the actual represented time. That is why we call updateOffset + // a second time. In case it wants us to change the offset again + // _changeInProgress == true case, then we have to adjust, because + // there is no such time in the given timezone. + function getSetOffset (input, keepLocalTime) { + var offset = this._offset || 0, + localAdjust; + if (!this.isValid()) { + return input != null ? this : NaN; + } + if (input != null) { + if (typeof input === 'string') { + input = offsetFromString(matchShortOffset, input); + } else if (Math.abs(input) < 16) { + input = input * 60; + } + if (!this._isUTC && keepLocalTime) { + localAdjust = getDateOffset(this); + } + this._offset = input; + this._isUTC = true; + if (localAdjust != null) { + this.add(localAdjust, 'm'); + } + if (offset !== input) { + if (!keepLocalTime || this._changeInProgress) { + add_subtract__addSubtract(this, create__createDuration(input - offset, 'm'), 1, false); + } else if (!this._changeInProgress) { + this._changeInProgress = true; + utils_hooks__hooks.updateOffset(this, true); + this._changeInProgress = null; + } + } + return this; + } else { + return this._isUTC ? offset : getDateOffset(this); + } + } + + function getSetZone (input, keepLocalTime) { + if (input != null) { + if (typeof input !== 'string') { + input = -input; + } + + this.utcOffset(input, keepLocalTime); + + return this; + } else { + return -this.utcOffset(); + } + } + + function setOffsetToUTC (keepLocalTime) { + return this.utcOffset(0, keepLocalTime); + } + + function setOffsetToLocal (keepLocalTime) { + if (this._isUTC) { + this.utcOffset(0, keepLocalTime); + this._isUTC = false; + + if (keepLocalTime) { + this.subtract(getDateOffset(this), 'm'); + } + } + return this; + } + + function setOffsetToParsedOffset () { + if (this._tzm) { + this.utcOffset(this._tzm); + } else if (typeof this._i === 'string') { + this.utcOffset(offsetFromString(matchOffset, this._i)); + } + return this; + } + + function hasAlignedHourOffset (input) { + if (!this.isValid()) { + return false; + } + input = input ? local__createLocal(input).utcOffset() : 0; + + return (this.utcOffset() - input) % 60 === 0; + } + + function isDaylightSavingTime () { + return ( + this.utcOffset() > this.clone().month(0).utcOffset() || + this.utcOffset() > this.clone().month(5).utcOffset() + ); + } + + function isDaylightSavingTimeShifted () { + if (!isUndefined(this._isDSTShifted)) { + return this._isDSTShifted; + } + + var c = {}; + + copyConfig(c, this); + c = prepareConfig(c); + + if (c._a) { + var other = c._isUTC ? create_utc__createUTC(c._a) : local__createLocal(c._a); + this._isDSTShifted = this.isValid() && + compareArrays(c._a, other.toArray()) > 0; + } else { + this._isDSTShifted = false; + } + + return this._isDSTShifted; + } + + function isLocal () { + return this.isValid() ? !this._isUTC : false; + } + + function isUtcOffset () { + return this.isValid() ? this._isUTC : false; + } + + function isUtc () { + return this.isValid() ? this._isUTC && this._offset === 0 : false; + } + + // ASP.NET json date format regex + var aspNetRegex = /^(\-)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?\d*)?$/; + + // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html + // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere + // and further modified to allow for strings containing both week and day + var isoRegex = /^(-)?P(?:(-?[0-9,.]*)Y)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)W)?(?:(-?[0-9,.]*)D)?(?:T(?:(-?[0-9,.]*)H)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)S)?)?$/; + + function create__createDuration (input, key) { + var duration = input, + // matching against regexp is expensive, do it on demand + match = null, + sign, + ret, + diffRes; + + if (isDuration(input)) { + duration = { + ms : input._milliseconds, + d : input._days, + M : input._months + }; + } else if (typeof input === 'number') { + duration = {}; + if (key) { + duration[key] = input; + } else { + duration.milliseconds = input; + } + } else if (!!(match = aspNetRegex.exec(input))) { + sign = (match[1] === '-') ? -1 : 1; + duration = { + y : 0, + d : toInt(match[DATE]) * sign, + h : toInt(match[HOUR]) * sign, + m : toInt(match[MINUTE]) * sign, + s : toInt(match[SECOND]) * sign, + ms : toInt(match[MILLISECOND]) * sign + }; + } else if (!!(match = isoRegex.exec(input))) { + sign = (match[1] === '-') ? -1 : 1; + duration = { + y : parseIso(match[2], sign), + M : parseIso(match[3], sign), + w : parseIso(match[4], sign), + d : parseIso(match[5], sign), + h : parseIso(match[6], sign), + m : parseIso(match[7], sign), + s : parseIso(match[8], sign) + }; + } else if (duration == null) {// checks for null or undefined + duration = {}; + } else if (typeof duration === 'object' && ('from' in duration || 'to' in duration)) { + diffRes = momentsDifference(local__createLocal(duration.from), local__createLocal(duration.to)); + + duration = {}; + duration.ms = diffRes.milliseconds; + duration.M = diffRes.months; + } + + ret = new Duration(duration); + + if (isDuration(input) && hasOwnProp(input, '_locale')) { + ret._locale = input._locale; + } + + return ret; + } + + create__createDuration.fn = Duration.prototype; + + function parseIso (inp, sign) { + // We'd normally use ~~inp for this, but unfortunately it also + // converts floats to ints. + // inp may be undefined, so careful calling replace on it. + var res = inp && parseFloat(inp.replace(',', '.')); + // apply sign while we're at it + return (isNaN(res) ? 0 : res) * sign; + } + + function positiveMomentsDifference(base, other) { + var res = {milliseconds: 0, months: 0}; + + res.months = other.month() - base.month() + + (other.year() - base.year()) * 12; + if (base.clone().add(res.months, 'M').isAfter(other)) { + --res.months; + } + + res.milliseconds = +other - +(base.clone().add(res.months, 'M')); + + return res; + } + + function momentsDifference(base, other) { + var res; + if (!(base.isValid() && other.isValid())) { + return {milliseconds: 0, months: 0}; + } + + other = cloneWithOffset(other, base); + if (base.isBefore(other)) { + res = positiveMomentsDifference(base, other); + } else { + res = positiveMomentsDifference(other, base); + res.milliseconds = -res.milliseconds; + res.months = -res.months; + } + + return res; + } + + function absRound (number) { + if (number < 0) { + return Math.round(-1 * number) * -1; + } else { + return Math.round(number); + } + } + + // TODO: remove 'name' arg after deprecation is removed + function createAdder(direction, name) { + return function (val, period) { + var dur, tmp; + //invert the arguments, but complain about it + if (period !== null && !isNaN(+period)) { + deprecateSimple(name, 'moment().' + name + '(period, number) is deprecated. Please use moment().' + name + '(number, period).'); + tmp = val; val = period; period = tmp; + } + + val = typeof val === 'string' ? +val : val; + dur = create__createDuration(val, period); + add_subtract__addSubtract(this, dur, direction); + return this; + }; + } + + function add_subtract__addSubtract (mom, duration, isAdding, updateOffset) { + var milliseconds = duration._milliseconds, + days = absRound(duration._days), + months = absRound(duration._months); + + if (!mom.isValid()) { + // No op + return; + } + + updateOffset = updateOffset == null ? true : updateOffset; + + if (milliseconds) { + mom._d.setTime(mom._d.valueOf() + milliseconds * isAdding); + } + if (days) { + get_set__set(mom, 'Date', get_set__get(mom, 'Date') + days * isAdding); + } + if (months) { + setMonth(mom, get_set__get(mom, 'Month') + months * isAdding); + } + if (updateOffset) { + utils_hooks__hooks.updateOffset(mom, days || months); + } + } + + var add_subtract__add = createAdder(1, 'add'); + var add_subtract__subtract = createAdder(-1, 'subtract'); + + function moment_calendar__calendar (time, formats) { + // We want to compare the start of today, vs this. + // Getting start-of-today depends on whether we're local/utc/offset or not. + var now = time || local__createLocal(), + sod = cloneWithOffset(now, this).startOf('day'), + diff = this.diff(sod, 'days', true), + format = diff < -6 ? 'sameElse' : + diff < -1 ? 'lastWeek' : + diff < 0 ? 'lastDay' : + diff < 1 ? 'sameDay' : + diff < 2 ? 'nextDay' : + diff < 7 ? 'nextWeek' : 'sameElse'; + + var output = formats && (isFunction(formats[format]) ? formats[format]() : formats[format]); + + return this.format(output || this.localeData().calendar(format, this, local__createLocal(now))); + } + + function clone () { + return new Moment(this); + } + + function isAfter (input, units) { + var localInput = isMoment(input) ? input : local__createLocal(input); + if (!(this.isValid() && localInput.isValid())) { + return false; + } + units = normalizeUnits(!isUndefined(units) ? units : 'millisecond'); + if (units === 'millisecond') { + return this.valueOf() > localInput.valueOf(); + } else { + return localInput.valueOf() < this.clone().startOf(units).valueOf(); + } + } + + function isBefore (input, units) { + var localInput = isMoment(input) ? input : local__createLocal(input); + if (!(this.isValid() && localInput.isValid())) { + return false; + } + units = normalizeUnits(!isUndefined(units) ? units : 'millisecond'); + if (units === 'millisecond') { + return this.valueOf() < localInput.valueOf(); + } else { + return this.clone().endOf(units).valueOf() < localInput.valueOf(); + } + } + + function isBetween (from, to, units, inclusivity) { + inclusivity = inclusivity || '()'; + return (inclusivity[0] === '(' ? this.isAfter(from, units) : !this.isBefore(from, units)) && + (inclusivity[1] === ')' ? this.isBefore(to, units) : !this.isAfter(to, units)); + } + + function isSame (input, units) { + var localInput = isMoment(input) ? input : local__createLocal(input), + inputMs; + if (!(this.isValid() && localInput.isValid())) { + return false; + } + units = normalizeUnits(units || 'millisecond'); + if (units === 'millisecond') { + return this.valueOf() === localInput.valueOf(); + } else { + inputMs = localInput.valueOf(); + return this.clone().startOf(units).valueOf() <= inputMs && inputMs <= this.clone().endOf(units).valueOf(); + } + } + + function isSameOrAfter (input, units) { + return this.isSame(input, units) || this.isAfter(input,units); + } + + function isSameOrBefore (input, units) { + return this.isSame(input, units) || this.isBefore(input,units); + } + + function diff (input, units, asFloat) { + var that, + zoneDelta, + delta, output; + + if (!this.isValid()) { + return NaN; + } + + that = cloneWithOffset(input, this); + + if (!that.isValid()) { + return NaN; + } + + zoneDelta = (that.utcOffset() - this.utcOffset()) * 6e4; + + units = normalizeUnits(units); + + if (units === 'year' || units === 'month' || units === 'quarter') { + output = monthDiff(this, that); + if (units === 'quarter') { + output = output / 3; + } else if (units === 'year') { + output = output / 12; + } + } else { + delta = this - that; + output = units === 'second' ? delta / 1e3 : // 1000 + units === 'minute' ? delta / 6e4 : // 1000 * 60 + units === 'hour' ? delta / 36e5 : // 1000 * 60 * 60 + units === 'day' ? (delta - zoneDelta) / 864e5 : // 1000 * 60 * 60 * 24, negate dst + units === 'week' ? (delta - zoneDelta) / 6048e5 : // 1000 * 60 * 60 * 24 * 7, negate dst + delta; + } + return asFloat ? output : absFloor(output); + } + + function monthDiff (a, b) { + // difference in months + var wholeMonthDiff = ((b.year() - a.year()) * 12) + (b.month() - a.month()), + // b is in (anchor - 1 month, anchor + 1 month) + anchor = a.clone().add(wholeMonthDiff, 'months'), + anchor2, adjust; + + if (b - anchor < 0) { + anchor2 = a.clone().add(wholeMonthDiff - 1, 'months'); + // linear across the month + adjust = (b - anchor) / (anchor - anchor2); + } else { + anchor2 = a.clone().add(wholeMonthDiff + 1, 'months'); + // linear across the month + adjust = (b - anchor) / (anchor2 - anchor); + } + + //check for negative zero, return zero if negative zero + return -(wholeMonthDiff + adjust) || 0; + } + + utils_hooks__hooks.defaultFormat = 'YYYY-MM-DDTHH:mm:ssZ'; + utils_hooks__hooks.defaultFormatUtc = 'YYYY-MM-DDTHH:mm:ss[Z]'; + + function toString () { + return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ'); + } + + function moment_format__toISOString () { + var m = this.clone().utc(); + if (0 < m.year() && m.year() <= 9999) { + if (isFunction(Date.prototype.toISOString)) { + // native implementation is ~50x faster, use it when we can + return this.toDate().toISOString(); + } else { + return formatMoment(m, 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); + } + } else { + return formatMoment(m, 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); + } + } + + function format (inputString) { + if (!inputString) { + inputString = this.isUtc() ? utils_hooks__hooks.defaultFormatUtc : utils_hooks__hooks.defaultFormat; + } + var output = formatMoment(this, inputString); + return this.localeData().postformat(output); + } + + function from (time, withoutSuffix) { + if (this.isValid() && + ((isMoment(time) && time.isValid()) || + local__createLocal(time).isValid())) { + return create__createDuration({to: this, from: time}).locale(this.locale()).humanize(!withoutSuffix); + } else { + return this.localeData().invalidDate(); + } + } + + function fromNow (withoutSuffix) { + return this.from(local__createLocal(), withoutSuffix); + } + + function to (time, withoutSuffix) { + if (this.isValid() && + ((isMoment(time) && time.isValid()) || + local__createLocal(time).isValid())) { + return create__createDuration({from: this, to: time}).locale(this.locale()).humanize(!withoutSuffix); + } else { + return this.localeData().invalidDate(); + } + } + + function toNow (withoutSuffix) { + return this.to(local__createLocal(), withoutSuffix); + } + + // If passed a locale key, it will set the locale for this + // instance. Otherwise, it will return the locale configuration + // variables for this instance. + function locale (key) { + var newLocaleData; + + if (key === undefined) { + return this._locale._abbr; + } else { + newLocaleData = locale_locales__getLocale(key); + if (newLocaleData != null) { + this._locale = newLocaleData; + } + return this; + } + } + + var lang = deprecate( + 'moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.', + function (key) { + if (key === undefined) { + return this.localeData(); + } else { + return this.locale(key); + } + } + ); + + function localeData () { + return this._locale; + } + + function startOf (units) { + units = normalizeUnits(units); + // the following switch intentionally omits break keywords + // to utilize falling through the cases. + switch (units) { + case 'year': + this.month(0); + /* falls through */ + case 'quarter': + case 'month': + this.date(1); + /* falls through */ + case 'week': + case 'isoWeek': + case 'day': + case 'date': + this.hours(0); + /* falls through */ + case 'hour': + this.minutes(0); + /* falls through */ + case 'minute': + this.seconds(0); + /* falls through */ + case 'second': + this.milliseconds(0); + } + + // weeks are a special case + if (units === 'week') { + this.weekday(0); + } + if (units === 'isoWeek') { + this.isoWeekday(1); + } + + // quarters are also special + if (units === 'quarter') { + this.month(Math.floor(this.month() / 3) * 3); + } + + return this; + } + + function endOf (units) { + units = normalizeUnits(units); + if (units === undefined || units === 'millisecond') { + return this; + } + + // 'date' is an alias for 'day', so it should be considered as such. + if (units === 'date') { + units = 'day'; + } + + return this.startOf(units).add(1, (units === 'isoWeek' ? 'week' : units)).subtract(1, 'ms'); + } + + function to_type__valueOf () { + return this._d.valueOf() - ((this._offset || 0) * 60000); + } + + function unix () { + return Math.floor(this.valueOf() / 1000); + } + + function toDate () { + return this._offset ? new Date(this.valueOf()) : this._d; + } + + function toArray () { + var m = this; + return [m.year(), m.month(), m.date(), m.hour(), m.minute(), m.second(), m.millisecond()]; + } + + function toObject () { + var m = this; + return { + years: m.year(), + months: m.month(), + date: m.date(), + hours: m.hours(), + minutes: m.minutes(), + seconds: m.seconds(), + milliseconds: m.milliseconds() + }; + } + + function toJSON () { + // new Date(NaN).toJSON() === null + return this.isValid() ? this.toISOString() : null; + } + + function moment_valid__isValid () { + return valid__isValid(this); + } + + function parsingFlags () { + return extend({}, getParsingFlags(this)); + } + + function invalidAt () { + return getParsingFlags(this).overflow; + } + + function creationData() { + return { + input: this._i, + format: this._f, + locale: this._locale, + isUTC: this._isUTC, + strict: this._strict + }; + } + + // FORMATTING + + addFormatToken(0, ['gg', 2], 0, function () { + return this.weekYear() % 100; + }); + + addFormatToken(0, ['GG', 2], 0, function () { + return this.isoWeekYear() % 100; + }); + + function addWeekYearFormatToken (token, getter) { + addFormatToken(0, [token, token.length], 0, getter); + } + + addWeekYearFormatToken('gggg', 'weekYear'); + addWeekYearFormatToken('ggggg', 'weekYear'); + addWeekYearFormatToken('GGGG', 'isoWeekYear'); + addWeekYearFormatToken('GGGGG', 'isoWeekYear'); + + // ALIASES + + addUnitAlias('weekYear', 'gg'); + addUnitAlias('isoWeekYear', 'GG'); + + // PARSING + + addRegexToken('G', matchSigned); + addRegexToken('g', matchSigned); + addRegexToken('GG', match1to2, match2); + addRegexToken('gg', match1to2, match2); + addRegexToken('GGGG', match1to4, match4); + addRegexToken('gggg', match1to4, match4); + addRegexToken('GGGGG', match1to6, match6); + addRegexToken('ggggg', match1to6, match6); + + addWeekParseToken(['gggg', 'ggggg', 'GGGG', 'GGGGG'], function (input, week, config, token) { + week[token.substr(0, 2)] = toInt(input); + }); + + addWeekParseToken(['gg', 'GG'], function (input, week, config, token) { + week[token] = utils_hooks__hooks.parseTwoDigitYear(input); + }); + + // MOMENTS + + function getSetWeekYear (input) { + return getSetWeekYearHelper.call(this, + input, + this.week(), + this.weekday(), + this.localeData()._week.dow, + this.localeData()._week.doy); + } + + function getSetISOWeekYear (input) { + return getSetWeekYearHelper.call(this, + input, this.isoWeek(), this.isoWeekday(), 1, 4); + } + + function getISOWeeksInYear () { + return weeksInYear(this.year(), 1, 4); + } + + function getWeeksInYear () { + var weekInfo = this.localeData()._week; + return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy); + } + + function getSetWeekYearHelper(input, week, weekday, dow, doy) { + var weeksTarget; + if (input == null) { + return weekOfYear(this, dow, doy).year; + } else { + weeksTarget = weeksInYear(input, dow, doy); + if (week > weeksTarget) { + week = weeksTarget; + } + return setWeekAll.call(this, input, week, weekday, dow, doy); + } + } + + function setWeekAll(weekYear, week, weekday, dow, doy) { + var dayOfYearData = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy), + date = createUTCDate(dayOfYearData.year, 0, dayOfYearData.dayOfYear); + + this.year(date.getUTCFullYear()); + this.month(date.getUTCMonth()); + this.date(date.getUTCDate()); + return this; + } + + // FORMATTING + + addFormatToken('Q', 0, 'Qo', 'quarter'); + + // ALIASES + + addUnitAlias('quarter', 'Q'); + + // PARSING + + addRegexToken('Q', match1); + addParseToken('Q', function (input, array) { + array[MONTH] = (toInt(input) - 1) * 3; + }); + + // MOMENTS + + function getSetQuarter (input) { + return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3); + } + + // FORMATTING + + addFormatToken('w', ['ww', 2], 'wo', 'week'); + addFormatToken('W', ['WW', 2], 'Wo', 'isoWeek'); + + // ALIASES + + addUnitAlias('week', 'w'); + addUnitAlias('isoWeek', 'W'); + + // PARSING + + addRegexToken('w', match1to2); + addRegexToken('ww', match1to2, match2); + addRegexToken('W', match1to2); + addRegexToken('WW', match1to2, match2); + + addWeekParseToken(['w', 'ww', 'W', 'WW'], function (input, week, config, token) { + week[token.substr(0, 1)] = toInt(input); + }); + + // HELPERS + + // LOCALES + + function localeWeek (mom) { + return weekOfYear(mom, this._week.dow, this._week.doy).week; + } + + var defaultLocaleWeek = { + dow : 0, // Sunday is the first day of the week. + doy : 6 // The week that contains Jan 1st is the first week of the year. + }; + + function localeFirstDayOfWeek () { + return this._week.dow; + } + + function localeFirstDayOfYear () { + return this._week.doy; + } + + // MOMENTS + + function getSetWeek (input) { + var week = this.localeData().week(this); + return input == null ? week : this.add((input - week) * 7, 'd'); + } + + function getSetISOWeek (input) { + var week = weekOfYear(this, 1, 4).week; + return input == null ? week : this.add((input - week) * 7, 'd'); + } + + // FORMATTING + + addFormatToken('D', ['DD', 2], 'Do', 'date'); + + // ALIASES + + addUnitAlias('date', 'D'); + + // PARSING + + addRegexToken('D', match1to2); + addRegexToken('DD', match1to2, match2); + addRegexToken('Do', function (isStrict, locale) { + return isStrict ? locale._ordinalParse : locale._ordinalParseLenient; + }); + + addParseToken(['D', 'DD'], DATE); + addParseToken('Do', function (input, array) { + array[DATE] = toInt(input.match(match1to2)[0], 10); + }); + + // MOMENTS + + var getSetDayOfMonth = makeGetSet('Date', true); + + // FORMATTING + + addFormatToken('d', 0, 'do', 'day'); + + addFormatToken('dd', 0, 0, function (format) { + return this.localeData().weekdaysMin(this, format); + }); + + addFormatToken('ddd', 0, 0, function (format) { + return this.localeData().weekdaysShort(this, format); + }); + + addFormatToken('dddd', 0, 0, function (format) { + return this.localeData().weekdays(this, format); + }); + + addFormatToken('e', 0, 0, 'weekday'); + addFormatToken('E', 0, 0, 'isoWeekday'); + + // ALIASES + + addUnitAlias('day', 'd'); + addUnitAlias('weekday', 'e'); + addUnitAlias('isoWeekday', 'E'); + + // PARSING + + addRegexToken('d', match1to2); + addRegexToken('e', match1to2); + addRegexToken('E', match1to2); + addRegexToken('dd', function (isStrict, locale) { + return locale.weekdaysMinRegex(isStrict); + }); + addRegexToken('ddd', function (isStrict, locale) { + return locale.weekdaysShortRegex(isStrict); + }); + addRegexToken('dddd', function (isStrict, locale) { + return locale.weekdaysRegex(isStrict); + }); + + addWeekParseToken(['dd', 'ddd', 'dddd'], function (input, week, config, token) { + var weekday = config._locale.weekdaysParse(input, token, config._strict); + // if we didn't get a weekday name, mark the date as invalid + if (weekday != null) { + week.d = weekday; + } else { + getParsingFlags(config).invalidWeekday = input; + } + }); + + addWeekParseToken(['d', 'e', 'E'], function (input, week, config, token) { + week[token] = toInt(input); + }); + + // HELPERS + + function parseWeekday(input, locale) { + if (typeof input !== 'string') { + return input; + } + + if (!isNaN(input)) { + return parseInt(input, 10); + } + + input = locale.weekdaysParse(input); + if (typeof input === 'number') { + return input; + } + + return null; + } + + // LOCALES + + var defaultLocaleWeekdays = 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'); + function localeWeekdays (m, format) { + return isArray(this._weekdays) ? this._weekdays[m.day()] : + this._weekdays[this._weekdays.isFormat.test(format) ? 'format' : 'standalone'][m.day()]; + } + + var defaultLocaleWeekdaysShort = 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'); + function localeWeekdaysShort (m) { + return this._weekdaysShort[m.day()]; + } + + var defaultLocaleWeekdaysMin = 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'); + function localeWeekdaysMin (m) { + return this._weekdaysMin[m.day()]; + } + + function day_of_week__handleStrictParse(weekdayName, format, strict) { + var i, ii, mom, llc = weekdayName.toLocaleLowerCase(); + if (!this._weekdaysParse) { + this._weekdaysParse = []; + this._shortWeekdaysParse = []; + this._minWeekdaysParse = []; + + for (i = 0; i < 7; ++i) { + mom = create_utc__createUTC([2000, 1]).day(i); + this._minWeekdaysParse[i] = this.weekdaysMin(mom, '').toLocaleLowerCase(); + this._shortWeekdaysParse[i] = this.weekdaysShort(mom, '').toLocaleLowerCase(); + this._weekdaysParse[i] = this.weekdays(mom, '').toLocaleLowerCase(); + } + } + + if (strict) { + if (format === 'dddd') { + ii = indexOf.call(this._weekdaysParse, llc); + return ii !== -1 ? ii : null; + } else if (format === 'ddd') { + ii = indexOf.call(this._shortWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf.call(this._minWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } + } else { + if (format === 'dddd') { + ii = indexOf.call(this._weekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._shortWeekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._minWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } else if (format === 'ddd') { + ii = indexOf.call(this._shortWeekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._weekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._minWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf.call(this._minWeekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._weekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._shortWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } + } + } + + function localeWeekdaysParse (weekdayName, format, strict) { + var i, mom, regex; + + if (this._weekdaysParseExact) { + return day_of_week__handleStrictParse.call(this, weekdayName, format, strict); + } + + if (!this._weekdaysParse) { + this._weekdaysParse = []; + this._minWeekdaysParse = []; + this._shortWeekdaysParse = []; + this._fullWeekdaysParse = []; + } + + for (i = 0; i < 7; i++) { + // make the regex if we don't have it already + + mom = create_utc__createUTC([2000, 1]).day(i); + if (strict && !this._fullWeekdaysParse[i]) { + this._fullWeekdaysParse[i] = new RegExp('^' + this.weekdays(mom, '').replace('.', '\.?') + '$', 'i'); + this._shortWeekdaysParse[i] = new RegExp('^' + this.weekdaysShort(mom, '').replace('.', '\.?') + '$', 'i'); + this._minWeekdaysParse[i] = new RegExp('^' + this.weekdaysMin(mom, '').replace('.', '\.?') + '$', 'i'); + } + if (!this._weekdaysParse[i]) { + regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, ''); + this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i'); + } + // test the regex + if (strict && format === 'dddd' && this._fullWeekdaysParse[i].test(weekdayName)) { + return i; + } else if (strict && format === 'ddd' && this._shortWeekdaysParse[i].test(weekdayName)) { + return i; + } else if (strict && format === 'dd' && this._minWeekdaysParse[i].test(weekdayName)) { + return i; + } else if (!strict && this._weekdaysParse[i].test(weekdayName)) { + return i; + } + } + } + + // MOMENTS + + function getSetDayOfWeek (input) { + if (!this.isValid()) { + return input != null ? this : NaN; + } + var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay(); + if (input != null) { + input = parseWeekday(input, this.localeData()); + return this.add(input - day, 'd'); + } else { + return day; + } + } + + function getSetLocaleDayOfWeek (input) { + if (!this.isValid()) { + return input != null ? this : NaN; + } + var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7; + return input == null ? weekday : this.add(input - weekday, 'd'); + } + + function getSetISODayOfWeek (input) { + if (!this.isValid()) { + return input != null ? this : NaN; + } + // behaves the same as moment#day except + // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6) + // as a setter, sunday should belong to the previous week. + return input == null ? this.day() || 7 : this.day(this.day() % 7 ? input : input - 7); + } + + var defaultWeekdaysRegex = matchWord; + function weekdaysRegex (isStrict) { + if (this._weekdaysParseExact) { + if (!hasOwnProp(this, '_weekdaysRegex')) { + computeWeekdaysParse.call(this); + } + if (isStrict) { + return this._weekdaysStrictRegex; + } else { + return this._weekdaysRegex; + } + } else { + return this._weekdaysStrictRegex && isStrict ? + this._weekdaysStrictRegex : this._weekdaysRegex; + } + } + + var defaultWeekdaysShortRegex = matchWord; + function weekdaysShortRegex (isStrict) { + if (this._weekdaysParseExact) { + if (!hasOwnProp(this, '_weekdaysRegex')) { + computeWeekdaysParse.call(this); + } + if (isStrict) { + return this._weekdaysShortStrictRegex; + } else { + return this._weekdaysShortRegex; + } + } else { + return this._weekdaysShortStrictRegex && isStrict ? + this._weekdaysShortStrictRegex : this._weekdaysShortRegex; + } + } + + var defaultWeekdaysMinRegex = matchWord; + function weekdaysMinRegex (isStrict) { + if (this._weekdaysParseExact) { + if (!hasOwnProp(this, '_weekdaysRegex')) { + computeWeekdaysParse.call(this); + } + if (isStrict) { + return this._weekdaysMinStrictRegex; + } else { + return this._weekdaysMinRegex; + } + } else { + return this._weekdaysMinStrictRegex && isStrict ? + this._weekdaysMinStrictRegex : this._weekdaysMinRegex; + } + } + + + function computeWeekdaysParse () { + function cmpLenRev(a, b) { + return b.length - a.length; + } + + var minPieces = [], shortPieces = [], longPieces = [], mixedPieces = [], + i, mom, minp, shortp, longp; + for (i = 0; i < 7; i++) { + // make the regex if we don't have it already + mom = create_utc__createUTC([2000, 1]).day(i); + minp = this.weekdaysMin(mom, ''); + shortp = this.weekdaysShort(mom, ''); + longp = this.weekdays(mom, ''); + minPieces.push(minp); + shortPieces.push(shortp); + longPieces.push(longp); + mixedPieces.push(minp); + mixedPieces.push(shortp); + mixedPieces.push(longp); + } + // Sorting makes sure if one weekday (or abbr) is a prefix of another it + // will match the longer piece. + minPieces.sort(cmpLenRev); + shortPieces.sort(cmpLenRev); + longPieces.sort(cmpLenRev); + mixedPieces.sort(cmpLenRev); + for (i = 0; i < 7; i++) { + shortPieces[i] = regexEscape(shortPieces[i]); + longPieces[i] = regexEscape(longPieces[i]); + mixedPieces[i] = regexEscape(mixedPieces[i]); + } + + this._weekdaysRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i'); + this._weekdaysShortRegex = this._weekdaysRegex; + this._weekdaysMinRegex = this._weekdaysRegex; + + this._weekdaysStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i'); + this._weekdaysShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i'); + this._weekdaysMinStrictRegex = new RegExp('^(' + minPieces.join('|') + ')', 'i'); + } + + // FORMATTING + + addFormatToken('DDD', ['DDDD', 3], 'DDDo', 'dayOfYear'); + + // ALIASES + + addUnitAlias('dayOfYear', 'DDD'); + + // PARSING + + addRegexToken('DDD', match1to3); + addRegexToken('DDDD', match3); + addParseToken(['DDD', 'DDDD'], function (input, array, config) { + config._dayOfYear = toInt(input); + }); + + // HELPERS + + // MOMENTS + + function getSetDayOfYear (input) { + var dayOfYear = Math.round((this.clone().startOf('day') - this.clone().startOf('year')) / 864e5) + 1; + return input == null ? dayOfYear : this.add((input - dayOfYear), 'd'); + } + + // FORMATTING + + function hFormat() { + return this.hours() % 12 || 12; + } + + function kFormat() { + return this.hours() || 24; + } + + addFormatToken('H', ['HH', 2], 0, 'hour'); + addFormatToken('h', ['hh', 2], 0, hFormat); + addFormatToken('k', ['kk', 2], 0, kFormat); + + addFormatToken('hmm', 0, 0, function () { + return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2); + }); + + addFormatToken('hmmss', 0, 0, function () { + return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2) + + zeroFill(this.seconds(), 2); + }); + + addFormatToken('Hmm', 0, 0, function () { + return '' + this.hours() + zeroFill(this.minutes(), 2); + }); + + addFormatToken('Hmmss', 0, 0, function () { + return '' + this.hours() + zeroFill(this.minutes(), 2) + + zeroFill(this.seconds(), 2); + }); - for (i = lists.length - 1; i >= 0; --i) { - makeList(lists[i]); + function meridiem (token, lowercase) { + addFormatToken(token, 0, 0, function () { + return this.localeData().meridiem(this.hours(), this.minutes(), lowercase); + }); } - moment.normalizeUnits = function (units) { - return normalizeUnits(units); - }; + meridiem('a', true); + meridiem('A', false); - moment.invalid = function (flags) { - var m = moment.utc(NaN); - if (flags != null) { - extend(m._pf, flags); - } - else { - m._pf.userInvalidated = true; - } + // ALIASES - return m; - }; + addUnitAlias('hour', 'h'); - moment.parseZone = function (input) { - return moment(input).parseZone(); - }; + // PARSING - /************************************ - Moment Prototype - ************************************/ + function matchMeridiem (isStrict, locale) { + return locale._meridiemParse; + } + addRegexToken('a', matchMeridiem); + addRegexToken('A', matchMeridiem); + addRegexToken('H', match1to2); + addRegexToken('h', match1to2); + addRegexToken('HH', match1to2, match2); + addRegexToken('hh', match1to2, match2); - extend(moment.fn = Moment.prototype, { + addRegexToken('hmm', match3to4); + addRegexToken('hmmss', match5to6); + addRegexToken('Hmm', match3to4); + addRegexToken('Hmmss', match5to6); - clone : function () { - return moment(this); - }, + addParseToken(['H', 'HH'], HOUR); + addParseToken(['a', 'A'], function (input, array, config) { + config._isPm = config._locale.isPM(input); + config._meridiem = input; + }); + addParseToken(['h', 'hh'], function (input, array, config) { + array[HOUR] = toInt(input); + getParsingFlags(config).bigHour = true; + }); + addParseToken('hmm', function (input, array, config) { + var pos = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos)); + array[MINUTE] = toInt(input.substr(pos)); + getParsingFlags(config).bigHour = true; + }); + addParseToken('hmmss', function (input, array, config) { + var pos1 = input.length - 4; + var pos2 = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos1)); + array[MINUTE] = toInt(input.substr(pos1, 2)); + array[SECOND] = toInt(input.substr(pos2)); + getParsingFlags(config).bigHour = true; + }); + addParseToken('Hmm', function (input, array, config) { + var pos = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos)); + array[MINUTE] = toInt(input.substr(pos)); + }); + addParseToken('Hmmss', function (input, array, config) { + var pos1 = input.length - 4; + var pos2 = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos1)); + array[MINUTE] = toInt(input.substr(pos1, 2)); + array[SECOND] = toInt(input.substr(pos2)); + }); - valueOf : function () { - return +this._d + ((this._offset || 0) * 60000); - }, + // LOCALES - unix : function () { - return Math.floor(+this / 1000); - }, + function localeIsPM (input) { + // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays + // Using charAt should be more compatible. + return ((input + '').toLowerCase().charAt(0) === 'p'); + } - toString : function () { - return this.clone().lang('en').format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ"); - }, + var defaultLocaleMeridiemParse = /[ap]\.?m?\.?/i; + function localeMeridiem (hours, minutes, isLower) { + if (hours > 11) { + return isLower ? 'pm' : 'PM'; + } else { + return isLower ? 'am' : 'AM'; + } + } - toDate : function () { - return this._offset ? new Date(+this) : this._d; - }, - toISOString : function () { - var m = moment(this).utc(); - if (0 < m.year() && m.year() <= 9999) { - return formatMoment(m, 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); - } else { - return formatMoment(m, 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); - } - }, + // MOMENTS - toArray : function () { - var m = this; - return [ - m.year(), - m.month(), - m.date(), - m.hours(), - m.minutes(), - m.seconds(), - m.milliseconds() - ]; - }, + // Setting the hour should keep the time, because the user explicitly + // specified which hour he wants. So trying to maintain the same hour (in + // a new timezone) makes sense. Adding/subtracting hours does not follow + // this rule. + var getSetHour = makeGetSet('Hours', true); - isValid : function () { - return isValid(this); - }, + // FORMATTING - isDSTShifted : function () { + addFormatToken('m', ['mm', 2], 0, 'minute'); - if (this._a) { - return this.isValid() && compareArrays(this._a, (this._isUTC ? moment.utc(this._a) : moment(this._a)).toArray()) > 0; - } + // ALIASES - return false; - }, + addUnitAlias('minute', 'm'); - parsingFlags : function () { - return extend({}, this._pf); - }, + // PARSING - invalidAt: function () { - return this._pf.overflow; - }, + addRegexToken('m', match1to2); + addRegexToken('mm', match1to2, match2); + addParseToken(['m', 'mm'], MINUTE); - utc : function () { - return this.zone(0); - }, + // MOMENTS - local : function () { - this.zone(0); - this._isUTC = false; - return this; - }, + var getSetMinute = makeGetSet('Minutes', false); - format : function (inputString) { - var output = formatMoment(this, inputString || moment.defaultFormat); - return this.lang().postformat(output); - }, + // FORMATTING - add : function (input, val) { - var dur; - // switch args to support add('s', 1) and add(1, 's') - if (typeof input === 'string') { - dur = moment.duration(+val, input); - } else { - dur = moment.duration(input, val); - } - addOrSubtractDurationFromMoment(this, dur, 1); - return this; - }, + addFormatToken('s', ['ss', 2], 0, 'second'); - subtract : function (input, val) { - var dur; - // switch args to support subtract('s', 1) and subtract(1, 's') - if (typeof input === 'string') { - dur = moment.duration(+val, input); - } else { - dur = moment.duration(input, val); - } - addOrSubtractDurationFromMoment(this, dur, -1); - return this; - }, + // ALIASES - diff : function (input, units, asFloat) { - var that = makeAs(input, this), - zoneDiff = (this.zone() - that.zone()) * 6e4, - diff, output; + addUnitAlias('second', 's'); - units = normalizeUnits(units); + // PARSING - if (units === 'year' || units === 'month') { - // average number of days in the months in the given dates - diff = (this.daysInMonth() + that.daysInMonth()) * 432e5; // 24 * 60 * 60 * 1000 / 2 - // difference in months - output = ((this.year() - that.year()) * 12) + (this.month() - that.month()); - // adjust by taking difference in days, average number of days - // and dst in the given months. - output += ((this - moment(this).startOf('month')) - - (that - moment(that).startOf('month'))) / diff; - // same as above but with zones, to negate all dst - output -= ((this.zone() - moment(this).startOf('month').zone()) - - (that.zone() - moment(that).startOf('month').zone())) * 6e4 / diff; - if (units === 'year') { - output = output / 12; - } - } else { - diff = (this - that); - output = units === 'second' ? diff / 1e3 : // 1000 - units === 'minute' ? diff / 6e4 : // 1000 * 60 - units === 'hour' ? diff / 36e5 : // 1000 * 60 * 60 - units === 'day' ? (diff - zoneDiff) / 864e5 : // 1000 * 60 * 60 * 24, negate dst - units === 'week' ? (diff - zoneDiff) / 6048e5 : // 1000 * 60 * 60 * 24 * 7, negate dst - diff; - } - return asFloat ? output : absRound(output); - }, + addRegexToken('s', match1to2); + addRegexToken('ss', match1to2, match2); + addParseToken(['s', 'ss'], SECOND); - from : function (time, withoutSuffix) { - return moment.duration(this.diff(time)).lang(this.lang()._abbr).humanize(!withoutSuffix); - }, + // MOMENTS - fromNow : function (withoutSuffix) { - return this.from(moment(), withoutSuffix); - }, + var getSetSecond = makeGetSet('Seconds', false); - calendar : function () { - // We want to compare the start of today, vs this. - // Getting start-of-today depends on whether we're zone'd or not. - var sod = makeAs(moment(), this).startOf('day'), - diff = this.diff(sod, 'days', true), - format = diff < -6 ? 'sameElse' : - diff < -1 ? 'lastWeek' : - diff < 0 ? 'lastDay' : - diff < 1 ? 'sameDay' : - diff < 2 ? 'nextDay' : - diff < 7 ? 'nextWeek' : 'sameElse'; - return this.format(this.lang().calendar(format, this)); - }, + // FORMATTING - isLeapYear : function () { - return isLeapYear(this.year()); - }, + addFormatToken('S', 0, 0, function () { + return ~~(this.millisecond() / 100); + }); - isDST : function () { - return (this.zone() < this.clone().month(0).zone() || - this.zone() < this.clone().month(5).zone()); - }, + addFormatToken(0, ['SS', 2], 0, function () { + return ~~(this.millisecond() / 10); + }); - day : function (input) { - var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay(); - if (input != null) { - input = parseWeekday(input, this.lang()); - return this.add({ d : input - day }); - } else { - return day; - } - }, + addFormatToken(0, ['SSS', 3], 0, 'millisecond'); + addFormatToken(0, ['SSSS', 4], 0, function () { + return this.millisecond() * 10; + }); + addFormatToken(0, ['SSSSS', 5], 0, function () { + return this.millisecond() * 100; + }); + addFormatToken(0, ['SSSSSS', 6], 0, function () { + return this.millisecond() * 1000; + }); + addFormatToken(0, ['SSSSSSS', 7], 0, function () { + return this.millisecond() * 10000; + }); + addFormatToken(0, ['SSSSSSSS', 8], 0, function () { + return this.millisecond() * 100000; + }); + addFormatToken(0, ['SSSSSSSSS', 9], 0, function () { + return this.millisecond() * 1000000; + }); - month : function (input) { - var utc = this._isUTC ? 'UTC' : '', - dayOfMonth; - if (input != null) { - if (typeof input === 'string') { - input = this.lang().monthsParse(input); - if (typeof input !== 'number') { - return this; - } - } + // ALIASES - dayOfMonth = this.date(); - this.date(1); - this._d['set' + utc + 'Month'](input); - this.date(Math.min(dayOfMonth, this.daysInMonth())); + addUnitAlias('millisecond', 'ms'); - moment.updateOffset(this); - return this; - } else { - return this._d['get' + utc + 'Month'](); - } - }, + // PARSING - startOf: function (units) { - units = normalizeUnits(units); - // the following switch intentionally omits break keywords - // to utilize falling through the cases. - switch (units) { - case 'year': - this.month(0); - /* falls through */ - case 'month': - this.date(1); - /* falls through */ - case 'week': - case 'isoWeek': - case 'day': - this.hours(0); - /* falls through */ - case 'hour': - this.minutes(0); - /* falls through */ - case 'minute': - this.seconds(0); - /* falls through */ - case 'second': - this.milliseconds(0); - /* falls through */ - } - - // weeks are a special case - if (units === 'week') { - this.weekday(0); - } else if (units === 'isoWeek') { - this.isoWeekday(1); - } + addRegexToken('S', match1to3, match1); + addRegexToken('SS', match1to3, match2); + addRegexToken('SSS', match1to3, match3); - return this; - }, + var token; + for (token = 'SSSS'; token.length <= 9; token += 'S') { + addRegexToken(token, matchUnsigned); + } - endOf: function (units) { - units = normalizeUnits(units); - return this.startOf(units).add((units === 'isoWeek' ? 'week' : units), 1).subtract('ms', 1); - }, + function parseMs(input, array) { + array[MILLISECOND] = toInt(('0.' + input) * 1000); + } - isAfter: function (input, units) { - units = typeof units !== 'undefined' ? units : 'millisecond'; - return +this.clone().startOf(units) > +moment(input).startOf(units); - }, + for (token = 'S'; token.length <= 9; token += 'S') { + addParseToken(token, parseMs); + } + // MOMENTS - isBefore: function (input, units) { - units = typeof units !== 'undefined' ? units : 'millisecond'; - return +this.clone().startOf(units) < +moment(input).startOf(units); - }, + var getSetMillisecond = makeGetSet('Milliseconds', false); - isSame: function (input, units) { - units = units || 'ms'; - return +this.clone().startOf(units) === +makeAs(input, this).startOf(units); - }, + // FORMATTING - min: function (other) { - other = moment.apply(null, arguments); - return other < this ? this : other; - }, + addFormatToken('z', 0, 0, 'zoneAbbr'); + addFormatToken('zz', 0, 0, 'zoneName'); + + // MOMENTS + + function getZoneAbbr () { + return this._isUTC ? 'UTC' : ''; + } + + function getZoneName () { + return this._isUTC ? 'Coordinated Universal Time' : ''; + } + + var momentPrototype__proto = Moment.prototype; + + momentPrototype__proto.add = add_subtract__add; + momentPrototype__proto.calendar = moment_calendar__calendar; + momentPrototype__proto.clone = clone; + momentPrototype__proto.diff = diff; + momentPrototype__proto.endOf = endOf; + momentPrototype__proto.format = format; + momentPrototype__proto.from = from; + momentPrototype__proto.fromNow = fromNow; + momentPrototype__proto.to = to; + momentPrototype__proto.toNow = toNow; + momentPrototype__proto.get = getSet; + momentPrototype__proto.invalidAt = invalidAt; + momentPrototype__proto.isAfter = isAfter; + momentPrototype__proto.isBefore = isBefore; + momentPrototype__proto.isBetween = isBetween; + momentPrototype__proto.isSame = isSame; + momentPrototype__proto.isSameOrAfter = isSameOrAfter; + momentPrototype__proto.isSameOrBefore = isSameOrBefore; + momentPrototype__proto.isValid = moment_valid__isValid; + momentPrototype__proto.lang = lang; + momentPrototype__proto.locale = locale; + momentPrototype__proto.localeData = localeData; + momentPrototype__proto.max = prototypeMax; + momentPrototype__proto.min = prototypeMin; + momentPrototype__proto.parsingFlags = parsingFlags; + momentPrototype__proto.set = getSet; + momentPrototype__proto.startOf = startOf; + momentPrototype__proto.subtract = add_subtract__subtract; + momentPrototype__proto.toArray = toArray; + momentPrototype__proto.toObject = toObject; + momentPrototype__proto.toDate = toDate; + momentPrototype__proto.toISOString = moment_format__toISOString; + momentPrototype__proto.toJSON = toJSON; + momentPrototype__proto.toString = toString; + momentPrototype__proto.unix = unix; + momentPrototype__proto.valueOf = to_type__valueOf; + momentPrototype__proto.creationData = creationData; - max: function (other) { - other = moment.apply(null, arguments); - return other > this ? this : other; - }, + // Year + momentPrototype__proto.year = getSetYear; + momentPrototype__proto.isLeapYear = getIsLeapYear; - zone : function (input) { - var offset = this._offset || 0; - if (input != null) { - if (typeof input === "string") { - input = timezoneMinutesFromString(input); - } - if (Math.abs(input) < 16) { - input = input * 60; - } - this._offset = input; - this._isUTC = true; - if (offset !== input) { - addOrSubtractDurationFromMoment(this, moment.duration(offset - input, 'm'), 1, true); - } - } else { - return this._isUTC ? offset : this._d.getTimezoneOffset(); - } - return this; - }, + // Week Year + momentPrototype__proto.weekYear = getSetWeekYear; + momentPrototype__proto.isoWeekYear = getSetISOWeekYear; - zoneAbbr : function () { - return this._isUTC ? "UTC" : ""; - }, + // Quarter + momentPrototype__proto.quarter = momentPrototype__proto.quarters = getSetQuarter; - zoneName : function () { - return this._isUTC ? "Coordinated Universal Time" : ""; - }, + // Month + momentPrototype__proto.month = getSetMonth; + momentPrototype__proto.daysInMonth = getDaysInMonth; - parseZone : function () { - if (this._tzm) { - this.zone(this._tzm); - } else if (typeof this._i === 'string') { - this.zone(this._i); - } - return this; - }, + // Week + momentPrototype__proto.week = momentPrototype__proto.weeks = getSetWeek; + momentPrototype__proto.isoWeek = momentPrototype__proto.isoWeeks = getSetISOWeek; + momentPrototype__proto.weeksInYear = getWeeksInYear; + momentPrototype__proto.isoWeeksInYear = getISOWeeksInYear; + + // Day + momentPrototype__proto.date = getSetDayOfMonth; + momentPrototype__proto.day = momentPrototype__proto.days = getSetDayOfWeek; + momentPrototype__proto.weekday = getSetLocaleDayOfWeek; + momentPrototype__proto.isoWeekday = getSetISODayOfWeek; + momentPrototype__proto.dayOfYear = getSetDayOfYear; + + // Hour + momentPrototype__proto.hour = momentPrototype__proto.hours = getSetHour; + + // Minute + momentPrototype__proto.minute = momentPrototype__proto.minutes = getSetMinute; + + // Second + momentPrototype__proto.second = momentPrototype__proto.seconds = getSetSecond; + + // Millisecond + momentPrototype__proto.millisecond = momentPrototype__proto.milliseconds = getSetMillisecond; + + // Offset + momentPrototype__proto.utcOffset = getSetOffset; + momentPrototype__proto.utc = setOffsetToUTC; + momentPrototype__proto.local = setOffsetToLocal; + momentPrototype__proto.parseZone = setOffsetToParsedOffset; + momentPrototype__proto.hasAlignedHourOffset = hasAlignedHourOffset; + momentPrototype__proto.isDST = isDaylightSavingTime; + momentPrototype__proto.isDSTShifted = isDaylightSavingTimeShifted; + momentPrototype__proto.isLocal = isLocal; + momentPrototype__proto.isUtcOffset = isUtcOffset; + momentPrototype__proto.isUtc = isUtc; + momentPrototype__proto.isUTC = isUtc; + + // Timezone + momentPrototype__proto.zoneAbbr = getZoneAbbr; + momentPrototype__proto.zoneName = getZoneName; + + // Deprecations + momentPrototype__proto.dates = deprecate('dates accessor is deprecated. Use date instead.', getSetDayOfMonth); + momentPrototype__proto.months = deprecate('months accessor is deprecated. Use month instead', getSetMonth); + momentPrototype__proto.years = deprecate('years accessor is deprecated. Use year instead', getSetYear); + momentPrototype__proto.zone = deprecate('moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779', getSetZone); + + var momentPrototype = momentPrototype__proto; + + function moment__createUnix (input) { + return local__createLocal(input * 1000); + } + + function moment__createInZone () { + return local__createLocal.apply(null, arguments).parseZone(); + } + + var defaultCalendar = { + sameDay : '[Today at] LT', + nextDay : '[Tomorrow at] LT', + nextWeek : 'dddd [at] LT', + lastDay : '[Yesterday at] LT', + lastWeek : '[Last] dddd [at] LT', + sameElse : 'L' + }; - hasAlignedHourOffset : function (input) { - if (!input) { - input = 0; - } - else { - input = moment(input).zone(); - } + function locale_calendar__calendar (key, mom, now) { + var output = this._calendar[key]; + return isFunction(output) ? output.call(mom, now) : output; + } - return (this.zone() - input) % 60 === 0; - }, + var defaultLongDateFormat = { + LTS : 'h:mm:ss A', + LT : 'h:mm A', + L : 'MM/DD/YYYY', + LL : 'MMMM D, YYYY', + LLL : 'MMMM D, YYYY h:mm A', + LLLL : 'dddd, MMMM D, YYYY h:mm A' + }; - daysInMonth : function () { - return daysInMonth(this.year(), this.month()); - }, + function longDateFormat (key) { + var format = this._longDateFormat[key], + formatUpper = this._longDateFormat[key.toUpperCase()]; - dayOfYear : function (input) { - var dayOfYear = round((moment(this).startOf('day') - moment(this).startOf('year')) / 864e5) + 1; - return input == null ? dayOfYear : this.add("d", (input - dayOfYear)); - }, + if (format || !formatUpper) { + return format; + } - quarter : function () { - return Math.ceil((this.month() + 1.0) / 3.0); - }, + this._longDateFormat[key] = formatUpper.replace(/MMMM|MM|DD|dddd/g, function (val) { + return val.slice(1); + }); - weekYear : function (input) { - var year = weekOfYear(this, this.lang()._week.dow, this.lang()._week.doy).year; - return input == null ? year : this.add("y", (input - year)); - }, + return this._longDateFormat[key]; + } - isoWeekYear : function (input) { - var year = weekOfYear(this, 1, 4).year; - return input == null ? year : this.add("y", (input - year)); - }, + var defaultInvalidDate = 'Invalid date'; - week : function (input) { - var week = this.lang().week(this); - return input == null ? week : this.add("d", (input - week) * 7); - }, + function invalidDate () { + return this._invalidDate; + } - isoWeek : function (input) { - var week = weekOfYear(this, 1, 4).week; - return input == null ? week : this.add("d", (input - week) * 7); - }, + var defaultOrdinal = '%d'; + var defaultOrdinalParse = /\d{1,2}/; - weekday : function (input) { - var weekday = (this.day() + 7 - this.lang()._week.dow) % 7; - return input == null ? weekday : this.add("d", input - weekday); - }, + function ordinal (number) { + return this._ordinal.replace('%d', number); + } - isoWeekday : function (input) { - // behaves the same as moment#day except - // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6) - // as a setter, sunday should belong to the previous week. - return input == null ? this.day() || 7 : this.day(this.day() % 7 ? input : input - 7); - }, + function preParsePostFormat (string) { + return string; + } - get : function (units) { - units = normalizeUnits(units); - return this[units](); - }, + var defaultRelativeTime = { + future : 'in %s', + past : '%s ago', + s : 'a few seconds', + m : 'a minute', + mm : '%d minutes', + h : 'an hour', + hh : '%d hours', + d : 'a day', + dd : '%d days', + M : 'a month', + MM : '%d months', + y : 'a year', + yy : '%d years' + }; - set : function (units, value) { - units = normalizeUnits(units); - if (typeof this[units] === 'function') { - this[units](value); + function relative__relativeTime (number, withoutSuffix, string, isFuture) { + var output = this._relativeTime[string]; + return (isFunction(output)) ? + output(number, withoutSuffix, string, isFuture) : + output.replace(/%d/i, number); + } + + function pastFuture (diff, output) { + var format = this._relativeTime[diff > 0 ? 'future' : 'past']; + return isFunction(format) ? format(output) : format.replace(/%s/i, output); + } + + var prototype__proto = Locale.prototype; + + prototype__proto._calendar = defaultCalendar; + prototype__proto.calendar = locale_calendar__calendar; + prototype__proto._longDateFormat = defaultLongDateFormat; + prototype__proto.longDateFormat = longDateFormat; + prototype__proto._invalidDate = defaultInvalidDate; + prototype__proto.invalidDate = invalidDate; + prototype__proto._ordinal = defaultOrdinal; + prototype__proto.ordinal = ordinal; + prototype__proto._ordinalParse = defaultOrdinalParse; + prototype__proto.preparse = preParsePostFormat; + prototype__proto.postformat = preParsePostFormat; + prototype__proto._relativeTime = defaultRelativeTime; + prototype__proto.relativeTime = relative__relativeTime; + prototype__proto.pastFuture = pastFuture; + prototype__proto.set = locale_set__set; + + // Month + prototype__proto.months = localeMonths; + prototype__proto._months = defaultLocaleMonths; + prototype__proto.monthsShort = localeMonthsShort; + prototype__proto._monthsShort = defaultLocaleMonthsShort; + prototype__proto.monthsParse = localeMonthsParse; + prototype__proto._monthsRegex = defaultMonthsRegex; + prototype__proto.monthsRegex = monthsRegex; + prototype__proto._monthsShortRegex = defaultMonthsShortRegex; + prototype__proto.monthsShortRegex = monthsShortRegex; + + // Week + prototype__proto.week = localeWeek; + prototype__proto._week = defaultLocaleWeek; + prototype__proto.firstDayOfYear = localeFirstDayOfYear; + prototype__proto.firstDayOfWeek = localeFirstDayOfWeek; + + // Day of Week + prototype__proto.weekdays = localeWeekdays; + prototype__proto._weekdays = defaultLocaleWeekdays; + prototype__proto.weekdaysMin = localeWeekdaysMin; + prototype__proto._weekdaysMin = defaultLocaleWeekdaysMin; + prototype__proto.weekdaysShort = localeWeekdaysShort; + prototype__proto._weekdaysShort = defaultLocaleWeekdaysShort; + prototype__proto.weekdaysParse = localeWeekdaysParse; + + prototype__proto._weekdaysRegex = defaultWeekdaysRegex; + prototype__proto.weekdaysRegex = weekdaysRegex; + prototype__proto._weekdaysShortRegex = defaultWeekdaysShortRegex; + prototype__proto.weekdaysShortRegex = weekdaysShortRegex; + prototype__proto._weekdaysMinRegex = defaultWeekdaysMinRegex; + prototype__proto.weekdaysMinRegex = weekdaysMinRegex; + + // Hours + prototype__proto.isPM = localeIsPM; + prototype__proto._meridiemParse = defaultLocaleMeridiemParse; + prototype__proto.meridiem = localeMeridiem; + + function lists__get (format, index, field, setter) { + var locale = locale_locales__getLocale(); + var utc = create_utc__createUTC().set(setter, index); + return locale[field](utc, format); + } + + function listMonthsImpl (format, index, field) { + if (typeof format === 'number') { + index = format; + format = undefined; + } + + format = format || ''; + + if (index != null) { + return lists__get(format, index, field, 'month'); + } + + var i; + var out = []; + for (i = 0; i < 12; i++) { + out[i] = lists__get(format, i, field, 'month'); + } + return out; + } + + // () + // (5) + // (fmt, 5) + // (fmt) + // (true) + // (true, 5) + // (true, fmt, 5) + // (true, fmt) + function listWeekdaysImpl (localeSorted, format, index, field) { + if (typeof localeSorted === 'boolean') { + if (typeof format === 'number') { + index = format; + format = undefined; } - return this; - }, - // If passed a language key, it will set the language for this - // instance. Otherwise, it will return the language configuration - // variables for this instance. - lang : function (key) { - if (key === undefined) { - return this._lang; - } else { - this._lang = getLangDefinition(key); - return this; + format = format || ''; + } else { + format = localeSorted; + index = format; + localeSorted = false; + + if (typeof format === 'number') { + index = format; + format = undefined; } + + format = format || ''; } - }); - // helper for adding shortcuts - function makeGetterAndSetter(name, key) { - moment.fn[name] = moment.fn[name + 's'] = function (input) { - var utc = this._isUTC ? 'UTC' : ''; - if (input != null) { - this._d['set' + utc + key](input); - moment.updateOffset(this); - return this; - } else { - return this._d['get' + utc + key](); - } - }; - } + var locale = locale_locales__getLocale(), + shift = localeSorted ? locale._week.dow : 0; - // loop through and add shortcuts (Month, Date, Hours, Minutes, Seconds, Milliseconds) - for (i = 0; i < proxyGettersAndSetters.length; i ++) { - makeGetterAndSetter(proxyGettersAndSetters[i].toLowerCase().replace(/s$/, ''), proxyGettersAndSetters[i]); - } + if (index != null) { + return lists__get(format, (index + shift) % 7, field, 'day'); + } - // add shortcut for year (uses different syntax than the getter/setter 'year' == 'FullYear') - makeGetterAndSetter('year', 'FullYear'); + var i; + var out = []; + for (i = 0; i < 7; i++) { + out[i] = lists__get(format, (i + shift) % 7, field, 'day'); + } + return out; + } - // add plural methods - moment.fn.days = moment.fn.day; - moment.fn.months = moment.fn.month; - moment.fn.weeks = moment.fn.week; - moment.fn.isoWeeks = moment.fn.isoWeek; + function lists__listMonths (format, index) { + return listMonthsImpl(format, index, 'months'); + } - // add aliased format methods - moment.fn.toJSON = moment.fn.toISOString; + function lists__listMonthsShort (format, index) { + return listMonthsImpl(format, index, 'monthsShort'); + } - /************************************ - Duration Prototype - ************************************/ + function lists__listWeekdays (localeSorted, format, index) { + return listWeekdaysImpl(localeSorted, format, index, 'weekdays'); + } + function lists__listWeekdaysShort (localeSorted, format, index) { + return listWeekdaysImpl(localeSorted, format, index, 'weekdaysShort'); + } - extend(moment.duration.fn = Duration.prototype, { + function lists__listWeekdaysMin (localeSorted, format, index) { + return listWeekdaysImpl(localeSorted, format, index, 'weekdaysMin'); + } - _bubble : function () { - var milliseconds = this._milliseconds, - days = this._days, - months = this._months, - data = this._data, - seconds, minutes, hours, years; + locale_locales__getSetGlobalLocale('en', { + ordinalParse: /\d{1,2}(th|st|nd|rd)/, + ordinal : function (number) { + var b = number % 10, + output = (toInt(number % 100 / 10) === 1) ? 'th' : + (b === 1) ? 'st' : + (b === 2) ? 'nd' : + (b === 3) ? 'rd' : 'th'; + return number + output; + } + }); - // The following code bubbles up values, see the tests for - // examples of what that means. - data.milliseconds = milliseconds % 1000; + // Side effect imports + utils_hooks__hooks.lang = deprecate('moment.lang is deprecated. Use moment.locale instead.', locale_locales__getSetGlobalLocale); + utils_hooks__hooks.langData = deprecate('moment.langData is deprecated. Use moment.localeData instead.', locale_locales__getLocale); - seconds = absRound(milliseconds / 1000); - data.seconds = seconds % 60; + var mathAbs = Math.abs; - minutes = absRound(seconds / 60); - data.minutes = minutes % 60; + function duration_abs__abs () { + var data = this._data; - hours = absRound(minutes / 60); - data.hours = hours % 24; + this._milliseconds = mathAbs(this._milliseconds); + this._days = mathAbs(this._days); + this._months = mathAbs(this._months); - days += absRound(hours / 24); - data.days = days % 30; + data.milliseconds = mathAbs(data.milliseconds); + data.seconds = mathAbs(data.seconds); + data.minutes = mathAbs(data.minutes); + data.hours = mathAbs(data.hours); + data.months = mathAbs(data.months); + data.years = mathAbs(data.years); - months += absRound(days / 30); - data.months = months % 12; + return this; + } - years = absRound(months / 12); - data.years = years; - }, + function duration_add_subtract__addSubtract (duration, input, value, direction) { + var other = create__createDuration(input, value); - weeks : function () { - return absRound(this.days() / 7); - }, + duration._milliseconds += direction * other._milliseconds; + duration._days += direction * other._days; + duration._months += direction * other._months; - valueOf : function () { - return this._milliseconds + - this._days * 864e5 + - (this._months % 12) * 2592e6 + - toInt(this._months / 12) * 31536e6; - }, + return duration._bubble(); + } - humanize : function (withSuffix) { - var difference = +this, - output = relativeTime(difference, !withSuffix, this.lang()); + // supports only 2.0-style add(1, 's') or add(duration) + function duration_add_subtract__add (input, value) { + return duration_add_subtract__addSubtract(this, input, value, 1); + } - if (withSuffix) { - output = this.lang().pastFuture(difference, output); - } + // supports only 2.0-style subtract(1, 's') or subtract(duration) + function duration_add_subtract__subtract (input, value) { + return duration_add_subtract__addSubtract(this, input, value, -1); + } - return this.lang().postformat(output); - }, + function absCeil (number) { + if (number < 0) { + return Math.floor(number); + } else { + return Math.ceil(number); + } + } - add : function (input, val) { - // supports only 2.0-style add(1, 's') or add(moment) - var dur = moment.duration(input, val); + function bubble () { + var milliseconds = this._milliseconds; + var days = this._days; + var months = this._months; + var data = this._data; + var seconds, minutes, hours, years, monthsFromDays; - this._milliseconds += dur._milliseconds; - this._days += dur._days; - this._months += dur._months; + // if we have a mix of positive and negative values, bubble down first + // check: https://github.com/moment/moment/issues/2166 + if (!((milliseconds >= 0 && days >= 0 && months >= 0) || + (milliseconds <= 0 && days <= 0 && months <= 0))) { + milliseconds += absCeil(monthsToDays(months) + days) * 864e5; + days = 0; + months = 0; + } - this._bubble(); + // The following code bubbles up values, see the tests for + // examples of what that means. + data.milliseconds = milliseconds % 1000; - return this; - }, + seconds = absFloor(milliseconds / 1000); + data.seconds = seconds % 60; - subtract : function (input, val) { - var dur = moment.duration(input, val); + minutes = absFloor(seconds / 60); + data.minutes = minutes % 60; - this._milliseconds -= dur._milliseconds; - this._days -= dur._days; - this._months -= dur._months; + hours = absFloor(minutes / 60); + data.hours = hours % 24; - this._bubble(); + days += absFloor(hours / 24); - return this; - }, + // convert days to months + monthsFromDays = absFloor(daysToMonths(days)); + months += monthsFromDays; + days -= absCeil(monthsToDays(monthsFromDays)); - get : function (units) { - units = normalizeUnits(units); - return this[units.toLowerCase() + 's'](); - }, + // 12 months -> 1 year + years = absFloor(months / 12); + months %= 12; - as : function (units) { - units = normalizeUnits(units); - return this['as' + units.charAt(0).toUpperCase() + units.slice(1) + 's'](); - }, + data.days = days; + data.months = months; + data.years = years; - lang : moment.fn.lang, - - toIsoString : function () { - // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js - var years = Math.abs(this.years()), - months = Math.abs(this.months()), - days = Math.abs(this.days()), - hours = Math.abs(this.hours()), - minutes = Math.abs(this.minutes()), - seconds = Math.abs(this.seconds() + this.milliseconds() / 1000); - - if (!this.asSeconds()) { - // this is the same as C#'s (Noda) and python (isodate)... - // but not other JS (goog.date) - return 'P0D'; - } - - return (this.asSeconds() < 0 ? '-' : '') + - 'P' + - (years ? years + 'Y' : '') + - (months ? months + 'M' : '') + - (days ? days + 'D' : '') + - ((hours || minutes || seconds) ? 'T' : '') + - (hours ? hours + 'H' : '') + - (minutes ? minutes + 'M' : '') + - (seconds ? seconds + 'S' : ''); - } - }); + return this; + } - function makeDurationGetter(name) { - moment.duration.fn[name] = function () { - return this._data[name]; - }; + function daysToMonths (days) { + // 400 years have 146097 days (taking into account leap year rules) + // 400 years have 12 months === 4800 + return days * 4800 / 146097; } - function makeDurationAsGetter(name, factor) { - moment.duration.fn['as' + name] = function () { - return +this / factor; - }; + function monthsToDays (months) { + // the reverse of daysToMonths + return months * 146097 / 4800; } - for (i in unitMillisecondFactors) { - if (unitMillisecondFactors.hasOwnProperty(i)) { - makeDurationAsGetter(i, unitMillisecondFactors[i]); - makeDurationGetter(i.toLowerCase()); + function as (units) { + var days; + var months; + var milliseconds = this._milliseconds; + + units = normalizeUnits(units); + + if (units === 'month' || units === 'year') { + days = this._days + milliseconds / 864e5; + months = this._months + daysToMonths(days); + return units === 'month' ? months : months / 12; + } else { + // handle milliseconds separately because of floating point math errors (issue #1867) + days = this._days + Math.round(monthsToDays(this._months)); + switch (units) { + case 'week' : return days / 7 + milliseconds / 6048e5; + case 'day' : return days + milliseconds / 864e5; + case 'hour' : return days * 24 + milliseconds / 36e5; + case 'minute' : return days * 1440 + milliseconds / 6e4; + case 'second' : return days * 86400 + milliseconds / 1000; + // Math.floor prevents floating point math errors here + case 'millisecond': return Math.floor(days * 864e5) + milliseconds; + default: throw new Error('Unknown unit ' + units); + } } } - makeDurationAsGetter('Weeks', 6048e5); - moment.duration.fn.asMonths = function () { - return (+this - this.years() * 31536e6) / 2592e6 + this.years() * 12; - }; + // TODO: Use this.as('ms')? + function duration_as__valueOf () { + return ( + this._milliseconds + + this._days * 864e5 + + (this._months % 12) * 2592e6 + + toInt(this._months / 12) * 31536e6 + ); + } + function makeAs (alias) { + return function () { + return this.as(alias); + }; + } - /************************************ - Default Lang - ************************************/ + var asMilliseconds = makeAs('ms'); + var asSeconds = makeAs('s'); + var asMinutes = makeAs('m'); + var asHours = makeAs('h'); + var asDays = makeAs('d'); + var asWeeks = makeAs('w'); + var asMonths = makeAs('M'); + var asYears = makeAs('y'); + function duration_get__get (units) { + units = normalizeUnits(units); + return this[units + 's'](); + } - // Set default language, other languages will inherit from English. - moment.lang('en', { - ordinal : function (number) { - var b = number % 10, - output = (toInt(number % 100 / 10) === 1) ? 'th' : - (b === 1) ? 'st' : - (b === 2) ? 'nd' : - (b === 3) ? 'rd' : 'th'; - return number + output; - } - }); + function makeGetter(name) { + return function () { + return this._data[name]; + }; + } + + var milliseconds = makeGetter('milliseconds'); + var seconds = makeGetter('seconds'); + var minutes = makeGetter('minutes'); + var hours = makeGetter('hours'); + var days = makeGetter('days'); + var months = makeGetter('months'); + var years = makeGetter('years'); - /* EMBED_LANGUAGES */ + function weeks () { + return absFloor(this.days() / 7); + } - /************************************ - Exposing Moment - ************************************/ + var round = Math.round; + var thresholds = { + s: 45, // seconds to minute + m: 45, // minutes to hour + h: 22, // hours to day + d: 26, // days to month + M: 11 // months to year + }; - function makeGlobal(deprecate) { - var warned = false, local_moment = moment; - /*global ender:false */ - if (typeof ender !== 'undefined') { - return; + // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize + function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) { + return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture); + } + + function duration_humanize__relativeTime (posNegDuration, withoutSuffix, locale) { + var duration = create__createDuration(posNegDuration).abs(); + var seconds = round(duration.as('s')); + var minutes = round(duration.as('m')); + var hours = round(duration.as('h')); + var days = round(duration.as('d')); + var months = round(duration.as('M')); + var years = round(duration.as('y')); + + var a = seconds < thresholds.s && ['s', seconds] || + minutes <= 1 && ['m'] || + minutes < thresholds.m && ['mm', minutes] || + hours <= 1 && ['h'] || + hours < thresholds.h && ['hh', hours] || + days <= 1 && ['d'] || + days < thresholds.d && ['dd', days] || + months <= 1 && ['M'] || + months < thresholds.M && ['MM', months] || + years <= 1 && ['y'] || ['yy', years]; + + a[2] = withoutSuffix; + a[3] = +posNegDuration > 0; + a[4] = locale; + return substituteTimeAgo.apply(null, a); + } + + // This function allows you to set a threshold for relative time strings + function duration_humanize__getSetRelativeTimeThreshold (threshold, limit) { + if (thresholds[threshold] === undefined) { + return false; } - // here, `this` means `window` in the browser, or `global` on the server - // add `moment` as a global object via a string identifier, - // for Closure Compiler "advanced" mode - if (deprecate) { - global.moment = function () { - if (!warned && console && console.warn) { - warned = true; - console.warn( - "Accessing Moment through the global scope is " + - "deprecated, and will be removed in an upcoming " + - "release."); - } - return local_moment.apply(null, arguments); - }; - extend(global.moment, local_moment); - } else { - global['moment'] = moment; + if (limit === undefined) { + return thresholds[threshold]; } + thresholds[threshold] = limit; + return true; } - // CommonJS module is defined - if (hasModule) { - module.exports = moment; - makeGlobal(true); - } else if (typeof define === "function" && define.amd) { - define("moment", function (require, exports, module) { - if (module.config && module.config() && module.config().noGlobal !== true) { - // If user provided noGlobal, he is aware of global - makeGlobal(module.config().noGlobal === undefined); - } + function humanize (withSuffix) { + var locale = this.localeData(); + var output = duration_humanize__relativeTime(this, !withSuffix, locale); + + if (withSuffix) { + output = locale.pastFuture(+this, output); + } + + return locale.postformat(output); + } + + var iso_string__abs = Math.abs; + + function iso_string__toISOString() { + // for ISO strings we do not use the normal bubbling rules: + // * milliseconds bubble up until they become hours + // * days do not bubble at all + // * months bubble up until they become years + // This is because there is no context-free conversion between hours and days + // (think of clock changes) + // and also not between days and months (28-31 days per month) + var seconds = iso_string__abs(this._milliseconds) / 1000; + var days = iso_string__abs(this._days); + var months = iso_string__abs(this._months); + var minutes, hours, years; + + // 3600 seconds -> 60 minutes -> 1 hour + minutes = absFloor(seconds / 60); + hours = absFloor(minutes / 60); + seconds %= 60; + minutes %= 60; + + // 12 months -> 1 year + years = absFloor(months / 12); + months %= 12; + + + // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js + var Y = years; + var M = months; + var D = days; + var h = hours; + var m = minutes; + var s = seconds; + var total = this.asSeconds(); + + if (!total) { + // this is the same as C#'s (Noda) and python (isodate)... + // but not other JS (goog.date) + return 'P0D'; + } + + return (total < 0 ? '-' : '') + + 'P' + + (Y ? Y + 'Y' : '') + + (M ? M + 'M' : '') + + (D ? D + 'D' : '') + + ((h || m || s) ? 'T' : '') + + (h ? h + 'H' : '') + + (m ? m + 'M' : '') + + (s ? s + 'S' : ''); + } + + var duration_prototype__proto = Duration.prototype; + + duration_prototype__proto.abs = duration_abs__abs; + duration_prototype__proto.add = duration_add_subtract__add; + duration_prototype__proto.subtract = duration_add_subtract__subtract; + duration_prototype__proto.as = as; + duration_prototype__proto.asMilliseconds = asMilliseconds; + duration_prototype__proto.asSeconds = asSeconds; + duration_prototype__proto.asMinutes = asMinutes; + duration_prototype__proto.asHours = asHours; + duration_prototype__proto.asDays = asDays; + duration_prototype__proto.asWeeks = asWeeks; + duration_prototype__proto.asMonths = asMonths; + duration_prototype__proto.asYears = asYears; + duration_prototype__proto.valueOf = duration_as__valueOf; + duration_prototype__proto._bubble = bubble; + duration_prototype__proto.get = duration_get__get; + duration_prototype__proto.milliseconds = milliseconds; + duration_prototype__proto.seconds = seconds; + duration_prototype__proto.minutes = minutes; + duration_prototype__proto.hours = hours; + duration_prototype__proto.days = days; + duration_prototype__proto.weeks = weeks; + duration_prototype__proto.months = months; + duration_prototype__proto.years = years; + duration_prototype__proto.humanize = humanize; + duration_prototype__proto.toISOString = iso_string__toISOString; + duration_prototype__proto.toString = iso_string__toISOString; + duration_prototype__proto.toJSON = iso_string__toISOString; + duration_prototype__proto.locale = locale; + duration_prototype__proto.localeData = localeData; + + // Deprecations + duration_prototype__proto.toIsoString = deprecate('toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)', iso_string__toISOString); + duration_prototype__proto.lang = lang; + + // Side effect imports + + // FORMATTING + + addFormatToken('X', 0, 0, 'unix'); + addFormatToken('x', 0, 0, 'valueOf'); + + // PARSING + + addRegexToken('x', matchSigned); + addRegexToken('X', matchTimestamp); + addParseToken('X', function (input, array, config) { + config._d = new Date(parseFloat(input, 10) * 1000); + }); + addParseToken('x', function (input, array, config) { + config._d = new Date(toInt(input)); + }); - return moment; - }); - } else { - makeGlobal(); - } -}).call(this); -;/** + // Side effect imports + + + utils_hooks__hooks.version = '2.13.0'; + + setHookCallback(local__createLocal); + + utils_hooks__hooks.fn = momentPrototype; + utils_hooks__hooks.min = min; + utils_hooks__hooks.max = max; + utils_hooks__hooks.now = now; + utils_hooks__hooks.utc = create_utc__createUTC; + utils_hooks__hooks.unix = moment__createUnix; + utils_hooks__hooks.months = lists__listMonths; + utils_hooks__hooks.isDate = isDate; + utils_hooks__hooks.locale = locale_locales__getSetGlobalLocale; + utils_hooks__hooks.invalid = valid__createInvalid; + utils_hooks__hooks.duration = create__createDuration; + utils_hooks__hooks.isMoment = isMoment; + utils_hooks__hooks.weekdays = lists__listWeekdays; + utils_hooks__hooks.parseZone = moment__createInZone; + utils_hooks__hooks.localeData = locale_locales__getLocale; + utils_hooks__hooks.isDuration = isDuration; + utils_hooks__hooks.monthsShort = lists__listMonthsShort; + utils_hooks__hooks.weekdaysMin = lists__listWeekdaysMin; + utils_hooks__hooks.defineLocale = defineLocale; + utils_hooks__hooks.updateLocale = updateLocale; + utils_hooks__hooks.locales = locale_locales__listLocales; + utils_hooks__hooks.weekdaysShort = lists__listWeekdaysShort; + utils_hooks__hooks.normalizeUnits = normalizeUnits; + utils_hooks__hooks.relativeTimeThreshold = duration_humanize__getSetRelativeTimeThreshold; + utils_hooks__hooks.prototype = momentPrototype; + + var _moment = utils_hooks__hooks; + + return _moment; + +}));;/** * Parse a text request to a json query object tree * * @param {String} string The string to parse @@ -5688,7 +7364,22 @@ return new Parser; this.mom = moment(str); this.setPrecision(MONTH); } else if (str.match(/\d\d\d\d/)) { - this.mom = moment(str); + // Creating a moment with only the year will show this deprecation + // warning: + // + // Deprecation warning: moment construction falls back to js Date. This is + // discouraged and will be removed in upcoming major release. Please refer + // to https://github.com/moment/moment/issues/1407 for more info. + // + // TL;DR: parsing year-only strings with momentjs falls back to native + // Date and it won't correctly represent the year in local time if UTF + // offset is negative. + // + // The solution is to use the format parameter, so momentjs won't fall + // back to the native Date and we will have the correct year in local + // time. + // + this.mom = moment(str, 'YYYY'); this.setPrecision(YEAR); } @@ -6467,7 +8158,7 @@ return new Parser; var ceilHeapSize = function (v) { // The asm.js spec says: // The heap object's byteLength must be either - // 2^n for n in [12, 24) or 2^24 * n for n ≥ 1. + // 2^n for n in [12, 24) or 2^24 * n for n ≥ 1. // Also, byteLengths smaller than 2^16 are deprecated. var p; // If v is smaller than 2^16, the smallest possible solution @@ -6898,7 +8589,9 @@ return new Parser; id, options); } // Already exists on destination - throw new jIO.util.jIOError("Conflict on '" + id + "'", + throw new jIO.util.jIOError("Conflict on '" + id + "': " + + JSON.stringify(doc) + " !== " + + JSON.stringify(remote_doc), 409); }); } @@ -7003,7 +8696,9 @@ return new Parser; return; } if (conflict_force !== true) { - throw new jIO.util.jIOError("Conflict on '" + id + "'", + throw new jIO.util.jIOError("Conflict on '" + id + "': " + + JSON.stringify(doc) + " !== " + + JSON.stringify(remote_doc), 409); } } @@ -7361,7 +9056,7 @@ return new Parser; */ /*jslint nomen: true*/ -/*global jIO*/ +/*global jIO, RSVP*/ /** * JIO Memory Storage. Type = 'memory'. @@ -7376,7 +9071,7 @@ return new Parser; * @class MemoryStorage */ -(function (jIO) { +(function (jIO, JSON, RSVP) { "use strict"; /** @@ -7395,13 +9090,13 @@ return new Parser; attachments: {} }; } - this._database[id].doc = metadata; + this._database[id].doc = JSON.stringify(metadata); return id; }; MemoryStorage.prototype.get = function (id) { try { - return this._database[id].doc; + return JSON.parse(this._database[id].doc); } catch (error) { if (error instanceof TypeError) { throw new jIO.util.jIOError( @@ -7448,7 +9143,7 @@ return new Parser; 404 ); } - return result; + return jIO.util.dataURItoBlob(result); } catch (error) { if (error instanceof TypeError) { throw new jIO.util.jIOError( @@ -7470,7 +9165,13 @@ return new Parser; } throw error; } - attachment_dict[name] = blob; + return new RSVP.Queue() + .push(function () { + return jIO.util.readBlobAsDataURL(blob); + }) + .push(function (evt) { + attachment_dict[name] = evt.target.result; + }); }; MemoryStorage.prototype.removeAttachment = function (id, name) { @@ -7501,7 +9202,7 @@ return new Parser; rows.push({ id: i, value: {}, - doc: this._database[i] + doc: JSON.parse(this._database[i].doc) }); } else { rows.push({ @@ -7517,7 +9218,7 @@ return new Parser; jIO.addStorage('memory', MemoryStorage); -}(jIO)); +}(jIO, JSON, RSVP)); ;/*jslint nomen: true*/ /*global RSVP, Blob, LZString, DOMException*/ (function (RSVP, Blob, LZString, DOMException) { @@ -8778,7 +10479,7 @@ return new Parser; .push(function () { var form = json._embedded._view, converted_json = { - portal_type: json.portal_type + portal_type: json._links.type.name }, form_data_json = {}, field, @@ -8786,6 +10487,11 @@ return new Parser; prefix_length, result; + if (json._links.hasOwnProperty('parent')) { + converted_json.parent_relative_url = + new URI(json._links.parent.href).segment(2); + } + form_data_json.form_id = { "key": [form.form_id.key], "default": form.form_id["default"] @@ -8905,7 +10611,6 @@ return new Parser; hateoas = JSON.parse(response.target.responseText); function pushResult(json) { - json.portal_type = json._links.type.name; return extractPropertyFromFormJSON(json) .push(function (json2) { return convertJSONToGet(json2); @@ -9023,7 +10728,6 @@ return new Parser; {"_view": this._default_view_reference}) .push(function (response) { var result = JSON.parse(response.target.responseText); - result.portal_type = result._links.type.name; // Remove all ERP5 hateoas links / convert them into jIO ID // XXX Change default action to an jio urn with attachment name inside @@ -9332,10 +11036,18 @@ return new Parser; }; QueryStorage.prototype.hasCapacity = function (name) { + var this_storage_capacity_list = ["limit", + "sort", + "select", + "query"]; + + if (this_storage_capacity_list.indexOf(name) !== -1) { + return true; + } if (name === "list") { return this._sub_storage.hasCapacity(name); } - return true; + return false; }; QueryStorage.prototype.buildQuery = function (options) { var substorage = this._sub_storage, diff --git a/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_jio_js.xml b/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_jio_js.xml index 1b9f4c21ab..20d549d7ca 100644 --- a/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_jio_js.xml +++ b/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_jio_js.xml @@ -236,7 +236,7 @@ </item> <item> <key> <string>serial</string> </key> - <value> <string>950.24601.18563.49681</string> </value> + <value> <string>951.35286.47701.22630</string> </value> </item> <item> <key> <string>state</string> </key> @@ -254,7 +254,7 @@ </tuple> <state> <tuple> - <float>1460128878.65</float> + <float>1466697567.97</float> <string>UTC</string> </tuple> </state> diff --git a/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/jio.js.js b/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/jio.js.js index 78d6289ce7..de2f614c12 100644 --- a/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/jio.js.js +++ b/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/jio.js.js @@ -169,7 +169,7 @@ var objectHelper = (function () { if (object.hasOwnProperty(propertyName)) { property = object[propertyName]; // be aware, arrays are 'object', too - if (typeof property === "object") { + if ((typeof property === "object") && !(property instanceof RegExp)) { deepFreeze(property); } } @@ -1527,422 +1527,243 @@ if (typeof define === 'function' && define.amd) { module.exports = LZString } ;//! moment.js -//! version : 2.5.0 +//! version : 2.13.0 //! authors : Tim Wood, Iskren Chernev, Moment.js contributors //! license : MIT //! momentjs.com -(function (undefined) { +;(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + global.moment = factory() +}(this, function () { 'use strict'; - /************************************ - Constants - ************************************/ + var hookCallback; - var moment, - VERSION = "2.5.0", - global = this, - round = Math.round, - i, - - YEAR = 0, - MONTH = 1, - DATE = 2, - HOUR = 3, - MINUTE = 4, - SECOND = 5, - MILLISECOND = 6, - - // internal storage for language config files - languages = {}, - - // check for nodeJS - hasModule = (typeof module !== 'undefined' && module.exports && typeof require !== 'undefined'), - - // ASP.NET json date format regex - aspNetJsonRegex = /^\/?Date\((\-?\d+)/i, - aspNetTimeSpanJsonRegex = /(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/, - - // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html - // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere - isoDurationRegex = /^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/, - - // format tokens - formattingTokens = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|X|zz?|ZZ?|.)/g, - localFormattingTokens = /(\[[^\[]*\])|(\\)?(LT|LL?L?L?|l{1,4})/g, - - // parsing token regexes - parseTokenOneOrTwoDigits = /\d\d?/, // 0 - 99 - parseTokenOneToThreeDigits = /\d{1,3}/, // 0 - 999 - parseTokenOneToFourDigits = /\d{1,4}/, // 0 - 9999 - parseTokenOneToSixDigits = /[+\-]?\d{1,6}/, // -999,999 - 999,999 - parseTokenDigits = /\d+/, // nonzero number of digits - parseTokenWord = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i, // any word (or two) characters or numbers including two/three word month in arabic. - parseTokenTimezone = /Z|[\+\-]\d\d:?\d\d/gi, // +00:00 -00:00 +0000 -0000 or Z - parseTokenT = /T/i, // T (ISO separator) - parseTokenTimestampMs = /[\+\-]?\d+(\.\d{1,3})?/, // 123456789 123456789.123 - - //strict parsing regexes - parseTokenOneDigit = /\d/, // 0 - 9 - parseTokenTwoDigits = /\d\d/, // 00 - 99 - parseTokenThreeDigits = /\d{3}/, // 000 - 999 - parseTokenFourDigits = /\d{4}/, // 0000 - 9999 - parseTokenSixDigits = /[+\-]?\d{6}/, // -999,999 - 999,999 - - // iso 8601 regex - // 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00) - isoRegex = /^\s*\d{4}-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/, - - isoFormat = 'YYYY-MM-DDTHH:mm:ssZ', - - isoDates = [ - 'YYYY-MM-DD', - 'GGGG-[W]WW', - 'GGGG-[W]WW-E', - 'YYYY-DDD' - ], - - // iso time formats and regexes - isoTimes = [ - ['HH:mm:ss.SSSS', /(T| )\d\d:\d\d:\d\d\.\d{1,3}/], - ['HH:mm:ss', /(T| )\d\d:\d\d:\d\d/], - ['HH:mm', /(T| )\d\d:\d\d/], - ['HH', /(T| )\d\d/] - ], - - // timezone chunker "+10:00" > ["10", "00"] or "-1530" > ["-15", "30"] - parseTimezoneChunker = /([\+\-]|\d\d)/gi, - - // getter and setter names - proxyGettersAndSetters = 'Date|Hours|Minutes|Seconds|Milliseconds'.split('|'), - unitMillisecondFactors = { - 'Milliseconds' : 1, - 'Seconds' : 1e3, - 'Minutes' : 6e4, - 'Hours' : 36e5, - 'Days' : 864e5, - 'Months' : 2592e6, - 'Years' : 31536e6 - }, - - unitAliases = { - ms : 'millisecond', - s : 'second', - m : 'minute', - h : 'hour', - d : 'day', - D : 'date', - w : 'week', - W : 'isoWeek', - M : 'month', - y : 'year', - DDD : 'dayOfYear', - e : 'weekday', - E : 'isoWeekday', - gg: 'weekYear', - GG: 'isoWeekYear' - }, - - camelFunctions = { - dayofyear : 'dayOfYear', - isoweekday : 'isoWeekday', - isoweek : 'isoWeek', - weekyear : 'weekYear', - isoweekyear : 'isoWeekYear' - }, - - // format function strings - formatFunctions = {}, - - // tokens to ordinalize and pad - ordinalizeTokens = 'DDD w W M D d'.split(' '), - paddedTokens = 'M D H h m s w W'.split(' '), - - formatTokenFunctions = { - M : function () { - return this.month() + 1; - }, - MMM : function (format) { - return this.lang().monthsShort(this, format); - }, - MMMM : function (format) { - return this.lang().months(this, format); - }, - D : function () { - return this.date(); - }, - DDD : function () { - return this.dayOfYear(); - }, - d : function () { - return this.day(); - }, - dd : function (format) { - return this.lang().weekdaysMin(this, format); - }, - ddd : function (format) { - return this.lang().weekdaysShort(this, format); - }, - dddd : function (format) { - return this.lang().weekdays(this, format); - }, - w : function () { - return this.week(); - }, - W : function () { - return this.isoWeek(); - }, - YY : function () { - return leftZeroFill(this.year() % 100, 2); - }, - YYYY : function () { - return leftZeroFill(this.year(), 4); - }, - YYYYY : function () { - return leftZeroFill(this.year(), 5); - }, - YYYYYY : function () { - var y = this.year(), sign = y >= 0 ? '+' : '-'; - return sign + leftZeroFill(Math.abs(y), 6); - }, - gg : function () { - return leftZeroFill(this.weekYear() % 100, 2); - }, - gggg : function () { - return this.weekYear(); - }, - ggggg : function () { - return leftZeroFill(this.weekYear(), 5); - }, - GG : function () { - return leftZeroFill(this.isoWeekYear() % 100, 2); - }, - GGGG : function () { - return this.isoWeekYear(); - }, - GGGGG : function () { - return leftZeroFill(this.isoWeekYear(), 5); - }, - e : function () { - return this.weekday(); - }, - E : function () { - return this.isoWeekday(); - }, - a : function () { - return this.lang().meridiem(this.hours(), this.minutes(), true); - }, - A : function () { - return this.lang().meridiem(this.hours(), this.minutes(), false); - }, - H : function () { - return this.hours(); - }, - h : function () { - return this.hours() % 12 || 12; - }, - m : function () { - return this.minutes(); - }, - s : function () { - return this.seconds(); - }, - S : function () { - return toInt(this.milliseconds() / 100); - }, - SS : function () { - return leftZeroFill(toInt(this.milliseconds() / 10), 2); - }, - SSS : function () { - return leftZeroFill(this.milliseconds(), 3); - }, - SSSS : function () { - return leftZeroFill(this.milliseconds(), 3); - }, - Z : function () { - var a = -this.zone(), - b = "+"; - if (a < 0) { - a = -a; - b = "-"; - } - return b + leftZeroFill(toInt(a / 60), 2) + ":" + leftZeroFill(toInt(a) % 60, 2); - }, - ZZ : function () { - var a = -this.zone(), - b = "+"; - if (a < 0) { - a = -a; - b = "-"; - } - return b + leftZeroFill(toInt(a / 60), 2) + leftZeroFill(toInt(a) % 60, 2); - }, - z : function () { - return this.zoneAbbr(); - }, - zz : function () { - return this.zoneName(); - }, - X : function () { - return this.unix(); - }, - Q : function () { - return this.quarter(); - } - }, - - lists = ['months', 'monthsShort', 'weekdays', 'weekdaysShort', 'weekdaysMin']; - - function padToken(func, count) { - return function (a) { - return leftZeroFill(func.call(this, a), count); - }; - } - function ordinalizeToken(func, period) { - return function (a) { - return this.lang().ordinal(func.call(this, a), period); - }; + function utils_hooks__hooks () { + return hookCallback.apply(null, arguments); } - while (ordinalizeTokens.length) { - i = ordinalizeTokens.pop(); - formatTokenFunctions[i + 'o'] = ordinalizeToken(formatTokenFunctions[i], i); - } - while (paddedTokens.length) { - i = paddedTokens.pop(); - formatTokenFunctions[i + i] = padToken(formatTokenFunctions[i], 2); + // This is done to register the method called with moment() + // without creating circular dependencies. + function setHookCallback (callback) { + hookCallback = callback; } - formatTokenFunctions.DDDD = padToken(formatTokenFunctions.DDD, 3); - - - /************************************ - Constructors - ************************************/ - - function Language() { + function isArray(input) { + return input instanceof Array || Object.prototype.toString.call(input) === '[object Array]'; } - // Moment prototype object - function Moment(config) { - checkOverflow(config); - extend(this, config); + function isDate(input) { + return input instanceof Date || Object.prototype.toString.call(input) === '[object Date]'; } - // Duration Constructor - function Duration(duration) { - var normalizedInput = normalizeObjectUnits(duration), - years = normalizedInput.year || 0, - months = normalizedInput.month || 0, - weeks = normalizedInput.week || 0, - days = normalizedInput.day || 0, - hours = normalizedInput.hour || 0, - minutes = normalizedInput.minute || 0, - seconds = normalizedInput.second || 0, - milliseconds = normalizedInput.millisecond || 0; - - // representation for dateAddRemove - this._milliseconds = +milliseconds + - seconds * 1e3 + // 1000 - minutes * 6e4 + // 1000 * 60 - hours * 36e5; // 1000 * 60 * 60 - // Because of dateAddRemove treats 24 hours as different from a - // day when working around DST, we need to store them separately - this._days = +days + - weeks * 7; - // It is impossible translate months into days without knowing - // which months you are are talking about, so we have to store - // it separately. - this._months = +months + - years * 12; - - this._data = {}; - - this._bubble(); + function map(arr, fn) { + var res = [], i; + for (i = 0; i < arr.length; ++i) { + res.push(fn(arr[i], i)); + } + return res; } - /************************************ - Helpers - ************************************/ - + function hasOwnProp(a, b) { + return Object.prototype.hasOwnProperty.call(a, b); + } function extend(a, b) { for (var i in b) { - if (b.hasOwnProperty(i)) { + if (hasOwnProp(b, i)) { a[i] = b[i]; } } - if (b.hasOwnProperty("toString")) { + if (hasOwnProp(b, 'toString')) { a.toString = b.toString; } - if (b.hasOwnProperty("valueOf")) { + if (hasOwnProp(b, 'valueOf')) { a.valueOf = b.valueOf; } return a; } - function absRound(number) { - if (number < 0) { - return Math.ceil(number); - } else { - return Math.floor(number); + function create_utc__createUTC (input, format, locale, strict) { + return createLocalOrUTC(input, format, locale, strict, true).utc(); + } + + function defaultParsingFlags() { + // We need to deep clone this object. + return { + empty : false, + unusedTokens : [], + unusedInput : [], + overflow : -2, + charsLeftOver : 0, + nullInput : false, + invalidMonth : null, + invalidFormat : false, + userInvalidated : false, + iso : false, + parsedDateParts : [], + meridiem : null + }; + } + + function getParsingFlags(m) { + if (m._pf == null) { + m._pf = defaultParsingFlags(); } + return m._pf; } - // left zero fill a number - // see http://jsperf.com/left-zero-filling for performance comparison - function leftZeroFill(number, targetLength, forceSign) { - var output = Math.abs(number) + '', - sign = number >= 0; + var some; + if (Array.prototype.some) { + some = Array.prototype.some; + } else { + some = function (fun) { + var t = Object(this); + var len = t.length >>> 0; + + for (var i = 0; i < len; i++) { + if (i in t && fun.call(this, t[i], i, t)) { + return true; + } + } + + return false; + }; + } + + function valid__isValid(m) { + if (m._isValid == null) { + var flags = getParsingFlags(m); + var parsedParts = some.call(flags.parsedDateParts, function (i) { + return i != null; + }); + m._isValid = !isNaN(m._d.getTime()) && + flags.overflow < 0 && + !flags.empty && + !flags.invalidMonth && + !flags.invalidWeekday && + !flags.nullInput && + !flags.invalidFormat && + !flags.userInvalidated && + (!flags.meridiem || (flags.meridiem && parsedParts)); - while (output.length < targetLength) { - output = '0' + output; + if (m._strict) { + m._isValid = m._isValid && + flags.charsLeftOver === 0 && + flags.unusedTokens.length === 0 && + flags.bigHour === undefined; + } } - return (sign ? (forceSign ? '+' : '') : '-') + output; + return m._isValid; } - // helper function for _.addTime and _.subtractTime - function addOrSubtractDurationFromMoment(mom, duration, isAdding, ignoreUpdateOffset) { - var milliseconds = duration._milliseconds, - days = duration._days, - months = duration._months, - minutes, - hours; + function valid__createInvalid (flags) { + var m = create_utc__createUTC(NaN); + if (flags != null) { + extend(getParsingFlags(m), flags); + } + else { + getParsingFlags(m).userInvalidated = true; + } - if (milliseconds) { - mom._d.setTime(+mom._d + milliseconds * isAdding); + return m; + } + + function isUndefined(input) { + return input === void 0; + } + + // Plugins that add properties should also add the key here (null value), + // so we can properly clone ourselves. + var momentProperties = utils_hooks__hooks.momentProperties = []; + + function copyConfig(to, from) { + var i, prop, val; + + if (!isUndefined(from._isAMomentObject)) { + to._isAMomentObject = from._isAMomentObject; } - // store the minutes and hours so we can restore them - if (days || months) { - minutes = mom.minute(); - hours = mom.hour(); + if (!isUndefined(from._i)) { + to._i = from._i; } - if (days) { - mom.date(mom.date() + days * isAdding); + if (!isUndefined(from._f)) { + to._f = from._f; } - if (months) { - mom.month(mom.month() + months * isAdding); + if (!isUndefined(from._l)) { + to._l = from._l; + } + if (!isUndefined(from._strict)) { + to._strict = from._strict; + } + if (!isUndefined(from._tzm)) { + to._tzm = from._tzm; + } + if (!isUndefined(from._isUTC)) { + to._isUTC = from._isUTC; + } + if (!isUndefined(from._offset)) { + to._offset = from._offset; + } + if (!isUndefined(from._pf)) { + to._pf = getParsingFlags(from); } - if (milliseconds && !ignoreUpdateOffset) { - moment.updateOffset(mom); + if (!isUndefined(from._locale)) { + to._locale = from._locale; } - // restore the minutes and hours after possibly changing dst - if (days || months) { - mom.minute(minutes); - mom.hour(hours); + + if (momentProperties.length > 0) { + for (i in momentProperties) { + prop = momentProperties[i]; + val = from[prop]; + if (!isUndefined(val)) { + to[prop] = val; + } + } } + + return to; } - // check if is an array - function isArray(input) { - return Object.prototype.toString.call(input) === '[object Array]'; + var updateInProgress = false; + + // Moment prototype object + function Moment(config) { + copyConfig(this, config); + this._d = new Date(config._d != null ? config._d.getTime() : NaN); + // Prevent infinite loop in case updateOffset creates new moment + // objects. + if (updateInProgress === false) { + updateInProgress = true; + utils_hooks__hooks.updateOffset(this); + updateInProgress = false; + } } - function isDate(input) { - return Object.prototype.toString.call(input) === '[object Date]' || - input instanceof Date; + function isMoment (obj) { + return obj instanceof Moment || (obj != null && obj._isAMomentObject != null); + } + + function absFloor (number) { + if (number < 0) { + return Math.ceil(number); + } else { + return Math.floor(number); + } + } + + function toInt(argumentForCoercion) { + var coercedNumber = +argumentForCoercion, + value = 0; + + if (coercedNumber !== 0 && isFinite(coercedNumber)) { + value = absFloor(coercedNumber); + } + + return value; } // compare two arrays, return the number of differences @@ -1960,432 +1781,379 @@ if (typeof define === 'function' && define.amd) { return diffs + lengthDiff; } - function normalizeUnits(units) { - if (units) { - var lowered = units.toLowerCase().replace(/(.)s$/, '$1'); - units = unitAliases[units] || camelFunctions[lowered] || lowered; + function warn(msg) { + if (utils_hooks__hooks.suppressDeprecationWarnings === false && + (typeof console !== 'undefined') && console.warn) { + console.warn('Deprecation warning: ' + msg); } - return units; } - function normalizeObjectUnits(inputObject) { - var normalizedInput = {}, - normalizedProp, - prop; + function deprecate(msg, fn) { + var firstTime = true; - for (prop in inputObject) { - if (inputObject.hasOwnProperty(prop)) { - normalizedProp = normalizeUnits(prop); - if (normalizedProp) { - normalizedInput[normalizedProp] = inputObject[prop]; - } + return extend(function () { + if (utils_hooks__hooks.deprecationHandler != null) { + utils_hooks__hooks.deprecationHandler(null, msg); } - } - - return normalizedInput; + if (firstTime) { + warn(msg + '\nArguments: ' + Array.prototype.slice.call(arguments).join(', ') + '\n' + (new Error()).stack); + firstTime = false; + } + return fn.apply(this, arguments); + }, fn); } - function makeList(field) { - var count, setter; + var deprecations = {}; - if (field.indexOf('week') === 0) { - count = 7; - setter = 'day'; - } - else if (field.indexOf('month') === 0) { - count = 12; - setter = 'month'; + function deprecateSimple(name, msg) { + if (utils_hooks__hooks.deprecationHandler != null) { + utils_hooks__hooks.deprecationHandler(name, msg); } - else { - return; + if (!deprecations[name]) { + warn(msg); + deprecations[name] = true; } + } - moment[field] = function (format, index) { - var i, getter, - method = moment.fn._lang[field], - results = []; - - if (typeof format === 'number') { - index = format; - format = undefined; - } - - getter = function (i) { - var m = moment().utc().set(setter, i); - return method.call(moment.fn._lang, m, format || ''); - }; + utils_hooks__hooks.suppressDeprecationWarnings = false; + utils_hooks__hooks.deprecationHandler = null; - if (index != null) { - return getter(index); - } - else { - for (i = 0; i < count; i++) { - results.push(getter(i)); - } - return results; - } - }; + function isFunction(input) { + return input instanceof Function || Object.prototype.toString.call(input) === '[object Function]'; } - function toInt(argumentForCoercion) { - var coercedNumber = +argumentForCoercion, - value = 0; + function isObject(input) { + return Object.prototype.toString.call(input) === '[object Object]'; + } - if (coercedNumber !== 0 && isFinite(coercedNumber)) { - if (coercedNumber >= 0) { - value = Math.floor(coercedNumber); + function locale_set__set (config) { + var prop, i; + for (i in config) { + prop = config[i]; + if (isFunction(prop)) { + this[i] = prop; } else { - value = Math.ceil(coercedNumber); + this['_' + i] = prop; } } - - return value; + this._config = config; + // Lenient ordinal parsing accepts just a number in addition to + // number + (possibly) stuff coming from _ordinalParseLenient. + this._ordinalParseLenient = new RegExp(this._ordinalParse.source + '|' + (/\d{1,2}/).source); } - function daysInMonth(year, month) { - return new Date(Date.UTC(year, month + 1, 0)).getUTCDate(); - } - - function daysInYear(year) { - return isLeapYear(year) ? 366 : 365; + function mergeConfigs(parentConfig, childConfig) { + var res = extend({}, parentConfig), prop; + for (prop in childConfig) { + if (hasOwnProp(childConfig, prop)) { + if (isObject(parentConfig[prop]) && isObject(childConfig[prop])) { + res[prop] = {}; + extend(res[prop], parentConfig[prop]); + extend(res[prop], childConfig[prop]); + } else if (childConfig[prop] != null) { + res[prop] = childConfig[prop]; + } else { + delete res[prop]; + } + } + } + return res; } - function isLeapYear(year) { - return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; + function Locale(config) { + if (config != null) { + this.set(config); + } } - function checkOverflow(m) { - var overflow; - if (m._a && m._pf.overflow === -2) { - overflow = - m._a[MONTH] < 0 || m._a[MONTH] > 11 ? MONTH : - m._a[DATE] < 1 || m._a[DATE] > daysInMonth(m._a[YEAR], m._a[MONTH]) ? DATE : - m._a[HOUR] < 0 || m._a[HOUR] > 23 ? HOUR : - m._a[MINUTE] < 0 || m._a[MINUTE] > 59 ? MINUTE : - m._a[SECOND] < 0 || m._a[SECOND] > 59 ? SECOND : - m._a[MILLISECOND] < 0 || m._a[MILLISECOND] > 999 ? MILLISECOND : - -1; + var keys; - if (m._pf._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) { - overflow = DATE; + if (Object.keys) { + keys = Object.keys; + } else { + keys = function (obj) { + var i, res = []; + for (i in obj) { + if (hasOwnProp(obj, i)) { + res.push(i); + } } - - m._pf.overflow = overflow; - } + return res; + }; } - function initializeParsingFlags(config) { - config._pf = { - empty : false, - unusedTokens : [], - unusedInput : [], - overflow : -2, - charsLeftOver : 0, - nullInput : false, - invalidMonth : null, - invalidFormat : false, - userInvalidated : false, - iso: false - }; + // internal storage for locale config files + var locales = {}; + var globalLocale; + + function normalizeLocale(key) { + return key ? key.toLowerCase().replace('_', '-') : key; } - function isValid(m) { - if (m._isValid == null) { - m._isValid = !isNaN(m._d.getTime()) && - m._pf.overflow < 0 && - !m._pf.empty && - !m._pf.invalidMonth && - !m._pf.nullInput && - !m._pf.invalidFormat && - !m._pf.userInvalidated; + // pick the locale from the array + // try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each + // substring from most specific to least, but move to the next array item if it's a more specific variant than the current root + function chooseLocale(names) { + var i = 0, j, next, locale, split; - if (m._strict) { - m._isValid = m._isValid && - m._pf.charsLeftOver === 0 && - m._pf.unusedTokens.length === 0; + while (i < names.length) { + split = normalizeLocale(names[i]).split('-'); + j = split.length; + next = normalizeLocale(names[i + 1]); + next = next ? next.split('-') : null; + while (j > 0) { + locale = loadLocale(split.slice(0, j).join('-')); + if (locale) { + return locale; + } + if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) { + //the next array item is better than a shallower substring of this one + break; + } + j--; } + i++; } - return m._isValid; - } - - function normalizeLanguage(key) { - return key ? key.toLowerCase().replace('_', '-') : key; + return null; } - // Return a moment from input, that is local/utc/zone equivalent to model. - function makeAs(input, model) { - return model._isUTC ? moment(input).zone(model._offset || 0) : - moment(input).local(); + function loadLocale(name) { + var oldLocale = null; + // TODO: Find a better way to register and load all the locales in Node + if (!locales[name] && (typeof module !== 'undefined') && + module && module.exports) { + try { + oldLocale = globalLocale._abbr; + require('./locale/' + name); + // because defineLocale currently also sets the global locale, we + // want to undo that for lazy loaded locales + locale_locales__getSetGlobalLocale(oldLocale); + } catch (e) { } + } + return locales[name]; } - /************************************ - Languages - ************************************/ + // This function will load locale and then set the global locale. If + // no arguments are passed in, it will simply return the current global + // locale key. + function locale_locales__getSetGlobalLocale (key, values) { + var data; + if (key) { + if (isUndefined(values)) { + data = locale_locales__getLocale(key); + } + else { + data = defineLocale(key, values); + } + if (data) { + // moment.duration._locale = moment._locale = data; + globalLocale = data; + } + } - extend(Language.prototype, { + return globalLocale._abbr; + } - set : function (config) { - var prop, i; - for (i in config) { - prop = config[i]; - if (typeof prop === 'function') { - this[i] = prop; + function defineLocale (name, config) { + if (config !== null) { + config.abbr = name; + if (locales[name] != null) { + deprecateSimple('defineLocaleOverride', + 'use moment.updateLocale(localeName, config) to change ' + + 'an existing locale. moment.defineLocale(localeName, ' + + 'config) should only be used for creating a new locale'); + config = mergeConfigs(locales[name]._config, config); + } else if (config.parentLocale != null) { + if (locales[config.parentLocale] != null) { + config = mergeConfigs(locales[config.parentLocale]._config, config); } else { - this['_' + i] = prop; + // treat as if there is no base config + deprecateSimple('parentLocaleUndefined', + 'specified parentLocale is not defined yet'); } } - }, - - _months : "January_February_March_April_May_June_July_August_September_October_November_December".split("_"), - months : function (m) { - return this._months[m.month()]; - }, + locales[name] = new Locale(config); - _monthsShort : "Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"), - monthsShort : function (m) { - return this._monthsShort[m.month()]; - }, + // backwards compat for now: also set the locale + locale_locales__getSetGlobalLocale(name); - monthsParse : function (monthName) { - var i, mom, regex; + return locales[name]; + } else { + // useful for testing + delete locales[name]; + return null; + } + } - if (!this._monthsParse) { - this._monthsParse = []; + function updateLocale(name, config) { + if (config != null) { + var locale; + if (locales[name] != null) { + config = mergeConfigs(locales[name]._config, config); } + locale = new Locale(config); + locale.parentLocale = locales[name]; + locales[name] = locale; - for (i = 0; i < 12; i++) { - // make the regex if we don't have it already - if (!this._monthsParse[i]) { - mom = moment.utc([2000, i]); - regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, ''); - this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i'); - } - // test the regex - if (this._monthsParse[i].test(monthName)) { - return i; + // backwards compat for now: also set the locale + locale_locales__getSetGlobalLocale(name); + } else { + // pass null for config to unupdate, useful for tests + if (locales[name] != null) { + if (locales[name].parentLocale != null) { + locales[name] = locales[name].parentLocale; + } else if (locales[name] != null) { + delete locales[name]; } } - }, - - _weekdays : "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"), - weekdays : function (m) { - return this._weekdays[m.day()]; - }, + } + return locales[name]; + } - _weekdaysShort : "Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"), - weekdaysShort : function (m) { - return this._weekdaysShort[m.day()]; - }, + // returns locale data + function locale_locales__getLocale (key) { + var locale; - _weekdaysMin : "Su_Mo_Tu_We_Th_Fr_Sa".split("_"), - weekdaysMin : function (m) { - return this._weekdaysMin[m.day()]; - }, + if (key && key._locale && key._locale._abbr) { + key = key._locale._abbr; + } - weekdaysParse : function (weekdayName) { - var i, mom, regex; + if (!key) { + return globalLocale; + } - if (!this._weekdaysParse) { - this._weekdaysParse = []; + if (!isArray(key)) { + //short-circuit everything else + locale = loadLocale(key); + if (locale) { + return locale; } + key = [key]; + } - for (i = 0; i < 7; i++) { - // make the regex if we don't have it already - if (!this._weekdaysParse[i]) { - mom = moment([2000, 1]).day(i); - regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, ''); - this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i'); - } - // test the regex - if (this._weekdaysParse[i].test(weekdayName)) { - return i; - } - } - }, + return chooseLocale(key); + } - _longDateFormat : { - LT : "h:mm A", - L : "MM/DD/YYYY", - LL : "MMMM D YYYY", - LLL : "MMMM D YYYY LT", - LLLL : "dddd, MMMM D YYYY LT" - }, - longDateFormat : function (key) { - var output = this._longDateFormat[key]; - if (!output && this._longDateFormat[key.toUpperCase()]) { - output = this._longDateFormat[key.toUpperCase()].replace(/MMMM|MM|DD|dddd/g, function (val) { - return val.slice(1); - }); - this._longDateFormat[key] = output; - } - return output; - }, - - isPM : function (input) { - // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays - // Using charAt should be more compatible. - return ((input + '').toLowerCase().charAt(0) === 'p'); - }, + function locale_locales__listLocales() { + return keys(locales); + } - _meridiemParse : /[ap]\.?m?\.?/i, - meridiem : function (hours, minutes, isLower) { - if (hours > 11) { - return isLower ? 'pm' : 'PM'; - } else { - return isLower ? 'am' : 'AM'; - } - }, + var aliases = {}; - _calendar : { - sameDay : '[Today at] LT', - nextDay : '[Tomorrow at] LT', - nextWeek : 'dddd [at] LT', - lastDay : '[Yesterday at] LT', - lastWeek : '[Last] dddd [at] LT', - sameElse : 'L' - }, - calendar : function (key, mom) { - var output = this._calendar[key]; - return typeof output === 'function' ? output.apply(mom) : output; - }, + function addUnitAlias (unit, shorthand) { + var lowerCase = unit.toLowerCase(); + aliases[lowerCase] = aliases[lowerCase + 's'] = aliases[shorthand] = unit; + } - _relativeTime : { - future : "in %s", - past : "%s ago", - s : "a few seconds", - m : "a minute", - mm : "%d minutes", - h : "an hour", - hh : "%d hours", - d : "a day", - dd : "%d days", - M : "a month", - MM : "%d months", - y : "a year", - yy : "%d years" - }, - relativeTime : function (number, withoutSuffix, string, isFuture) { - var output = this._relativeTime[string]; - return (typeof output === 'function') ? - output(number, withoutSuffix, string, isFuture) : - output.replace(/%d/i, number); - }, - pastFuture : function (diff, output) { - var format = this._relativeTime[diff > 0 ? 'future' : 'past']; - return typeof format === 'function' ? format(output) : format.replace(/%s/i, output); - }, + function normalizeUnits(units) { + return typeof units === 'string' ? aliases[units] || aliases[units.toLowerCase()] : undefined; + } - ordinal : function (number) { - return this._ordinal.replace("%d", number); - }, - _ordinal : "%d", + function normalizeObjectUnits(inputObject) { + var normalizedInput = {}, + normalizedProp, + prop; - preparse : function (string) { - return string; - }, + for (prop in inputObject) { + if (hasOwnProp(inputObject, prop)) { + normalizedProp = normalizeUnits(prop); + if (normalizedProp) { + normalizedInput[normalizedProp] = inputObject[prop]; + } + } + } - postformat : function (string) { - return string; - }, + return normalizedInput; + } - week : function (mom) { - return weekOfYear(mom, this._week.dow, this._week.doy).week; - }, + function makeGetSet (unit, keepTime) { + return function (value) { + if (value != null) { + get_set__set(this, unit, value); + utils_hooks__hooks.updateOffset(this, keepTime); + return this; + } else { + return get_set__get(this, unit); + } + }; + } - _week : { - dow : 0, // Sunday is the first day of the week. - doy : 6 // The week that contains Jan 1st is the first week of the year. - }, + function get_set__get (mom, unit) { + return mom.isValid() ? + mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]() : NaN; + } - _invalidDate: 'Invalid date', - invalidDate: function () { - return this._invalidDate; + function get_set__set (mom, unit, value) { + if (mom.isValid()) { + mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value); } - }); + } - // Loads a language definition into the `languages` cache. The function - // takes a key and optionally values. If not in the browser and no values - // are provided, it will load the language file module. As a convenience, - // this function also returns the language values. - function loadLang(key, values) { - values.abbr = key; - if (!languages[key]) { - languages[key] = new Language(); + // MOMENTS + + function getSet (units, value) { + var unit; + if (typeof units === 'object') { + for (unit in units) { + this.set(unit, units[unit]); + } + } else { + units = normalizeUnits(units); + if (isFunction(this[units])) { + return this[units](value); + } } - languages[key].set(values); - return languages[key]; + return this; } - // Remove a language from the `languages` cache. Mostly useful in tests. - function unloadLang(key) { - delete languages[key]; + function zeroFill(number, targetLength, forceSign) { + var absNumber = '' + Math.abs(number), + zerosToFill = targetLength - absNumber.length, + sign = number >= 0; + return (sign ? (forceSign ? '+' : '') : '-') + + Math.pow(10, Math.max(0, zerosToFill)).toString().substr(1) + absNumber; } - // Determines which language definition to use and returns it. - // - // With no parameters, it will return the global language. If you - // pass in a language key, such as 'en', it will return the - // definition for 'en', so long as 'en' has already been loaded using - // moment.lang. - function getLangDefinition(key) { - var i = 0, j, lang, next, split, - get = function (k) { - if (!languages[k] && hasModule) { - try { - require('./lang/' + k); - } catch (e) { } - } - return languages[k]; - }; + var formattingTokens = /(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g; - if (!key) { - return moment.fn._lang; - } + var localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g; - if (!isArray(key)) { - //short-circuit everything else - lang = get(key); - if (lang) { - return lang; - } - key = [key]; - } + var formatFunctions = {}; - //pick the language from the array - //try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each - //substring from most specific to least, but move to the next array item if it's a more specific variant than the current root - while (i < key.length) { - split = normalizeLanguage(key[i]).split('-'); - j = split.length; - next = normalizeLanguage(key[i + 1]); - next = next ? next.split('-') : null; - while (j > 0) { - lang = get(split.slice(0, j).join('-')); - if (lang) { - return lang; - } - if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) { - //the next array item is better than a shallower substring of this one - break; - } - j--; - } - i++; + var formatTokenFunctions = {}; + + // token: 'M' + // padded: ['MM', 2] + // ordinal: 'Mo' + // callback: function () { this.month() + 1 } + function addFormatToken (token, padded, ordinal, callback) { + var func = callback; + if (typeof callback === 'string') { + func = function () { + return this[callback](); + }; + } + if (token) { + formatTokenFunctions[token] = func; + } + if (padded) { + formatTokenFunctions[padded[0]] = function () { + return zeroFill(func.apply(this, arguments), padded[1], padded[2]); + }; + } + if (ordinal) { + formatTokenFunctions[ordinal] = function () { + return this.localeData().ordinal(func.apply(this, arguments), token); + }; } - return moment.fn._lang; } - /************************************ - Formatting - ************************************/ - - function removeFormattingTokens(input) { if (input.match(/\[[\s\S]/)) { - return input.replace(/^\[|\]$/g, ""); + return input.replace(/^\[|\]$/g, ''); } - return input.replace(/\\/g, ""); + return input.replace(/\\/g, ''); } function makeFormatFunction(format) { @@ -2400,7 +2168,7 @@ if (typeof define === 'function' && define.amd) { } return function (mom) { - var output = ""; + var output = '', i; for (i = 0; i < length; i++) { output += array[i] instanceof Function ? array[i].call(mom, format) : array[i]; } @@ -2410,25 +2178,21 @@ if (typeof define === 'function' && define.amd) { // format date using native date object function formatMoment(m, format) { - if (!m.isValid()) { - return m.lang().invalidDate(); + return m.localeData().invalidDate(); } - format = expandFormat(format, m.lang()); - - if (!formatFunctions[format]) { - formatFunctions[format] = makeFormatFunction(format); - } + format = expandFormat(format, m.localeData()); + formatFunctions[format] = formatFunctions[format] || makeFormatFunction(format); return formatFunctions[format](m); } - function expandFormat(format, lang) { + function expandFormat(format, locale) { var i = 5; function replaceLongDateFormatTokens(input) { - return lang.longDateFormat(input) || input; + return locale.longDateFormat(input) || input; } localFormattingTokens.lastIndex = 0; @@ -2441,3789 +2205,5714 @@ if (typeof define === 'function' && define.amd) { return format; } + var match1 = /\d/; // 0 - 9 + var match2 = /\d\d/; // 00 - 99 + var match3 = /\d{3}/; // 000 - 999 + var match4 = /\d{4}/; // 0000 - 9999 + var match6 = /[+-]?\d{6}/; // -999999 - 999999 + var match1to2 = /\d\d?/; // 0 - 99 + var match3to4 = /\d\d\d\d?/; // 999 - 9999 + var match5to6 = /\d\d\d\d\d\d?/; // 99999 - 999999 + var match1to3 = /\d{1,3}/; // 0 - 999 + var match1to4 = /\d{1,4}/; // 0 - 9999 + var match1to6 = /[+-]?\d{1,6}/; // -999999 - 999999 - /************************************ - Parsing - ************************************/ - - - // get the regex to find the next token - function getParseRegexForToken(token, config) { - var a, strict = config._strict; - switch (token) { - case 'DDDD': - return parseTokenThreeDigits; - case 'YYYY': - case 'GGGG': - case 'gggg': - return strict ? parseTokenFourDigits : parseTokenOneToFourDigits; - case 'YYYYYY': - case 'YYYYY': - case 'GGGGG': - case 'ggggg': - return strict ? parseTokenSixDigits : parseTokenOneToSixDigits; - case 'S': - if (strict) { return parseTokenOneDigit; } - /* falls through */ - case 'SS': - if (strict) { return parseTokenTwoDigits; } - /* falls through */ - case 'SSS': - case 'DDD': - return strict ? parseTokenThreeDigits : parseTokenOneToThreeDigits; - case 'MMM': - case 'MMMM': - case 'dd': - case 'ddd': - case 'dddd': - return parseTokenWord; - case 'a': - case 'A': - return getLangDefinition(config._l)._meridiemParse; - case 'X': - return parseTokenTimestampMs; - case 'Z': - case 'ZZ': - return parseTokenTimezone; - case 'T': - return parseTokenT; - case 'SSSS': - return parseTokenDigits; - case 'MM': - case 'DD': - case 'YY': - case 'GG': - case 'gg': - case 'HH': - case 'hh': - case 'mm': - case 'ss': - case 'ww': - case 'WW': - return strict ? parseTokenTwoDigits : parseTokenOneOrTwoDigits; - case 'M': - case 'D': - case 'd': - case 'H': - case 'h': - case 'm': - case 's': - case 'w': - case 'W': - case 'e': - case 'E': - return strict ? parseTokenOneDigit : parseTokenOneOrTwoDigits; - default : - a = new RegExp(regexpEscape(unescapeFormat(token.replace('\\', '')), "i")); - return a; - } + var matchUnsigned = /\d+/; // 0 - inf + var matchSigned = /[+-]?\d+/; // -inf - inf + + var matchOffset = /Z|[+-]\d\d:?\d\d/gi; // +00:00 -00:00 +0000 -0000 or Z + var matchShortOffset = /Z|[+-]\d\d(?::?\d\d)?/gi; // +00 -00 +00:00 -00:00 +0000 -0000 or Z + + var matchTimestamp = /[+-]?\d+(\.\d{1,3})?/; // 123456789 123456789.123 + + // any word (or two) characters or numbers including two/three word month in arabic. + // includes scottish gaelic two word and hyphenated months + var matchWord = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i; + + + var regexes = {}; + + function addRegexToken (token, regex, strictRegex) { + regexes[token] = isFunction(regex) ? regex : function (isStrict, localeData) { + return (isStrict && strictRegex) ? strictRegex : regex; + }; } - function timezoneMinutesFromString(string) { - string = string || ""; - var possibleTzMatches = (string.match(parseTokenTimezone) || []), - tzChunk = possibleTzMatches[possibleTzMatches.length - 1] || [], - parts = (tzChunk + '').match(parseTimezoneChunker) || ['-', 0, 0], - minutes = +(parts[1] * 60) + toInt(parts[2]); + function getParseRegexForToken (token, config) { + if (!hasOwnProp(regexes, token)) { + return new RegExp(unescapeFormat(token)); + } - return parts[0] === '+' ? -minutes : minutes; + return regexes[token](config._strict, config._locale); } - // function to convert string input to date - function addTimeToArrayFromToken(token, input, config) { - var a, datePartArray = config._a; + // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript + function unescapeFormat(s) { + return regexEscape(s.replace('\\', '').replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) { + return p1 || p2 || p3 || p4; + })); + } - switch (token) { - // MONTH - case 'M' : // fall through to MM - case 'MM' : - if (input != null) { - datePartArray[MONTH] = toInt(input) - 1; - } - break; - case 'MMM' : // fall through to MMMM - case 'MMMM' : - a = getLangDefinition(config._l).monthsParse(input); - // if we didn't find a month name, mark the date as invalid. - if (a != null) { - datePartArray[MONTH] = a; - } else { - config._pf.invalidMonth = input; - } - break; - // DAY OF MONTH - case 'D' : // fall through to DD - case 'DD' : - if (input != null) { - datePartArray[DATE] = toInt(input); - } - break; - // DAY OF YEAR - case 'DDD' : // fall through to DDDD - case 'DDDD' : - if (input != null) { - config._dayOfYear = toInt(input); - } + function regexEscape(s) { + return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); + } - break; - // YEAR - case 'YY' : - datePartArray[YEAR] = toInt(input) + (toInt(input) > 68 ? 1900 : 2000); - break; - case 'YYYY' : - case 'YYYYY' : - case 'YYYYYY' : - datePartArray[YEAR] = toInt(input); - break; - // AM / PM - case 'a' : // fall through to A - case 'A' : - config._isPm = getLangDefinition(config._l).isPM(input); - break; - // 24 HOUR - case 'H' : // fall through to hh - case 'HH' : // fall through to hh - case 'h' : // fall through to hh - case 'hh' : - datePartArray[HOUR] = toInt(input); - break; - // MINUTE - case 'm' : // fall through to mm - case 'mm' : - datePartArray[MINUTE] = toInt(input); - break; - // SECOND - case 's' : // fall through to ss - case 'ss' : - datePartArray[SECOND] = toInt(input); - break; - // MILLISECOND - case 'S' : - case 'SS' : - case 'SSS' : - case 'SSSS' : - datePartArray[MILLISECOND] = toInt(('0.' + input) * 1000); - break; - // UNIX TIMESTAMP WITH MS - case 'X': - config._d = new Date(parseFloat(input) * 1000); - break; - // TIMEZONE - case 'Z' : // fall through to ZZ - case 'ZZ' : - config._useUTC = true; - config._tzm = timezoneMinutesFromString(input); - break; - case 'w': - case 'ww': - case 'W': - case 'WW': - case 'd': - case 'dd': - case 'ddd': - case 'dddd': - case 'e': - case 'E': - token = token.substr(0, 1); - /* falls through */ - case 'gg': - case 'gggg': - case 'GG': - case 'GGGG': - case 'GGGGG': - token = token.substr(0, 2); - if (input) { - config._w = config._w || {}; - config._w[token] = input; - } - break; + var tokens = {}; + + function addParseToken (token, callback) { + var i, func = callback; + if (typeof token === 'string') { + token = [token]; + } + if (typeof callback === 'number') { + func = function (input, array) { + array[callback] = toInt(input); + }; + } + for (i = 0; i < token.length; i++) { + tokens[token[i]] = func; } } - // convert an array to a date. - // the array should mirror the parameters below - // note: all values past the year are optional and will default to the lowest possible value. - // [year, month, day , hour, minute, second, millisecond] - function dateFromConfig(config) { - var i, date, input = [], currentDate, - yearToUse, fixYear, w, temp, lang, weekday, week; + function addWeekParseToken (token, callback) { + addParseToken(token, function (input, array, config, token) { + config._w = config._w || {}; + callback(input, config._w, config, token); + }); + } - if (config._d) { - return; + function addTimeToArrayFromToken(token, input, config) { + if (input != null && hasOwnProp(tokens, token)) { + tokens[token](input, config._a, config, token); } + } - currentDate = currentDateArray(config); + var YEAR = 0; + var MONTH = 1; + var DATE = 2; + var HOUR = 3; + var MINUTE = 4; + var SECOND = 5; + var MILLISECOND = 6; + var WEEK = 7; + var WEEKDAY = 8; - //compute day of the year from weeks and weekdays - if (config._w && config._a[DATE] == null && config._a[MONTH] == null) { - fixYear = function (val) { - var int_val = parseInt(val, 10); - return val ? - (val.length < 3 ? (int_val > 68 ? 1900 + int_val : 2000 + int_val) : int_val) : - (config._a[YEAR] == null ? moment().weekYear() : config._a[YEAR]); - }; + var indexOf; - w = config._w; - if (w.GG != null || w.W != null || w.E != null) { - temp = dayOfYearFromWeeks(fixYear(w.GG), w.W || 1, w.E, 4, 1); + if (Array.prototype.indexOf) { + indexOf = Array.prototype.indexOf; + } else { + indexOf = function (o) { + // I know + var i; + for (i = 0; i < this.length; ++i) { + if (this[i] === o) { + return i; + } } - else { - lang = getLangDefinition(config._l); - weekday = w.d != null ? parseWeekday(w.d, lang) : - (w.e != null ? parseInt(w.e, 10) + lang._week.dow : 0); + return -1; + }; + } - week = parseInt(w.w, 10) || 1; + function daysInMonth(year, month) { + return new Date(Date.UTC(year, month + 1, 0)).getUTCDate(); + } - //if we're parsing 'd', then the low day numbers may be next week - if (w.d != null && weekday < lang._week.dow) { - week++; - } + // FORMATTING - temp = dayOfYearFromWeeks(fixYear(w.gg), week, weekday, lang._week.doy, lang._week.dow); - } + addFormatToken('M', ['MM', 2], 'Mo', function () { + return this.month() + 1; + }); - config._a[YEAR] = temp.year; - config._dayOfYear = temp.dayOfYear; - } + addFormatToken('MMM', 0, 0, function (format) { + return this.localeData().monthsShort(this, format); + }); - //if the day of the year is set, figure out what it is - if (config._dayOfYear) { - yearToUse = config._a[YEAR] == null ? currentDate[YEAR] : config._a[YEAR]; + addFormatToken('MMMM', 0, 0, function (format) { + return this.localeData().months(this, format); + }); - if (config._dayOfYear > daysInYear(yearToUse)) { - config._pf._overflowDayOfYear = true; - } + // ALIASES - date = makeUTCDate(yearToUse, 0, config._dayOfYear); - config._a[MONTH] = date.getUTCMonth(); - config._a[DATE] = date.getUTCDate(); - } + addUnitAlias('month', 'M'); - // Default to current date. - // * if no year, month, day of month are given, default to today - // * if day of month is given, default month and year - // * if month is given, default only year - // * if year is given, don't default anything - for (i = 0; i < 3 && config._a[i] == null; ++i) { - config._a[i] = input[i] = currentDate[i]; - } + // PARSING - // Zero out whatever was not defaulted, including time - for (; i < 7; i++) { - config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i]; + addRegexToken('M', match1to2); + addRegexToken('MM', match1to2, match2); + addRegexToken('MMM', function (isStrict, locale) { + return locale.monthsShortRegex(isStrict); + }); + addRegexToken('MMMM', function (isStrict, locale) { + return locale.monthsRegex(isStrict); + }); + + addParseToken(['M', 'MM'], function (input, array) { + array[MONTH] = toInt(input) - 1; + }); + + addParseToken(['MMM', 'MMMM'], function (input, array, config, token) { + var month = config._locale.monthsParse(input, token, config._strict); + // if we didn't find a month name, mark the date as invalid. + if (month != null) { + array[MONTH] = month; + } else { + getParsingFlags(config).invalidMonth = input; } + }); - // add the offsets to the time to be parsed so that we can have a clean array for checking isValid - input[HOUR] += toInt((config._tzm || 0) / 60); - input[MINUTE] += toInt((config._tzm || 0) % 60); + // LOCALES - config._d = (config._useUTC ? makeUTCDate : makeDate).apply(null, input); + var MONTHS_IN_FORMAT = /D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/; + var defaultLocaleMonths = 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'); + function localeMonths (m, format) { + return isArray(this._months) ? this._months[m.month()] : + this._months[MONTHS_IN_FORMAT.test(format) ? 'format' : 'standalone'][m.month()]; } - function dateFromObject(config) { - var normalizedInput; + var defaultLocaleMonthsShort = 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'); + function localeMonthsShort (m, format) { + return isArray(this._monthsShort) ? this._monthsShort[m.month()] : + this._monthsShort[MONTHS_IN_FORMAT.test(format) ? 'format' : 'standalone'][m.month()]; + } - if (config._d) { - return; + function units_month__handleStrictParse(monthName, format, strict) { + var i, ii, mom, llc = monthName.toLocaleLowerCase(); + if (!this._monthsParse) { + // this is not used + this._monthsParse = []; + this._longMonthsParse = []; + this._shortMonthsParse = []; + for (i = 0; i < 12; ++i) { + mom = create_utc__createUTC([2000, i]); + this._shortMonthsParse[i] = this.monthsShort(mom, '').toLocaleLowerCase(); + this._longMonthsParse[i] = this.months(mom, '').toLocaleLowerCase(); + } } - normalizedInput = normalizeObjectUnits(config._i); - config._a = [ - normalizedInput.year, - normalizedInput.month, - normalizedInput.day, - normalizedInput.hour, - normalizedInput.minute, - normalizedInput.second, - normalizedInput.millisecond - ]; - - dateFromConfig(config); - } - - function currentDateArray(config) { - var now = new Date(); - if (config._useUTC) { - return [ - now.getUTCFullYear(), - now.getUTCMonth(), - now.getUTCDate() - ]; + if (strict) { + if (format === 'MMM') { + ii = indexOf.call(this._shortMonthsParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf.call(this._longMonthsParse, llc); + return ii !== -1 ? ii : null; + } } else { - return [now.getFullYear(), now.getMonth(), now.getDate()]; + if (format === 'MMM') { + ii = indexOf.call(this._shortMonthsParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._longMonthsParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf.call(this._longMonthsParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._shortMonthsParse, llc); + return ii !== -1 ? ii : null; + } } } - // date from string and format string - function makeDateFromStringAndFormat(config) { - - config._a = []; - config._pf.empty = true; + function localeMonthsParse (monthName, format, strict) { + var i, mom, regex; - // This array is used to make a Date, either with `new Date` or `Date.UTC` - var lang = getLangDefinition(config._l), - string = '' + config._i, - i, parsedInput, tokens, token, skipped, - stringLength = string.length, - totalParsedInputLength = 0; + if (this._monthsParseExact) { + return units_month__handleStrictParse.call(this, monthName, format, strict); + } - tokens = expandFormat(config._f, lang).match(formattingTokens) || []; + if (!this._monthsParse) { + this._monthsParse = []; + this._longMonthsParse = []; + this._shortMonthsParse = []; + } - for (i = 0; i < tokens.length; i++) { - token = tokens[i]; - parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0]; - if (parsedInput) { - skipped = string.substr(0, string.indexOf(parsedInput)); - if (skipped.length > 0) { - config._pf.unusedInput.push(skipped); - } - string = string.slice(string.indexOf(parsedInput) + parsedInput.length); - totalParsedInputLength += parsedInput.length; + // TODO: add sorting + // Sorting makes sure if one month (or abbr) is a prefix of another + // see sorting in computeMonthsParse + for (i = 0; i < 12; i++) { + // make the regex if we don't have it already + mom = create_utc__createUTC([2000, i]); + if (strict && !this._longMonthsParse[i]) { + this._longMonthsParse[i] = new RegExp('^' + this.months(mom, '').replace('.', '') + '$', 'i'); + this._shortMonthsParse[i] = new RegExp('^' + this.monthsShort(mom, '').replace('.', '') + '$', 'i'); } - // don't parse if it's not a known token - if (formatTokenFunctions[token]) { - if (parsedInput) { - config._pf.empty = false; - } - else { - config._pf.unusedTokens.push(token); - } - addTimeToArrayFromToken(token, parsedInput, config); + if (!strict && !this._monthsParse[i]) { + regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, ''); + this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i'); } - else if (config._strict && !parsedInput) { - config._pf.unusedTokens.push(token); + // test the regex + if (strict && format === 'MMMM' && this._longMonthsParse[i].test(monthName)) { + return i; + } else if (strict && format === 'MMM' && this._shortMonthsParse[i].test(monthName)) { + return i; + } else if (!strict && this._monthsParse[i].test(monthName)) { + return i; } } + } - // add remaining unparsed input length to the string - config._pf.charsLeftOver = stringLength - totalParsedInputLength; - if (string.length > 0) { - config._pf.unusedInput.push(string); - } + // MOMENTS - // handle am pm - if (config._isPm && config._a[HOUR] < 12) { - config._a[HOUR] += 12; + function setMonth (mom, value) { + var dayOfMonth; + + if (!mom.isValid()) { + // No op + return mom; } - // if is 12 am, change hours to 0 - if (config._isPm === false && config._a[HOUR] === 12) { - config._a[HOUR] = 0; + + if (typeof value === 'string') { + if (/^\d+$/.test(value)) { + value = toInt(value); + } else { + value = mom.localeData().monthsParse(value); + // TODO: Another silent failure? + if (typeof value !== 'number') { + return mom; + } + } } - dateFromConfig(config); - checkOverflow(config); + dayOfMonth = Math.min(mom.date(), daysInMonth(mom.year(), value)); + mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth); + return mom; } - function unescapeFormat(s) { - return s.replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) { - return p1 || p2 || p3 || p4; - }); + function getSetMonth (value) { + if (value != null) { + setMonth(this, value); + utils_hooks__hooks.updateOffset(this, true); + return this; + } else { + return get_set__get(this, 'Month'); + } } - // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript - function regexpEscape(s) { - return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); + function getDaysInMonth () { + return daysInMonth(this.year(), this.month()); } - // date from string and array of format strings - function makeDateFromStringAndArray(config) { - var tempConfig, - bestMoment, - - scoreToBeat, - i, - currentScore; + var defaultMonthsShortRegex = matchWord; + function monthsShortRegex (isStrict) { + if (this._monthsParseExact) { + if (!hasOwnProp(this, '_monthsRegex')) { + computeMonthsParse.call(this); + } + if (isStrict) { + return this._monthsShortStrictRegex; + } else { + return this._monthsShortRegex; + } + } else { + return this._monthsShortStrictRegex && isStrict ? + this._monthsShortStrictRegex : this._monthsShortRegex; + } + } - if (config._f.length === 0) { - config._pf.invalidFormat = true; - config._d = new Date(NaN); - return; + var defaultMonthsRegex = matchWord; + function monthsRegex (isStrict) { + if (this._monthsParseExact) { + if (!hasOwnProp(this, '_monthsRegex')) { + computeMonthsParse.call(this); + } + if (isStrict) { + return this._monthsStrictRegex; + } else { + return this._monthsRegex; + } + } else { + return this._monthsStrictRegex && isStrict ? + this._monthsStrictRegex : this._monthsRegex; } + } - for (i = 0; i < config._f.length; i++) { - currentScore = 0; - tempConfig = extend({}, config); - initializeParsingFlags(tempConfig); - tempConfig._f = config._f[i]; - makeDateFromStringAndFormat(tempConfig); + function computeMonthsParse () { + function cmpLenRev(a, b) { + return b.length - a.length; + } - if (!isValid(tempConfig)) { - continue; - } + var shortPieces = [], longPieces = [], mixedPieces = [], + i, mom; + for (i = 0; i < 12; i++) { + // make the regex if we don't have it already + mom = create_utc__createUTC([2000, i]); + shortPieces.push(this.monthsShort(mom, '')); + longPieces.push(this.months(mom, '')); + mixedPieces.push(this.months(mom, '')); + mixedPieces.push(this.monthsShort(mom, '')); + } + // Sorting makes sure if one month (or abbr) is a prefix of another it + // will match the longer piece. + shortPieces.sort(cmpLenRev); + longPieces.sort(cmpLenRev); + mixedPieces.sort(cmpLenRev); + for (i = 0; i < 12; i++) { + shortPieces[i] = regexEscape(shortPieces[i]); + longPieces[i] = regexEscape(longPieces[i]); + mixedPieces[i] = regexEscape(mixedPieces[i]); + } - // if there is any input that was not parsed add a penalty for that format - currentScore += tempConfig._pf.charsLeftOver; + this._monthsRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i'); + this._monthsShortRegex = this._monthsRegex; + this._monthsStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i'); + this._monthsShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i'); + } - //or tokens - currentScore += tempConfig._pf.unusedTokens.length * 10; + function checkOverflow (m) { + var overflow; + var a = m._a; - tempConfig._pf.score = currentScore; + if (a && getParsingFlags(m).overflow === -2) { + overflow = + a[MONTH] < 0 || a[MONTH] > 11 ? MONTH : + a[DATE] < 1 || a[DATE] > daysInMonth(a[YEAR], a[MONTH]) ? DATE : + a[HOUR] < 0 || a[HOUR] > 24 || (a[HOUR] === 24 && (a[MINUTE] !== 0 || a[SECOND] !== 0 || a[MILLISECOND] !== 0)) ? HOUR : + a[MINUTE] < 0 || a[MINUTE] > 59 ? MINUTE : + a[SECOND] < 0 || a[SECOND] > 59 ? SECOND : + a[MILLISECOND] < 0 || a[MILLISECOND] > 999 ? MILLISECOND : + -1; - if (scoreToBeat == null || currentScore < scoreToBeat) { - scoreToBeat = currentScore; - bestMoment = tempConfig; + if (getParsingFlags(m)._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) { + overflow = DATE; + } + if (getParsingFlags(m)._overflowWeeks && overflow === -1) { + overflow = WEEK; + } + if (getParsingFlags(m)._overflowWeekday && overflow === -1) { + overflow = WEEKDAY; } + + getParsingFlags(m).overflow = overflow; } - extend(config, bestMoment || tempConfig); + return m; } + // iso 8601 regex + // 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00) + var extendedIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?/; + var basicIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?/; + + var tzRegex = /Z|[+-]\d\d(?::?\d\d)?/; + + var isoDates = [ + ['YYYYYY-MM-DD', /[+-]\d{6}-\d\d-\d\d/], + ['YYYY-MM-DD', /\d{4}-\d\d-\d\d/], + ['GGGG-[W]WW-E', /\d{4}-W\d\d-\d/], + ['GGGG-[W]WW', /\d{4}-W\d\d/, false], + ['YYYY-DDD', /\d{4}-\d{3}/], + ['YYYY-MM', /\d{4}-\d\d/, false], + ['YYYYYYMMDD', /[+-]\d{10}/], + ['YYYYMMDD', /\d{8}/], + // YYYYMM is NOT allowed by the standard + ['GGGG[W]WWE', /\d{4}W\d{3}/], + ['GGGG[W]WW', /\d{4}W\d{2}/, false], + ['YYYYDDD', /\d{7}/] + ]; + + // iso time formats and regexes + var isoTimes = [ + ['HH:mm:ss.SSSS', /\d\d:\d\d:\d\d\.\d+/], + ['HH:mm:ss,SSSS', /\d\d:\d\d:\d\d,\d+/], + ['HH:mm:ss', /\d\d:\d\d:\d\d/], + ['HH:mm', /\d\d:\d\d/], + ['HHmmss.SSSS', /\d\d\d\d\d\d\.\d+/], + ['HHmmss,SSSS', /\d\d\d\d\d\d,\d+/], + ['HHmmss', /\d\d\d\d\d\d/], + ['HHmm', /\d\d\d\d/], + ['HH', /\d\d/] + ]; + + var aspNetJsonRegex = /^\/?Date\((\-?\d+)/i; + // date from iso format - function makeDateFromString(config) { - var i, + function configFromISO(config) { + var i, l, string = config._i, - match = isoRegex.exec(string); + match = extendedIsoRegex.exec(string) || basicIsoRegex.exec(string), + allowTime, dateFormat, timeFormat, tzFormat; if (match) { - config._pf.iso = true; - for (i = 4; i > 0; i--) { - if (match[i]) { - // match[5] should be "T" or undefined - config._f = isoDates[i - 1] + (match[6] || " "); + getParsingFlags(config).iso = true; + + for (i = 0, l = isoDates.length; i < l; i++) { + if (isoDates[i][1].exec(match[1])) { + dateFormat = isoDates[i][0]; + allowTime = isoDates[i][2] !== false; break; } } - for (i = 0; i < 4; i++) { - if (isoTimes[i][1].exec(string)) { - config._f += isoTimes[i][0]; - break; + if (dateFormat == null) { + config._isValid = false; + return; + } + if (match[3]) { + for (i = 0, l = isoTimes.length; i < l; i++) { + if (isoTimes[i][1].exec(match[3])) { + // match[2] should be 'T' or space + timeFormat = (match[2] || ' ') + isoTimes[i][0]; + break; + } + } + if (timeFormat == null) { + config._isValid = false; + return; } } - if (string.match(parseTokenTimezone)) { - config._f += "Z"; + if (!allowTime && timeFormat != null) { + config._isValid = false; + return; } - makeDateFromStringAndFormat(config); - } - else { - config._d = new Date(string); + if (match[4]) { + if (tzRegex.exec(match[4])) { + tzFormat = 'Z'; + } else { + config._isValid = false; + return; + } + } + config._f = dateFormat + (timeFormat || '') + (tzFormat || ''); + configFromStringAndFormat(config); + } else { + config._isValid = false; } } - function makeDateFromInput(config) { - var input = config._i, - matched = aspNetJsonRegex.exec(input); + // date from iso format or fallback + function configFromString(config) { + var matched = aspNetJsonRegex.exec(config._i); - if (input === undefined) { - config._d = new Date(); - } else if (matched) { + if (matched !== null) { config._d = new Date(+matched[1]); - } else if (typeof input === 'string') { - makeDateFromString(config); - } else if (isArray(input)) { - config._a = input.slice(0); - dateFromConfig(config); - } else if (isDate(input)) { - config._d = new Date(+input); - } else if (typeof(input) === 'object') { - dateFromObject(config); - } else { - config._d = new Date(input); + return; + } + + configFromISO(config); + if (config._isValid === false) { + delete config._isValid; + utils_hooks__hooks.createFromInputFallback(config); } } - function makeDate(y, m, d, h, M, s, ms) { + utils_hooks__hooks.createFromInputFallback = deprecate( + 'moment construction falls back to js Date. This is ' + + 'discouraged and will be removed in upcoming major ' + + 'release. Please refer to ' + + 'https://github.com/moment/moment/issues/1407 for more info.', + function (config) { + config._d = new Date(config._i + (config._useUTC ? ' UTC' : '')); + } + ); + + function createDate (y, m, d, h, M, s, ms) { //can't just apply() to create a date: //http://stackoverflow.com/questions/181348/instantiating-a-javascript-object-by-calling-prototype-constructor-apply var date = new Date(y, m, d, h, M, s, ms); - //the date constructor doesn't accept years < 1970 - if (y < 1970) { + //the date constructor remaps years 0-99 to 1900-1999 + if (y < 100 && y >= 0 && isFinite(date.getFullYear())) { date.setFullYear(y); } return date; } - function makeUTCDate(y) { + function createUTCDate (y) { var date = new Date(Date.UTC.apply(null, arguments)); - if (y < 1970) { + + //the Date.UTC function remaps years 0-99 to 1900-1999 + if (y < 100 && y >= 0 && isFinite(date.getUTCFullYear())) { date.setUTCFullYear(y); } return date; } - function parseWeekday(input, language) { - if (typeof input === 'string') { - if (!isNaN(input)) { - input = parseInt(input, 10); - } - else { - input = language.weekdaysParse(input); - if (typeof input !== 'number') { - return null; - } - } - } - return input; - } + // FORMATTING - /************************************ - Relative Time - ************************************/ + addFormatToken('Y', 0, 0, function () { + var y = this.year(); + return y <= 9999 ? '' + y : '+' + y; + }); + + addFormatToken(0, ['YY', 2], 0, function () { + return this.year() % 100; + }); + addFormatToken(0, ['YYYY', 4], 0, 'year'); + addFormatToken(0, ['YYYYY', 5], 0, 'year'); + addFormatToken(0, ['YYYYYY', 6, true], 0, 'year'); - // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize - function substituteTimeAgo(string, number, withoutSuffix, isFuture, lang) { - return lang.relativeTime(number || 1, !!withoutSuffix, string, isFuture); + // ALIASES + + addUnitAlias('year', 'y'); + + // PARSING + + addRegexToken('Y', matchSigned); + addRegexToken('YY', match1to2, match2); + addRegexToken('YYYY', match1to4, match4); + addRegexToken('YYYYY', match1to6, match6); + addRegexToken('YYYYYY', match1to6, match6); + + addParseToken(['YYYYY', 'YYYYYY'], YEAR); + addParseToken('YYYY', function (input, array) { + array[YEAR] = input.length === 2 ? utils_hooks__hooks.parseTwoDigitYear(input) : toInt(input); + }); + addParseToken('YY', function (input, array) { + array[YEAR] = utils_hooks__hooks.parseTwoDigitYear(input); + }); + addParseToken('Y', function (input, array) { + array[YEAR] = parseInt(input, 10); + }); + + // HELPERS + + function daysInYear(year) { + return isLeapYear(year) ? 366 : 365; } - function relativeTime(milliseconds, withoutSuffix, lang) { - var seconds = round(Math.abs(milliseconds) / 1000), - minutes = round(seconds / 60), - hours = round(minutes / 60), - days = round(hours / 24), - years = round(days / 365), - args = seconds < 45 && ['s', seconds] || - minutes === 1 && ['m'] || - minutes < 45 && ['mm', minutes] || - hours === 1 && ['h'] || - hours < 22 && ['hh', hours] || - days === 1 && ['d'] || - days <= 25 && ['dd', days] || - days <= 45 && ['M'] || - days < 345 && ['MM', round(days / 30)] || - years === 1 && ['y'] || ['yy', years]; - args[2] = withoutSuffix; - args[3] = milliseconds > 0; - args[4] = lang; - return substituteTimeAgo.apply({}, args); + function isLeapYear(year) { + return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; } + // HOOKS - /************************************ - Week of Year - ************************************/ + utils_hooks__hooks.parseTwoDigitYear = function (input) { + return toInt(input) + (toInt(input) > 68 ? 1900 : 2000); + }; + // MOMENTS - // firstDayOfWeek 0 = sun, 6 = sat - // the day of the week that starts the week - // (usually sunday or monday) - // firstDayOfWeekOfYear 0 = sun, 6 = sat - // the first week is the week that contains the first - // of this day of the week - // (eg. ISO weeks use thursday (4)) - function weekOfYear(mom, firstDayOfWeek, firstDayOfWeekOfYear) { - var end = firstDayOfWeekOfYear - firstDayOfWeek, - daysToDayOfWeek = firstDayOfWeekOfYear - mom.day(), - adjustedMoment; + var getSetYear = makeGetSet('FullYear', true); + function getIsLeapYear () { + return isLeapYear(this.year()); + } - if (daysToDayOfWeek > end) { - daysToDayOfWeek -= 7; - } + // start-of-first-week - start-of-year + function firstWeekOffset(year, dow, doy) { + var // first-week day -- which january is always in the first week (4 for iso, 1 for other) + fwd = 7 + dow - doy, + // first-week day local weekday -- which local weekday is fwd + fwdlw = (7 + createUTCDate(year, 0, fwd).getUTCDay() - dow) % 7; - if (daysToDayOfWeek < end - 7) { - daysToDayOfWeek += 7; + return -fwdlw + fwd - 1; + } + + //http://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday + function dayOfYearFromWeeks(year, week, weekday, dow, doy) { + var localWeekday = (7 + weekday - dow) % 7, + weekOffset = firstWeekOffset(year, dow, doy), + dayOfYear = 1 + 7 * (week - 1) + localWeekday + weekOffset, + resYear, resDayOfYear; + + if (dayOfYear <= 0) { + resYear = year - 1; + resDayOfYear = daysInYear(resYear) + dayOfYear; + } else if (dayOfYear > daysInYear(year)) { + resYear = year + 1; + resDayOfYear = dayOfYear - daysInYear(year); + } else { + resYear = year; + resDayOfYear = dayOfYear; } - adjustedMoment = moment(mom).add('d', daysToDayOfWeek); return { - week: Math.ceil(adjustedMoment.dayOfYear() / 7), - year: adjustedMoment.year() + year: resYear, + dayOfYear: resDayOfYear }; } - //http://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday - function dayOfYearFromWeeks(year, week, weekday, firstDayOfWeekOfYear, firstDayOfWeek) { - // The only solid way to create an iso date from year is to use - // a string format (Date.UTC handles only years > 1900). Don't ask why - // it doesn't need Z at the end. - var d = new Date(leftZeroFill(year, 6, true) + '-01-01').getUTCDay(), - daysToAdd, dayOfYear; + function weekOfYear(mom, dow, doy) { + var weekOffset = firstWeekOffset(mom.year(), dow, doy), + week = Math.floor((mom.dayOfYear() - weekOffset - 1) / 7) + 1, + resWeek, resYear; - weekday = weekday != null ? weekday : firstDayOfWeek; - daysToAdd = firstDayOfWeek - d + (d > firstDayOfWeekOfYear ? 7 : 0); - dayOfYear = 7 * (week - 1) + (weekday - firstDayOfWeek) + daysToAdd + 1; + if (week < 1) { + resYear = mom.year() - 1; + resWeek = week + weeksInYear(resYear, dow, doy); + } else if (week > weeksInYear(mom.year(), dow, doy)) { + resWeek = week - weeksInYear(mom.year(), dow, doy); + resYear = mom.year() + 1; + } else { + resYear = mom.year(); + resWeek = week; + } return { - year: dayOfYear > 0 ? year : year - 1, - dayOfYear: dayOfYear > 0 ? dayOfYear : daysInYear(year - 1) + dayOfYear + week: resWeek, + year: resYear }; } - /************************************ - Top Level Functions - ************************************/ + function weeksInYear(year, dow, doy) { + var weekOffset = firstWeekOffset(year, dow, doy), + weekOffsetNext = firstWeekOffset(year + 1, dow, doy); + return (daysInYear(year) - weekOffset + weekOffsetNext) / 7; + } - function makeMoment(config) { - var input = config._i, - format = config._f; + // Pick the first defined of two or three arguments. + function defaults(a, b, c) { + if (a != null) { + return a; + } + if (b != null) { + return b; + } + return c; + } - if (typeof config._pf === 'undefined') { - initializeParsingFlags(config); + function currentDateArray(config) { + // hooks is actually the exported moment object + var nowValue = new Date(utils_hooks__hooks.now()); + if (config._useUTC) { + return [nowValue.getUTCFullYear(), nowValue.getUTCMonth(), nowValue.getUTCDate()]; } + return [nowValue.getFullYear(), nowValue.getMonth(), nowValue.getDate()]; + } + + // convert an array to a date. + // the array should mirror the parameters below + // note: all values past the year are optional and will default to the lowest possible value. + // [year, month, day , hour, minute, second, millisecond] + function configFromArray (config) { + var i, date, input = [], currentDate, yearToUse; - if (input === null) { - return moment.invalid({nullInput: true}); + if (config._d) { + return; } - if (typeof input === 'string') { - config._i = input = getLangDefinition().preparse(input); + currentDate = currentDateArray(config); + + //compute day of the year from weeks and weekdays + if (config._w && config._a[DATE] == null && config._a[MONTH] == null) { + dayOfYearFromWeekInfo(config); } - if (moment.isMoment(input)) { - config = extend({}, input); + //if the day of the year is set, figure out what it is + if (config._dayOfYear) { + yearToUse = defaults(config._a[YEAR], currentDate[YEAR]); - config._d = new Date(+input._d); - } else if (format) { - if (isArray(format)) { - makeDateFromStringAndArray(config); - } else { - makeDateFromStringAndFormat(config); + if (config._dayOfYear > daysInYear(yearToUse)) { + getParsingFlags(config)._overflowDayOfYear = true; } - } else { - makeDateFromInput(config); + + date = createUTCDate(yearToUse, 0, config._dayOfYear); + config._a[MONTH] = date.getUTCMonth(); + config._a[DATE] = date.getUTCDate(); } - return new Moment(config); - } + // Default to current date. + // * if no year, month, day of month are given, default to today + // * if day of month is given, default month and year + // * if month is given, default only year + // * if year is given, don't default anything + for (i = 0; i < 3 && config._a[i] == null; ++i) { + config._a[i] = input[i] = currentDate[i]; + } - moment = function (input, format, lang, strict) { - if (typeof(lang) === "boolean") { - strict = lang; - lang = undefined; + // Zero out whatever was not defaulted, including time + for (; i < 7; i++) { + config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i]; } - return makeMoment({ - _i : input, - _f : format, - _l : lang, - _strict : strict, - _isUTC : false - }); - }; - // creating with utc - moment.utc = function (input, format, lang, strict) { - var m; + // Check for 24:00:00.000 + if (config._a[HOUR] === 24 && + config._a[MINUTE] === 0 && + config._a[SECOND] === 0 && + config._a[MILLISECOND] === 0) { + config._nextDay = true; + config._a[HOUR] = 0; + } - if (typeof(lang) === "boolean") { - strict = lang; - lang = undefined; + config._d = (config._useUTC ? createUTCDate : createDate).apply(null, input); + // Apply timezone offset from input. The actual utcOffset can be changed + // with parseZone. + if (config._tzm != null) { + config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm); } - m = makeMoment({ - _useUTC : true, - _isUTC : true, - _l : lang, - _i : input, - _f : format, - _strict : strict - }).utc(); - return m; - }; + if (config._nextDay) { + config._a[HOUR] = 24; + } + } - // creating with unix timestamp (in seconds) - moment.unix = function (input) { - return moment(input * 1000); - }; + function dayOfYearFromWeekInfo(config) { + var w, weekYear, week, weekday, dow, doy, temp, weekdayOverflow; - // duration - moment.duration = function (input, key) { - var duration = input, - // matching against regexp is expensive, do it on demand - match = null, - sign, - ret, - parseIso; + w = config._w; + if (w.GG != null || w.W != null || w.E != null) { + dow = 1; + doy = 4; - if (moment.isDuration(input)) { - duration = { - ms: input._milliseconds, - d: input._days, - M: input._months - }; - } else if (typeof input === 'number') { - duration = {}; - if (key) { - duration[key] = input; + // TODO: We need to take the current isoWeekYear, but that depends on + // how we interpret now (local, utc, fixed offset). So create + // a now version of current config (take local/utc/offset flags, and + // create now). + weekYear = defaults(w.GG, config._a[YEAR], weekOfYear(local__createLocal(), 1, 4).year); + week = defaults(w.W, 1); + weekday = defaults(w.E, 1); + if (weekday < 1 || weekday > 7) { + weekdayOverflow = true; + } + } else { + dow = config._locale._week.dow; + doy = config._locale._week.doy; + + weekYear = defaults(w.gg, config._a[YEAR], weekOfYear(local__createLocal(), dow, doy).year); + week = defaults(w.w, 1); + + if (w.d != null) { + // weekday -- low day numbers are considered next week + weekday = w.d; + if (weekday < 0 || weekday > 6) { + weekdayOverflow = true; + } + } else if (w.e != null) { + // local weekday -- counting starts from begining of week + weekday = w.e + dow; + if (w.e < 0 || w.e > 6) { + weekdayOverflow = true; + } } else { - duration.milliseconds = input; + // default to begining of week + weekday = dow; } - } else if (!!(match = aspNetTimeSpanJsonRegex.exec(input))) { - sign = (match[1] === "-") ? -1 : 1; - duration = { - y: 0, - d: toInt(match[DATE]) * sign, - h: toInt(match[HOUR]) * sign, - m: toInt(match[MINUTE]) * sign, - s: toInt(match[SECOND]) * sign, - ms: toInt(match[MILLISECOND]) * sign - }; - } else if (!!(match = isoDurationRegex.exec(input))) { - sign = (match[1] === "-") ? -1 : 1; - parseIso = function (inp) { - // We'd normally use ~~inp for this, but unfortunately it also - // converts floats to ints. - // inp may be undefined, so careful calling replace on it. - var res = inp && parseFloat(inp.replace(',', '.')); - // apply sign while we're at it - return (isNaN(res) ? 0 : res) * sign; - }; - duration = { - y: parseIso(match[2]), - M: parseIso(match[3]), - d: parseIso(match[4]), - h: parseIso(match[5]), - m: parseIso(match[6]), - s: parseIso(match[7]), - w: parseIso(match[8]) - }; } + if (week < 1 || week > weeksInYear(weekYear, dow, doy)) { + getParsingFlags(config)._overflowWeeks = true; + } else if (weekdayOverflow != null) { + getParsingFlags(config)._overflowWeekday = true; + } else { + temp = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy); + config._a[YEAR] = temp.year; + config._dayOfYear = temp.dayOfYear; + } + } - ret = new Duration(duration); + // constant that refers to the ISO standard + utils_hooks__hooks.ISO_8601 = function () {}; - if (moment.isDuration(input) && input.hasOwnProperty('_lang')) { - ret._lang = input._lang; + // date from string and format string + function configFromStringAndFormat(config) { + // TODO: Move this to another part of the creation flow to prevent circular deps + if (config._f === utils_hooks__hooks.ISO_8601) { + configFromISO(config); + return; } - return ret; - }; - - // version number - moment.version = VERSION; + config._a = []; + getParsingFlags(config).empty = true; - // default format - moment.defaultFormat = isoFormat; + // This array is used to make a Date, either with `new Date` or `Date.UTC` + var string = '' + config._i, + i, parsedInput, tokens, token, skipped, + stringLength = string.length, + totalParsedInputLength = 0; - // This function will be called whenever a moment is mutated. - // It is intended to keep the offset in sync with the timezone. - moment.updateOffset = function () {}; + tokens = expandFormat(config._f, config._locale).match(formattingTokens) || []; - // This function will load languages and then set the global language. If - // no arguments are passed in, it will simply return the current global - // language key. - moment.lang = function (key, values) { - var r; - if (!key) { - return moment.fn._lang._abbr; - } - if (values) { - loadLang(normalizeLanguage(key), values); - } else if (values === null) { - unloadLang(key); - key = 'en'; - } else if (!languages[key]) { - getLangDefinition(key); - } - r = moment.duration.fn._lang = moment.fn._lang = getLangDefinition(key); - return r._abbr; - }; + for (i = 0; i < tokens.length; i++) { + token = tokens[i]; + parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0]; + // console.log('token', token, 'parsedInput', parsedInput, + // 'regex', getParseRegexForToken(token, config)); + if (parsedInput) { + skipped = string.substr(0, string.indexOf(parsedInput)); + if (skipped.length > 0) { + getParsingFlags(config).unusedInput.push(skipped); + } + string = string.slice(string.indexOf(parsedInput) + parsedInput.length); + totalParsedInputLength += parsedInput.length; + } + // don't parse if it's not a known token + if (formatTokenFunctions[token]) { + if (parsedInput) { + getParsingFlags(config).empty = false; + } + else { + getParsingFlags(config).unusedTokens.push(token); + } + addTimeToArrayFromToken(token, parsedInput, config); + } + else if (config._strict && !parsedInput) { + getParsingFlags(config).unusedTokens.push(token); + } + } - // returns language data - moment.langData = function (key) { - if (key && key._lang && key._lang._abbr) { - key = key._lang._abbr; + // add remaining unparsed input length to the string + getParsingFlags(config).charsLeftOver = stringLength - totalParsedInputLength; + if (string.length > 0) { + getParsingFlags(config).unusedInput.push(string); } - return getLangDefinition(key); - }; - // compare moment object - moment.isMoment = function (obj) { - return obj instanceof Moment; - }; + // clear _12h flag if hour is <= 12 + if (getParsingFlags(config).bigHour === true && + config._a[HOUR] <= 12 && + config._a[HOUR] > 0) { + getParsingFlags(config).bigHour = undefined; + } - // for typechecking Duration objects - moment.isDuration = function (obj) { - return obj instanceof Duration; - }; + getParsingFlags(config).parsedDateParts = config._a.slice(0); + getParsingFlags(config).meridiem = config._meridiem; + // handle meridiem + config._a[HOUR] = meridiemFixWrap(config._locale, config._a[HOUR], config._meridiem); - for (i = lists.length - 1; i >= 0; --i) { - makeList(lists[i]); + configFromArray(config); + checkOverflow(config); } - moment.normalizeUnits = function (units) { - return normalizeUnits(units); - }; - moment.invalid = function (flags) { - var m = moment.utc(NaN); - if (flags != null) { - extend(m._pf, flags); + function meridiemFixWrap (locale, hour, meridiem) { + var isPm; + + if (meridiem == null) { + // nothing to do + return hour; } - else { - m._pf.userInvalidated = true; + if (locale.meridiemHour != null) { + return locale.meridiemHour(hour, meridiem); + } else if (locale.isPM != null) { + // Fallback + isPm = locale.isPM(meridiem); + if (isPm && hour < 12) { + hour += 12; + } + if (!isPm && hour === 12) { + hour = 0; + } + return hour; + } else { + // this is not supposed to happen + return hour; } + } - return m; - }; - - moment.parseZone = function (input) { - return moment(input).parseZone(); - }; - - /************************************ - Moment Prototype - ************************************/ + // date from string and array of format strings + function configFromStringAndArray(config) { + var tempConfig, + bestMoment, + scoreToBeat, + i, + currentScore; - extend(moment.fn = Moment.prototype, { + if (config._f.length === 0) { + getParsingFlags(config).invalidFormat = true; + config._d = new Date(NaN); + return; + } - clone : function () { - return moment(this); - }, + for (i = 0; i < config._f.length; i++) { + currentScore = 0; + tempConfig = copyConfig({}, config); + if (config._useUTC != null) { + tempConfig._useUTC = config._useUTC; + } + tempConfig._f = config._f[i]; + configFromStringAndFormat(tempConfig); - valueOf : function () { - return +this._d + ((this._offset || 0) * 60000); - }, + if (!valid__isValid(tempConfig)) { + continue; + } - unix : function () { - return Math.floor(+this / 1000); - }, + // if there is any input that was not parsed add a penalty for that format + currentScore += getParsingFlags(tempConfig).charsLeftOver; - toString : function () { - return this.clone().lang('en').format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ"); - }, + //or tokens + currentScore += getParsingFlags(tempConfig).unusedTokens.length * 10; - toDate : function () { - return this._offset ? new Date(+this) : this._d; - }, + getParsingFlags(tempConfig).score = currentScore; - toISOString : function () { - var m = moment(this).utc(); - if (0 < m.year() && m.year() <= 9999) { - return formatMoment(m, 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); - } else { - return formatMoment(m, 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); + if (scoreToBeat == null || currentScore < scoreToBeat) { + scoreToBeat = currentScore; + bestMoment = tempConfig; } - }, - - toArray : function () { - var m = this; - return [ - m.year(), - m.month(), - m.date(), - m.hours(), - m.minutes(), - m.seconds(), - m.milliseconds() - ]; - }, + } - isValid : function () { - return isValid(this); - }, + extend(config, bestMoment || tempConfig); + } - isDSTShifted : function () { + function configFromObject(config) { + if (config._d) { + return; + } - if (this._a) { - return this.isValid() && compareArrays(this._a, (this._isUTC ? moment.utc(this._a) : moment(this._a)).toArray()) > 0; - } + var i = normalizeObjectUnits(config._i); + config._a = map([i.year, i.month, i.day || i.date, i.hour, i.minute, i.second, i.millisecond], function (obj) { + return obj && parseInt(obj, 10); + }); - return false; - }, + configFromArray(config); + } - parsingFlags : function () { - return extend({}, this._pf); - }, + function createFromConfig (config) { + var res = new Moment(checkOverflow(prepareConfig(config))); + if (res._nextDay) { + // Adding is smart enough around DST + res.add(1, 'd'); + res._nextDay = undefined; + } - invalidAt: function () { - return this._pf.overflow; - }, + return res; + } - utc : function () { - return this.zone(0); - }, + function prepareConfig (config) { + var input = config._i, + format = config._f; - local : function () { - this.zone(0); - this._isUTC = false; - return this; - }, + config._locale = config._locale || locale_locales__getLocale(config._l); - format : function (inputString) { - var output = formatMoment(this, inputString || moment.defaultFormat); - return this.lang().postformat(output); - }, + if (input === null || (format === undefined && input === '')) { + return valid__createInvalid({nullInput: true}); + } - add : function (input, val) { - var dur; - // switch args to support add('s', 1) and add(1, 's') - if (typeof input === 'string') { - dur = moment.duration(+val, input); - } else { - dur = moment.duration(input, val); - } - addOrSubtractDurationFromMoment(this, dur, 1); - return this; - }, + if (typeof input === 'string') { + config._i = input = config._locale.preparse(input); + } - subtract : function (input, val) { - var dur; - // switch args to support subtract('s', 1) and subtract(1, 's') - if (typeof input === 'string') { - dur = moment.duration(+val, input); - } else { - dur = moment.duration(input, val); - } - addOrSubtractDurationFromMoment(this, dur, -1); - return this; - }, + if (isMoment(input)) { + return new Moment(checkOverflow(input)); + } else if (isArray(format)) { + configFromStringAndArray(config); + } else if (format) { + configFromStringAndFormat(config); + } else if (isDate(input)) { + config._d = input; + } else { + configFromInput(config); + } - diff : function (input, units, asFloat) { - var that = makeAs(input, this), - zoneDiff = (this.zone() - that.zone()) * 6e4, - diff, output; + if (!valid__isValid(config)) { + config._d = null; + } - units = normalizeUnits(units); + return config; + } - if (units === 'year' || units === 'month') { - // average number of days in the months in the given dates - diff = (this.daysInMonth() + that.daysInMonth()) * 432e5; // 24 * 60 * 60 * 1000 / 2 - // difference in months - output = ((this.year() - that.year()) * 12) + (this.month() - that.month()); - // adjust by taking difference in days, average number of days - // and dst in the given months. - output += ((this - moment(this).startOf('month')) - - (that - moment(that).startOf('month'))) / diff; - // same as above but with zones, to negate all dst - output -= ((this.zone() - moment(this).startOf('month').zone()) - - (that.zone() - moment(that).startOf('month').zone())) * 6e4 / diff; - if (units === 'year') { - output = output / 12; - } - } else { - diff = (this - that); - output = units === 'second' ? diff / 1e3 : // 1000 - units === 'minute' ? diff / 6e4 : // 1000 * 60 - units === 'hour' ? diff / 36e5 : // 1000 * 60 * 60 - units === 'day' ? (diff - zoneDiff) / 864e5 : // 1000 * 60 * 60 * 24, negate dst - units === 'week' ? (diff - zoneDiff) / 6048e5 : // 1000 * 60 * 60 * 24 * 7, negate dst - diff; - } - return asFloat ? output : absRound(output); - }, + function configFromInput(config) { + var input = config._i; + if (input === undefined) { + config._d = new Date(utils_hooks__hooks.now()); + } else if (isDate(input)) { + config._d = new Date(input.valueOf()); + } else if (typeof input === 'string') { + configFromString(config); + } else if (isArray(input)) { + config._a = map(input.slice(0), function (obj) { + return parseInt(obj, 10); + }); + configFromArray(config); + } else if (typeof(input) === 'object') { + configFromObject(config); + } else if (typeof(input) === 'number') { + // from milliseconds + config._d = new Date(input); + } else { + utils_hooks__hooks.createFromInputFallback(config); + } + } - from : function (time, withoutSuffix) { - return moment.duration(this.diff(time)).lang(this.lang()._abbr).humanize(!withoutSuffix); - }, + function createLocalOrUTC (input, format, locale, strict, isUTC) { + var c = {}; - fromNow : function (withoutSuffix) { - return this.from(moment(), withoutSuffix); - }, + if (typeof(locale) === 'boolean') { + strict = locale; + locale = undefined; + } + // object construction must be done this way. + // https://github.com/moment/moment/issues/1423 + c._isAMomentObject = true; + c._useUTC = c._isUTC = isUTC; + c._l = locale; + c._i = input; + c._f = format; + c._strict = strict; - calendar : function () { - // We want to compare the start of today, vs this. - // Getting start-of-today depends on whether we're zone'd or not. - var sod = makeAs(moment(), this).startOf('day'), - diff = this.diff(sod, 'days', true), - format = diff < -6 ? 'sameElse' : - diff < -1 ? 'lastWeek' : - diff < 0 ? 'lastDay' : - diff < 1 ? 'sameDay' : - diff < 2 ? 'nextDay' : - diff < 7 ? 'nextWeek' : 'sameElse'; - return this.format(this.lang().calendar(format, this)); - }, + return createFromConfig(c); + } - isLeapYear : function () { - return isLeapYear(this.year()); - }, + function local__createLocal (input, format, locale, strict) { + return createLocalOrUTC(input, format, locale, strict, false); + } - isDST : function () { - return (this.zone() < this.clone().month(0).zone() || - this.zone() < this.clone().month(5).zone()); - }, + var prototypeMin = deprecate( + 'moment().min is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548', + function () { + var other = local__createLocal.apply(null, arguments); + if (this.isValid() && other.isValid()) { + return other < this ? this : other; + } else { + return valid__createInvalid(); + } + } + ); - day : function (input) { - var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay(); - if (input != null) { - input = parseWeekday(input, this.lang()); - return this.add({ d : input - day }); + var prototypeMax = deprecate( + 'moment().max is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548', + function () { + var other = local__createLocal.apply(null, arguments); + if (this.isValid() && other.isValid()) { + return other > this ? this : other; } else { - return day; + return valid__createInvalid(); } - }, - - month : function (input) { - var utc = this._isUTC ? 'UTC' : '', - dayOfMonth; - - if (input != null) { - if (typeof input === 'string') { - input = this.lang().monthsParse(input); - if (typeof input !== 'number') { - return this; - } - } - - dayOfMonth = this.date(); - this.date(1); - this._d['set' + utc + 'Month'](input); - this.date(Math.min(dayOfMonth, this.daysInMonth())); + } + ); - moment.updateOffset(this); - return this; - } else { - return this._d['get' + utc + 'Month'](); + // Pick a moment m from moments so that m[fn](other) is true for all + // other. This relies on the function fn to be transitive. + // + // moments should either be an array of moment objects or an array, whose + // first element is an array of moment objects. + function pickBy(fn, moments) { + var res, i; + if (moments.length === 1 && isArray(moments[0])) { + moments = moments[0]; + } + if (!moments.length) { + return local__createLocal(); + } + res = moments[0]; + for (i = 1; i < moments.length; ++i) { + if (!moments[i].isValid() || moments[i][fn](res)) { + res = moments[i]; } - }, + } + return res; + } - startOf: function (units) { - units = normalizeUnits(units); - // the following switch intentionally omits break keywords - // to utilize falling through the cases. - switch (units) { - case 'year': - this.month(0); - /* falls through */ - case 'month': - this.date(1); - /* falls through */ - case 'week': - case 'isoWeek': - case 'day': - this.hours(0); - /* falls through */ - case 'hour': - this.minutes(0); - /* falls through */ - case 'minute': - this.seconds(0); - /* falls through */ - case 'second': - this.milliseconds(0); - /* falls through */ - } - - // weeks are a special case - if (units === 'week') { - this.weekday(0); - } else if (units === 'isoWeek') { - this.isoWeekday(1); - } + // TODO: Use [].sort instead? + function min () { + var args = [].slice.call(arguments, 0); - return this; - }, + return pickBy('isBefore', args); + } - endOf: function (units) { - units = normalizeUnits(units); - return this.startOf(units).add((units === 'isoWeek' ? 'week' : units), 1).subtract('ms', 1); - }, + function max () { + var args = [].slice.call(arguments, 0); - isAfter: function (input, units) { - units = typeof units !== 'undefined' ? units : 'millisecond'; - return +this.clone().startOf(units) > +moment(input).startOf(units); - }, + return pickBy('isAfter', args); + } - isBefore: function (input, units) { - units = typeof units !== 'undefined' ? units : 'millisecond'; - return +this.clone().startOf(units) < +moment(input).startOf(units); - }, + var now = function () { + return Date.now ? Date.now() : +(new Date()); + }; - isSame: function (input, units) { - units = units || 'ms'; - return +this.clone().startOf(units) === +makeAs(input, this).startOf(units); - }, + function Duration (duration) { + var normalizedInput = normalizeObjectUnits(duration), + years = normalizedInput.year || 0, + quarters = normalizedInput.quarter || 0, + months = normalizedInput.month || 0, + weeks = normalizedInput.week || 0, + days = normalizedInput.day || 0, + hours = normalizedInput.hour || 0, + minutes = normalizedInput.minute || 0, + seconds = normalizedInput.second || 0, + milliseconds = normalizedInput.millisecond || 0; - min: function (other) { - other = moment.apply(null, arguments); - return other < this ? this : other; - }, + // representation for dateAddRemove + this._milliseconds = +milliseconds + + seconds * 1e3 + // 1000 + minutes * 6e4 + // 1000 * 60 + hours * 1000 * 60 * 60; //using 1000 * 60 * 60 instead of 36e5 to avoid floating point rounding errors https://github.com/moment/moment/issues/2978 + // Because of dateAddRemove treats 24 hours as different from a + // day when working around DST, we need to store them separately + this._days = +days + + weeks * 7; + // It is impossible translate months into days without knowing + // which months you are are talking about, so we have to store + // it separately. + this._months = +months + + quarters * 3 + + years * 12; - max: function (other) { - other = moment.apply(null, arguments); - return other > this ? this : other; - }, + this._data = {}; - zone : function (input) { - var offset = this._offset || 0; - if (input != null) { - if (typeof input === "string") { - input = timezoneMinutesFromString(input); - } - if (Math.abs(input) < 16) { - input = input * 60; - } - this._offset = input; - this._isUTC = true; - if (offset !== input) { - addOrSubtractDurationFromMoment(this, moment.duration(offset - input, 'm'), 1, true); - } - } else { - return this._isUTC ? offset : this._d.getTimezoneOffset(); - } - return this; - }, + this._locale = locale_locales__getLocale(); - zoneAbbr : function () { - return this._isUTC ? "UTC" : ""; - }, + this._bubble(); + } - zoneName : function () { - return this._isUTC ? "Coordinated Universal Time" : ""; - }, + function isDuration (obj) { + return obj instanceof Duration; + } - parseZone : function () { - if (this._tzm) { - this.zone(this._tzm); - } else if (typeof this._i === 'string') { - this.zone(this._i); - } - return this; - }, + // FORMATTING - hasAlignedHourOffset : function (input) { - if (!input) { - input = 0; - } - else { - input = moment(input).zone(); + function offset (token, separator) { + addFormatToken(token, 0, 0, function () { + var offset = this.utcOffset(); + var sign = '+'; + if (offset < 0) { + offset = -offset; + sign = '-'; } + return sign + zeroFill(~~(offset / 60), 2) + separator + zeroFill(~~(offset) % 60, 2); + }); + } - return (this.zone() - input) % 60 === 0; - }, + offset('Z', ':'); + offset('ZZ', ''); - daysInMonth : function () { - return daysInMonth(this.year(), this.month()); - }, + // PARSING - dayOfYear : function (input) { - var dayOfYear = round((moment(this).startOf('day') - moment(this).startOf('year')) / 864e5) + 1; - return input == null ? dayOfYear : this.add("d", (input - dayOfYear)); - }, + addRegexToken('Z', matchShortOffset); + addRegexToken('ZZ', matchShortOffset); + addParseToken(['Z', 'ZZ'], function (input, array, config) { + config._useUTC = true; + config._tzm = offsetFromString(matchShortOffset, input); + }); - quarter : function () { - return Math.ceil((this.month() + 1.0) / 3.0); - }, + // HELPERS - weekYear : function (input) { - var year = weekOfYear(this, this.lang()._week.dow, this.lang()._week.doy).year; - return input == null ? year : this.add("y", (input - year)); - }, + // timezone chunker + // '+10:00' > ['10', '00'] + // '-1530' > ['-15', '30'] + var chunkOffset = /([\+\-]|\d\d)/gi; - isoWeekYear : function (input) { - var year = weekOfYear(this, 1, 4).year; - return input == null ? year : this.add("y", (input - year)); - }, + function offsetFromString(matcher, string) { + var matches = ((string || '').match(matcher) || []); + var chunk = matches[matches.length - 1] || []; + var parts = (chunk + '').match(chunkOffset) || ['-', 0, 0]; + var minutes = +(parts[1] * 60) + toInt(parts[2]); - week : function (input) { - var week = this.lang().week(this); - return input == null ? week : this.add("d", (input - week) * 7); - }, + return parts[0] === '+' ? minutes : -minutes; + } - isoWeek : function (input) { - var week = weekOfYear(this, 1, 4).week; - return input == null ? week : this.add("d", (input - week) * 7); - }, + // Return a moment from input, that is local/utc/zone equivalent to model. + function cloneWithOffset(input, model) { + var res, diff; + if (model._isUTC) { + res = model.clone(); + diff = (isMoment(input) || isDate(input) ? input.valueOf() : local__createLocal(input).valueOf()) - res.valueOf(); + // Use low-level api, because this fn is low-level api. + res._d.setTime(res._d.valueOf() + diff); + utils_hooks__hooks.updateOffset(res, false); + return res; + } else { + return local__createLocal(input).local(); + } + } - weekday : function (input) { - var weekday = (this.day() + 7 - this.lang()._week.dow) % 7; - return input == null ? weekday : this.add("d", input - weekday); - }, + function getDateOffset (m) { + // On Firefox.24 Date#getTimezoneOffset returns a floating point. + // https://github.com/moment/moment/pull/1871 + return -Math.round(m._d.getTimezoneOffset() / 15) * 15; + } - isoWeekday : function (input) { - // behaves the same as moment#day except - // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6) - // as a setter, sunday should belong to the previous week. - return input == null ? this.day() || 7 : this.day(this.day() % 7 ? input : input - 7); - }, + // HOOKS - get : function (units) { - units = normalizeUnits(units); - return this[units](); - }, + // This function will be called whenever a moment is mutated. + // It is intended to keep the offset in sync with the timezone. + utils_hooks__hooks.updateOffset = function () {}; - set : function (units, value) { - units = normalizeUnits(units); - if (typeof this[units] === 'function') { - this[units](value); - } - return this; - }, + // MOMENTS - // If passed a language key, it will set the language for this - // instance. Otherwise, it will return the language configuration - // variables for this instance. - lang : function (key) { - if (key === undefined) { - return this._lang; - } else { - this._lang = getLangDefinition(key); - return this; + // keepLocalTime = true means only change the timezone, without + // affecting the local hour. So 5:31:26 +0300 --[utcOffset(2, true)]--> + // 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset + // +0200, so we adjust the time as needed, to be valid. + // + // Keeping the time actually adds/subtracts (one hour) + // from the actual represented time. That is why we call updateOffset + // a second time. In case it wants us to change the offset again + // _changeInProgress == true case, then we have to adjust, because + // there is no such time in the given timezone. + function getSetOffset (input, keepLocalTime) { + var offset = this._offset || 0, + localAdjust; + if (!this.isValid()) { + return input != null ? this : NaN; + } + if (input != null) { + if (typeof input === 'string') { + input = offsetFromString(matchShortOffset, input); + } else if (Math.abs(input) < 16) { + input = input * 60; + } + if (!this._isUTC && keepLocalTime) { + localAdjust = getDateOffset(this); + } + this._offset = input; + this._isUTC = true; + if (localAdjust != null) { + this.add(localAdjust, 'm'); + } + if (offset !== input) { + if (!keepLocalTime || this._changeInProgress) { + add_subtract__addSubtract(this, create__createDuration(input - offset, 'm'), 1, false); + } else if (!this._changeInProgress) { + this._changeInProgress = true; + utils_hooks__hooks.updateOffset(this, true); + this._changeInProgress = null; + } } + return this; + } else { + return this._isUTC ? offset : getDateOffset(this); } - }); + } - // helper for adding shortcuts - function makeGetterAndSetter(name, key) { - moment.fn[name] = moment.fn[name + 's'] = function (input) { - var utc = this._isUTC ? 'UTC' : ''; - if (input != null) { - this._d['set' + utc + key](input); - moment.updateOffset(this); - return this; - } else { - return this._d['get' + utc + key](); + function getSetZone (input, keepLocalTime) { + if (input != null) { + if (typeof input !== 'string') { + input = -input; } - }; + + this.utcOffset(input, keepLocalTime); + + return this; + } else { + return -this.utcOffset(); + } } - // loop through and add shortcuts (Month, Date, Hours, Minutes, Seconds, Milliseconds) - for (i = 0; i < proxyGettersAndSetters.length; i ++) { - makeGetterAndSetter(proxyGettersAndSetters[i].toLowerCase().replace(/s$/, ''), proxyGettersAndSetters[i]); + function setOffsetToUTC (keepLocalTime) { + return this.utcOffset(0, keepLocalTime); } - // add shortcut for year (uses different syntax than the getter/setter 'year' == 'FullYear') - makeGetterAndSetter('year', 'FullYear'); + function setOffsetToLocal (keepLocalTime) { + if (this._isUTC) { + this.utcOffset(0, keepLocalTime); + this._isUTC = false; - // add plural methods - moment.fn.days = moment.fn.day; - moment.fn.months = moment.fn.month; - moment.fn.weeks = moment.fn.week; - moment.fn.isoWeeks = moment.fn.isoWeek; + if (keepLocalTime) { + this.subtract(getDateOffset(this), 'm'); + } + } + return this; + } - // add aliased format methods - moment.fn.toJSON = moment.fn.toISOString; + function setOffsetToParsedOffset () { + if (this._tzm) { + this.utcOffset(this._tzm); + } else if (typeof this._i === 'string') { + this.utcOffset(offsetFromString(matchOffset, this._i)); + } + return this; + } - /************************************ - Duration Prototype - ************************************/ + function hasAlignedHourOffset (input) { + if (!this.isValid()) { + return false; + } + input = input ? local__createLocal(input).utcOffset() : 0; + return (this.utcOffset() - input) % 60 === 0; + } - extend(moment.duration.fn = Duration.prototype, { + function isDaylightSavingTime () { + return ( + this.utcOffset() > this.clone().month(0).utcOffset() || + this.utcOffset() > this.clone().month(5).utcOffset() + ); + } - _bubble : function () { - var milliseconds = this._milliseconds, - days = this._days, - months = this._months, - data = this._data, - seconds, minutes, hours, years; + function isDaylightSavingTimeShifted () { + if (!isUndefined(this._isDSTShifted)) { + return this._isDSTShifted; + } - // The following code bubbles up values, see the tests for - // examples of what that means. - data.milliseconds = milliseconds % 1000; + var c = {}; - seconds = absRound(milliseconds / 1000); - data.seconds = seconds % 60; + copyConfig(c, this); + c = prepareConfig(c); - minutes = absRound(seconds / 60); - data.minutes = minutes % 60; + if (c._a) { + var other = c._isUTC ? create_utc__createUTC(c._a) : local__createLocal(c._a); + this._isDSTShifted = this.isValid() && + compareArrays(c._a, other.toArray()) > 0; + } else { + this._isDSTShifted = false; + } - hours = absRound(minutes / 60); - data.hours = hours % 24; + return this._isDSTShifted; + } - days += absRound(hours / 24); - data.days = days % 30; + function isLocal () { + return this.isValid() ? !this._isUTC : false; + } - months += absRound(days / 30); - data.months = months % 12; + function isUtcOffset () { + return this.isValid() ? this._isUTC : false; + } - years = absRound(months / 12); - data.years = years; - }, + function isUtc () { + return this.isValid() ? this._isUTC && this._offset === 0 : false; + } - weeks : function () { - return absRound(this.days() / 7); - }, + // ASP.NET json date format regex + var aspNetRegex = /^(\-)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?\d*)?$/; - valueOf : function () { - return this._milliseconds + - this._days * 864e5 + - (this._months % 12) * 2592e6 + - toInt(this._months / 12) * 31536e6; - }, + // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html + // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere + // and further modified to allow for strings containing both week and day + var isoRegex = /^(-)?P(?:(-?[0-9,.]*)Y)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)W)?(?:(-?[0-9,.]*)D)?(?:T(?:(-?[0-9,.]*)H)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)S)?)?$/; - humanize : function (withSuffix) { - var difference = +this, - output = relativeTime(difference, !withSuffix, this.lang()); + function create__createDuration (input, key) { + var duration = input, + // matching against regexp is expensive, do it on demand + match = null, + sign, + ret, + diffRes; - if (withSuffix) { - output = this.lang().pastFuture(difference, output); + if (isDuration(input)) { + duration = { + ms : input._milliseconds, + d : input._days, + M : input._months + }; + } else if (typeof input === 'number') { + duration = {}; + if (key) { + duration[key] = input; + } else { + duration.milliseconds = input; } + } else if (!!(match = aspNetRegex.exec(input))) { + sign = (match[1] === '-') ? -1 : 1; + duration = { + y : 0, + d : toInt(match[DATE]) * sign, + h : toInt(match[HOUR]) * sign, + m : toInt(match[MINUTE]) * sign, + s : toInt(match[SECOND]) * sign, + ms : toInt(match[MILLISECOND]) * sign + }; + } else if (!!(match = isoRegex.exec(input))) { + sign = (match[1] === '-') ? -1 : 1; + duration = { + y : parseIso(match[2], sign), + M : parseIso(match[3], sign), + w : parseIso(match[4], sign), + d : parseIso(match[5], sign), + h : parseIso(match[6], sign), + m : parseIso(match[7], sign), + s : parseIso(match[8], sign) + }; + } else if (duration == null) {// checks for null or undefined + duration = {}; + } else if (typeof duration === 'object' && ('from' in duration || 'to' in duration)) { + diffRes = momentsDifference(local__createLocal(duration.from), local__createLocal(duration.to)); - return this.lang().postformat(output); - }, - - add : function (input, val) { - // supports only 2.0-style add(1, 's') or add(moment) - var dur = moment.duration(input, val); + duration = {}; + duration.ms = diffRes.milliseconds; + duration.M = diffRes.months; + } - this._milliseconds += dur._milliseconds; - this._days += dur._days; - this._months += dur._months; + ret = new Duration(duration); - this._bubble(); + if (isDuration(input) && hasOwnProp(input, '_locale')) { + ret._locale = input._locale; + } - return this; - }, + return ret; + } - subtract : function (input, val) { - var dur = moment.duration(input, val); + create__createDuration.fn = Duration.prototype; - this._milliseconds -= dur._milliseconds; - this._days -= dur._days; - this._months -= dur._months; + function parseIso (inp, sign) { + // We'd normally use ~~inp for this, but unfortunately it also + // converts floats to ints. + // inp may be undefined, so careful calling replace on it. + var res = inp && parseFloat(inp.replace(',', '.')); + // apply sign while we're at it + return (isNaN(res) ? 0 : res) * sign; + } - this._bubble(); + function positiveMomentsDifference(base, other) { + var res = {milliseconds: 0, months: 0}; - return this; - }, + res.months = other.month() - base.month() + + (other.year() - base.year()) * 12; + if (base.clone().add(res.months, 'M').isAfter(other)) { + --res.months; + } - get : function (units) { - units = normalizeUnits(units); - return this[units.toLowerCase() + 's'](); - }, + res.milliseconds = +other - +(base.clone().add(res.months, 'M')); - as : function (units) { - units = normalizeUnits(units); - return this['as' + units.charAt(0).toUpperCase() + units.slice(1) + 's'](); - }, + return res; + } - lang : moment.fn.lang, - - toIsoString : function () { - // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js - var years = Math.abs(this.years()), - months = Math.abs(this.months()), - days = Math.abs(this.days()), - hours = Math.abs(this.hours()), - minutes = Math.abs(this.minutes()), - seconds = Math.abs(this.seconds() + this.milliseconds() / 1000); - - if (!this.asSeconds()) { - // this is the same as C#'s (Noda) and python (isodate)... - // but not other JS (goog.date) - return 'P0D'; - } - - return (this.asSeconds() < 0 ? '-' : '') + - 'P' + - (years ? years + 'Y' : '') + - (months ? months + 'M' : '') + - (days ? days + 'D' : '') + - ((hours || minutes || seconds) ? 'T' : '') + - (hours ? hours + 'H' : '') + - (minutes ? minutes + 'M' : '') + - (seconds ? seconds + 'S' : ''); + function momentsDifference(base, other) { + var res; + if (!(base.isValid() && other.isValid())) { + return {milliseconds: 0, months: 0}; } - }); - function makeDurationGetter(name) { - moment.duration.fn[name] = function () { - return this._data[name]; - }; - } + other = cloneWithOffset(other, base); + if (base.isBefore(other)) { + res = positiveMomentsDifference(base, other); + } else { + res = positiveMomentsDifference(other, base); + res.milliseconds = -res.milliseconds; + res.months = -res.months; + } - function makeDurationAsGetter(name, factor) { - moment.duration.fn['as' + name] = function () { - return +this / factor; - }; + return res; } - for (i in unitMillisecondFactors) { - if (unitMillisecondFactors.hasOwnProperty(i)) { - makeDurationAsGetter(i, unitMillisecondFactors[i]); - makeDurationGetter(i.toLowerCase()); + function absRound (number) { + if (number < 0) { + return Math.round(-1 * number) * -1; + } else { + return Math.round(number); } } - makeDurationAsGetter('Weeks', 6048e5); - moment.duration.fn.asMonths = function () { - return (+this - this.years() * 31536e6) / 2592e6 + this.years() * 12; - }; + // TODO: remove 'name' arg after deprecation is removed + function createAdder(direction, name) { + return function (val, period) { + var dur, tmp; + //invert the arguments, but complain about it + if (period !== null && !isNaN(+period)) { + deprecateSimple(name, 'moment().' + name + '(period, number) is deprecated. Please use moment().' + name + '(number, period).'); + tmp = val; val = period; period = tmp; + } + + val = typeof val === 'string' ? +val : val; + dur = create__createDuration(val, period); + add_subtract__addSubtract(this, dur, direction); + return this; + }; + } + function add_subtract__addSubtract (mom, duration, isAdding, updateOffset) { + var milliseconds = duration._milliseconds, + days = absRound(duration._days), + months = absRound(duration._months); - /************************************ - Default Lang - ************************************/ + if (!mom.isValid()) { + // No op + return; + } + updateOffset = updateOffset == null ? true : updateOffset; - // Set default language, other languages will inherit from English. - moment.lang('en', { - ordinal : function (number) { - var b = number % 10, - output = (toInt(number % 100 / 10) === 1) ? 'th' : - (b === 1) ? 'st' : - (b === 2) ? 'nd' : - (b === 3) ? 'rd' : 'th'; - return number + output; + if (milliseconds) { + mom._d.setTime(mom._d.valueOf() + milliseconds * isAdding); } - }); + if (days) { + get_set__set(mom, 'Date', get_set__get(mom, 'Date') + days * isAdding); + } + if (months) { + setMonth(mom, get_set__get(mom, 'Month') + months * isAdding); + } + if (updateOffset) { + utils_hooks__hooks.updateOffset(mom, days || months); + } + } - /* EMBED_LANGUAGES */ + var add_subtract__add = createAdder(1, 'add'); + var add_subtract__subtract = createAdder(-1, 'subtract'); - /************************************ - Exposing Moment - ************************************/ + function moment_calendar__calendar (time, formats) { + // We want to compare the start of today, vs this. + // Getting start-of-today depends on whether we're local/utc/offset or not. + var now = time || local__createLocal(), + sod = cloneWithOffset(now, this).startOf('day'), + diff = this.diff(sod, 'days', true), + format = diff < -6 ? 'sameElse' : + diff < -1 ? 'lastWeek' : + diff < 0 ? 'lastDay' : + diff < 1 ? 'sameDay' : + diff < 2 ? 'nextDay' : + diff < 7 ? 'nextWeek' : 'sameElse'; - function makeGlobal(deprecate) { - var warned = false, local_moment = moment; - /*global ender:false */ - if (typeof ender !== 'undefined') { - return; + var output = formats && (isFunction(formats[format]) ? formats[format]() : formats[format]); + + return this.format(output || this.localeData().calendar(format, this, local__createLocal(now))); + } + + function clone () { + return new Moment(this); + } + + function isAfter (input, units) { + var localInput = isMoment(input) ? input : local__createLocal(input); + if (!(this.isValid() && localInput.isValid())) { + return false; } - // here, `this` means `window` in the browser, or `global` on the server - // add `moment` as a global object via a string identifier, - // for Closure Compiler "advanced" mode - if (deprecate) { - global.moment = function () { - if (!warned && console && console.warn) { - warned = true; - console.warn( - "Accessing Moment through the global scope is " + - "deprecated, and will be removed in an upcoming " + - "release."); - } - return local_moment.apply(null, arguments); - }; - extend(global.moment, local_moment); + units = normalizeUnits(!isUndefined(units) ? units : 'millisecond'); + if (units === 'millisecond') { + return this.valueOf() > localInput.valueOf(); } else { - global['moment'] = moment; + return localInput.valueOf() < this.clone().startOf(units).valueOf(); } } - // CommonJS module is defined - if (hasModule) { - module.exports = moment; - makeGlobal(true); - } else if (typeof define === "function" && define.amd) { - define("moment", function (require, exports, module) { - if (module.config && module.config() && module.config().noGlobal !== true) { - // If user provided noGlobal, he is aware of global - makeGlobal(module.config().noGlobal === undefined); - } - - return moment; - }); - } else { - makeGlobal(); + function isBefore (input, units) { + var localInput = isMoment(input) ? input : local__createLocal(input); + if (!(this.isValid() && localInput.isValid())) { + return false; + } + units = normalizeUnits(!isUndefined(units) ? units : 'millisecond'); + if (units === 'millisecond') { + return this.valueOf() < localInput.valueOf(); + } else { + return this.clone().endOf(units).valueOf() < localInput.valueOf(); + } } -}).call(this); -;/** - * Parse a text request to a json query object tree - * - * @param {String} string The string to parse - * @return {Object} The json query tree - */ -function parseStringToObject(string) { -; -/* - Default template driver for JS/CC generated parsers running as - browser-based JavaScript/ECMAScript applications. - - WARNING: This parser template will not run as console and has lesser - features for debugging than the console derivates for the - various JavaScript platforms. - - Features: - - Parser trace messages - - Integrated panic-mode error recovery - - Written 2007, 2008 by Jan Max Meyer, J.M.K S.F. Software Technologies - - This is in the public domain. -*/ -var NODEJS__dbg_withtrace = false; -var NODEJS__dbg_string = new String(); + function isBetween (from, to, units, inclusivity) { + inclusivity = inclusivity || '()'; + return (inclusivity[0] === '(' ? this.isAfter(from, units) : !this.isBefore(from, units)) && + (inclusivity[1] === ')' ? this.isBefore(to, units) : !this.isAfter(to, units)); + } -function __NODEJS_dbg_print( text ) -{ - NODEJS__dbg_string += text + "\n"; -} + function isSame (input, units) { + var localInput = isMoment(input) ? input : local__createLocal(input), + inputMs; + if (!(this.isValid() && localInput.isValid())) { + return false; + } + units = normalizeUnits(units || 'millisecond'); + if (units === 'millisecond') { + return this.valueOf() === localInput.valueOf(); + } else { + inputMs = localInput.valueOf(); + return this.clone().startOf(units).valueOf() <= inputMs && inputMs <= this.clone().endOf(units).valueOf(); + } + } -function __NODEJS_lex( info ) -{ - var state = 0; - var match = -1; - var match_pos = 0; - var start = 0; - var pos = info.offset + 1; - - do - { - pos--; - state = 0; - match = -2; - start = pos; - - if( info.src.length <= start ) - return 19; - - do - { - -switch( state ) -{ - case 0: - if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 8 ) || ( info.src.charCodeAt( pos ) >= 10 && info.src.charCodeAt( pos ) <= 31 ) || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || info.src.charCodeAt( pos ) == 59 || ( info.src.charCodeAt( pos ) >= 63 && info.src.charCodeAt( pos ) <= 64 ) || ( info.src.charCodeAt( pos ) >= 66 && info.src.charCodeAt( pos ) <= 77 ) || ( info.src.charCodeAt( pos ) >= 80 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1; - else if( info.src.charCodeAt( pos ) == 9 ) state = 2; - else if( info.src.charCodeAt( pos ) == 40 ) state = 3; - else if( info.src.charCodeAt( pos ) == 41 ) state = 4; - else if( info.src.charCodeAt( pos ) == 60 || info.src.charCodeAt( pos ) == 62 ) state = 5; - else if( info.src.charCodeAt( pos ) == 33 ) state = 11; - else if( info.src.charCodeAt( pos ) == 79 ) state = 12; - else if( info.src.charCodeAt( pos ) == 32 ) state = 13; - else if( info.src.charCodeAt( pos ) == 61 ) state = 14; - else if( info.src.charCodeAt( pos ) == 34 ) state = 15; - else if( info.src.charCodeAt( pos ) == 65 ) state = 19; - else if( info.src.charCodeAt( pos ) == 78 ) state = 20; - else state = -1; - break; - - case 1: - if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1; - else if( info.src.charCodeAt( pos ) == 58 ) state = 6; - else state = -1; - match = 10; - match_pos = pos; - break; - - case 2: - if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1; - else if( info.src.charCodeAt( pos ) == 58 ) state = 6; - else state = -1; - match = 1; - match_pos = pos; - break; - - case 3: - state = -1; - match = 3; - match_pos = pos; - break; - - case 4: - state = -1; - match = 4; - match_pos = pos; - break; - - case 5: - if( info.src.charCodeAt( pos ) == 61 ) state = 14; - else state = -1; - match = 11; - match_pos = pos; - break; - - case 6: - state = -1; - match = 8; - match_pos = pos; - break; - - case 7: - state = -1; - match = 9; - match_pos = pos; - break; - - case 8: - if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1; - else if( info.src.charCodeAt( pos ) == 58 ) state = 6; - else state = -1; - match = 6; - match_pos = pos; - break; - - case 9: - if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1; - else if( info.src.charCodeAt( pos ) == 58 ) state = 6; - else state = -1; - match = 5; - match_pos = pos; - break; - - case 10: - if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1; - else if( info.src.charCodeAt( pos ) == 58 ) state = 6; - else state = -1; - match = 7; - match_pos = pos; - break; - - case 11: - if( info.src.charCodeAt( pos ) == 61 ) state = 14; - else state = -1; - break; - - case 12: - if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 81 ) || ( info.src.charCodeAt( pos ) >= 83 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1; - else if( info.src.charCodeAt( pos ) == 58 ) state = 6; - else if( info.src.charCodeAt( pos ) == 82 ) state = 8; - else state = -1; - match = 10; - match_pos = pos; - break; - - case 13: - state = -1; - match = 1; - match_pos = pos; - break; - - case 14: - state = -1; - match = 11; - match_pos = pos; - break; - - case 15: - if( info.src.charCodeAt( pos ) == 34 ) state = 7; - else if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 33 ) || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 91 ) || ( info.src.charCodeAt( pos ) >= 93 && info.src.charCodeAt( pos ) <= 254 ) || info.src.charCodeAt( pos ) > 255 ) state = 15; - else if( info.src.charCodeAt( pos ) == 92 ) state = 17; - else state = -1; - break; - - case 16: - if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 67 ) || ( info.src.charCodeAt( pos ) >= 69 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1; - else if( info.src.charCodeAt( pos ) == 58 ) state = 6; - else if( info.src.charCodeAt( pos ) == 68 ) state = 9; - else state = -1; - match = 10; - match_pos = pos; - break; - - case 17: - if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 254 ) ) state = 15; - else state = -1; - break; - - case 18: - if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 83 ) || ( info.src.charCodeAt( pos ) >= 85 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1; - else if( info.src.charCodeAt( pos ) == 58 ) state = 6; - else if( info.src.charCodeAt( pos ) == 84 ) state = 10; - else state = -1; - match = 10; - match_pos = pos; - break; - - case 19: - if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 77 ) || ( info.src.charCodeAt( pos ) >= 79 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1; - else if( info.src.charCodeAt( pos ) == 58 ) state = 6; - else if( info.src.charCodeAt( pos ) == 78 ) state = 16; - else state = -1; - match = 10; - match_pos = pos; - break; - - case 20: - if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 78 ) || ( info.src.charCodeAt( pos ) >= 80 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1; - else if( info.src.charCodeAt( pos ) == 58 ) state = 6; - else if( info.src.charCodeAt( pos ) == 79 ) state = 18; - else state = -1; - match = 10; - match_pos = pos; - break; + function isSameOrAfter (input, units) { + return this.isSame(input, units) || this.isAfter(input,units); + } -} + function isSameOrBefore (input, units) { + return this.isSame(input, units) || this.isBefore(input,units); + } + function diff (input, units, asFloat) { + var that, + zoneDelta, + delta, output; - pos++; + if (!this.isValid()) { + return NaN; + } - } - while( state > -1 ); + that = cloneWithOffset(input, this); - } - while( 1 > -1 && match == 1 ); + if (!that.isValid()) { + return NaN; + } - if( match > -1 ) - { - info.att = info.src.substr( start, match_pos - start ); - info.offset = match_pos; - + zoneDelta = (that.utcOffset() - this.utcOffset()) * 6e4; - } - else - { - info.att = new String(); - match = -1; - } + units = normalizeUnits(units); - return match; -} + if (units === 'year' || units === 'month' || units === 'quarter') { + output = monthDiff(this, that); + if (units === 'quarter') { + output = output / 3; + } else if (units === 'year') { + output = output / 12; + } + } else { + delta = this - that; + output = units === 'second' ? delta / 1e3 : // 1000 + units === 'minute' ? delta / 6e4 : // 1000 * 60 + units === 'hour' ? delta / 36e5 : // 1000 * 60 * 60 + units === 'day' ? (delta - zoneDelta) / 864e5 : // 1000 * 60 * 60 * 24, negate dst + units === 'week' ? (delta - zoneDelta) / 6048e5 : // 1000 * 60 * 60 * 24 * 7, negate dst + delta; + } + return asFloat ? output : absFloor(output); + } + + function monthDiff (a, b) { + // difference in months + var wholeMonthDiff = ((b.year() - a.year()) * 12) + (b.month() - a.month()), + // b is in (anchor - 1 month, anchor + 1 month) + anchor = a.clone().add(wholeMonthDiff, 'months'), + anchor2, adjust; + + if (b - anchor < 0) { + anchor2 = a.clone().add(wholeMonthDiff - 1, 'months'); + // linear across the month + adjust = (b - anchor) / (anchor - anchor2); + } else { + anchor2 = a.clone().add(wholeMonthDiff + 1, 'months'); + // linear across the month + adjust = (b - anchor) / (anchor2 - anchor); + } + //check for negative zero, return zero if negative zero + return -(wholeMonthDiff + adjust) || 0; + } -function __NODEJS_parse( src, err_off, err_la ) -{ - var sstack = new Array(); - var vstack = new Array(); - var err_cnt = 0; - var act; - var go; - var la; - var rval; - var parseinfo = new Function( "", "var offset; var src; var att;" ); - var info = new parseinfo(); - -/* Pop-Table */ -var pop_tab = new Array( - new Array( 0/* begin' */, 1 ), - new Array( 13/* begin */, 1 ), - new Array( 12/* search_text */, 1 ), - new Array( 12/* search_text */, 2 ), - new Array( 12/* search_text */, 3 ), - new Array( 14/* and_expression */, 1 ), - new Array( 14/* and_expression */, 3 ), - new Array( 15/* boolean_expression */, 2 ), - new Array( 15/* boolean_expression */, 1 ), - new Array( 16/* expression */, 3 ), - new Array( 16/* expression */, 2 ), - new Array( 16/* expression */, 1 ), - new Array( 17/* value */, 2 ), - new Array( 17/* value */, 1 ), - new Array( 18/* string */, 1 ), - new Array( 18/* string */, 1 ) -); - -/* Action-Table */ -var act_tab = new Array( - /* State 0 */ new Array( 7/* "NOT" */,5 , 3/* "LEFT_PARENTHESE" */,7 , 8/* "COLUMN" */,8 , 11/* "OPERATOR" */,10 , 10/* "WORD" */,12 , 9/* "STRING" */,13 ), - /* State 1 */ new Array( 19/* "$" */,0 ), - /* State 2 */ new Array( 19/* "$" */,-1 ), - /* State 3 */ new Array( 6/* "OR" */,14 , 7/* "NOT" */,5 , 3/* "LEFT_PARENTHESE" */,7 , 8/* "COLUMN" */,8 , 11/* "OPERATOR" */,10 , 10/* "WORD" */,12 , 9/* "STRING" */,13 , 19/* "$" */,-2 , 4/* "RIGHT_PARENTHESE" */,-2 ), - /* State 4 */ new Array( 5/* "AND" */,16 , 19/* "$" */,-5 , 7/* "NOT" */,-5 , 3/* "LEFT_PARENTHESE" */,-5 , 8/* "COLUMN" */,-5 , 11/* "OPERATOR" */,-5 , 10/* "WORD" */,-5 , 9/* "STRING" */,-5 , 6/* "OR" */,-5 , 4/* "RIGHT_PARENTHESE" */,-5 ), - /* State 5 */ new Array( 3/* "LEFT_PARENTHESE" */,7 , 8/* "COLUMN" */,8 , 11/* "OPERATOR" */,10 , 10/* "WORD" */,12 , 9/* "STRING" */,13 ), - /* State 6 */ new Array( 19/* "$" */,-8 , 7/* "NOT" */,-8 , 3/* "LEFT_PARENTHESE" */,-8 , 8/* "COLUMN" */,-8 , 11/* "OPERATOR" */,-8 , 10/* "WORD" */,-8 , 9/* "STRING" */,-8 , 6/* "OR" */,-8 , 5/* "AND" */,-8 , 4/* "RIGHT_PARENTHESE" */,-8 ), - /* State 7 */ new Array( 7/* "NOT" */,5 , 3/* "LEFT_PARENTHESE" */,7 , 8/* "COLUMN" */,8 , 11/* "OPERATOR" */,10 , 10/* "WORD" */,12 , 9/* "STRING" */,13 ), - /* State 8 */ new Array( 3/* "LEFT_PARENTHESE" */,7 , 8/* "COLUMN" */,8 , 11/* "OPERATOR" */,10 , 10/* "WORD" */,12 , 9/* "STRING" */,13 ), - /* State 9 */ new Array( 19/* "$" */,-11 , 7/* "NOT" */,-11 , 3/* "LEFT_PARENTHESE" */,-11 , 8/* "COLUMN" */,-11 , 11/* "OPERATOR" */,-11 , 10/* "WORD" */,-11 , 9/* "STRING" */,-11 , 6/* "OR" */,-11 , 5/* "AND" */,-11 , 4/* "RIGHT_PARENTHESE" */,-11 ), - /* State 10 */ new Array( 10/* "WORD" */,12 , 9/* "STRING" */,13 ), - /* State 11 */ new Array( 19/* "$" */,-13 , 7/* "NOT" */,-13 , 3/* "LEFT_PARENTHESE" */,-13 , 8/* "COLUMN" */,-13 , 11/* "OPERATOR" */,-13 , 10/* "WORD" */,-13 , 9/* "STRING" */,-13 , 6/* "OR" */,-13 , 5/* "AND" */,-13 , 4/* "RIGHT_PARENTHESE" */,-13 ), - /* State 12 */ new Array( 19/* "$" */,-14 , 7/* "NOT" */,-14 , 3/* "LEFT_PARENTHESE" */,-14 , 8/* "COLUMN" */,-14 , 11/* "OPERATOR" */,-14 , 10/* "WORD" */,-14 , 9/* "STRING" */,-14 , 6/* "OR" */,-14 , 5/* "AND" */,-14 , 4/* "RIGHT_PARENTHESE" */,-14 ), - /* State 13 */ new Array( 19/* "$" */,-15 , 7/* "NOT" */,-15 , 3/* "LEFT_PARENTHESE" */,-15 , 8/* "COLUMN" */,-15 , 11/* "OPERATOR" */,-15 , 10/* "WORD" */,-15 , 9/* "STRING" */,-15 , 6/* "OR" */,-15 , 5/* "AND" */,-15 , 4/* "RIGHT_PARENTHESE" */,-15 ), - /* State 14 */ new Array( 7/* "NOT" */,5 , 3/* "LEFT_PARENTHESE" */,7 , 8/* "COLUMN" */,8 , 11/* "OPERATOR" */,10 , 10/* "WORD" */,12 , 9/* "STRING" */,13 ), - /* State 15 */ new Array( 19/* "$" */,-3 , 4/* "RIGHT_PARENTHESE" */,-3 ), - /* State 16 */ new Array( 7/* "NOT" */,5 , 3/* "LEFT_PARENTHESE" */,7 , 8/* "COLUMN" */,8 , 11/* "OPERATOR" */,10 , 10/* "WORD" */,12 , 9/* "STRING" */,13 ), - /* State 17 */ new Array( 19/* "$" */,-7 , 7/* "NOT" */,-7 , 3/* "LEFT_PARENTHESE" */,-7 , 8/* "COLUMN" */,-7 , 11/* "OPERATOR" */,-7 , 10/* "WORD" */,-7 , 9/* "STRING" */,-7 , 6/* "OR" */,-7 , 5/* "AND" */,-7 , 4/* "RIGHT_PARENTHESE" */,-7 ), - /* State 18 */ new Array( 4/* "RIGHT_PARENTHESE" */,23 ), - /* State 19 */ new Array( 19/* "$" */,-10 , 7/* "NOT" */,-10 , 3/* "LEFT_PARENTHESE" */,-10 , 8/* "COLUMN" */,-10 , 11/* "OPERATOR" */,-10 , 10/* "WORD" */,-10 , 9/* "STRING" */,-10 , 6/* "OR" */,-10 , 5/* "AND" */,-10 , 4/* "RIGHT_PARENTHESE" */,-10 ), - /* State 20 */ new Array( 19/* "$" */,-12 , 7/* "NOT" */,-12 , 3/* "LEFT_PARENTHESE" */,-12 , 8/* "COLUMN" */,-12 , 11/* "OPERATOR" */,-12 , 10/* "WORD" */,-12 , 9/* "STRING" */,-12 , 6/* "OR" */,-12 , 5/* "AND" */,-12 , 4/* "RIGHT_PARENTHESE" */,-12 ), - /* State 21 */ new Array( 19/* "$" */,-4 , 4/* "RIGHT_PARENTHESE" */,-4 ), - /* State 22 */ new Array( 19/* "$" */,-6 , 7/* "NOT" */,-6 , 3/* "LEFT_PARENTHESE" */,-6 , 8/* "COLUMN" */,-6 , 11/* "OPERATOR" */,-6 , 10/* "WORD" */,-6 , 9/* "STRING" */,-6 , 6/* "OR" */,-6 , 4/* "RIGHT_PARENTHESE" */,-6 ), - /* State 23 */ new Array( 19/* "$" */,-9 , 7/* "NOT" */,-9 , 3/* "LEFT_PARENTHESE" */,-9 , 8/* "COLUMN" */,-9 , 11/* "OPERATOR" */,-9 , 10/* "WORD" */,-9 , 9/* "STRING" */,-9 , 6/* "OR" */,-9 , 5/* "AND" */,-9 , 4/* "RIGHT_PARENTHESE" */,-9 ) -); - -/* Goto-Table */ -var goto_tab = new Array( - /* State 0 */ new Array( 13/* begin */,1 , 12/* search_text */,2 , 14/* and_expression */,3 , 15/* boolean_expression */,4 , 16/* expression */,6 , 17/* value */,9 , 18/* string */,11 ), - /* State 1 */ new Array( ), - /* State 2 */ new Array( ), - /* State 3 */ new Array( 12/* search_text */,15 , 14/* and_expression */,3 , 15/* boolean_expression */,4 , 16/* expression */,6 , 17/* value */,9 , 18/* string */,11 ), - /* State 4 */ new Array( ), - /* State 5 */ new Array( 16/* expression */,17 , 17/* value */,9 , 18/* string */,11 ), - /* State 6 */ new Array( ), - /* State 7 */ new Array( 12/* search_text */,18 , 14/* and_expression */,3 , 15/* boolean_expression */,4 , 16/* expression */,6 , 17/* value */,9 , 18/* string */,11 ), - /* State 8 */ new Array( 16/* expression */,19 , 17/* value */,9 , 18/* string */,11 ), - /* State 9 */ new Array( ), - /* State 10 */ new Array( 18/* string */,20 ), - /* State 11 */ new Array( ), - /* State 12 */ new Array( ), - /* State 13 */ new Array( ), - /* State 14 */ new Array( 12/* search_text */,21 , 14/* and_expression */,3 , 15/* boolean_expression */,4 , 16/* expression */,6 , 17/* value */,9 , 18/* string */,11 ), - /* State 15 */ new Array( ), - /* State 16 */ new Array( 14/* and_expression */,22 , 15/* boolean_expression */,4 , 16/* expression */,6 , 17/* value */,9 , 18/* string */,11 ), - /* State 17 */ new Array( ), - /* State 18 */ new Array( ), - /* State 19 */ new Array( ), - /* State 20 */ new Array( ), - /* State 21 */ new Array( ), - /* State 22 */ new Array( ), - /* State 23 */ new Array( ) -); - - - -/* Symbol labels */ -var labels = new Array( - "begin'" /* Non-terminal symbol */, - "WHITESPACE" /* Terminal symbol */, - "WHITESPACE" /* Terminal symbol */, - "LEFT_PARENTHESE" /* Terminal symbol */, - "RIGHT_PARENTHESE" /* Terminal symbol */, - "AND" /* Terminal symbol */, - "OR" /* Terminal symbol */, - "NOT" /* Terminal symbol */, - "COLUMN" /* Terminal symbol */, - "STRING" /* Terminal symbol */, - "WORD" /* Terminal symbol */, - "OPERATOR" /* Terminal symbol */, - "search_text" /* Non-terminal symbol */, - "begin" /* Non-terminal symbol */, - "and_expression" /* Non-terminal symbol */, - "boolean_expression" /* Non-terminal symbol */, - "expression" /* Non-terminal symbol */, - "value" /* Non-terminal symbol */, - "string" /* Non-terminal symbol */, - "$" /* Terminal symbol */ -); - - - - info.offset = 0; - info.src = src; - info.att = new String(); - - if( !err_off ) - err_off = new Array(); - if( !err_la ) - err_la = new Array(); - - sstack.push( 0 ); - vstack.push( 0 ); - - la = __NODEJS_lex( info ); - - while( true ) - { - act = 25; - for( var i = 0; i < act_tab[sstack[sstack.length-1]].length; i+=2 ) - { - if( act_tab[sstack[sstack.length-1]][i] == la ) - { - act = act_tab[sstack[sstack.length-1]][i+1]; - break; - } - } - - if( NODEJS__dbg_withtrace && sstack.length > 0 ) - { - __NODEJS_dbg_print( "\nState " + sstack[sstack.length-1] + "\n" + - "\tLookahead: " + labels[la] + " (\"" + info.att + "\")\n" + - "\tAction: " + act + "\n" + - "\tSource: \"" + info.src.substr( info.offset, 30 ) + ( ( info.offset + 30 < info.src.length ) ? - "..." : "" ) + "\"\n" + - "\tStack: " + sstack.join() + "\n" + - "\tValue stack: " + vstack.join() + "\n" ); - } - - - //Panic-mode: Try recovery when parse-error occurs! - if( act == 25 ) - { - if( NODEJS__dbg_withtrace ) - __NODEJS_dbg_print( "Error detected: There is no reduce or shift on the symbol " + labels[la] ); - - err_cnt++; - err_off.push( info.offset - info.att.length ); - err_la.push( new Array() ); - for( var i = 0; i < act_tab[sstack[sstack.length-1]].length; i+=2 ) - err_la[err_la.length-1].push( labels[act_tab[sstack[sstack.length-1]][i]] ); - - //Remember the original stack! - var rsstack = new Array(); - var rvstack = new Array(); - for( var i = 0; i < sstack.length; i++ ) - { - rsstack[i] = sstack[i]; - rvstack[i] = vstack[i]; - } - - while( act == 25 && la != 19 ) - { - if( NODEJS__dbg_withtrace ) - __NODEJS_dbg_print( "\tError recovery\n" + - "Current lookahead: " + labels[la] + " (" + info.att + ")\n" + - "Action: " + act + "\n\n" ); - if( la == -1 ) - info.offset++; - - while( act == 25 && sstack.length > 0 ) - { - sstack.pop(); - vstack.pop(); - - if( sstack.length == 0 ) - break; - - act = 25; - for( var i = 0; i < act_tab[sstack[sstack.length-1]].length; i+=2 ) - { - if( act_tab[sstack[sstack.length-1]][i] == la ) - { - act = act_tab[sstack[sstack.length-1]][i+1]; - break; - } - } - } - - if( act != 25 ) - break; - - for( var i = 0; i < rsstack.length; i++ ) - { - sstack.push( rsstack[i] ); - vstack.push( rvstack[i] ); - } - - la = __NODEJS_lex( info ); - } - - if( act == 25 ) - { - if( NODEJS__dbg_withtrace ) - __NODEJS_dbg_print( "\tError recovery failed, terminating parse process..." ); - break; - } - - - if( NODEJS__dbg_withtrace ) - __NODEJS_dbg_print( "\tError recovery succeeded, continuing" ); - } - - /* - if( act == 25 ) - break; - */ - - - //Shift - if( act > 0 ) - { - if( NODEJS__dbg_withtrace ) - __NODEJS_dbg_print( "Shifting symbol: " + labels[la] + " (" + info.att + ")" ); - - sstack.push( act ); - vstack.push( info.att ); - - la = __NODEJS_lex( info ); - - if( NODEJS__dbg_withtrace ) - __NODEJS_dbg_print( "\tNew lookahead symbol: " + labels[la] + " (" + info.att + ")" ); - } - //Reduce - else - { - act *= -1; - - if( NODEJS__dbg_withtrace ) - __NODEJS_dbg_print( "Reducing by producution: " + act ); - - rval = void(0); - - if( NODEJS__dbg_withtrace ) - __NODEJS_dbg_print( "\tPerforming semantic action..." ); - -switch( act ) -{ - case 0: - { - rval = vstack[ vstack.length - 1 ]; - } - break; - case 1: - { - result = vstack[ vstack.length - 1 ]; - } - break; - case 2: - { - rval = vstack[ vstack.length - 1 ]; - } - break; - case 3: - { - rval = mkComplexQuery('OR',[vstack[ vstack.length - 2 ],vstack[ vstack.length - 1 ]]); - } - break; - case 4: - { - rval = mkComplexQuery('OR',[vstack[ vstack.length - 3 ],vstack[ vstack.length - 1 ]]); - } - break; - case 5: - { - rval = vstack[ vstack.length - 1 ] ; - } - break; - case 6: - { - rval = mkComplexQuery('AND',[vstack[ vstack.length - 3 ],vstack[ vstack.length - 1 ]]); - } - break; - case 7: - { - rval = mkNotQuery(vstack[ vstack.length - 1 ]); - } - break; - case 8: - { - rval = vstack[ vstack.length - 1 ]; - } - break; - case 9: - { - rval = vstack[ vstack.length - 2 ]; - } - break; - case 10: - { - simpleQuerySetKey(vstack[ vstack.length - 1 ],vstack[ vstack.length - 2 ].split(':').slice(0,-1).join(':')); rval = vstack[ vstack.length - 1 ]; - } - break; - case 11: - { - rval = vstack[ vstack.length - 1 ]; - } - break; - case 12: - { - vstack[ vstack.length - 1 ].operator = vstack[ vstack.length - 2 ] ; rval = vstack[ vstack.length - 1 ]; - } - break; - case 13: - { - rval = vstack[ vstack.length - 1 ]; - } - break; - case 14: - { - rval = mkSimpleQuery('',vstack[ vstack.length - 1 ]); - } - break; - case 15: - { - rval = mkSimpleQuery('',vstack[ vstack.length - 1 ].split('"').slice(1,-1).join('"')); - } - break; -} + utils_hooks__hooks.defaultFormat = 'YYYY-MM-DDTHH:mm:ssZ'; + utils_hooks__hooks.defaultFormatUtc = 'YYYY-MM-DDTHH:mm:ss[Z]'; + function toString () { + return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ'); + } + function moment_format__toISOString () { + var m = this.clone().utc(); + if (0 < m.year() && m.year() <= 9999) { + if (isFunction(Date.prototype.toISOString)) { + // native implementation is ~50x faster, use it when we can + return this.toDate().toISOString(); + } else { + return formatMoment(m, 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); + } + } else { + return formatMoment(m, 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); + } + } - if( NODEJS__dbg_withtrace ) - __NODEJS_dbg_print( "\tPopping " + pop_tab[act][1] + " off the stack..." ); - - for( var i = 0; i < pop_tab[act][1]; i++ ) - { - sstack.pop(); - vstack.pop(); - } - - go = -1; - for( var i = 0; i < goto_tab[sstack[sstack.length-1]].length; i+=2 ) - { - if( goto_tab[sstack[sstack.length-1]][i] == pop_tab[act][0] ) - { - go = goto_tab[sstack[sstack.length-1]][i+1]; - break; - } - } - - if( act == 0 ) - break; - - if( NODEJS__dbg_withtrace ) - __NODEJS_dbg_print( "\tPushing non-terminal " + labels[ pop_tab[act][0] ] ); - - sstack.push( go ); - vstack.push( rval ); - } - - if( NODEJS__dbg_withtrace ) - { - alert( NODEJS__dbg_string ); - NODEJS__dbg_string = new String(); - } - } - - if( NODEJS__dbg_withtrace ) - { - __NODEJS_dbg_print( "\nParse complete." ); - alert( NODEJS__dbg_string ); - } - - return err_cnt; -} + function format (inputString) { + if (!inputString) { + inputString = this.isUtc() ? utils_hooks__hooks.defaultFormatUtc : utils_hooks__hooks.defaultFormat; + } + var output = formatMoment(this, inputString); + return this.localeData().postformat(output); + } + function from (time, withoutSuffix) { + if (this.isValid() && + ((isMoment(time) && time.isValid()) || + local__createLocal(time).isValid())) { + return create__createDuration({to: this, from: time}).locale(this.locale()).humanize(!withoutSuffix); + } else { + return this.localeData().invalidDate(); + } + } + function fromNow (withoutSuffix) { + return this.from(local__createLocal(), withoutSuffix); + } -var arrayExtend = function () { - var j, i, newlist = [], list_list = arguments; - for (j = 0; j < list_list.length; j += 1) { - for (i = 0; i < list_list[j].length; i += 1) { - newlist.push(list_list[j][i]); + function to (time, withoutSuffix) { + if (this.isValid() && + ((isMoment(time) && time.isValid()) || + local__createLocal(time).isValid())) { + return create__createDuration({from: this, to: time}).locale(this.locale()).humanize(!withoutSuffix); + } else { + return this.localeData().invalidDate(); + } } - } - return newlist; -}, mkSimpleQuery = function (key, value, operator) { - var object = {"type": "simple", "key": key, "value": value}; - if (operator !== undefined) { - object.operator = operator; - } - return object; + function toNow (withoutSuffix) { + return this.to(local__createLocal(), withoutSuffix); + } -}, mkNotQuery = function (query) { - if (query.operator === "NOT") { - return query.query_list[0]; - } - return {"type": "complex", "operator": "NOT", "query_list": [query]}; + // If passed a locale key, it will set the locale for this + // instance. Otherwise, it will return the locale configuration + // variables for this instance. + function locale (key) { + var newLocaleData; -}, mkComplexQuery = function (operator, query_list) { - var i, query_list2 = []; - for (i = 0; i < query_list.length; i += 1) { - if (query_list[i].operator === operator) { - query_list2 = arrayExtend(query_list2, query_list[i].query_list); - } else { - query_list2.push(query_list[i]); + if (key === undefined) { + return this._locale._abbr; + } else { + newLocaleData = locale_locales__getLocale(key); + if (newLocaleData != null) { + this._locale = newLocaleData; + } + return this; + } } - } - return {type:"complex",operator:operator,query_list:query_list2}; -}, simpleQuerySetKey = function (query, key) { - var i; - if (query.type === "complex") { - for (i = 0; i < query.query_list.length; ++i) { - simpleQuerySetKey (query.query_list[i],key); + var lang = deprecate( + 'moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.', + function (key) { + if (key === undefined) { + return this.localeData(); + } else { + return this.locale(key); + } + } + ); + + function localeData () { + return this._locale; } - return true; - } - if (query.type === "simple" && !query.key) { - query.key = key; - return true; - } - return false; -}, - error_offsets = [], - error_lookaheads = [], - error_count = 0, - result; -if ((error_count = __NODEJS_parse(string, error_offsets, error_lookaheads)) > 0) { - var i; - for (i = 0; i < error_count; i += 1) { - throw new Error("Parse error near \"" + - string.substr(error_offsets[i]) + - "\", expecting \"" + - error_lookaheads[i].join() + "\""); - } -} + function startOf (units) { + units = normalizeUnits(units); + // the following switch intentionally omits break keywords + // to utilize falling through the cases. + switch (units) { + case 'year': + this.month(0); + /* falls through */ + case 'quarter': + case 'month': + this.date(1); + /* falls through */ + case 'week': + case 'isoWeek': + case 'day': + case 'date': + this.hours(0); + /* falls through */ + case 'hour': + this.minutes(0); + /* falls through */ + case 'minute': + this.seconds(0); + /* falls through */ + case 'second': + this.milliseconds(0); + } -; return result; -} // parseStringToObject + // weeks are a special case + if (units === 'week') { + this.weekday(0); + } + if (units === 'isoWeek') { + this.isoWeekday(1); + } -;/*global RSVP, window, parseStringToObject*/ -/*jslint nomen: true, maxlen: 90*/ -(function (RSVP, window, parseStringToObject) { - "use strict"; + // quarters are also special + if (units === 'quarter') { + this.month(Math.floor(this.month() / 3) * 3); + } - var query_class_dict = {}, - regexp_escape = /[\-\[\]{}()*+?.,\\\^$|#\s]/g, - regexp_percent = /%/g, - regexp_underscore = /_/g, - regexp_operator = /^(?:AND|OR|NOT)$/i, - regexp_comparaison = /^(?:!?=|<=?|>=?)$/i; + return this; + } - /** - * Convert metadata values to array of strings. ex: - * - * "a" -> ["a"], - * {"content": "a"} -> ["a"] - * - * @param {Any} value The metadata value - * @return {Array} The value in string array format - */ - function metadataValueToStringArray(value) { - var i, new_value = []; - if (value === undefined) { - return undefined; + function endOf (units) { + units = normalizeUnits(units); + if (units === undefined || units === 'millisecond') { + return this; + } + + // 'date' is an alias for 'day', so it should be considered as such. + if (units === 'date') { + units = 'day'; + } + + return this.startOf(units).add(1, (units === 'isoWeek' ? 'week' : units)).subtract(1, 'ms'); } - if (!Array.isArray(value)) { - value = [value]; + + function to_type__valueOf () { + return this._d.valueOf() - ((this._offset || 0) * 60000); } - for (i = 0; i < value.length; i += 1) { - if (typeof value[i] === 'object') { - new_value[i] = value[i].content; - } else { - new_value[i] = value[i]; - } + + function unix () { + return Math.floor(this.valueOf() / 1000); } - return new_value; - } - /** - * A sort function to sort items by key - * - * @param {String} key The key to sort on - * @param {String} [way="ascending"] 'ascending' or 'descending' - * @return {Function} The sort function - */ - function sortFunction(key, way) { - var result; - if (way === 'descending') { - result = 1; - } else if (way === 'ascending') { - result = -1; - } else { - throw new TypeError("Query.sortFunction(): " + - "Argument 2 must be 'ascending' or 'descending'"); + function toDate () { + return this._offset ? new Date(this.valueOf()) : this._d; } - return function (a, b) { - // this comparison is 5 times faster than json comparison - var i, l; - a = metadataValueToStringArray(a[key]) || []; - b = metadataValueToStringArray(b[key]) || []; - l = a.length > b.length ? a.length : b.length; - for (i = 0; i < l; i += 1) { - if (a[i] === undefined) { - return result; - } - if (b[i] === undefined) { - return -result; - } - if (a[i] > b[i]) { - return -result; - } - if (a[i] < b[i]) { - return result; - } - } - return 0; - }; - } - /** - * Sort a list of items, according to keys and directions. - * - * @param {Array} sort_on_option List of couples [key, direction] - * @param {Array} list The item list to sort - * @return {Array} The filtered list - */ - function sortOn(sort_on_option, list) { - var sort_index; - if (!Array.isArray(sort_on_option)) { - throw new TypeError("jioquery.sortOn(): " + - "Argument 1 is not of type 'array'"); + function toArray () { + var m = this; + return [m.year(), m.month(), m.date(), m.hour(), m.minute(), m.second(), m.millisecond()]; } - for (sort_index = sort_on_option.length - 1; sort_index >= 0; - sort_index -= 1) { - list.sort(sortFunction( - sort_on_option[sort_index][0], - sort_on_option[sort_index][1] - )); + + function toObject () { + var m = this; + return { + years: m.year(), + months: m.month(), + date: m.date(), + hours: m.hours(), + minutes: m.minutes(), + seconds: m.seconds(), + milliseconds: m.milliseconds() + }; } - return list; - } - /** - * Limit a list of items, according to index and length. - * - * @param {Array} limit_option A couple [from, length] - * @param {Array} list The item list to limit - * @return {Array} The filtered list - */ - function limit(limit_option, list) { - if (!Array.isArray(limit_option)) { - throw new TypeError("jioquery.limit(): " + - "Argument 1 is not of type 'array'"); + function toJSON () { + // new Date(NaN).toJSON() === null + return this.isValid() ? this.toISOString() : null; } - if (!Array.isArray(list)) { - throw new TypeError("jioquery.limit(): " + - "Argument 2 is not of type 'array'"); + + function moment_valid__isValid () { + return valid__isValid(this); } - list.splice(0, limit_option[0]); - if (limit_option[1]) { - list.splice(limit_option[1]); + + function parsingFlags () { + return extend({}, getParsingFlags(this)); } - return list; - } - /** - * Filter a list of items, modifying them to select only wanted keys. - * - * @param {Array} select_option Key list to keep - * @param {Array} list The item list to filter - * @return {Array} The filtered list - */ - function select(select_option, list) { - var i, j, new_item; - if (!Array.isArray(select_option)) { - throw new TypeError("jioquery.select(): " + - "Argument 1 is not of type Array"); + function invalidAt () { + return getParsingFlags(this).overflow; } - if (!Array.isArray(list)) { - throw new TypeError("jioquery.select(): " + - "Argument 2 is not of type Array"); + + function creationData() { + return { + input: this._i, + format: this._f, + locale: this._locale, + isUTC: this._isUTC, + strict: this._strict + }; } - for (i = 0; i < list.length; i += 1) { - new_item = {}; - for (j = 0; j < select_option.length; j += 1) { - if (list[i].hasOwnProperty([select_option[j]])) { - new_item[select_option[j]] = list[i][select_option[j]]; - } - } - for (j in new_item) { - if (new_item.hasOwnProperty(j)) { - list[i] = new_item; - break; - } - } + + // FORMATTING + + addFormatToken(0, ['gg', 2], 0, function () { + return this.weekYear() % 100; + }); + + addFormatToken(0, ['GG', 2], 0, function () { + return this.isoWeekYear() % 100; + }); + + function addWeekYearFormatToken (token, getter) { + addFormatToken(0, [token, token.length], 0, getter); } - return list; - } - /** - * The query to use to filter a list of objects. - * This is an abstract class. - * - * @class Query - * @constructor - */ - function Query() { + addWeekYearFormatToken('gggg', 'weekYear'); + addWeekYearFormatToken('ggggg', 'weekYear'); + addWeekYearFormatToken('GGGG', 'isoWeekYear'); + addWeekYearFormatToken('GGGGG', 'isoWeekYear'); - /** - * Called before parsing the query. Must be overridden! - * - * @method onParseStart - * @param {Object} object The object shared in the parse process - * @param {Object} option Some option gave in parse() - */ - // this.onParseStart = emptyFunction; + // ALIASES - /** - * Called when parsing a simple query. Must be overridden! - * - * @method onParseSimpleQuery - * @param {Object} object The object shared in the parse process - * @param {Object} option Some option gave in parse() - */ - // this.onParseSimpleQuery = emptyFunction; + addUnitAlias('weekYear', 'gg'); + addUnitAlias('isoWeekYear', 'GG'); - /** - * Called when parsing a complex query. Must be overridden! - * - * @method onParseComplexQuery - * @param {Object} object The object shared in the parse process - * @param {Object} option Some option gave in parse() - */ - // this.onParseComplexQuery = emptyFunction; + // PARSING - /** - * Called after parsing the query. Must be overridden! - * - * @method onParseEnd - * @param {Object} object The object shared in the parse process - * @param {Object} option Some option gave in parse() - */ - // this.onParseEnd = emptyFunction; + addRegexToken('G', matchSigned); + addRegexToken('g', matchSigned); + addRegexToken('GG', match1to2, match2); + addRegexToken('gg', match1to2, match2); + addRegexToken('GGGG', match1to4, match4); + addRegexToken('gggg', match1to4, match4); + addRegexToken('GGGGG', match1to6, match6); + addRegexToken('ggggg', match1to6, match6); - return; - } + addWeekParseToken(['gggg', 'ggggg', 'GGGG', 'GGGGG'], function (input, week, config, token) { + week[token.substr(0, 2)] = toInt(input); + }); - /** - * Filter the item list with matching item only - * - * @method exec - * @param {Array} item_list The list of object - * @param {Object} [option] Some operation option - * @param {Array} [option.select_list] A object keys to retrieve - * @param {Array} [option.sort_on] Couples of object keys and "ascending" - * or "descending" - * @param {Array} [option.limit] Couple of integer, first is an index and - * second is the length. - */ - Query.prototype.exec = function (item_list, option) { - if (!Array.isArray(item_list)) { - throw new TypeError("Query().exec(): Argument 1 is not of type 'array'"); + addWeekParseToken(['gg', 'GG'], function (input, week, config, token) { + week[token] = utils_hooks__hooks.parseTwoDigitYear(input); + }); + + // MOMENTS + + function getSetWeekYear (input) { + return getSetWeekYearHelper.call(this, + input, + this.week(), + this.weekday(), + this.localeData()._week.dow, + this.localeData()._week.doy); } - if (option === undefined) { - option = {}; + + function getSetISOWeekYear (input) { + return getSetWeekYearHelper.call(this, + input, this.isoWeek(), this.isoWeekday(), 1, 4); } - if (typeof option !== 'object') { - throw new TypeError("Query().exec(): " + - "Optional argument 2 is not of type 'object'"); + + function getISOWeeksInYear () { + return weeksInYear(this.year(), 1, 4); } - var context = this, - i; - for (i = item_list.length - 1; i >= 0; i -= 1) { - if (!context.match(item_list[i])) { - item_list.splice(i, 1); - } + + function getWeeksInYear () { + var weekInfo = this.localeData()._week; + return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy); } - if (option.sort_on) { - sortOn(option.sort_on, item_list); + function getSetWeekYearHelper(input, week, weekday, dow, doy) { + var weeksTarget; + if (input == null) { + return weekOfYear(this, dow, doy).year; + } else { + weeksTarget = weeksInYear(input, dow, doy); + if (week > weeksTarget) { + week = weeksTarget; + } + return setWeekAll.call(this, input, week, weekday, dow, doy); + } } - if (option.limit) { - limit(option.limit, item_list); + function setWeekAll(weekYear, week, weekday, dow, doy) { + var dayOfYearData = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy), + date = createUTCDate(dayOfYearData.year, 0, dayOfYearData.dayOfYear); + + this.year(date.getUTCFullYear()); + this.month(date.getUTCMonth()); + this.date(date.getUTCDate()); + return this; } - select(option.select_list || [], item_list); + // FORMATTING - return new RSVP.Queue() - .push(function () { - return item_list; - }); - }; + addFormatToken('Q', 0, 'Qo', 'quarter'); - /** - * Test if an item matches this query - * - * @method match - * @param {Object} item The object to test - * @return {Boolean} true if match, false otherwise - */ - Query.prototype.match = function () { - return true; - }; + // ALIASES - /** - * Browse the Query in deep calling parser method in each step. - * - * `onParseStart` is called first, on end `onParseEnd` is called. - * It starts from the simple queries at the bottom of the tree calling the - * parser method `onParseSimpleQuery`, and go up calling the - * `onParseComplexQuery` method. - * - * @method parse - * @param {Object} option Any options you want (except 'parsed') - * @return {Any} The parse result - */ - Query.prototype.parse = function (option) { - var that = this, - object; - /** - * The recursive parser. - * - * @param {Object} object The object shared in the parse process - * @param {Object} options Some options usable in the parseMethods - * @return {Any} The parser result - */ - function recParse(object, option) { - var query = object.parsed, - queue = new RSVP.Queue(), - i; + addUnitAlias('quarter', 'Q'); - function enqueue(j) { - queue - .push(function () { - object.parsed = query.query_list[j]; - return recParse(object, option); - }) - .push(function () { - query.query_list[j] = object.parsed; - }); - } + // PARSING - if (query.type === "complex") { + addRegexToken('Q', match1); + addParseToken('Q', function (input, array) { + array[MONTH] = (toInt(input) - 1) * 3; + }); + // MOMENTS - for (i = 0; i < query.query_list.length; i += 1) { - enqueue(i); - } + function getSetQuarter (input) { + return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3); + } - return queue - .push(function () { - object.parsed = query; - return that.onParseComplexQuery(object, option); - }); + // FORMATTING - } - if (query.type === "simple") { - return that.onParseSimpleQuery(object, option); - } + addFormatToken('w', ['ww', 2], 'wo', 'week'); + addFormatToken('W', ['WW', 2], 'Wo', 'isoWeek'); + + // ALIASES + + addUnitAlias('week', 'w'); + addUnitAlias('isoWeek', 'W'); + + // PARSING + + addRegexToken('w', match1to2); + addRegexToken('ww', match1to2, match2); + addRegexToken('W', match1to2); + addRegexToken('WW', match1to2, match2); + + addWeekParseToken(['w', 'ww', 'W', 'WW'], function (input, week, config, token) { + week[token.substr(0, 1)] = toInt(input); + }); + + // HELPERS + + // LOCALES + + function localeWeek (mom) { + return weekOfYear(mom, this._week.dow, this._week.doy).week; } - object = { - parsed: JSON.parse(JSON.stringify(that.serialized())) + + var defaultLocaleWeek = { + dow : 0, // Sunday is the first day of the week. + doy : 6 // The week that contains Jan 1st is the first week of the year. }; - return new RSVP.Queue() - .push(function () { - return that.onParseStart(object, option); - }) - .push(function () { - return recParse(object, option); - }) - .push(function () { - return that.onParseEnd(object, option); - }) - .push(function () { - return object.parsed; - }); - }; + function localeFirstDayOfWeek () { + return this._week.dow; + } - /** - * Convert this query to a parsable string. - * - * @method toString - * @return {String} The string version of this query - */ - Query.prototype.toString = function () { - return ""; - }; + function localeFirstDayOfYear () { + return this._week.doy; + } - /** - * Convert this query to an jsonable object in order to be remake thanks to - * QueryFactory class. - * - * @method serialized - * @return {Object} The jsonable object - */ - Query.prototype.serialized = function () { - return undefined; - }; + // MOMENTS - /** - * Provides static methods to create Query object - * - * @class QueryFactory - */ - function QueryFactory() { - return; - } + function getSetWeek (input) { + var week = this.localeData().week(this); + return input == null ? week : this.add((input - week) * 7, 'd'); + } - /** - * Escapes regexp special chars from a string. - * - * @param {String} string The string to escape - * @return {String} The escaped string - */ - function stringEscapeRegexpCharacters(string) { - return string.replace(regexp_escape, "\\$&"); - } + function getSetISOWeek (input) { + var week = weekOfYear(this, 1, 4).week; + return input == null ? week : this.add((input - week) * 7, 'd'); + } - /** - * Inherits the prototype methods from one constructor into another. The - * prototype of `constructor` will be set to a new object created from - * `superConstructor`. - * - * @param {Function} constructor The constructor which inherits the super one - * @param {Function} superConstructor The super constructor - */ - function inherits(constructor, superConstructor) { - constructor.super_ = superConstructor; - constructor.prototype = Object.create(superConstructor.prototype, { - "constructor": { - "configurable": true, - "enumerable": false, - "writable": true, - "value": constructor - } - }); - } + // FORMATTING - /** - * Convert a search text to a regexp. - * - * @param {String} string The string to convert - * @param {Boolean} [use_wildcard_character=true] Use wildcard "%" and "_" - * @return {RegExp} The search text regexp - */ - function searchTextToRegExp(string, use_wildcard_characters) { - if (typeof string !== 'string') { - throw new TypeError("jioquery.searchTextToRegExp(): " + - "Argument 1 is not of type 'string'"); - } - if (use_wildcard_characters === false) { - return new RegExp("^" + stringEscapeRegexpCharacters(string) + "$"); - } - return new RegExp("^" + stringEscapeRegexpCharacters(string) - .replace(regexp_percent, '.*') - .replace(regexp_underscore, '.') + "$"); - } + addFormatToken('D', ['DD', 2], 'Do', 'date'); - /** - * The ComplexQuery inherits from Query, and compares one or several metadata - * values. - * - * @class ComplexQuery - * @extends Query - * @param {Object} [spec={}] The specifications - * @param {String} [spec.operator="AND"] The compare method to use - * @param {String} spec.key The metadata key - * @param {String} spec.value The value of the metadata to compare - */ - function ComplexQuery(spec, key_schema) { - Query.call(this); + // ALIASES - /** - * Logical operator to use to compare object values - * - * @attribute operator - * @type String - * @default "AND" - * @optional - */ - this.operator = spec.operator; + addUnitAlias('date', 'D'); - /** - * The sub Query list which are used to query an item. - * - * @attribute query_list - * @type Array - * @default [] - * @optional - */ - this.query_list = spec.query_list || []; - this.query_list = this.query_list.map( - // decorate the map to avoid sending the index as key_schema argument - function (o) { return QueryFactory.create(o, key_schema); } - ); + // PARSING - } - inherits(ComplexQuery, Query); + addRegexToken('D', match1to2); + addRegexToken('DD', match1to2, match2); + addRegexToken('Do', function (isStrict, locale) { + return isStrict ? locale._ordinalParse : locale._ordinalParseLenient; + }); - ComplexQuery.prototype.operator = "AND"; - ComplexQuery.prototype.type = "complex"; + addParseToken(['D', 'DD'], DATE); + addParseToken('Do', function (input, array) { + array[DATE] = toInt(input.match(match1to2)[0], 10); + }); - /** - * #crossLink "Query/match:method" - */ - ComplexQuery.prototype.match = function (item) { - var operator = this.operator; - if (!(regexp_operator.test(operator))) { - operator = "AND"; - } - return this[operator.toUpperCase()](item); - }; + // MOMENTS - /** - * #crossLink "Query/toString:method" - */ - ComplexQuery.prototype.toString = function () { - var str_list = [], this_operator = this.operator; - if (this.operator === "NOT") { - str_list.push("NOT ("); - str_list.push(this.query_list[0].toString()); - str_list.push(")"); - return str_list.join(" "); - } - this.query_list.forEach(function (query) { - str_list.push("("); - str_list.push(query.toString()); - str_list.push(")"); - str_list.push(this_operator); + var getSetDayOfMonth = makeGetSet('Date', true); + + // FORMATTING + + addFormatToken('d', 0, 'do', 'day'); + + addFormatToken('dd', 0, 0, function (format) { + return this.localeData().weekdaysMin(this, format); }); - str_list.length -= 1; - return str_list.join(" "); - }; - /** - * #crossLink "Query/serialized:method" - */ - ComplexQuery.prototype.serialized = function () { - var s = { - "type": "complex", - "operator": this.operator, - "query_list": [] - }; - this.query_list.forEach(function (query) { - s.query_list.push( - typeof query.toJSON === "function" ? query.toJSON() : query - ); + addFormatToken('ddd', 0, 0, function (format) { + return this.localeData().weekdaysShort(this, format); }); - return s; - }; - ComplexQuery.prototype.toJSON = ComplexQuery.prototype.serialized; - /** - * Comparison operator, test if all sub queries match the - * item value - * - * @method AND - * @param {Object} item The item to match - * @return {Boolean} true if all match, false otherwise - */ - ComplexQuery.prototype.AND = function (item) { - var result = true, - i = 0; + addFormatToken('dddd', 0, 0, function (format) { + return this.localeData().weekdays(this, format); + }); - while (result && (i !== this.query_list.length)) { - result = this.query_list[i].match(item); - i += 1; - } - return result; + addFormatToken('e', 0, 0, 'weekday'); + addFormatToken('E', 0, 0, 'isoWeekday'); - }; + // ALIASES - /** - * Comparison operator, test if one of the sub queries matches the - * item value - * - * @method OR - * @param {Object} item The item to match - * @return {Boolean} true if one match, false otherwise - */ - ComplexQuery.prototype.OR = function (item) { - var result = false, - i = 0; + addUnitAlias('day', 'd'); + addUnitAlias('weekday', 'e'); + addUnitAlias('isoWeekday', 'E'); - while ((!result) && (i !== this.query_list.length)) { - result = this.query_list[i].match(item); - i += 1; - } + // PARSING - return result; - }; + addRegexToken('d', match1to2); + addRegexToken('e', match1to2); + addRegexToken('E', match1to2); + addRegexToken('dd', function (isStrict, locale) { + return locale.weekdaysMinRegex(isStrict); + }); + addRegexToken('ddd', function (isStrict, locale) { + return locale.weekdaysShortRegex(isStrict); + }); + addRegexToken('dddd', function (isStrict, locale) { + return locale.weekdaysRegex(isStrict); + }); - /** - * Comparison operator, test if the sub query does not match the - * item value - * - * @method NOT - * @param {Object} item The item to match - * @return {Boolean} true if one match, false otherwise - */ - ComplexQuery.prototype.NOT = function (item) { - return !this.query_list[0].match(item); - }; + addWeekParseToken(['dd', 'ddd', 'dddd'], function (input, week, config, token) { + var weekday = config._locale.weekdaysParse(input, token, config._strict); + // if we didn't get a weekday name, mark the date as invalid + if (weekday != null) { + week.d = weekday; + } else { + getParsingFlags(config).invalidWeekday = input; + } + }); - /** - * Creates Query object from a search text string or a serialized version - * of a Query. - * - * @method create - * @static - * @param {Object,String} object The search text or the serialized version - * of a Query - * @return {Query} A Query object - */ - QueryFactory.create = function (object, key_schema) { - if (object === "") { - return new Query(); - } - if (typeof object === "string") { - object = parseStringToObject(object); + addWeekParseToken(['d', 'e', 'E'], function (input, week, config, token) { + week[token] = toInt(input); + }); + + // HELPERS + + function parseWeekday(input, locale) { + if (typeof input !== 'string') { + return input; + } + + if (!isNaN(input)) { + return parseInt(input, 10); + } + + input = locale.weekdaysParse(input); + if (typeof input === 'number') { + return input; + } + + return null; } - if (typeof (object || {}).type === "string" && - query_class_dict[object.type]) { - return new query_class_dict[object.type](object, key_schema); + + // LOCALES + + var defaultLocaleWeekdays = 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'); + function localeWeekdays (m, format) { + return isArray(this._weekdays) ? this._weekdays[m.day()] : + this._weekdays[this._weekdays.isFormat.test(format) ? 'format' : 'standalone'][m.day()]; } - throw new TypeError("QueryFactory.create(): " + - "Argument 1 is not a search text or a parsable object"); - }; - function objectToSearchText(query) { - var str_list = []; - if (query.type === "complex") { - str_list.push("("); - (query.query_list || []).forEach(function (sub_query) { - str_list.push(objectToSearchText(sub_query)); - str_list.push(query.operator); - }); - str_list.length -= 1; - str_list.push(")"); - return str_list.join(" "); + var defaultLocaleWeekdaysShort = 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'); + function localeWeekdaysShort (m) { + return this._weekdaysShort[m.day()]; } - if (query.type === "simple") { - return (query.key ? query.key + ": " : "") + - (query.operator || "") + ' "' + query.value + '"'; + + var defaultLocaleWeekdaysMin = 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'); + function localeWeekdaysMin (m) { + return this._weekdaysMin[m.day()]; } - throw new TypeError("This object is not a query"); - } - function checkKeySchema(key_schema) { - var prop; + function day_of_week__handleStrictParse(weekdayName, format, strict) { + var i, ii, mom, llc = weekdayName.toLocaleLowerCase(); + if (!this._weekdaysParse) { + this._weekdaysParse = []; + this._shortWeekdaysParse = []; + this._minWeekdaysParse = []; - if (key_schema !== undefined) { - if (typeof key_schema !== 'object') { - throw new TypeError("SimpleQuery().create(): " + - "key_schema is not of type 'object'"); - } - // key_set is mandatory - if (key_schema.key_set === undefined) { - throw new TypeError("SimpleQuery().create(): " + - "key_schema has no 'key_set' property"); - } - for (prop in key_schema) { - if (key_schema.hasOwnProperty(prop)) { - switch (prop) { - case 'key_set': - case 'cast_lookup': - case 'match_lookup': - break; - default: - throw new TypeError("SimpleQuery().create(): " + - "key_schema has unknown property '" + prop + "'"); - } + for (i = 0; i < 7; ++i) { + mom = create_utc__createUTC([2000, 1]).day(i); + this._minWeekdaysParse[i] = this.weekdaysMin(mom, '').toLocaleLowerCase(); + this._shortWeekdaysParse[i] = this.weekdaysShort(mom, '').toLocaleLowerCase(); + this._weekdaysParse[i] = this.weekdays(mom, '').toLocaleLowerCase(); + } } - } - } - } - /** - * The SimpleQuery inherits from Query, and compares one metadata value - * - * @class SimpleQuery - * @extends Query - * @param {Object} [spec={}] The specifications - * @param {String} [spec.operator="="] The compare method to use - * @param {String} spec.key The metadata key - * @param {String} spec.value The value of the metadata to compare - */ - function SimpleQuery(spec, key_schema) { - Query.call(this); + if (strict) { + if (format === 'dddd') { + ii = indexOf.call(this._weekdaysParse, llc); + return ii !== -1 ? ii : null; + } else if (format === 'ddd') { + ii = indexOf.call(this._shortWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf.call(this._minWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } + } else { + if (format === 'dddd') { + ii = indexOf.call(this._weekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._shortWeekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._minWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } else if (format === 'ddd') { + ii = indexOf.call(this._shortWeekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._weekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._minWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf.call(this._minWeekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._weekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._shortWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } + } + } - checkKeySchema(key_schema); + function localeWeekdaysParse (weekdayName, format, strict) { + var i, mom, regex; - this._key_schema = key_schema || {}; + if (this._weekdaysParseExact) { + return day_of_week__handleStrictParse.call(this, weekdayName, format, strict); + } - /** - * Operator to use to compare object values - * - * @attribute operator - * @type String - * @optional - */ - this.operator = spec.operator; + if (!this._weekdaysParse) { + this._weekdaysParse = []; + this._minWeekdaysParse = []; + this._shortWeekdaysParse = []; + this._fullWeekdaysParse = []; + } - /** - * Key of the object which refers to the value to compare - * - * @attribute key - * @type String - */ - this.key = spec.key; + for (i = 0; i < 7; i++) { + // make the regex if we don't have it already - /** - * Value is used to do the comparison with the object value - * - * @attribute value - * @type String - */ - this.value = spec.value; + mom = create_utc__createUTC([2000, 1]).day(i); + if (strict && !this._fullWeekdaysParse[i]) { + this._fullWeekdaysParse[i] = new RegExp('^' + this.weekdays(mom, '').replace('.', '\.?') + '$', 'i'); + this._shortWeekdaysParse[i] = new RegExp('^' + this.weekdaysShort(mom, '').replace('.', '\.?') + '$', 'i'); + this._minWeekdaysParse[i] = new RegExp('^' + this.weekdaysMin(mom, '').replace('.', '\.?') + '$', 'i'); + } + if (!this._weekdaysParse[i]) { + regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, ''); + this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i'); + } + // test the regex + if (strict && format === 'dddd' && this._fullWeekdaysParse[i].test(weekdayName)) { + return i; + } else if (strict && format === 'ddd' && this._shortWeekdaysParse[i].test(weekdayName)) { + return i; + } else if (strict && format === 'dd' && this._minWeekdaysParse[i].test(weekdayName)) { + return i; + } else if (!strict && this._weekdaysParse[i].test(weekdayName)) { + return i; + } + } + } - } - inherits(SimpleQuery, Query); + // MOMENTS - SimpleQuery.prototype.type = "simple"; + function getSetDayOfWeek (input) { + if (!this.isValid()) { + return input != null ? this : NaN; + } + var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay(); + if (input != null) { + input = parseWeekday(input, this.localeData()); + return this.add(input - day, 'd'); + } else { + return day; + } + } - function checkKey(key) { - var prop; + function getSetLocaleDayOfWeek (input) { + if (!this.isValid()) { + return input != null ? this : NaN; + } + var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7; + return input == null ? weekday : this.add(input - weekday, 'd'); + } - if (key.read_from === undefined) { - throw new TypeError("Custom key is missing the read_from property"); + function getSetISODayOfWeek (input) { + if (!this.isValid()) { + return input != null ? this : NaN; + } + // behaves the same as moment#day except + // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6) + // as a setter, sunday should belong to the previous week. + return input == null ? this.day() || 7 : this.day(this.day() % 7 ? input : input - 7); } - for (prop in key) { - if (key.hasOwnProperty(prop)) { - switch (prop) { - case 'read_from': - case 'cast_to': - case 'equal_match': - break; - default: - throw new TypeError("Custom key has unknown property '" + - prop + "'"); + var defaultWeekdaysRegex = matchWord; + function weekdaysRegex (isStrict) { + if (this._weekdaysParseExact) { + if (!hasOwnProp(this, '_weekdaysRegex')) { + computeWeekdaysParse.call(this); + } + if (isStrict) { + return this._weekdaysStrictRegex; + } else { + return this._weekdaysRegex; + } + } else { + return this._weekdaysStrictRegex && isStrict ? + this._weekdaysStrictRegex : this._weekdaysRegex; } - } } - } - /** - * #crossLink "Query/match:method" - */ - SimpleQuery.prototype.match = function (item) { - var object_value = null, - equal_match = null, - cast_to = null, - matchMethod = null, - operator = this.operator, - value = null, - key = this.key; + var defaultWeekdaysShortRegex = matchWord; + function weekdaysShortRegex (isStrict) { + if (this._weekdaysParseExact) { + if (!hasOwnProp(this, '_weekdaysRegex')) { + computeWeekdaysParse.call(this); + } + if (isStrict) { + return this._weekdaysShortStrictRegex; + } else { + return this._weekdaysShortRegex; + } + } else { + return this._weekdaysShortStrictRegex && isStrict ? + this._weekdaysShortStrictRegex : this._weekdaysShortRegex; + } + } - if (!(regexp_comparaison.test(operator))) { - // `operator` is not correct, we have to change it to "like" or "=" - if (regexp_percent.test(this.value)) { - // `value` contains a non escaped `%` - operator = "like"; - } else { - // `value` does not contain non escaped `%` - operator = "="; - } + var defaultWeekdaysMinRegex = matchWord; + function weekdaysMinRegex (isStrict) { + if (this._weekdaysParseExact) { + if (!hasOwnProp(this, '_weekdaysRegex')) { + computeWeekdaysParse.call(this); + } + if (isStrict) { + return this._weekdaysMinStrictRegex; + } else { + return this._weekdaysMinRegex; + } + } else { + return this._weekdaysMinStrictRegex && isStrict ? + this._weekdaysMinStrictRegex : this._weekdaysMinRegex; + } } - matchMethod = this[operator]; - if (this._key_schema.key_set && this._key_schema.key_set[key] !== undefined) { - key = this._key_schema.key_set[key]; + function computeWeekdaysParse () { + function cmpLenRev(a, b) { + return b.length - a.length; + } + + var minPieces = [], shortPieces = [], longPieces = [], mixedPieces = [], + i, mom, minp, shortp, longp; + for (i = 0; i < 7; i++) { + // make the regex if we don't have it already + mom = create_utc__createUTC([2000, 1]).day(i); + minp = this.weekdaysMin(mom, ''); + shortp = this.weekdaysShort(mom, ''); + longp = this.weekdays(mom, ''); + minPieces.push(minp); + shortPieces.push(shortp); + longPieces.push(longp); + mixedPieces.push(minp); + mixedPieces.push(shortp); + mixedPieces.push(longp); + } + // Sorting makes sure if one weekday (or abbr) is a prefix of another it + // will match the longer piece. + minPieces.sort(cmpLenRev); + shortPieces.sort(cmpLenRev); + longPieces.sort(cmpLenRev); + mixedPieces.sort(cmpLenRev); + for (i = 0; i < 7; i++) { + shortPieces[i] = regexEscape(shortPieces[i]); + longPieces[i] = regexEscape(longPieces[i]); + mixedPieces[i] = regexEscape(mixedPieces[i]); + } + + this._weekdaysRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i'); + this._weekdaysShortRegex = this._weekdaysRegex; + this._weekdaysMinRegex = this._weekdaysRegex; + + this._weekdaysStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i'); + this._weekdaysShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i'); + this._weekdaysMinStrictRegex = new RegExp('^(' + minPieces.join('|') + ')', 'i'); } - if (typeof key === 'object') { - checkKey(key); - object_value = item[key.read_from]; + // FORMATTING - equal_match = key.equal_match; + addFormatToken('DDD', ['DDDD', 3], 'DDDo', 'dayOfYear'); - // equal_match can be a string - if (typeof equal_match === 'string') { - // XXX raise error if equal_match not in match_lookup - equal_match = this._key_schema.match_lookup[equal_match]; - } + // ALIASES - // equal_match overrides the default '=' operator - if (equal_match !== undefined) { - matchMethod = (operator === "=" || operator === "like" ? - equal_match : matchMethod); - } + addUnitAlias('dayOfYear', 'DDD'); - value = this.value; - cast_to = key.cast_to; - if (cast_to) { - // cast_to can be a string - if (typeof cast_to === 'string') { - // XXX raise error if cast_to not in cast_lookup - cast_to = this._key_schema.cast_lookup[cast_to]; - } + // PARSING - try { - value = cast_to(value); - } catch (e) { - value = undefined; - } + addRegexToken('DDD', match1to3); + addRegexToken('DDDD', match3); + addParseToken(['DDD', 'DDDD'], function (input, array, config) { + config._dayOfYear = toInt(input); + }); - try { - object_value = cast_to(object_value); - } catch (e) { - object_value = undefined; - } - } - } else { - object_value = item[key]; - value = this.value; - } - if (object_value === undefined || value === undefined) { - return false; + // HELPERS + + // MOMENTS + + function getSetDayOfYear (input) { + var dayOfYear = Math.round((this.clone().startOf('day') - this.clone().startOf('year')) / 864e5) + 1; + return input == null ? dayOfYear : this.add((input - dayOfYear), 'd'); } - return matchMethod(object_value, value); - }; - /** - * #crossLink "Query/toString:method" - */ - SimpleQuery.prototype.toString = function () { - return (this.key ? this.key + ":" : "") + - (this.operator ? " " + this.operator : "") + ' "' + this.value + '"'; - }; + // FORMATTING - /** - * #crossLink "Query/serialized:method" - */ - SimpleQuery.prototype.serialized = function () { - var object = { - "type": "simple", - "key": this.key, - "value": this.value - }; - if (this.operator !== undefined) { - object.operator = this.operator; + function hFormat() { + return this.hours() % 12 || 12; } - return object; - }; - SimpleQuery.prototype.toJSON = SimpleQuery.prototype.serialized; - /** - * Comparison operator, test if this query value matches the item value - * - * @method = - * @param {String} object_value The value to compare - * @param {String} comparison_value The comparison value - * @return {Boolean} true if match, false otherwise - */ - SimpleQuery.prototype["="] = function (object_value, comparison_value) { - var value, i; - if (!Array.isArray(object_value)) { - object_value = [object_value]; + function kFormat() { + return this.hours() || 24; } - for (i = 0; i < object_value.length; i += 1) { - value = object_value[i]; - if (typeof value === 'object' && value.hasOwnProperty('content')) { - value = value.content; - } - if (typeof value.cmp === "function") { - return (value.cmp(comparison_value) === 0); - } - if (comparison_value.toString() === value.toString()) { - return true; - } + + addFormatToken('H', ['HH', 2], 0, 'hour'); + addFormatToken('h', ['hh', 2], 0, hFormat); + addFormatToken('k', ['kk', 2], 0, kFormat); + + addFormatToken('hmm', 0, 0, function () { + return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2); + }); + + addFormatToken('hmmss', 0, 0, function () { + return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2) + + zeroFill(this.seconds(), 2); + }); + + addFormatToken('Hmm', 0, 0, function () { + return '' + this.hours() + zeroFill(this.minutes(), 2); + }); + + addFormatToken('Hmmss', 0, 0, function () { + return '' + this.hours() + zeroFill(this.minutes(), 2) + + zeroFill(this.seconds(), 2); + }); + + function meridiem (token, lowercase) { + addFormatToken(token, 0, 0, function () { + return this.localeData().meridiem(this.hours(), this.minutes(), lowercase); + }); } - return false; - }; - /** - * Comparison operator, test if this query value matches the item value - * - * @method like - * @param {String} object_value The value to compare - * @param {String} comparison_value The comparison value - * @return {Boolean} true if match, false otherwise - */ - SimpleQuery.prototype.like = function (object_value, comparison_value) { - var value, i; - if (!Array.isArray(object_value)) { - object_value = [object_value]; - } - for (i = 0; i < object_value.length; i += 1) { - value = object_value[i]; - if (typeof value === 'object' && value.hasOwnProperty('content')) { - value = value.content; - } - if (typeof value.cmp === "function") { - return (value.cmp(comparison_value) === 0); - } - if ( - searchTextToRegExp(comparison_value.toString()).test(value.toString()) - ) { - return true; - } - } - return false; - }; + meridiem('a', true); + meridiem('A', false); - /** - * Comparison operator, test if this query value does not match the item value - * - * @method != - * @param {String} object_value The value to compare - * @param {String} comparison_value The comparison value - * @return {Boolean} true if not match, false otherwise - */ - SimpleQuery.prototype["!="] = function (object_value, comparison_value) { - var value, i; - if (!Array.isArray(object_value)) { - object_value = [object_value]; - } - for (i = 0; i < object_value.length; i += 1) { - value = object_value[i]; - if (typeof value === 'object' && value.hasOwnProperty('content')) { - value = value.content; - } - if (typeof value.cmp === "function") { - return (value.cmp(comparison_value) !== 0); - } - if (comparison_value.toString() === value.toString()) { - return false; - } - } - return true; - }; + // ALIASES - /** - * Comparison operator, test if this query value is lower than the item value - * - * @method < - * @param {Number, String} object_value The value to compare - * @param {Number, String} comparison_value The comparison value - * @return {Boolean} true if lower, false otherwise - */ - SimpleQuery.prototype["<"] = function (object_value, comparison_value) { - var value; - if (!Array.isArray(object_value)) { - object_value = [object_value]; - } - value = object_value[0]; - if (typeof value === 'object' && value.hasOwnProperty('content')) { - value = value.content; - } - if (typeof value.cmp === "function") { - return (value.cmp(comparison_value) < 0); - } - return (value < comparison_value); - }; + addUnitAlias('hour', 'h'); - /** - * Comparison operator, test if this query value is equal or lower than the - * item value - * - * @method <= - * @param {Number, String} object_value The value to compare - * @param {Number, String} comparison_value The comparison value - * @return {Boolean} true if equal or lower, false otherwise - */ - SimpleQuery.prototype["<="] = function (object_value, comparison_value) { - var value; - if (!Array.isArray(object_value)) { - object_value = [object_value]; - } - value = object_value[0]; - if (typeof value === 'object' && value.hasOwnProperty('content')) { - value = value.content; - } - if (typeof value.cmp === "function") { - return (value.cmp(comparison_value) <= 0); - } - return (value <= comparison_value); - }; + // PARSING - /** - * Comparison operator, test if this query value is greater than the item - * value - * - * @method > - * @param {Number, String} object_value The value to compare - * @param {Number, String} comparison_value The comparison value - * @return {Boolean} true if greater, false otherwise - */ - SimpleQuery.prototype[">"] = function (object_value, comparison_value) { - var value; - if (!Array.isArray(object_value)) { - object_value = [object_value]; + function matchMeridiem (isStrict, locale) { + return locale._meridiemParse; } - value = object_value[0]; - if (typeof value === 'object' && value.hasOwnProperty('content')) { - value = value.content; - } - if (typeof value.cmp === "function") { - return (value.cmp(comparison_value) > 0); - } - return (value > comparison_value); - }; - /** - * Comparison operator, test if this query value is equal or greater than the - * item value - * - * @method >= - * @param {Number, String} object_value The value to compare - * @param {Number, String} comparison_value The comparison value - * @return {Boolean} true if equal or greater, false otherwise - */ - SimpleQuery.prototype[">="] = function (object_value, comparison_value) { - var value; - if (!Array.isArray(object_value)) { - object_value = [object_value]; - } - value = object_value[0]; - if (typeof value === 'object' && value.hasOwnProperty('content')) { - value = value.content; + addRegexToken('a', matchMeridiem); + addRegexToken('A', matchMeridiem); + addRegexToken('H', match1to2); + addRegexToken('h', match1to2); + addRegexToken('HH', match1to2, match2); + addRegexToken('hh', match1to2, match2); + + addRegexToken('hmm', match3to4); + addRegexToken('hmmss', match5to6); + addRegexToken('Hmm', match3to4); + addRegexToken('Hmmss', match5to6); + + addParseToken(['H', 'HH'], HOUR); + addParseToken(['a', 'A'], function (input, array, config) { + config._isPm = config._locale.isPM(input); + config._meridiem = input; + }); + addParseToken(['h', 'hh'], function (input, array, config) { + array[HOUR] = toInt(input); + getParsingFlags(config).bigHour = true; + }); + addParseToken('hmm', function (input, array, config) { + var pos = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos)); + array[MINUTE] = toInt(input.substr(pos)); + getParsingFlags(config).bigHour = true; + }); + addParseToken('hmmss', function (input, array, config) { + var pos1 = input.length - 4; + var pos2 = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos1)); + array[MINUTE] = toInt(input.substr(pos1, 2)); + array[SECOND] = toInt(input.substr(pos2)); + getParsingFlags(config).bigHour = true; + }); + addParseToken('Hmm', function (input, array, config) { + var pos = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos)); + array[MINUTE] = toInt(input.substr(pos)); + }); + addParseToken('Hmmss', function (input, array, config) { + var pos1 = input.length - 4; + var pos2 = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos1)); + array[MINUTE] = toInt(input.substr(pos1, 2)); + array[SECOND] = toInt(input.substr(pos2)); + }); + + // LOCALES + + function localeIsPM (input) { + // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays + // Using charAt should be more compatible. + return ((input + '').toLowerCase().charAt(0) === 'p'); } - if (typeof value.cmp === "function") { - return (value.cmp(comparison_value) >= 0); + + var defaultLocaleMeridiemParse = /[ap]\.?m?\.?/i; + function localeMeridiem (hours, minutes, isLower) { + if (hours > 11) { + return isLower ? 'pm' : 'PM'; + } else { + return isLower ? 'am' : 'AM'; + } } - return (value >= comparison_value); - }; - query_class_dict.simple = SimpleQuery; - query_class_dict.complex = ComplexQuery; - Query.parseStringToObject = parseStringToObject; - Query.objectToSearchText = objectToSearchText; + // MOMENTS - window.Query = Query; - window.SimpleQuery = SimpleQuery; - window.ComplexQuery = ComplexQuery; - window.QueryFactory = QueryFactory; + // Setting the hour should keep the time, because the user explicitly + // specified which hour he wants. So trying to maintain the same hour (in + // a new timezone) makes sense. Adding/subtracting hours does not follow + // this rule. + var getSetHour = makeGetSet('Hours', true); -}(RSVP, window, parseStringToObject)); -;/*global window, moment */ -/*jslint nomen: true, maxlen: 200*/ -(function (window, moment) { - "use strict"; + // FORMATTING -// /** -// * Add a secured (write permission denied) property to an object. -// * -// * @param {Object} object The object to fill -// * @param {String} key The object key where to store the property -// * @param {Any} value The value to store -// */ -// function _export(key, value) { -// Object.defineProperty(to_export, key, { -// "configurable": false, -// "enumerable": true, -// "writable": false, -// "value": value -// }); -// } + addFormatToken('m', ['mm', 2], 0, 'minute'); - var YEAR = 'year', - MONTH = 'month', - DAY = 'day', - HOUR = 'hour', - MIN = 'minute', - SEC = 'second', - MSEC = 'millisecond', - precision_grade = { - 'year': 0, - 'month': 1, - 'day': 2, - 'hour': 3, - 'minute': 4, - 'second': 5, - 'millisecond': 6 - }, - lesserPrecision = function (p1, p2) { - return (precision_grade[p1] < precision_grade[p2]) ? p1 : p2; - }, - JIODate; + // ALIASES + addUnitAlias('minute', 'm'); - JIODate = function (str) { - // in case of forgotten 'new' - if (!(this instanceof JIODate)) { - return new JIODate(str); - } + // PARSING - if (str instanceof JIODate) { - this.mom = str.mom.clone(); - this._precision = str._precision; - return; - } + addRegexToken('m', match1to2); + addRegexToken('mm', match1to2, match2); + addParseToken(['m', 'mm'], MINUTE); - if (str === undefined) { - this.mom = moment(); - this.setPrecision(MSEC); - return; - } + // MOMENTS - this.mom = null; - this._str = str; + var getSetMinute = makeGetSet('Minutes', false); - // http://www.w3.org/TR/NOTE-datetime - // http://dotat.at/tmp/ISO_8601-2004_E.pdf + // FORMATTING - // XXX these regexps fail to detect many invalid dates. + addFormatToken('s', ['ss', 2], 0, 'second'); - if (str.match(/\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+\-][0-2]\d:[0-5]\d|Z)/) - || str.match(/\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d/)) { - // ISO, milliseconds - this.mom = moment(str); - this.setPrecision(MSEC); - } else if (str.match(/\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+\-][0-2]\d:[0-5]\d|Z)/) - || str.match(/\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d/)) { - // ISO, seconds - this.mom = moment(str); - this.setPrecision(SEC); - } else if (str.match(/\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+\-][0-2]\d:[0-5]\d|Z)/) - || str.match(/\d\d\d\d-\d\d-\d\d \d\d:\d\d/)) { - // ISO, minutes - this.mom = moment(str); - this.setPrecision(MIN); - } else if (str.match(/\d\d\d\d-\d\d-\d\d \d\d/)) { - this.mom = moment(str); - this.setPrecision(HOUR); - } else if (str.match(/\d\d\d\d-\d\d-\d\d/)) { - this.mom = moment(str); - this.setPrecision(DAY); - } else if (str.match(/\d\d\d\d-\d\d/)) { - this.mom = moment(str); - this.setPrecision(MONTH); - } else if (str.match(/\d\d\d\d/)) { - this.mom = moment(str); - this.setPrecision(YEAR); - } + // ALIASES - if (!this.mom) { - throw new Error("Cannot parse: " + str); - } + addUnitAlias('second', 's'); - }; + // PARSING + addRegexToken('s', match1to2); + addRegexToken('ss', match1to2, match2); + addParseToken(['s', 'ss'], SECOND); - JIODate.prototype.setPrecision = function (prec) { - this._precision = prec; - }; + // MOMENTS + var getSetSecond = makeGetSet('Seconds', false); - JIODate.prototype.getPrecision = function () { - return this._precision; - }; + // FORMATTING + addFormatToken('S', 0, 0, function () { + return ~~(this.millisecond() / 100); + }); - JIODate.prototype.cmp = function (other) { - var m1 = this.mom, - m2 = other.mom, - p = lesserPrecision(this._precision, other._precision); - return m1.isBefore(m2, p) ? -1 : (m1.isSame(m2, p) ? 0 : +1); - }; + addFormatToken(0, ['SS', 2], 0, function () { + return ~~(this.millisecond() / 10); + }); + addFormatToken(0, ['SSS', 3], 0, 'millisecond'); + addFormatToken(0, ['SSSS', 4], 0, function () { + return this.millisecond() * 10; + }); + addFormatToken(0, ['SSSSS', 5], 0, function () { + return this.millisecond() * 100; + }); + addFormatToken(0, ['SSSSSS', 6], 0, function () { + return this.millisecond() * 1000; + }); + addFormatToken(0, ['SSSSSSS', 7], 0, function () { + return this.millisecond() * 10000; + }); + addFormatToken(0, ['SSSSSSSS', 8], 0, function () { + return this.millisecond() * 100000; + }); + addFormatToken(0, ['SSSSSSSSS', 9], 0, function () { + return this.millisecond() * 1000000; + }); - JIODate.prototype.toPrecisionString = function (precision) { - var fmt; - precision = precision || this._precision; + // ALIASES - fmt = { - 'millisecond': 'YYYY-MM-DD HH:mm:ss.SSS', - 'second': 'YYYY-MM-DD HH:mm:ss', - 'minute': 'YYYY-MM-DD HH:mm', - 'hour': 'YYYY-MM-DD HH', - 'day': 'YYYY-MM-DD', - 'month': 'YYYY-MM', - 'year': 'YYYY' - }[precision]; + addUnitAlias('millisecond', 'ms'); - if (!fmt) { - throw new TypeError("Unsupported precision value '" + precision + "'"); + // PARSING + + addRegexToken('S', match1to3, match1); + addRegexToken('SS', match1to3, match2); + addRegexToken('SSS', match1to3, match3); + + var token; + for (token = 'SSSS'; token.length <= 9; token += 'S') { + addRegexToken(token, matchUnsigned); } - return this.mom.format(fmt); - }; + function parseMs(input, array) { + array[MILLISECOND] = toInt(('0.' + input) * 1000); + } + for (token = 'S'; token.length <= 9; token += 'S') { + addParseToken(token, parseMs); + } + // MOMENTS - JIODate.prototype.toString = function () { - return this._str; - }; + var getSetMillisecond = makeGetSet('Milliseconds', false); + // FORMATTING -// _export('JIODate', JIODate); -// -// _export('YEAR', YEAR); -// _export('MONTH', MONTH); -// _export('DAY', DAY); -// _export('HOUR', HOUR); -// _export('MIN', MIN); -// _export('SEC', SEC); -// _export('MSEC', MSEC); + addFormatToken('z', 0, 0, 'zoneAbbr'); + addFormatToken('zz', 0, 0, 'zoneName'); + + // MOMENTS + + function getZoneAbbr () { + return this._isUTC ? 'UTC' : ''; + } + + function getZoneName () { + return this._isUTC ? 'Coordinated Universal Time' : ''; + } + + var momentPrototype__proto = Moment.prototype; + + momentPrototype__proto.add = add_subtract__add; + momentPrototype__proto.calendar = moment_calendar__calendar; + momentPrototype__proto.clone = clone; + momentPrototype__proto.diff = diff; + momentPrototype__proto.endOf = endOf; + momentPrototype__proto.format = format; + momentPrototype__proto.from = from; + momentPrototype__proto.fromNow = fromNow; + momentPrototype__proto.to = to; + momentPrototype__proto.toNow = toNow; + momentPrototype__proto.get = getSet; + momentPrototype__proto.invalidAt = invalidAt; + momentPrototype__proto.isAfter = isAfter; + momentPrototype__proto.isBefore = isBefore; + momentPrototype__proto.isBetween = isBetween; + momentPrototype__proto.isSame = isSame; + momentPrototype__proto.isSameOrAfter = isSameOrAfter; + momentPrototype__proto.isSameOrBefore = isSameOrBefore; + momentPrototype__proto.isValid = moment_valid__isValid; + momentPrototype__proto.lang = lang; + momentPrototype__proto.locale = locale; + momentPrototype__proto.localeData = localeData; + momentPrototype__proto.max = prototypeMax; + momentPrototype__proto.min = prototypeMin; + momentPrototype__proto.parsingFlags = parsingFlags; + momentPrototype__proto.set = getSet; + momentPrototype__proto.startOf = startOf; + momentPrototype__proto.subtract = add_subtract__subtract; + momentPrototype__proto.toArray = toArray; + momentPrototype__proto.toObject = toObject; + momentPrototype__proto.toDate = toDate; + momentPrototype__proto.toISOString = moment_format__toISOString; + momentPrototype__proto.toJSON = toJSON; + momentPrototype__proto.toString = toString; + momentPrototype__proto.unix = unix; + momentPrototype__proto.valueOf = to_type__valueOf; + momentPrototype__proto.creationData = creationData; - window.jiodate = { - JIODate: JIODate, - YEAR: YEAR, - MONTH: MONTH, - DAY: DAY, - HOUR: HOUR, - MIN: MIN, - SEC: SEC, - MSEC: MSEC - }; -}(window, moment)); -;/*global window, RSVP, Blob, XMLHttpRequest, QueryFactory, Query, atob, - FileReader, ArrayBuffer, Uint8Array */ -(function (window, RSVP, Blob, QueryFactory, Query, atob, - FileReader, ArrayBuffer, Uint8Array) { - "use strict"; + // Year + momentPrototype__proto.year = getSetYear; + momentPrototype__proto.isLeapYear = getIsLeapYear; - var util = {}, - jIO; + // Week Year + momentPrototype__proto.weekYear = getSetWeekYear; + momentPrototype__proto.isoWeekYear = getSetISOWeekYear; - function jIOError(message, status_code) { - if ((message !== undefined) && (typeof message !== "string")) { - throw new TypeError('You must pass a string.'); + // Quarter + momentPrototype__proto.quarter = momentPrototype__proto.quarters = getSetQuarter; + + // Month + momentPrototype__proto.month = getSetMonth; + momentPrototype__proto.daysInMonth = getDaysInMonth; + + // Week + momentPrototype__proto.week = momentPrototype__proto.weeks = getSetWeek; + momentPrototype__proto.isoWeek = momentPrototype__proto.isoWeeks = getSetISOWeek; + momentPrototype__proto.weeksInYear = getWeeksInYear; + momentPrototype__proto.isoWeeksInYear = getISOWeeksInYear; + + // Day + momentPrototype__proto.date = getSetDayOfMonth; + momentPrototype__proto.day = momentPrototype__proto.days = getSetDayOfWeek; + momentPrototype__proto.weekday = getSetLocaleDayOfWeek; + momentPrototype__proto.isoWeekday = getSetISODayOfWeek; + momentPrototype__proto.dayOfYear = getSetDayOfYear; + + // Hour + momentPrototype__proto.hour = momentPrototype__proto.hours = getSetHour; + + // Minute + momentPrototype__proto.minute = momentPrototype__proto.minutes = getSetMinute; + + // Second + momentPrototype__proto.second = momentPrototype__proto.seconds = getSetSecond; + + // Millisecond + momentPrototype__proto.millisecond = momentPrototype__proto.milliseconds = getSetMillisecond; + + // Offset + momentPrototype__proto.utcOffset = getSetOffset; + momentPrototype__proto.utc = setOffsetToUTC; + momentPrototype__proto.local = setOffsetToLocal; + momentPrototype__proto.parseZone = setOffsetToParsedOffset; + momentPrototype__proto.hasAlignedHourOffset = hasAlignedHourOffset; + momentPrototype__proto.isDST = isDaylightSavingTime; + momentPrototype__proto.isDSTShifted = isDaylightSavingTimeShifted; + momentPrototype__proto.isLocal = isLocal; + momentPrototype__proto.isUtcOffset = isUtcOffset; + momentPrototype__proto.isUtc = isUtc; + momentPrototype__proto.isUTC = isUtc; + + // Timezone + momentPrototype__proto.zoneAbbr = getZoneAbbr; + momentPrototype__proto.zoneName = getZoneName; + + // Deprecations + momentPrototype__proto.dates = deprecate('dates accessor is deprecated. Use date instead.', getSetDayOfMonth); + momentPrototype__proto.months = deprecate('months accessor is deprecated. Use month instead', getSetMonth); + momentPrototype__proto.years = deprecate('years accessor is deprecated. Use year instead', getSetYear); + momentPrototype__proto.zone = deprecate('moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779', getSetZone); + + var momentPrototype = momentPrototype__proto; + + function moment__createUnix (input) { + return local__createLocal(input * 1000); + } + + function moment__createInZone () { + return local__createLocal.apply(null, arguments).parseZone(); + } + + var defaultCalendar = { + sameDay : '[Today at] LT', + nextDay : '[Tomorrow at] LT', + nextWeek : 'dddd [at] LT', + lastDay : '[Yesterday at] LT', + lastWeek : '[Last] dddd [at] LT', + sameElse : 'L' + }; + + function locale_calendar__calendar (key, mom, now) { + var output = this._calendar[key]; + return isFunction(output) ? output.call(mom, now) : output; } - this.message = message || "Default Message"; - this.status_code = status_code || 500; - } - jIOError.prototype = new Error(); - jIOError.prototype.constructor = jIOError; - util.jIOError = jIOError; - /** - * Send request with XHR and return a promise. xhr.onload: The promise is - * resolved when the status code is lower than 400 with the xhr object as - * first parameter. xhr.onerror: reject with xhr object as first - * parameter. xhr.onprogress: notifies the xhr object. - * - * @param {Object} param The parameters - * @param {String} [param.type="GET"] The request method - * @param {String} [param.dataType=""] The data type to retrieve - * @param {String} param.url The url - * @param {Any} [param.data] The data to send - * @param {Function} [param.beforeSend] A function called just before the - * send request. The first parameter of this function is the XHR object. - * @return {Promise} The promise - */ - function ajax(param) { - var xhr = new XMLHttpRequest(); - return new RSVP.Promise(function (resolve, reject, notify) { - var k; - xhr.open(param.type || "GET", param.url, true); - xhr.responseType = param.dataType || ""; - if (typeof param.headers === 'object' && param.headers !== null) { - for (k in param.headers) { - if (param.headers.hasOwnProperty(k)) { - xhr.setRequestHeader(k, param.headers[k]); - } - } - } - xhr.addEventListener("load", function (e) { - if (e.target.status >= 400) { - return reject(e); - } - resolve(e); - }); - xhr.addEventListener("error", reject); - xhr.addEventListener("progress", notify); - if (typeof param.xhrFields === 'object' && param.xhrFields !== null) { - for (k in param.xhrFields) { - if (param.xhrFields.hasOwnProperty(k)) { - xhr[k] = param.xhrFields[k]; - } - } - } - if (typeof param.beforeSend === 'function') { - param.beforeSend(xhr); - } - xhr.send(param.data); - }, function () { - xhr.abort(); - }); - } - util.ajax = ajax; + var defaultLongDateFormat = { + LTS : 'h:mm:ss A', + LT : 'h:mm A', + L : 'MM/DD/YYYY', + LL : 'MMMM D, YYYY', + LLL : 'MMMM D, YYYY h:mm A', + LLLL : 'dddd, MMMM D, YYYY h:mm A' + }; - function readBlobAsText(blob, encoding) { - var fr = new FileReader(); - return new RSVP.Promise(function (resolve, reject, notify) { - fr.addEventListener("load", resolve); - fr.addEventListener("error", reject); - fr.addEventListener("progress", notify); - fr.readAsText(blob, encoding); - }, function () { - fr.abort(); - }); - } - util.readBlobAsText = readBlobAsText; + function longDateFormat (key) { + var format = this._longDateFormat[key], + formatUpper = this._longDateFormat[key.toUpperCase()]; - function readBlobAsArrayBuffer(blob) { - var fr = new FileReader(); - return new RSVP.Promise(function (resolve, reject, notify) { - fr.addEventListener("load", resolve); - fr.addEventListener("error", reject); - fr.addEventListener("progress", notify); - fr.readAsArrayBuffer(blob); - }, function () { - fr.abort(); - }); - } - util.readBlobAsArrayBuffer = readBlobAsArrayBuffer; + if (format || !formatUpper) { + return format; + } - function readBlobAsDataURL(blob) { - var fr = new FileReader(); - return new RSVP.Promise(function (resolve, reject, notify) { - fr.addEventListener("load", resolve); - fr.addEventListener("error", reject); - fr.addEventListener("progress", notify); - fr.readAsDataURL(blob); - }, function () { - fr.abort(); - }); - } - util.readBlobAsDataURL = readBlobAsDataURL; + this._longDateFormat[key] = formatUpper.replace(/MMMM|MM|DD|dddd/g, function (val) { + return val.slice(1); + }); - // https://gist.github.com/davoclavo/4424731 - function dataURItoBlob(dataURI) { - // convert base64 to raw binary data held in a string - var byteString = atob(dataURI.split(',')[1]), - // separate out the mime component - mimeString = dataURI.split(',')[0].split(':')[1], - // write the bytes of the string to an ArrayBuffer - arrayBuffer = new ArrayBuffer(byteString.length), - _ia = new Uint8Array(arrayBuffer), - i; - mimeString = mimeString.slice(0, mimeString.length - ";base64".length); - for (i = 0; i < byteString.length; i += 1) { - _ia[i] = byteString.charCodeAt(i); + return this._longDateFormat[key]; } - return new Blob([arrayBuffer], {type: mimeString}); - } - util.dataURItoBlob = dataURItoBlob; + var defaultInvalidDate = 'Invalid date'; - // tools - function checkId(argument_list, storage, method_name) { - if (typeof argument_list[0] !== 'string' || argument_list[0] === '') { - throw new jIO.util.jIOError( - "Document id must be a non empty string on '" + storage.__type + - "." + method_name + "'.", - 400 - ); + function invalidDate () { + return this._invalidDate; } - } - function checkAttachmentId(argument_list, storage, method_name) { - if (typeof argument_list[1] !== 'string' || argument_list[1] === '') { - throw new jIO.util.jIOError( - "Attachment id must be a non empty string on '" + storage.__type + - "." + method_name + "'.", - 400 - ); + var defaultOrdinal = '%d'; + var defaultOrdinalParse = /\d{1,2}/; + + function ordinal (number) { + return this._ordinal.replace('%d', number); } - } - function declareMethod(klass, name, precondition_function, post_function) { - klass.prototype[name] = function () { - var argument_list = arguments, - context = this, - precondition_result; + function preParsePostFormat (string) { + return string; + } - return new RSVP.Queue() - .push(function () { - if (precondition_function !== undefined) { - return precondition_function.apply( - context.__storage, - [argument_list, context, name] - ); - } - }) - .push(function (result) { - var storage_method = context.__storage[name]; - precondition_result = result; - if (storage_method === undefined) { - throw new jIO.util.jIOError( - "Capacity '" + name + "' is not implemented on '" + - context.__type + "'", - 501 - ); - } - return storage_method.apply( - context.__storage, - argument_list - ); - }) - .push(function (result) { - if (post_function !== undefined) { - return post_function.call( - context, - argument_list, - result, - precondition_result - ); - } - return result; - }); + var defaultRelativeTime = { + future : 'in %s', + past : '%s ago', + s : 'a few seconds', + m : 'a minute', + mm : '%d minutes', + h : 'an hour', + hh : '%d hours', + d : 'a day', + dd : '%d days', + M : 'a month', + MM : '%d months', + y : 'a year', + yy : '%d years' }; - // Allow chain - return this; - } + function relative__relativeTime (number, withoutSuffix, string, isFuture) { + var output = this._relativeTime[string]; + return (isFunction(output)) ? + output(number, withoutSuffix, string, isFuture) : + output.replace(/%d/i, number); + } + + function pastFuture (diff, output) { + var format = this._relativeTime[diff > 0 ? 'future' : 'past']; + return isFunction(format) ? format(output) : format.replace(/%s/i, output); + } + + var prototype__proto = Locale.prototype; + + prototype__proto._calendar = defaultCalendar; + prototype__proto.calendar = locale_calendar__calendar; + prototype__proto._longDateFormat = defaultLongDateFormat; + prototype__proto.longDateFormat = longDateFormat; + prototype__proto._invalidDate = defaultInvalidDate; + prototype__proto.invalidDate = invalidDate; + prototype__proto._ordinal = defaultOrdinal; + prototype__proto.ordinal = ordinal; + prototype__proto._ordinalParse = defaultOrdinalParse; + prototype__proto.preparse = preParsePostFormat; + prototype__proto.postformat = preParsePostFormat; + prototype__proto._relativeTime = defaultRelativeTime; + prototype__proto.relativeTime = relative__relativeTime; + prototype__proto.pastFuture = pastFuture; + prototype__proto.set = locale_set__set; + + // Month + prototype__proto.months = localeMonths; + prototype__proto._months = defaultLocaleMonths; + prototype__proto.monthsShort = localeMonthsShort; + prototype__proto._monthsShort = defaultLocaleMonthsShort; + prototype__proto.monthsParse = localeMonthsParse; + prototype__proto._monthsRegex = defaultMonthsRegex; + prototype__proto.monthsRegex = monthsRegex; + prototype__proto._monthsShortRegex = defaultMonthsShortRegex; + prototype__proto.monthsShortRegex = monthsShortRegex; + + // Week + prototype__proto.week = localeWeek; + prototype__proto._week = defaultLocaleWeek; + prototype__proto.firstDayOfYear = localeFirstDayOfYear; + prototype__proto.firstDayOfWeek = localeFirstDayOfWeek; + + // Day of Week + prototype__proto.weekdays = localeWeekdays; + prototype__proto._weekdays = defaultLocaleWeekdays; + prototype__proto.weekdaysMin = localeWeekdaysMin; + prototype__proto._weekdaysMin = defaultLocaleWeekdaysMin; + prototype__proto.weekdaysShort = localeWeekdaysShort; + prototype__proto._weekdaysShort = defaultLocaleWeekdaysShort; + prototype__proto.weekdaysParse = localeWeekdaysParse; + + prototype__proto._weekdaysRegex = defaultWeekdaysRegex; + prototype__proto.weekdaysRegex = weekdaysRegex; + prototype__proto._weekdaysShortRegex = defaultWeekdaysShortRegex; + prototype__proto.weekdaysShortRegex = weekdaysShortRegex; + prototype__proto._weekdaysMinRegex = defaultWeekdaysMinRegex; + prototype__proto.weekdaysMinRegex = weekdaysMinRegex; + + // Hours + prototype__proto.isPM = localeIsPM; + prototype__proto._meridiemParse = defaultLocaleMeridiemParse; + prototype__proto.meridiem = localeMeridiem; + + function lists__get (format, index, field, setter) { + var locale = locale_locales__getLocale(); + var utc = create_utc__createUTC().set(setter, index); + return locale[field](utc, format); + } + + function listMonthsImpl (format, index, field) { + if (typeof format === 'number') { + index = format; + format = undefined; + } + + format = format || ''; + + if (index != null) { + return lists__get(format, index, field, 'month'); + } + + var i; + var out = []; + for (i = 0; i < 12; i++) { + out[i] = lists__get(format, i, field, 'month'); + } + return out; + } + + // () + // (5) + // (fmt, 5) + // (fmt) + // (true) + // (true, 5) + // (true, fmt, 5) + // (true, fmt) + function listWeekdaysImpl (localeSorted, format, index, field) { + if (typeof localeSorted === 'boolean') { + if (typeof format === 'number') { + index = format; + format = undefined; + } + format = format || ''; + } else { + format = localeSorted; + index = format; + localeSorted = false; + if (typeof format === 'number') { + index = format; + format = undefined; + } - ///////////////////////////////////////////////////////////////// - // jIO Storage Proxy - ///////////////////////////////////////////////////////////////// - function JioProxyStorage(type, storage) { - if (!(this instanceof JioProxyStorage)) { - return new JioProxyStorage(); - } - this.__type = type; - this.__storage = storage; - } + format = format || ''; + } - declareMethod(JioProxyStorage, "put", checkId, function (argument_list) { - return argument_list[0]; - }); - declareMethod(JioProxyStorage, "get", checkId); - declareMethod(JioProxyStorage, "bulk"); - declareMethod(JioProxyStorage, "remove", checkId, function (argument_list) { - return argument_list[0]; - }); + var locale = locale_locales__getLocale(), + shift = localeSorted ? locale._week.dow : 0; - JioProxyStorage.prototype.post = function () { - var context = this, - argument_list = arguments; - return new RSVP.Queue() - .push(function () { - var storage_method = context.__storage.post; - if (storage_method === undefined) { - throw new jIO.util.jIOError( - "Capacity 'post' is not implemented on '" + context.__type + "'", - 501 - ); + if (index != null) { + return lists__get(format, (index + shift) % 7, field, 'day'); } - return context.__storage.post.apply(context.__storage, argument_list); - }); - }; - - declareMethod(JioProxyStorage, 'putAttachment', function (argument_list, - storage, - method_name) { - checkId(argument_list, storage, method_name); - checkAttachmentId(argument_list, storage, method_name); - var options = argument_list[3] || {}; + var i; + var out = []; + for (i = 0; i < 7; i++) { + out[i] = lists__get(format, (i + shift) % 7, field, 'day'); + } + return out; + } - if (typeof argument_list[2] === 'string') { - argument_list[2] = new Blob([argument_list[2]], { - "type": options._content_type || options._mimetype || - "text/plain;charset=utf-8" - }); - } else if (!(argument_list[2] instanceof Blob)) { - throw new jIO.util.jIOError( - 'Attachment content is not a blob', - 400 - ); + function lists__listMonths (format, index) { + return listMonthsImpl(format, index, 'months'); } - }); - declareMethod(JioProxyStorage, 'removeAttachment', function (argument_list, - storage, - method_name) { - checkId(argument_list, storage, method_name); - checkAttachmentId(argument_list, storage, method_name); - }); + function lists__listMonthsShort (format, index) { + return listMonthsImpl(format, index, 'monthsShort'); + } - declareMethod(JioProxyStorage, 'getAttachment', function (argument_list, - storage, - method_name) { - var result = "blob"; -// if (param.storage_spec.type !== "indexeddb" && -// param.storage_spec.type !== "dav" && -// (param.kwargs._start !== undefined -// || param.kwargs._end !== undefined)) { -// restCommandRejecter(param, [ -// 'bad_request', -// 'unsupport', -// '_start, _end not support' -// ]); -// return false; -// } - checkId(argument_list, storage, method_name); - checkAttachmentId(argument_list, storage, method_name); - // Drop optional parameters, which are only used in postfunction - if (argument_list[2] !== undefined) { - result = argument_list[2].format || result; - delete argument_list[2].format; + function lists__listWeekdays (localeSorted, format, index) { + return listWeekdaysImpl(localeSorted, format, index, 'weekdays'); } - return result; - }, function (argument_list, blob, convert) { - var result; - if (!(blob instanceof Blob)) { - throw new jIO.util.jIOError( - "'getAttachment' (" + argument_list[0] + " , " + - argument_list[1] + ") on '" + this.__type + - "' does not return a Blob.", - 501 - ); + + function lists__listWeekdaysShort (localeSorted, format, index) { + return listWeekdaysImpl(localeSorted, format, index, 'weekdaysShort'); } - if (convert === "blob") { - result = blob; - } else if (convert === "data_url") { - result = new RSVP.Queue() - .push(function () { - return jIO.util.readBlobAsDataURL(blob); - }) - .push(function (evt) { - return evt.target.result; - }); - } else if (convert === "array_buffer") { - result = new RSVP.Queue() - .push(function () { - return jIO.util.readBlobAsArrayBuffer(blob); - }) - .push(function (evt) { - return evt.target.result; - }); - } else if (convert === "text") { - result = new RSVP.Queue() - .push(function () { - return jIO.util.readBlobAsText(blob); - }) - .push(function (evt) { - return evt.target.result; - }); - } else if (convert === "json") { - result = new RSVP.Queue() - .push(function () { - return jIO.util.readBlobAsText(blob); - }) - .push(function (evt) { - return JSON.parse(evt.target.result); - }); - } else { - throw new jIO.util.jIOError( - this.__type + ".getAttachment format: '" + convert + - "' is not supported", - 400 - ); + + function lists__listWeekdaysMin (localeSorted, format, index) { + return listWeekdaysImpl(localeSorted, format, index, 'weekdaysMin'); } - return result; - }); - JioProxyStorage.prototype.buildQuery = function () { - var storage_method = this.__storage.buildQuery, - context = this, - argument_list = arguments; - if (storage_method === undefined) { - throw new jIO.util.jIOError( - "Capacity 'buildQuery' is not implemented on '" + this.__type + "'", - 501 - ); + locale_locales__getSetGlobalLocale('en', { + ordinalParse: /\d{1,2}(th|st|nd|rd)/, + ordinal : function (number) { + var b = number % 10, + output = (toInt(number % 100 / 10) === 1) ? 'th' : + (b === 1) ? 'st' : + (b === 2) ? 'nd' : + (b === 3) ? 'rd' : 'th'; + return number + output; + } + }); + + // Side effect imports + utils_hooks__hooks.lang = deprecate('moment.lang is deprecated. Use moment.locale instead.', locale_locales__getSetGlobalLocale); + utils_hooks__hooks.langData = deprecate('moment.langData is deprecated. Use moment.localeData instead.', locale_locales__getLocale); + + var mathAbs = Math.abs; + + function duration_abs__abs () { + var data = this._data; + + this._milliseconds = mathAbs(this._milliseconds); + this._days = mathAbs(this._days); + this._months = mathAbs(this._months); + + data.milliseconds = mathAbs(data.milliseconds); + data.seconds = mathAbs(data.seconds); + data.minutes = mathAbs(data.minutes); + data.hours = mathAbs(data.hours); + data.months = mathAbs(data.months); + data.years = mathAbs(data.years); + + return this; } - return new RSVP.Queue() - .push(function () { - return storage_method.apply( - context.__storage, - argument_list - ); - }); - }; - JioProxyStorage.prototype.hasCapacity = function (name) { - var storage_method = this.__storage.hasCapacity, - capacity_method = this.__storage[name]; - if (capacity_method !== undefined) { - return true; + function duration_add_subtract__addSubtract (duration, input, value, direction) { + var other = create__createDuration(input, value); + + duration._milliseconds += direction * other._milliseconds; + duration._days += direction * other._days; + duration._months += direction * other._months; + + return duration._bubble(); } - if ((storage_method === undefined) || - !storage_method.apply(this.__storage, arguments)) { - throw new jIO.util.jIOError( - "Capacity '" + name + "' is not implemented on '" + this.__type + "'", - 501 - ); + + // supports only 2.0-style add(1, 's') or add(duration) + function duration_add_subtract__add (input, value) { + return duration_add_subtract__addSubtract(this, input, value, 1); } - return true; - }; - JioProxyStorage.prototype.allDocs = function (options) { - var context = this; - if (options === undefined) { - options = {}; + // supports only 2.0-style subtract(1, 's') or subtract(duration) + function duration_add_subtract__subtract (input, value) { + return duration_add_subtract__addSubtract(this, input, value, -1); } - return new RSVP.Queue() - .push(function () { - if (context.hasCapacity("list") && - ((options.query === undefined) || context.hasCapacity("query")) && - ((options.sort_on === undefined) || context.hasCapacity("sort")) && - ((options.select_list === undefined) || - context.hasCapacity("select")) && - ((options.include_docs === undefined) || - context.hasCapacity("include")) && - ((options.limit === undefined) || context.hasCapacity("limit"))) { - return context.buildQuery(options); + + function absCeil (number) { + if (number < 0) { + return Math.floor(number); + } else { + return Math.ceil(number); } - }) - .push(function (result) { - return { - data: { - rows: result, - total_rows: result.length - } - }; - }); - }; + } - declareMethod(JioProxyStorage, "allAttachments", checkId); - declareMethod(JioProxyStorage, "repair"); + function bubble () { + var milliseconds = this._milliseconds; + var days = this._days; + var months = this._months; + var data = this._data; + var seconds, minutes, hours, years, monthsFromDays; - JioProxyStorage.prototype.repair = function () { - var context = this, - argument_list = arguments; - return new RSVP.Queue() - .push(function () { - var storage_method = context.__storage.repair; - if (storage_method !== undefined) { - return context.__storage.repair.apply(context.__storage, - argument_list); + // if we have a mix of positive and negative values, bubble down first + // check: https://github.com/moment/moment/issues/2166 + if (!((milliseconds >= 0 && days >= 0 && months >= 0) || + (milliseconds <= 0 && days <= 0 && months <= 0))) { + milliseconds += absCeil(monthsToDays(months) + days) * 864e5; + days = 0; + months = 0; } - }); - }; - ///////////////////////////////////////////////////////////////// - // Storage builder - ///////////////////////////////////////////////////////////////// - function JioBuilder() { - if (!(this instanceof JioBuilder)) { - return new JioBuilder(); - } - this.__storage_types = {}; - } + // The following code bubbles up values, see the tests for + // examples of what that means. + data.milliseconds = milliseconds % 1000; - JioBuilder.prototype.createJIO = function (storage_spec, util) { + seconds = absFloor(milliseconds / 1000); + data.seconds = seconds % 60; - if (typeof storage_spec.type !== 'string') { - throw new TypeError("Invalid storage description"); - } - if (!this.__storage_types[storage_spec.type]) { - throw new TypeError("Unknown storage '" + storage_spec.type + "'"); - } + minutes = absFloor(seconds / 60); + data.minutes = minutes % 60; - return new JioProxyStorage( - storage_spec.type, - new this.__storage_types[storage_spec.type](storage_spec, util) - ); + hours = absFloor(minutes / 60); + data.hours = hours % 24; - }; + days += absFloor(hours / 24); - JioBuilder.prototype.addStorage = function (type, Constructor) { - if (typeof type !== 'string') { - throw new TypeError( - "jIO.addStorage(): Argument 1 is not of type 'string'" - ); + // convert days to months + monthsFromDays = absFloor(daysToMonths(days)); + months += monthsFromDays; + days -= absCeil(monthsToDays(monthsFromDays)); + + // 12 months -> 1 year + years = absFloor(months / 12); + months %= 12; + + data.days = days; + data.months = months; + data.years = years; + + return this; } - if (typeof Constructor !== 'function') { - throw new TypeError("jIO.addStorage(): " + - "Argument 2 is not of type 'function'"); + + function daysToMonths (days) { + // 400 years have 146097 days (taking into account leap year rules) + // 400 years have 12 months === 4800 + return days * 4800 / 146097; + } + + function monthsToDays (months) { + // the reverse of daysToMonths + return months * 146097 / 4800; + } + + function as (units) { + var days; + var months; + var milliseconds = this._milliseconds; + + units = normalizeUnits(units); + + if (units === 'month' || units === 'year') { + days = this._days + milliseconds / 864e5; + months = this._months + daysToMonths(days); + return units === 'month' ? months : months / 12; + } else { + // handle milliseconds separately because of floating point math errors (issue #1867) + days = this._days + Math.round(monthsToDays(this._months)); + switch (units) { + case 'week' : return days / 7 + milliseconds / 6048e5; + case 'day' : return days + milliseconds / 864e5; + case 'hour' : return days * 24 + milliseconds / 36e5; + case 'minute' : return days * 1440 + milliseconds / 6e4; + case 'second' : return days * 86400 + milliseconds / 1000; + // Math.floor prevents floating point math errors here + case 'millisecond': return Math.floor(days * 864e5) + milliseconds; + default: throw new Error('Unknown unit ' + units); + } + } + } + + // TODO: Use this.as('ms')? + function duration_as__valueOf () { + return ( + this._milliseconds + + this._days * 864e5 + + (this._months % 12) * 2592e6 + + toInt(this._months / 12) * 31536e6 + ); + } + + function makeAs (alias) { + return function () { + return this.as(alias); + }; + } + + var asMilliseconds = makeAs('ms'); + var asSeconds = makeAs('s'); + var asMinutes = makeAs('m'); + var asHours = makeAs('h'); + var asDays = makeAs('d'); + var asWeeks = makeAs('w'); + var asMonths = makeAs('M'); + var asYears = makeAs('y'); + + function duration_get__get (units) { + units = normalizeUnits(units); + return this[units + 's'](); + } + + function makeGetter(name) { + return function () { + return this._data[name]; + }; + } + + var milliseconds = makeGetter('milliseconds'); + var seconds = makeGetter('seconds'); + var minutes = makeGetter('minutes'); + var hours = makeGetter('hours'); + var days = makeGetter('days'); + var months = makeGetter('months'); + var years = makeGetter('years'); + + function weeks () { + return absFloor(this.days() / 7); + } + + var round = Math.round; + var thresholds = { + s: 45, // seconds to minute + m: 45, // minutes to hour + h: 22, // hours to day + d: 26, // days to month + M: 11 // months to year + }; + + // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize + function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) { + return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture); + } + + function duration_humanize__relativeTime (posNegDuration, withoutSuffix, locale) { + var duration = create__createDuration(posNegDuration).abs(); + var seconds = round(duration.as('s')); + var minutes = round(duration.as('m')); + var hours = round(duration.as('h')); + var days = round(duration.as('d')); + var months = round(duration.as('M')); + var years = round(duration.as('y')); + + var a = seconds < thresholds.s && ['s', seconds] || + minutes <= 1 && ['m'] || + minutes < thresholds.m && ['mm', minutes] || + hours <= 1 && ['h'] || + hours < thresholds.h && ['hh', hours] || + days <= 1 && ['d'] || + days < thresholds.d && ['dd', days] || + months <= 1 && ['M'] || + months < thresholds.M && ['MM', months] || + years <= 1 && ['y'] || ['yy', years]; + + a[2] = withoutSuffix; + a[3] = +posNegDuration > 0; + a[4] = locale; + return substituteTimeAgo.apply(null, a); + } + + // This function allows you to set a threshold for relative time strings + function duration_humanize__getSetRelativeTimeThreshold (threshold, limit) { + if (thresholds[threshold] === undefined) { + return false; + } + if (limit === undefined) { + return thresholds[threshold]; + } + thresholds[threshold] = limit; + return true; + } + + function humanize (withSuffix) { + var locale = this.localeData(); + var output = duration_humanize__relativeTime(this, !withSuffix, locale); + + if (withSuffix) { + output = locale.pastFuture(+this, output); + } + + return locale.postformat(output); + } + + var iso_string__abs = Math.abs; + + function iso_string__toISOString() { + // for ISO strings we do not use the normal bubbling rules: + // * milliseconds bubble up until they become hours + // * days do not bubble at all + // * months bubble up until they become years + // This is because there is no context-free conversion between hours and days + // (think of clock changes) + // and also not between days and months (28-31 days per month) + var seconds = iso_string__abs(this._milliseconds) / 1000; + var days = iso_string__abs(this._days); + var months = iso_string__abs(this._months); + var minutes, hours, years; + + // 3600 seconds -> 60 minutes -> 1 hour + minutes = absFloor(seconds / 60); + hours = absFloor(minutes / 60); + seconds %= 60; + minutes %= 60; + + // 12 months -> 1 year + years = absFloor(months / 12); + months %= 12; + + + // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js + var Y = years; + var M = months; + var D = days; + var h = hours; + var m = minutes; + var s = seconds; + var total = this.asSeconds(); + + if (!total) { + // this is the same as C#'s (Noda) and python (isodate)... + // but not other JS (goog.date) + return 'P0D'; + } + + return (total < 0 ? '-' : '') + + 'P' + + (Y ? Y + 'Y' : '') + + (M ? M + 'M' : '') + + (D ? D + 'D' : '') + + ((h || m || s) ? 'T' : '') + + (h ? h + 'H' : '') + + (m ? m + 'M' : '') + + (s ? s + 'S' : ''); + } + + var duration_prototype__proto = Duration.prototype; + + duration_prototype__proto.abs = duration_abs__abs; + duration_prototype__proto.add = duration_add_subtract__add; + duration_prototype__proto.subtract = duration_add_subtract__subtract; + duration_prototype__proto.as = as; + duration_prototype__proto.asMilliseconds = asMilliseconds; + duration_prototype__proto.asSeconds = asSeconds; + duration_prototype__proto.asMinutes = asMinutes; + duration_prototype__proto.asHours = asHours; + duration_prototype__proto.asDays = asDays; + duration_prototype__proto.asWeeks = asWeeks; + duration_prototype__proto.asMonths = asMonths; + duration_prototype__proto.asYears = asYears; + duration_prototype__proto.valueOf = duration_as__valueOf; + duration_prototype__proto._bubble = bubble; + duration_prototype__proto.get = duration_get__get; + duration_prototype__proto.milliseconds = milliseconds; + duration_prototype__proto.seconds = seconds; + duration_prototype__proto.minutes = minutes; + duration_prototype__proto.hours = hours; + duration_prototype__proto.days = days; + duration_prototype__proto.weeks = weeks; + duration_prototype__proto.months = months; + duration_prototype__proto.years = years; + duration_prototype__proto.humanize = humanize; + duration_prototype__proto.toISOString = iso_string__toISOString; + duration_prototype__proto.toString = iso_string__toISOString; + duration_prototype__proto.toJSON = iso_string__toISOString; + duration_prototype__proto.locale = locale; + duration_prototype__proto.localeData = localeData; + + // Deprecations + duration_prototype__proto.toIsoString = deprecate('toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)', iso_string__toISOString); + duration_prototype__proto.lang = lang; + + // Side effect imports + + // FORMATTING + + addFormatToken('X', 0, 0, 'unix'); + addFormatToken('x', 0, 0, 'valueOf'); + + // PARSING + + addRegexToken('x', matchSigned); + addRegexToken('X', matchTimestamp); + addParseToken('X', function (input, array, config) { + config._d = new Date(parseFloat(input, 10) * 1000); + }); + addParseToken('x', function (input, array, config) { + config._d = new Date(toInt(input)); + }); + + // Side effect imports + + + utils_hooks__hooks.version = '2.13.0'; + + setHookCallback(local__createLocal); + + utils_hooks__hooks.fn = momentPrototype; + utils_hooks__hooks.min = min; + utils_hooks__hooks.max = max; + utils_hooks__hooks.now = now; + utils_hooks__hooks.utc = create_utc__createUTC; + utils_hooks__hooks.unix = moment__createUnix; + utils_hooks__hooks.months = lists__listMonths; + utils_hooks__hooks.isDate = isDate; + utils_hooks__hooks.locale = locale_locales__getSetGlobalLocale; + utils_hooks__hooks.invalid = valid__createInvalid; + utils_hooks__hooks.duration = create__createDuration; + utils_hooks__hooks.isMoment = isMoment; + utils_hooks__hooks.weekdays = lists__listWeekdays; + utils_hooks__hooks.parseZone = moment__createInZone; + utils_hooks__hooks.localeData = locale_locales__getLocale; + utils_hooks__hooks.isDuration = isDuration; + utils_hooks__hooks.monthsShort = lists__listMonthsShort; + utils_hooks__hooks.weekdaysMin = lists__listWeekdaysMin; + utils_hooks__hooks.defineLocale = defineLocale; + utils_hooks__hooks.updateLocale = updateLocale; + utils_hooks__hooks.locales = locale_locales__listLocales; + utils_hooks__hooks.weekdaysShort = lists__listWeekdaysShort; + utils_hooks__hooks.normalizeUnits = normalizeUnits; + utils_hooks__hooks.relativeTimeThreshold = duration_humanize__getSetRelativeTimeThreshold; + utils_hooks__hooks.prototype = momentPrototype; + + var _moment = utils_hooks__hooks; + + return _moment; + +}));;/** + * Parse a text request to a json query object tree + * + * @param {String} string The string to parse + * @return {Object} The json query tree + */ +function parseStringToObject(string) { + +var arrayExtend = function () { + var j, i, newlist = [], list_list = arguments; + for (j = 0; j < list_list.length; j += 1) { + for (i = 0; i < list_list[j].length; i += 1) { + newlist.push(list_list[j][i]); + } + } + return newlist; + +}, mkSimpleQuery = function (key, value, operator) { + var object = {"type": "simple", "key": key, "value": value}; + if (operator !== undefined) { + object.operator = operator; + } + return object; + +}, mkNotQuery = function (query) { + if (query.operator === "NOT") { + return query.query_list[0]; + } + return {"type": "complex", "operator": "NOT", "query_list": [query]}; + +}, mkComplexQuery = function (operator, query_list) { + var i, query_list2 = []; + for (i = 0; i < query_list.length; i += 1) { + if (query_list[i].operator === operator) { + query_list2 = arrayExtend(query_list2, query_list[i].query_list); + } else { + query_list2.push(query_list[i]); + } + } + return {type:"complex",operator:operator,query_list:query_list2}; + +}, simpleQuerySetKey = function (query, key) { + var i; + if (query.type === "complex") { + for (i = 0; i < query.query_list.length; ++i) { + simpleQuerySetKey (query.query_list[i],key); + } + return true; + } + if (query.type === "simple" && !query.key) { + query.key = key; + return true; + } + return false; +}, + error_offsets = [], + error_lookaheads = [], + error_count = 0, + result; +;/* parser generated by jison 0.4.16 */ +/* + Returns a Parser object of the following structure: + + Parser: { + yy: {} + } + + Parser.prototype: { + yy: {}, + trace: function(), + symbols_: {associative list: name ==> number}, + terminals_: {associative list: number ==> name}, + productions_: [...], + performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate, $$, _$), + table: [...], + defaultActions: {...}, + parseError: function(str, hash), + parse: function(input), + + lexer: { + EOF: 1, + parseError: function(str, hash), + setInput: function(input), + input: function(), + unput: function(str), + more: function(), + less: function(n), + pastInput: function(), + upcomingInput: function(), + showPosition: function(), + test_match: function(regex_match_array, rule_index), + next: function(), + lex: function(), + begin: function(condition), + popState: function(), + _currentRules: function(), + topState: function(), + pushState: function(condition), + + options: { + ranges: boolean (optional: true ==> token location info will include a .range[] member) + flex: boolean (optional: true ==> flex-like lexing behaviour where the rules are tested exhaustively to find the longest match) + backtrack_lexer: boolean (optional: true ==> lexer regexes are tested in order and for each matching regex the action code is invoked; the lexer terminates the scan when a token is returned by the action code) + }, + + performAction: function(yy, yy_, $avoiding_name_collisions, YY_START), + rules: [...], + conditions: {associative list: name ==> set}, + } + } + + + token location info (@$, _$, etc.): { + first_line: n, + last_line: n, + first_column: n, + last_column: n, + range: [start_number, end_number] (where the numbers are indexes into the input string, regular zero-based) + } + + + the parseError function receives a 'hash' object with these members for lexer and parser errors: { + text: (matched text) + token: (the produced terminal token, if any) + line: (yylineno) + } + while parser (grammar) errors will also provide these members, i.e. parser errors deliver a superset of attributes: { + loc: (yylloc) + expected: (string describing the set of expected tokens) + recoverable: (boolean: TRUE when the parser has a error recovery rule available for this particular error) + } +*/ +var parser = (function(){ +var o=function(k,v,o,l){for(o=o||{},l=k.length;l--;o[k[l]]=v);return o},$V0=[1,5],$V1=[1,7],$V2=[1,8],$V3=[1,10],$V4=[1,12],$V5=[1,6,7,15],$V6=[1,6,7,9,12,14,15,16,19,21],$V7=[1,6,7,9,11,12,14,15,16,19,21],$V8=[2,17]; +var parser = {trace: function trace() { }, +yy: {}, +symbols_: {"error":2,"begin":3,"search_text":4,"end":5,"EOF":6,"NEWLINE":7,"and_expression":8,"OR":9,"boolean_expression":10,"AND":11,"NOT":12,"expression":13,"LEFT_PARENTHESE":14,"RIGHT_PARENTHESE":15,"WORD":16,"DEFINITION":17,"value":18,"OPERATOR":19,"string":20,"QUOTE":21,"QUOTED_STRING":22,"$accept":0,"$end":1}, +terminals_: {2:"error",6:"EOF",7:"NEWLINE",9:"OR",11:"AND",12:"NOT",14:"LEFT_PARENTHESE",15:"RIGHT_PARENTHESE",16:"WORD",17:"DEFINITION",19:"OPERATOR",21:"QUOTE",22:"QUOTED_STRING"}, +productions_: [0,[3,2],[5,0],[5,1],[5,1],[4,1],[4,2],[4,3],[8,1],[8,3],[10,2],[10,1],[13,3],[13,3],[13,1],[18,2],[18,1],[20,1],[20,3]], +performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate /* action[1] */, $$ /* vstack */, _$ /* lstack */) { +/* this == yyval */ + +var $0 = $$.length - 1; +switch (yystate) { +case 1: + return $$[$0-1]; +break; +case 5: case 8: case 11: case 14: case 16: + this.$ = $$[$0]; +break; +case 6: + this.$ = mkComplexQuery('OR', [$$[$0-1], $$[$0]]); +break; +case 7: + this.$ = mkComplexQuery('OR', [$$[$0-2], $$[$0]]); +break; +case 9: + this.$ = mkComplexQuery('AND', [$$[$0-2], $$[$0]]); +break; +case 10: + this.$ = mkNotQuery($$[$0]); +break; +case 12: + this.$ = $$[$0-1]; +break; +case 13: + simpleQuerySetKey($$[$0], $$[$0-2]); this.$ = $$[$0]; +break; +case 15: + $$[$0].operator = $$[$0-1] ; this.$ = $$[$0]; +break; +case 17: + this.$ = mkSimpleQuery('', $$[$0]); +break; +case 18: + this.$ = mkSimpleQuery('', $$[$0-1]); +break; +} +}, +table: [{3:1,4:2,8:3,10:4,12:$V0,13:6,14:$V1,16:$V2,18:9,19:$V3,20:11,21:$V4},{1:[3]},{1:[2,2],5:13,6:[1,14],7:[1,15]},o($V5,[2,5],{8:3,10:4,13:6,18:9,20:11,4:16,9:[1,17],12:$V0,14:$V1,16:$V2,19:$V3,21:$V4}),o($V6,[2,8],{11:[1,18]}),{13:19,14:$V1,16:$V2,18:9,19:$V3,20:11,21:$V4},o($V7,[2,11]),{4:20,8:3,10:4,12:$V0,13:6,14:$V1,16:$V2,18:9,19:$V3,20:11,21:$V4},o($V7,$V8,{17:[1,21]}),o($V7,[2,14]),{16:[1,23],20:22,21:$V4},o($V7,[2,16]),{22:[1,24]},{1:[2,1]},{1:[2,3]},{1:[2,4]},o($V5,[2,6]),{4:25,8:3,10:4,12:$V0,13:6,14:$V1,16:$V2,18:9,19:$V3,20:11,21:$V4},{8:26,10:4,12:$V0,13:6,14:$V1,16:$V2,18:9,19:$V3,20:11,21:$V4},o($V7,[2,10]),{15:[1,27]},{13:28,14:$V1,16:$V2,18:9,19:$V3,20:11,21:$V4},o($V7,[2,15]),o($V7,$V8),{21:[1,29]},o($V5,[2,7]),o($V6,[2,9]),o($V7,[2,12]),o($V7,[2,13]),o($V7,[2,18])], +defaultActions: {13:[2,1],14:[2,3],15:[2,4]}, +parseError: function parseError(str, hash) { + if (hash.recoverable) { + this.trace(str); + } else { + function _parseError (msg, hash) { + this.message = msg; + this.hash = hash; + } + _parseError.prototype = new Error(); + + throw new _parseError(str, hash); + } +}, +parse: function parse(input) { + var self = this, stack = [0], tstack = [], vstack = [null], lstack = [], table = this.table, yytext = '', yylineno = 0, yyleng = 0, recovering = 0, TERROR = 2, EOF = 1; + var args = lstack.slice.call(arguments, 1); + var lexer = Object.create(this.lexer); + var sharedState = { yy: {} }; + for (var k in this.yy) { + if (Object.prototype.hasOwnProperty.call(this.yy, k)) { + sharedState.yy[k] = this.yy[k]; + } + } + lexer.setInput(input, sharedState.yy); + sharedState.yy.lexer = lexer; + sharedState.yy.parser = this; + if (typeof lexer.yylloc == 'undefined') { + lexer.yylloc = {}; + } + var yyloc = lexer.yylloc; + lstack.push(yyloc); + var ranges = lexer.options && lexer.options.ranges; + if (typeof sharedState.yy.parseError === 'function') { + this.parseError = sharedState.yy.parseError; + } else { + this.parseError = Object.getPrototypeOf(this).parseError; + } + function popStack(n) { + stack.length = stack.length - 2 * n; + vstack.length = vstack.length - n; + lstack.length = lstack.length - n; + } + _token_stack: + var lex = function () { + var token; + token = lexer.lex() || EOF; + if (typeof token !== 'number') { + token = self.symbols_[token] || token; + } + return token; + }; + var symbol, preErrorSymbol, state, action, a, r, yyval = {}, p, len, newState, expected; + while (true) { + state = stack[stack.length - 1]; + if (this.defaultActions[state]) { + action = this.defaultActions[state]; + } else { + if (symbol === null || typeof symbol == 'undefined') { + symbol = lex(); + } + action = table[state] && table[state][symbol]; + } + if (typeof action === 'undefined' || !action.length || !action[0]) { + var errStr = ''; + expected = []; + for (p in table[state]) { + if (this.terminals_[p] && p > TERROR) { + expected.push('\'' + this.terminals_[p] + '\''); + } + } + if (lexer.showPosition) { + errStr = 'Parse error on line ' + (yylineno + 1) + ':\n' + lexer.showPosition() + '\nExpecting ' + expected.join(', ') + ', got \'' + (this.terminals_[symbol] || symbol) + '\''; + } else { + errStr = 'Parse error on line ' + (yylineno + 1) + ': Unexpected ' + (symbol == EOF ? 'end of input' : '\'' + (this.terminals_[symbol] || symbol) + '\''); + } + this.parseError(errStr, { + text: lexer.match, + token: this.terminals_[symbol] || symbol, + line: lexer.yylineno, + loc: yyloc, + expected: expected + }); + } + if (action[0] instanceof Array && action.length > 1) { + throw new Error('Parse Error: multiple actions possible at state: ' + state + ', token: ' + symbol); + } + switch (action[0]) { + case 1: + stack.push(symbol); + vstack.push(lexer.yytext); + lstack.push(lexer.yylloc); + stack.push(action[1]); + symbol = null; + if (!preErrorSymbol) { + yyleng = lexer.yyleng; + yytext = lexer.yytext; + yylineno = lexer.yylineno; + yyloc = lexer.yylloc; + if (recovering > 0) { + recovering--; + } + } else { + symbol = preErrorSymbol; + preErrorSymbol = null; + } + break; + case 2: + len = this.productions_[action[1]][1]; + yyval.$ = vstack[vstack.length - len]; + yyval._$ = { + first_line: lstack[lstack.length - (len || 1)].first_line, + last_line: lstack[lstack.length - 1].last_line, + first_column: lstack[lstack.length - (len || 1)].first_column, + last_column: lstack[lstack.length - 1].last_column + }; + if (ranges) { + yyval._$.range = [ + lstack[lstack.length - (len || 1)].range[0], + lstack[lstack.length - 1].range[1] + ]; + } + r = this.performAction.apply(yyval, [ + yytext, + yyleng, + yylineno, + sharedState.yy, + action[1], + vstack, + lstack + ].concat(args)); + if (typeof r !== 'undefined') { + return r; + } + if (len) { + stack = stack.slice(0, -1 * len * 2); + vstack = vstack.slice(0, -1 * len); + lstack = lstack.slice(0, -1 * len); + } + stack.push(this.productions_[action[1]][0]); + vstack.push(yyval.$); + lstack.push(yyval._$); + newState = table[stack[stack.length - 2]][stack[stack.length - 1]]; + stack.push(newState); + break; + case 3: + return true; + } + } + return true; +}}; +/* generated by jison-lex 0.3.4 */ +var lexer = (function(){ +var lexer = ({ + +EOF:1, + +parseError:function parseError(str, hash) { + if (this.yy.parser) { + this.yy.parser.parseError(str, hash); + } else { + throw new Error(str); + } + }, + +// resets the lexer, sets new input +setInput:function (input, yy) { + this.yy = yy || this.yy || {}; + this._input = input; + this._more = this._backtrack = this.done = false; + this.yylineno = this.yyleng = 0; + this.yytext = this.matched = this.match = ''; + this.conditionStack = ['INITIAL']; + this.yylloc = { + first_line: 1, + first_column: 0, + last_line: 1, + last_column: 0 + }; + if (this.options.ranges) { + this.yylloc.range = [0,0]; + } + this.offset = 0; + return this; + }, + +// consumes and returns one char from the input +input:function () { + var ch = this._input[0]; + this.yytext += ch; + this.yyleng++; + this.offset++; + this.match += ch; + this.matched += ch; + var lines = ch.match(/(?:\r\n?|\n).*/g); + if (lines) { + this.yylineno++; + this.yylloc.last_line++; + } else { + this.yylloc.last_column++; + } + if (this.options.ranges) { + this.yylloc.range[1]++; + } + + this._input = this._input.slice(1); + return ch; + }, + +// unshifts one char (or a string) into the input +unput:function (ch) { + var len = ch.length; + var lines = ch.split(/(?:\r\n?|\n)/g); + + this._input = ch + this._input; + this.yytext = this.yytext.substr(0, this.yytext.length - len); + //this.yyleng -= len; + this.offset -= len; + var oldLines = this.match.split(/(?:\r\n?|\n)/g); + this.match = this.match.substr(0, this.match.length - 1); + this.matched = this.matched.substr(0, this.matched.length - 1); + + if (lines.length - 1) { + this.yylineno -= lines.length - 1; + } + var r = this.yylloc.range; + + this.yylloc = { + first_line: this.yylloc.first_line, + last_line: this.yylineno + 1, + first_column: this.yylloc.first_column, + last_column: lines ? + (lines.length === oldLines.length ? this.yylloc.first_column : 0) + + oldLines[oldLines.length - lines.length].length - lines[0].length : + this.yylloc.first_column - len + }; + + if (this.options.ranges) { + this.yylloc.range = [r[0], r[0] + this.yyleng - len]; + } + this.yyleng = this.yytext.length; + return this; + }, + +// When called from action, caches matched text and appends it on next action +more:function () { + this._more = true; + return this; + }, + +// When called from action, signals the lexer that this rule fails to match the input, so the next matching rule (regex) should be tested instead. +reject:function () { + if (this.options.backtrack_lexer) { + this._backtrack = true; + } else { + return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n' + this.showPosition(), { + text: "", + token: null, + line: this.yylineno + }); + + } + return this; + }, + +// retain first n characters of the match +less:function (n) { + this.unput(this.match.slice(n)); + }, + +// displays already matched input, i.e. for error messages +pastInput:function () { + var past = this.matched.substr(0, this.matched.length - this.match.length); + return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, ""); + }, + +// displays upcoming input, i.e. for error messages +upcomingInput:function () { + var next = this.match; + if (next.length < 20) { + next += this._input.substr(0, 20-next.length); + } + return (next.substr(0,20) + (next.length > 20 ? '...' : '')).replace(/\n/g, ""); + }, + +// displays the character position where the lexing error occurred, i.e. for error messages +showPosition:function () { + var pre = this.pastInput(); + var c = new Array(pre.length + 1).join("-"); + return pre + this.upcomingInput() + "\n" + c + "^"; + }, + +// test the lexed token: return FALSE when not a match, otherwise return token +test_match:function (match, indexed_rule) { + var token, + lines, + backup; + + if (this.options.backtrack_lexer) { + // save context + backup = { + yylineno: this.yylineno, + yylloc: { + first_line: this.yylloc.first_line, + last_line: this.last_line, + first_column: this.yylloc.first_column, + last_column: this.yylloc.last_column + }, + yytext: this.yytext, + match: this.match, + matches: this.matches, + matched: this.matched, + yyleng: this.yyleng, + offset: this.offset, + _more: this._more, + _input: this._input, + yy: this.yy, + conditionStack: this.conditionStack.slice(0), + done: this.done + }; + if (this.options.ranges) { + backup.yylloc.range = this.yylloc.range.slice(0); + } + } + + lines = match[0].match(/(?:\r\n?|\n).*/g); + if (lines) { + this.yylineno += lines.length; + } + this.yylloc = { + first_line: this.yylloc.last_line, + last_line: this.yylineno + 1, + first_column: this.yylloc.last_column, + last_column: lines ? + lines[lines.length - 1].length - lines[lines.length - 1].match(/\r?\n?/)[0].length : + this.yylloc.last_column + match[0].length + }; + this.yytext += match[0]; + this.match += match[0]; + this.matches = match; + this.yyleng = this.yytext.length; + if (this.options.ranges) { + this.yylloc.range = [this.offset, this.offset += this.yyleng]; + } + this._more = false; + this._backtrack = false; + this._input = this._input.slice(match[0].length); + this.matched += match[0]; + token = this.performAction.call(this, this.yy, this, indexed_rule, this.conditionStack[this.conditionStack.length - 1]); + if (this.done && this._input) { + this.done = false; + } + if (token) { + return token; + } else if (this._backtrack) { + // recover context + for (var k in backup) { + this[k] = backup[k]; + } + return false; // rule action called reject() implying the next rule should be tested instead. + } + return false; + }, + +// return next match in input +next:function () { + if (this.done) { + return this.EOF; + } + if (!this._input) { + this.done = true; + } + + var token, + match, + tempMatch, + index; + if (!this._more) { + this.yytext = ''; + this.match = ''; + } + var rules = this._currentRules(); + for (var i = 0; i < rules.length; i++) { + tempMatch = this._input.match(this.rules[rules[i]]); + if (tempMatch && (!match || tempMatch[0].length > match[0].length)) { + match = tempMatch; + index = i; + if (this.options.backtrack_lexer) { + token = this.test_match(tempMatch, rules[i]); + if (token !== false) { + return token; + } else if (this._backtrack) { + match = false; + continue; // rule action called reject() implying a rule MISmatch. + } else { + // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace) + return false; + } + } else if (!this.options.flex) { + break; + } + } + } + if (match) { + token = this.test_match(match, rules[index]); + if (token !== false) { + return token; + } + // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace) + return false; + } + if (this._input === "") { + return this.EOF; + } else { + return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. Unrecognized text.\n' + this.showPosition(), { + text: "", + token: null, + line: this.yylineno + }); + } + }, + +// return next match that has a token +lex:function lex() { + var r = this.next(); + if (r) { + return r; + } else { + return this.lex(); + } + }, + +// activates a new lexer condition state (pushes the new lexer condition state onto the condition stack) +begin:function begin(condition) { + this.conditionStack.push(condition); + }, + +// pop the previously active lexer condition state off the condition stack +popState:function popState() { + var n = this.conditionStack.length - 1; + if (n > 0) { + return this.conditionStack.pop(); + } else { + return this.conditionStack[0]; + } + }, + +// produce the lexer rule set which is active for the currently active lexer condition state +_currentRules:function _currentRules() { + if (this.conditionStack.length && this.conditionStack[this.conditionStack.length - 1]) { + return this.conditions[this.conditionStack[this.conditionStack.length - 1]].rules; + } else { + return this.conditions["INITIAL"].rules; + } + }, + +// return the currently active lexer condition state; when an index argument is provided it produces the N-th previous condition state, if available +topState:function topState(n) { + n = this.conditionStack.length - 1 - Math.abs(n || 0); + if (n >= 0) { + return this.conditionStack[n]; + } else { + return "INITIAL"; + } + }, + +// alias for begin(condition) +pushState:function pushState(condition) { + this.begin(condition); + }, + +// return the number of states currently on the stack +stateStackSize:function stateStackSize() { + return this.conditionStack.length; + }, +options: {}, +performAction: function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) { +var YYSTATE=YY_START; +switch($avoiding_name_collisions) { +case 0:this.begin("letsquote"); return "QUOTE"; +break; +case 1:this.popState(); this.begin("endquote"); return "QUOTED_STRING"; +break; +case 2:this.popState(); return "QUOTE"; +break; +case 3:/* skip whitespace */ +break; +case 4:return "LEFT_PARENTHESE"; +break; +case 5:return "RIGHT_PARENTHESE"; +break; +case 6:return "AND"; +break; +case 7:return "OR"; +break; +case 8:return "NOT"; +break; +case 9:return "DEFINITION"; +break; +case 10:return 19; +break; +case 11:return 16; +break; +case 12:return 6; +break; +} +}, +rules: [/^(?:")/,/^(?:(\\"|[^"])*)/,/^(?:")/,/^(?:[^\S]+)/,/^(?:\()/,/^(?:\))/,/^(?:AND\b)/,/^(?:OR\b)/,/^(?:NOT\b)/,/^(?::)/,/^(?:(!?=|<=?|>=?))/,/^(?:[^\s\n"():><!=]+)/,/^(?:$)/], +conditions: {"endquote":{"rules":[2],"inclusive":false},"letsquote":{"rules":[1],"inclusive":false},"INITIAL":{"rules":[0,3,4,5,6,7,8,9,10,11,12],"inclusive":true}} +}); +return lexer; +})(); +parser.lexer = lexer; +function Parser () { + this.yy = {}; +} +Parser.prototype = parser;parser.Parser = Parser; +return new Parser; +})();; return parser.parse(string); +} // parseStringToObject + +;/*global RSVP, window, parseStringToObject*/ +/*jslint nomen: true, maxlen: 90*/ +(function (RSVP, window, parseStringToObject) { + "use strict"; + + var query_class_dict = {}, + regexp_escape = /[\-\[\]{}()*+?.,\\\^$|#\s]/g, + regexp_percent = /%/g, + regexp_underscore = /_/g, + regexp_operator = /^(?:AND|OR|NOT)$/i, + regexp_comparaison = /^(?:!?=|<=?|>=?)$/i; + + /** + * Convert metadata values to array of strings. ex: + * + * "a" -> ["a"], + * {"content": "a"} -> ["a"] + * + * @param {Any} value The metadata value + * @return {Array} The value in string array format + */ + function metadataValueToStringArray(value) { + var i, new_value = []; + if (value === undefined) { + return undefined; + } + if (!Array.isArray(value)) { + value = [value]; + } + for (i = 0; i < value.length; i += 1) { + if (typeof value[i] === 'object') { + new_value[i] = value[i].content; + } else { + new_value[i] = value[i]; + } + } + return new_value; + } + + /** + * A sort function to sort items by key + * + * @param {String} key The key to sort on + * @param {String} [way="ascending"] 'ascending' or 'descending' + * @return {Function} The sort function + */ + function sortFunction(key, way) { + var result; + if (way === 'descending') { + result = 1; + } else if (way === 'ascending') { + result = -1; + } else { + throw new TypeError("Query.sortFunction(): " + + "Argument 2 must be 'ascending' or 'descending'"); + } + return function (a, b) { + // this comparison is 5 times faster than json comparison + var i, l; + a = metadataValueToStringArray(a[key]) || []; + b = metadataValueToStringArray(b[key]) || []; + l = a.length > b.length ? a.length : b.length; + for (i = 0; i < l; i += 1) { + if (a[i] === undefined) { + return result; + } + if (b[i] === undefined) { + return -result; + } + if (a[i] > b[i]) { + return -result; + } + if (a[i] < b[i]) { + return result; + } + } + return 0; + }; + } + + /** + * Sort a list of items, according to keys and directions. + * + * @param {Array} sort_on_option List of couples [key, direction] + * @param {Array} list The item list to sort + * @return {Array} The filtered list + */ + function sortOn(sort_on_option, list) { + var sort_index; + if (!Array.isArray(sort_on_option)) { + throw new TypeError("jioquery.sortOn(): " + + "Argument 1 is not of type 'array'"); + } + for (sort_index = sort_on_option.length - 1; sort_index >= 0; + sort_index -= 1) { + list.sort(sortFunction( + sort_on_option[sort_index][0], + sort_on_option[sort_index][1] + )); + } + return list; + } + + /** + * Limit a list of items, according to index and length. + * + * @param {Array} limit_option A couple [from, length] + * @param {Array} list The item list to limit + * @return {Array} The filtered list + */ + function limit(limit_option, list) { + if (!Array.isArray(limit_option)) { + throw new TypeError("jioquery.limit(): " + + "Argument 1 is not of type 'array'"); + } + if (!Array.isArray(list)) { + throw new TypeError("jioquery.limit(): " + + "Argument 2 is not of type 'array'"); + } + list.splice(0, limit_option[0]); + if (limit_option[1]) { + list.splice(limit_option[1]); + } + return list; + } + + /** + * Filter a list of items, modifying them to select only wanted keys. + * + * @param {Array} select_option Key list to keep + * @param {Array} list The item list to filter + * @return {Array} The filtered list + */ + function select(select_option, list) { + var i, j, new_item; + if (!Array.isArray(select_option)) { + throw new TypeError("jioquery.select(): " + + "Argument 1 is not of type Array"); + } + if (!Array.isArray(list)) { + throw new TypeError("jioquery.select(): " + + "Argument 2 is not of type Array"); + } + for (i = 0; i < list.length; i += 1) { + new_item = {}; + for (j = 0; j < select_option.length; j += 1) { + if (list[i].hasOwnProperty([select_option[j]])) { + new_item[select_option[j]] = list[i][select_option[j]]; + } + } + for (j in new_item) { + if (new_item.hasOwnProperty(j)) { + list[i] = new_item; + break; + } + } + } + return list; + } + + /** + * The query to use to filter a list of objects. + * This is an abstract class. + * + * @class Query + * @constructor + */ + function Query() { + + /** + * Called before parsing the query. Must be overridden! + * + * @method onParseStart + * @param {Object} object The object shared in the parse process + * @param {Object} option Some option gave in parse() + */ + // this.onParseStart = emptyFunction; + + /** + * Called when parsing a simple query. Must be overridden! + * + * @method onParseSimpleQuery + * @param {Object} object The object shared in the parse process + * @param {Object} option Some option gave in parse() + */ + // this.onParseSimpleQuery = emptyFunction; + + /** + * Called when parsing a complex query. Must be overridden! + * + * @method onParseComplexQuery + * @param {Object} object The object shared in the parse process + * @param {Object} option Some option gave in parse() + */ + // this.onParseComplexQuery = emptyFunction; + + /** + * Called after parsing the query. Must be overridden! + * + * @method onParseEnd + * @param {Object} object The object shared in the parse process + * @param {Object} option Some option gave in parse() + */ + // this.onParseEnd = emptyFunction; + + return; + } + + /** + * Filter the item list with matching item only + * + * @method exec + * @param {Array} item_list The list of object + * @param {Object} [option] Some operation option + * @param {Array} [option.select_list] A object keys to retrieve + * @param {Array} [option.sort_on] Couples of object keys and "ascending" + * or "descending" + * @param {Array} [option.limit] Couple of integer, first is an index and + * second is the length. + */ + Query.prototype.exec = function (item_list, option) { + if (!Array.isArray(item_list)) { + throw new TypeError("Query().exec(): Argument 1 is not of type 'array'"); + } + if (option === undefined) { + option = {}; + } + if (typeof option !== 'object') { + throw new TypeError("Query().exec(): " + + "Optional argument 2 is not of type 'object'"); + } + var context = this, + i; + for (i = item_list.length - 1; i >= 0; i -= 1) { + if (!context.match(item_list[i])) { + item_list.splice(i, 1); + } + } + + if (option.sort_on) { + sortOn(option.sort_on, item_list); + } + + if (option.limit) { + limit(option.limit, item_list); + } + + select(option.select_list || [], item_list); + + return new RSVP.Queue() + .push(function () { + return item_list; + }); + }; + + /** + * Test if an item matches this query + * + * @method match + * @param {Object} item The object to test + * @return {Boolean} true if match, false otherwise + */ + Query.prototype.match = function () { + return true; + }; + + /** + * Browse the Query in deep calling parser method in each step. + * + * `onParseStart` is called first, on end `onParseEnd` is called. + * It starts from the simple queries at the bottom of the tree calling the + * parser method `onParseSimpleQuery`, and go up calling the + * `onParseComplexQuery` method. + * + * @method parse + * @param {Object} option Any options you want (except 'parsed') + * @return {Any} The parse result + */ + Query.prototype.parse = function (option) { + var that = this, + object; + /** + * The recursive parser. + * + * @param {Object} object The object shared in the parse process + * @param {Object} options Some options usable in the parseMethods + * @return {Any} The parser result + */ + function recParse(object, option) { + var query = object.parsed, + queue = new RSVP.Queue(), + i; + + function enqueue(j) { + queue + .push(function () { + object.parsed = query.query_list[j]; + return recParse(object, option); + }) + .push(function () { + query.query_list[j] = object.parsed; + }); + } + + if (query.type === "complex") { + + + for (i = 0; i < query.query_list.length; i += 1) { + enqueue(i); + } + + return queue + .push(function () { + object.parsed = query; + return that.onParseComplexQuery(object, option); + }); + + } + if (query.type === "simple") { + return that.onParseSimpleQuery(object, option); + } + } + object = { + parsed: JSON.parse(JSON.stringify(that.serialized())) + }; + return new RSVP.Queue() + .push(function () { + return that.onParseStart(object, option); + }) + .push(function () { + return recParse(object, option); + }) + .push(function () { + return that.onParseEnd(object, option); + }) + .push(function () { + return object.parsed; + }); + + }; + + /** + * Convert this query to a parsable string. + * + * @method toString + * @return {String} The string version of this query + */ + Query.prototype.toString = function () { + return ""; + }; + + /** + * Convert this query to an jsonable object in order to be remake thanks to + * QueryFactory class. + * + * @method serialized + * @return {Object} The jsonable object + */ + Query.prototype.serialized = function () { + return undefined; + }; + + /** + * Provides static methods to create Query object + * + * @class QueryFactory + */ + function QueryFactory() { + return; + } + + /** + * Escapes regexp special chars from a string. + * + * @param {String} string The string to escape + * @return {String} The escaped string + */ + function stringEscapeRegexpCharacters(string) { + return string.replace(regexp_escape, "\\$&"); + } + + /** + * Inherits the prototype methods from one constructor into another. The + * prototype of `constructor` will be set to a new object created from + * `superConstructor`. + * + * @param {Function} constructor The constructor which inherits the super one + * @param {Function} superConstructor The super constructor + */ + function inherits(constructor, superConstructor) { + constructor.super_ = superConstructor; + constructor.prototype = Object.create(superConstructor.prototype, { + "constructor": { + "configurable": true, + "enumerable": false, + "writable": true, + "value": constructor + } + }); + } + + /** + * Convert a search text to a regexp. + * + * @param {String} string The string to convert + * @param {Boolean} [use_wildcard_character=true] Use wildcard "%" and "_" + * @return {RegExp} The search text regexp + */ + function searchTextToRegExp(string, use_wildcard_characters) { + if (typeof string !== 'string') { + throw new TypeError("jioquery.searchTextToRegExp(): " + + "Argument 1 is not of type 'string'"); + } + if (use_wildcard_characters === false) { + return new RegExp("^" + stringEscapeRegexpCharacters(string) + "$"); + } + return new RegExp("^" + stringEscapeRegexpCharacters(string) + .replace(regexp_percent, '.*') + .replace(regexp_underscore, '.') + "$"); + } + + /** + * The ComplexQuery inherits from Query, and compares one or several metadata + * values. + * + * @class ComplexQuery + * @extends Query + * @param {Object} [spec={}] The specifications + * @param {String} [spec.operator="AND"] The compare method to use + * @param {String} spec.key The metadata key + * @param {String} spec.value The value of the metadata to compare + */ + function ComplexQuery(spec, key_schema) { + Query.call(this); + + /** + * Logical operator to use to compare object values + * + * @attribute operator + * @type String + * @default "AND" + * @optional + */ + this.operator = spec.operator; + + /** + * The sub Query list which are used to query an item. + * + * @attribute query_list + * @type Array + * @default [] + * @optional + */ + this.query_list = spec.query_list || []; + this.query_list = this.query_list.map( + // decorate the map to avoid sending the index as key_schema argument + function (o) { return QueryFactory.create(o, key_schema); } + ); + + } + inherits(ComplexQuery, Query); + + ComplexQuery.prototype.operator = "AND"; + ComplexQuery.prototype.type = "complex"; + + /** + * #crossLink "Query/match:method" + */ + ComplexQuery.prototype.match = function (item) { + var operator = this.operator; + if (!(regexp_operator.test(operator))) { + operator = "AND"; + } + return this[operator.toUpperCase()](item); + }; + + /** + * #crossLink "Query/toString:method" + */ + ComplexQuery.prototype.toString = function () { + var str_list = [], this_operator = this.operator; + if (this.operator === "NOT") { + str_list.push("NOT ("); + str_list.push(this.query_list[0].toString()); + str_list.push(")"); + return str_list.join(" "); + } + this.query_list.forEach(function (query) { + str_list.push("("); + str_list.push(query.toString()); + str_list.push(")"); + str_list.push(this_operator); + }); + str_list.length -= 1; + return str_list.join(" "); + }; + + /** + * #crossLink "Query/serialized:method" + */ + ComplexQuery.prototype.serialized = function () { + var s = { + "type": "complex", + "operator": this.operator, + "query_list": [] + }; + this.query_list.forEach(function (query) { + s.query_list.push( + typeof query.toJSON === "function" ? query.toJSON() : query + ); + }); + return s; + }; + ComplexQuery.prototype.toJSON = ComplexQuery.prototype.serialized; + + /** + * Comparison operator, test if all sub queries match the + * item value + * + * @method AND + * @param {Object} item The item to match + * @return {Boolean} true if all match, false otherwise + */ + ComplexQuery.prototype.AND = function (item) { + var result = true, + i = 0; + + while (result && (i !== this.query_list.length)) { + result = this.query_list[i].match(item); + i += 1; + } + return result; + + }; + + /** + * Comparison operator, test if one of the sub queries matches the + * item value + * + * @method OR + * @param {Object} item The item to match + * @return {Boolean} true if one match, false otherwise + */ + ComplexQuery.prototype.OR = function (item) { + var result = false, + i = 0; + + while ((!result) && (i !== this.query_list.length)) { + result = this.query_list[i].match(item); + i += 1; + } + + return result; + }; + + /** + * Comparison operator, test if the sub query does not match the + * item value + * + * @method NOT + * @param {Object} item The item to match + * @return {Boolean} true if one match, false otherwise + */ + ComplexQuery.prototype.NOT = function (item) { + return !this.query_list[0].match(item); + }; + + /** + * Creates Query object from a search text string or a serialized version + * of a Query. + * + * @method create + * @static + * @param {Object,String} object The search text or the serialized version + * of a Query + * @return {Query} A Query object + */ + QueryFactory.create = function (object, key_schema) { + if (object === "") { + return new Query(); + } + if (typeof object === "string") { + object = parseStringToObject(object); + } + if (typeof (object || {}).type === "string" && + query_class_dict[object.type]) { + return new query_class_dict[object.type](object, key_schema); + } + throw new TypeError("QueryFactory.create(): " + + "Argument 1 is not a search text or a parsable object"); + }; + + function objectToSearchText(query) { + var str_list = []; + if (query.type === "complex") { + str_list.push("("); + (query.query_list || []).forEach(function (sub_query) { + str_list.push(objectToSearchText(sub_query)); + str_list.push(query.operator); + }); + str_list.length -= 1; + str_list.push(")"); + return str_list.join(" "); + } + if (query.type === "simple") { + return (query.key ? query.key + ": " : "") + + (query.operator || "") + ' "' + query.value + '"'; + } + throw new TypeError("This object is not a query"); + } + + function checkKeySchema(key_schema) { + var prop; + + if (key_schema !== undefined) { + if (typeof key_schema !== 'object') { + throw new TypeError("SimpleQuery().create(): " + + "key_schema is not of type 'object'"); + } + // key_set is mandatory + if (key_schema.key_set === undefined) { + throw new TypeError("SimpleQuery().create(): " + + "key_schema has no 'key_set' property"); + } + for (prop in key_schema) { + if (key_schema.hasOwnProperty(prop)) { + switch (prop) { + case 'key_set': + case 'cast_lookup': + case 'match_lookup': + break; + default: + throw new TypeError("SimpleQuery().create(): " + + "key_schema has unknown property '" + prop + "'"); + } + } + } + } + } + + /** + * The SimpleQuery inherits from Query, and compares one metadata value + * + * @class SimpleQuery + * @extends Query + * @param {Object} [spec={}] The specifications + * @param {String} [spec.operator="="] The compare method to use + * @param {String} spec.key The metadata key + * @param {String} spec.value The value of the metadata to compare + */ + function SimpleQuery(spec, key_schema) { + Query.call(this); + + checkKeySchema(key_schema); + + this._key_schema = key_schema || {}; + + /** + * Operator to use to compare object values + * + * @attribute operator + * @type String + * @optional + */ + this.operator = spec.operator; + + /** + * Key of the object which refers to the value to compare + * + * @attribute key + * @type String + */ + this.key = spec.key; + + /** + * Value is used to do the comparison with the object value + * + * @attribute value + * @type String + */ + this.value = spec.value; + + } + inherits(SimpleQuery, Query); + + SimpleQuery.prototype.type = "simple"; + + function checkKey(key) { + var prop; + + if (key.read_from === undefined) { + throw new TypeError("Custom key is missing the read_from property"); + } + + for (prop in key) { + if (key.hasOwnProperty(prop)) { + switch (prop) { + case 'read_from': + case 'cast_to': + case 'equal_match': + break; + default: + throw new TypeError("Custom key has unknown property '" + + prop + "'"); + } + } + } + } + + /** + * #crossLink "Query/match:method" + */ + SimpleQuery.prototype.match = function (item) { + var object_value = null, + equal_match = null, + cast_to = null, + matchMethod = null, + operator = this.operator, + value = null, + key = this.key; + + if (!(regexp_comparaison.test(operator))) { + // `operator` is not correct, we have to change it to "like" or "=" + if (regexp_percent.test(this.value)) { + // `value` contains a non escaped `%` + operator = "like"; + } else { + // `value` does not contain non escaped `%` + operator = "="; + } + } + + matchMethod = this[operator]; + + if (this._key_schema.key_set && this._key_schema.key_set[key] !== undefined) { + key = this._key_schema.key_set[key]; + } + + if (typeof key === 'object') { + checkKey(key); + object_value = item[key.read_from]; + + equal_match = key.equal_match; + + // equal_match can be a string + if (typeof equal_match === 'string') { + // XXX raise error if equal_match not in match_lookup + equal_match = this._key_schema.match_lookup[equal_match]; + } + + // equal_match overrides the default '=' operator + if (equal_match !== undefined) { + matchMethod = (operator === "=" || operator === "like" ? + equal_match : matchMethod); + } + + value = this.value; + cast_to = key.cast_to; + if (cast_to) { + // cast_to can be a string + if (typeof cast_to === 'string') { + // XXX raise error if cast_to not in cast_lookup + cast_to = this._key_schema.cast_lookup[cast_to]; + } + + try { + value = cast_to(value); + } catch (e) { + value = undefined; + } + + try { + object_value = cast_to(object_value); + } catch (e) { + object_value = undefined; + } + } + } else { + object_value = item[key]; + value = this.value; + } + if (object_value === undefined || value === undefined) { + return false; + } + return matchMethod(object_value, value); + }; + + /** + * #crossLink "Query/toString:method" + */ + SimpleQuery.prototype.toString = function () { + return (this.key ? this.key + ":" : "") + + (this.operator ? " " + this.operator : "") + ' "' + this.value + '"'; + }; + + /** + * #crossLink "Query/serialized:method" + */ + SimpleQuery.prototype.serialized = function () { + var object = { + "type": "simple", + "key": this.key, + "value": this.value + }; + if (this.operator !== undefined) { + object.operator = this.operator; + } + return object; + }; + SimpleQuery.prototype.toJSON = SimpleQuery.prototype.serialized; + + /** + * Comparison operator, test if this query value matches the item value + * + * @method = + * @param {String} object_value The value to compare + * @param {String} comparison_value The comparison value + * @return {Boolean} true if match, false otherwise + */ + SimpleQuery.prototype["="] = function (object_value, comparison_value) { + var value, i; + if (!Array.isArray(object_value)) { + object_value = [object_value]; + } + for (i = 0; i < object_value.length; i += 1) { + value = object_value[i]; + if (typeof value === 'object' && value.hasOwnProperty('content')) { + value = value.content; + } + if (typeof value.cmp === "function") { + return (value.cmp(comparison_value) === 0); + } + if (comparison_value.toString() === value.toString()) { + return true; + } + } + return false; + }; + + /** + * Comparison operator, test if this query value matches the item value + * + * @method like + * @param {String} object_value The value to compare + * @param {String} comparison_value The comparison value + * @return {Boolean} true if match, false otherwise + */ + SimpleQuery.prototype.like = function (object_value, comparison_value) { + var value, i; + if (!Array.isArray(object_value)) { + object_value = [object_value]; + } + for (i = 0; i < object_value.length; i += 1) { + value = object_value[i]; + if (typeof value === 'object' && value.hasOwnProperty('content')) { + value = value.content; + } + if (typeof value.cmp === "function") { + return (value.cmp(comparison_value) === 0); + } + if ( + searchTextToRegExp(comparison_value.toString()).test(value.toString()) + ) { + return true; + } + } + return false; + }; + + /** + * Comparison operator, test if this query value does not match the item value + * + * @method != + * @param {String} object_value The value to compare + * @param {String} comparison_value The comparison value + * @return {Boolean} true if not match, false otherwise + */ + SimpleQuery.prototype["!="] = function (object_value, comparison_value) { + var value, i; + if (!Array.isArray(object_value)) { + object_value = [object_value]; + } + for (i = 0; i < object_value.length; i += 1) { + value = object_value[i]; + if (typeof value === 'object' && value.hasOwnProperty('content')) { + value = value.content; + } + if (typeof value.cmp === "function") { + return (value.cmp(comparison_value) !== 0); + } + if (comparison_value.toString() === value.toString()) { + return false; + } + } + return true; + }; + + /** + * Comparison operator, test if this query value is lower than the item value + * + * @method < + * @param {Number, String} object_value The value to compare + * @param {Number, String} comparison_value The comparison value + * @return {Boolean} true if lower, false otherwise + */ + SimpleQuery.prototype["<"] = function (object_value, comparison_value) { + var value; + if (!Array.isArray(object_value)) { + object_value = [object_value]; + } + value = object_value[0]; + if (typeof value === 'object' && value.hasOwnProperty('content')) { + value = value.content; + } + if (typeof value.cmp === "function") { + return (value.cmp(comparison_value) < 0); + } + return (value < comparison_value); + }; + + /** + * Comparison operator, test if this query value is equal or lower than the + * item value + * + * @method <= + * @param {Number, String} object_value The value to compare + * @param {Number, String} comparison_value The comparison value + * @return {Boolean} true if equal or lower, false otherwise + */ + SimpleQuery.prototype["<="] = function (object_value, comparison_value) { + var value; + if (!Array.isArray(object_value)) { + object_value = [object_value]; + } + value = object_value[0]; + if (typeof value === 'object' && value.hasOwnProperty('content')) { + value = value.content; + } + if (typeof value.cmp === "function") { + return (value.cmp(comparison_value) <= 0); + } + return (value <= comparison_value); + }; + + /** + * Comparison operator, test if this query value is greater than the item + * value + * + * @method > + * @param {Number, String} object_value The value to compare + * @param {Number, String} comparison_value The comparison value + * @return {Boolean} true if greater, false otherwise + */ + SimpleQuery.prototype[">"] = function (object_value, comparison_value) { + var value; + if (!Array.isArray(object_value)) { + object_value = [object_value]; + } + value = object_value[0]; + if (typeof value === 'object' && value.hasOwnProperty('content')) { + value = value.content; + } + if (typeof value.cmp === "function") { + return (value.cmp(comparison_value) > 0); + } + return (value > comparison_value); + }; + + /** + * Comparison operator, test if this query value is equal or greater than the + * item value + * + * @method >= + * @param {Number, String} object_value The value to compare + * @param {Number, String} comparison_value The comparison value + * @return {Boolean} true if equal or greater, false otherwise + */ + SimpleQuery.prototype[">="] = function (object_value, comparison_value) { + var value; + if (!Array.isArray(object_value)) { + object_value = [object_value]; + } + value = object_value[0]; + if (typeof value === 'object' && value.hasOwnProperty('content')) { + value = value.content; + } + if (typeof value.cmp === "function") { + return (value.cmp(comparison_value) >= 0); + } + return (value >= comparison_value); + }; + + query_class_dict.simple = SimpleQuery; + query_class_dict.complex = ComplexQuery; + + Query.parseStringToObject = parseStringToObject; + Query.objectToSearchText = objectToSearchText; + + window.Query = Query; + window.SimpleQuery = SimpleQuery; + window.ComplexQuery = ComplexQuery; + window.QueryFactory = QueryFactory; + +}(RSVP, window, parseStringToObject)); +;/*global window, moment */ +/*jslint nomen: true, maxlen: 200*/ +(function (window, moment) { + "use strict"; + +// /** +// * Add a secured (write permission denied) property to an object. +// * +// * @param {Object} object The object to fill +// * @param {String} key The object key where to store the property +// * @param {Any} value The value to store +// */ +// function _export(key, value) { +// Object.defineProperty(to_export, key, { +// "configurable": false, +// "enumerable": true, +// "writable": false, +// "value": value +// }); +// } + + var YEAR = 'year', + MONTH = 'month', + DAY = 'day', + HOUR = 'hour', + MIN = 'minute', + SEC = 'second', + MSEC = 'millisecond', + precision_grade = { + 'year': 0, + 'month': 1, + 'day': 2, + 'hour': 3, + 'minute': 4, + 'second': 5, + 'millisecond': 6 + }, + lesserPrecision = function (p1, p2) { + return (precision_grade[p1] < precision_grade[p2]) ? p1 : p2; + }, + JIODate; + + + JIODate = function (str) { + // in case of forgotten 'new' + if (!(this instanceof JIODate)) { + return new JIODate(str); + } + + if (str instanceof JIODate) { + this.mom = str.mom.clone(); + this._precision = str._precision; + return; + } + + if (str === undefined) { + this.mom = moment(); + this.setPrecision(MSEC); + return; + } + + this.mom = null; + this._str = str; + + // http://www.w3.org/TR/NOTE-datetime + // http://dotat.at/tmp/ISO_8601-2004_E.pdf + + // XXX these regexps fail to detect many invalid dates. + + if (str.match(/\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+\-][0-2]\d:[0-5]\d|Z)/) + || str.match(/\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d/)) { + // ISO, milliseconds + this.mom = moment(str); + this.setPrecision(MSEC); + } else if (str.match(/\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+\-][0-2]\d:[0-5]\d|Z)/) + || str.match(/\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d/)) { + // ISO, seconds + this.mom = moment(str); + this.setPrecision(SEC); + } else if (str.match(/\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+\-][0-2]\d:[0-5]\d|Z)/) + || str.match(/\d\d\d\d-\d\d-\d\d \d\d:\d\d/)) { + // ISO, minutes + this.mom = moment(str); + this.setPrecision(MIN); + } else if (str.match(/\d\d\d\d-\d\d-\d\d \d\d/)) { + this.mom = moment(str); + this.setPrecision(HOUR); + } else if (str.match(/\d\d\d\d-\d\d-\d\d/)) { + this.mom = moment(str); + this.setPrecision(DAY); + } else if (str.match(/\d\d\d\d-\d\d/)) { + this.mom = moment(str); + this.setPrecision(MONTH); + } else if (str.match(/\d\d\d\d/)) { + // Creating a moment with only the year will show this deprecation + // warning: + // + // Deprecation warning: moment construction falls back to js Date. This is + // discouraged and will be removed in upcoming major release. Please refer + // to https://github.com/moment/moment/issues/1407 for more info. + // + // TL;DR: parsing year-only strings with momentjs falls back to native + // Date and it won't correctly represent the year in local time if UTF + // offset is negative. + // + // The solution is to use the format parameter, so momentjs won't fall + // back to the native Date and we will have the correct year in local + // time. + // + this.mom = moment(str, 'YYYY'); + this.setPrecision(YEAR); + } + + if (!this.mom) { + throw new Error("Cannot parse: " + str); + } + + }; + + + JIODate.prototype.setPrecision = function (prec) { + this._precision = prec; + }; + + + JIODate.prototype.getPrecision = function () { + return this._precision; + }; + + + JIODate.prototype.cmp = function (other) { + var m1 = this.mom, + m2 = other.mom, + p = lesserPrecision(this._precision, other._precision); + return m1.isBefore(m2, p) ? -1 : (m1.isSame(m2, p) ? 0 : +1); + }; + + + JIODate.prototype.toPrecisionString = function (precision) { + var fmt; + + precision = precision || this._precision; + + fmt = { + 'millisecond': 'YYYY-MM-DD HH:mm:ss.SSS', + 'second': 'YYYY-MM-DD HH:mm:ss', + 'minute': 'YYYY-MM-DD HH:mm', + 'hour': 'YYYY-MM-DD HH', + 'day': 'YYYY-MM-DD', + 'month': 'YYYY-MM', + 'year': 'YYYY' + }[precision]; + + if (!fmt) { + throw new TypeError("Unsupported precision value '" + precision + "'"); + } + + return this.mom.format(fmt); + }; + + + JIODate.prototype.toString = function () { + return this._str; + }; + + +// _export('JIODate', JIODate); +// +// _export('YEAR', YEAR); +// _export('MONTH', MONTH); +// _export('DAY', DAY); +// _export('HOUR', HOUR); +// _export('MIN', MIN); +// _export('SEC', SEC); +// _export('MSEC', MSEC); + + window.jiodate = { + JIODate: JIODate, + YEAR: YEAR, + MONTH: MONTH, + DAY: DAY, + HOUR: HOUR, + MIN: MIN, + SEC: SEC, + MSEC: MSEC + }; +}(window, moment)); +;/*global window, RSVP, Blob, XMLHttpRequest, QueryFactory, Query, atob, + FileReader, ArrayBuffer, Uint8Array */ +(function (window, RSVP, Blob, QueryFactory, Query, atob, + FileReader, ArrayBuffer, Uint8Array) { + "use strict"; + + var util = {}, + jIO; + + function jIOError(message, status_code) { + if ((message !== undefined) && (typeof message !== "string")) { + throw new TypeError('You must pass a string.'); + } + this.message = message || "Default Message"; + this.status_code = status_code || 500; + } + jIOError.prototype = new Error(); + jIOError.prototype.constructor = jIOError; + util.jIOError = jIOError; + + /** + * Send request with XHR and return a promise. xhr.onload: The promise is + * resolved when the status code is lower than 400 with the xhr object as + * first parameter. xhr.onerror: reject with xhr object as first + * parameter. xhr.onprogress: notifies the xhr object. + * + * @param {Object} param The parameters + * @param {String} [param.type="GET"] The request method + * @param {String} [param.dataType=""] The data type to retrieve + * @param {String} param.url The url + * @param {Any} [param.data] The data to send + * @param {Function} [param.beforeSend] A function called just before the + * send request. The first parameter of this function is the XHR object. + * @return {Promise} The promise + */ + function ajax(param) { + var xhr = new XMLHttpRequest(); + return new RSVP.Promise(function (resolve, reject, notify) { + var k; + xhr.open(param.type || "GET", param.url, true); + xhr.responseType = param.dataType || ""; + if (typeof param.headers === 'object' && param.headers !== null) { + for (k in param.headers) { + if (param.headers.hasOwnProperty(k)) { + xhr.setRequestHeader(k, param.headers[k]); + } + } + } + xhr.addEventListener("load", function (e) { + if (e.target.status >= 400) { + return reject(e); + } + resolve(e); + }); + xhr.addEventListener("error", reject); + xhr.addEventListener("progress", notify); + if (typeof param.xhrFields === 'object' && param.xhrFields !== null) { + for (k in param.xhrFields) { + if (param.xhrFields.hasOwnProperty(k)) { + xhr[k] = param.xhrFields[k]; + } + } + } + if (typeof param.beforeSend === 'function') { + param.beforeSend(xhr); + } + xhr.send(param.data); + }, function () { + xhr.abort(); + }); + } + util.ajax = ajax; + + function readBlobAsText(blob, encoding) { + var fr = new FileReader(); + return new RSVP.Promise(function (resolve, reject, notify) { + fr.addEventListener("load", resolve); + fr.addEventListener("error", reject); + fr.addEventListener("progress", notify); + fr.readAsText(blob, encoding); + }, function () { + fr.abort(); + }); + } + util.readBlobAsText = readBlobAsText; + + function readBlobAsArrayBuffer(blob) { + var fr = new FileReader(); + return new RSVP.Promise(function (resolve, reject, notify) { + fr.addEventListener("load", resolve); + fr.addEventListener("error", reject); + fr.addEventListener("progress", notify); + fr.readAsArrayBuffer(blob); + }, function () { + fr.abort(); + }); + } + util.readBlobAsArrayBuffer = readBlobAsArrayBuffer; + + function readBlobAsDataURL(blob) { + var fr = new FileReader(); + return new RSVP.Promise(function (resolve, reject, notify) { + fr.addEventListener("load", resolve); + fr.addEventListener("error", reject); + fr.addEventListener("progress", notify); + fr.readAsDataURL(blob); + }, function () { + fr.abort(); + }); + } + util.readBlobAsDataURL = readBlobAsDataURL; + + // https://gist.github.com/davoclavo/4424731 + function dataURItoBlob(dataURI) { + // convert base64 to raw binary data held in a string + var byteString = atob(dataURI.split(',')[1]), + // separate out the mime component + mimeString = dataURI.split(',')[0].split(':')[1], + // write the bytes of the string to an ArrayBuffer + arrayBuffer = new ArrayBuffer(byteString.length), + _ia = new Uint8Array(arrayBuffer), + i; + mimeString = mimeString.slice(0, mimeString.length - ";base64".length); + for (i = 0; i < byteString.length; i += 1) { + _ia[i] = byteString.charCodeAt(i); + } + return new Blob([arrayBuffer], {type: mimeString}); + } + + util.dataURItoBlob = dataURItoBlob; + + // tools + function checkId(argument_list, storage, method_name) { + if (typeof argument_list[0] !== 'string' || argument_list[0] === '') { + throw new jIO.util.jIOError( + "Document id must be a non empty string on '" + storage.__type + + "." + method_name + "'.", + 400 + ); + } + } + + function checkAttachmentId(argument_list, storage, method_name) { + if (typeof argument_list[1] !== 'string' || argument_list[1] === '') { + throw new jIO.util.jIOError( + "Attachment id must be a non empty string on '" + storage.__type + + "." + method_name + "'.", + 400 + ); + } + } + + function declareMethod(klass, name, precondition_function, post_function) { + klass.prototype[name] = function () { + var argument_list = arguments, + context = this, + precondition_result; + + return new RSVP.Queue() + .push(function () { + if (precondition_function !== undefined) { + return precondition_function.apply( + context.__storage, + [argument_list, context, name] + ); + } + }) + .push(function (result) { + var storage_method = context.__storage[name]; + precondition_result = result; + if (storage_method === undefined) { + throw new jIO.util.jIOError( + "Capacity '" + name + "' is not implemented on '" + + context.__type + "'", + 501 + ); + } + return storage_method.apply( + context.__storage, + argument_list + ); + }) + .push(function (result) { + if (post_function !== undefined) { + return post_function.call( + context, + argument_list, + result, + precondition_result + ); + } + return result; + }); + }; + // Allow chain + return this; + } + + + + + ///////////////////////////////////////////////////////////////// + // jIO Storage Proxy + ///////////////////////////////////////////////////////////////// + function JioProxyStorage(type, storage) { + if (!(this instanceof JioProxyStorage)) { + return new JioProxyStorage(); + } + this.__type = type; + this.__storage = storage; + } + + declareMethod(JioProxyStorage, "put", checkId, function (argument_list) { + return argument_list[0]; + }); + declareMethod(JioProxyStorage, "get", checkId); + declareMethod(JioProxyStorage, "bulk"); + declareMethod(JioProxyStorage, "remove", checkId, function (argument_list) { + return argument_list[0]; + }); + + JioProxyStorage.prototype.post = function () { + var context = this, + argument_list = arguments; + return new RSVP.Queue() + .push(function () { + var storage_method = context.__storage.post; + if (storage_method === undefined) { + throw new jIO.util.jIOError( + "Capacity 'post' is not implemented on '" + context.__type + "'", + 501 + ); + } + return context.__storage.post.apply(context.__storage, argument_list); + }); + }; + + declareMethod(JioProxyStorage, 'putAttachment', function (argument_list, + storage, + method_name) { + checkId(argument_list, storage, method_name); + checkAttachmentId(argument_list, storage, method_name); + + var options = argument_list[3] || {}; + + if (typeof argument_list[2] === 'string') { + argument_list[2] = new Blob([argument_list[2]], { + "type": options._content_type || options._mimetype || + "text/plain;charset=utf-8" + }); + } else if (!(argument_list[2] instanceof Blob)) { + throw new jIO.util.jIOError( + 'Attachment content is not a blob', + 400 + ); + } + }); + + declareMethod(JioProxyStorage, 'removeAttachment', function (argument_list, + storage, + method_name) { + checkId(argument_list, storage, method_name); + checkAttachmentId(argument_list, storage, method_name); + }); + + declareMethod(JioProxyStorage, 'getAttachment', function (argument_list, + storage, + method_name) { + var result = "blob"; +// if (param.storage_spec.type !== "indexeddb" && +// param.storage_spec.type !== "dav" && +// (param.kwargs._start !== undefined +// || param.kwargs._end !== undefined)) { +// restCommandRejecter(param, [ +// 'bad_request', +// 'unsupport', +// '_start, _end not support' +// ]); +// return false; +// } + checkId(argument_list, storage, method_name); + checkAttachmentId(argument_list, storage, method_name); + // Drop optional parameters, which are only used in postfunction + if (argument_list[2] !== undefined) { + result = argument_list[2].format || result; + delete argument_list[2].format; + } + return result; + }, function (argument_list, blob, convert) { + var result; + if (!(blob instanceof Blob)) { + throw new jIO.util.jIOError( + "'getAttachment' (" + argument_list[0] + " , " + + argument_list[1] + ") on '" + this.__type + + "' does not return a Blob.", + 501 + ); + } + if (convert === "blob") { + result = blob; + } else if (convert === "data_url") { + result = new RSVP.Queue() + .push(function () { + return jIO.util.readBlobAsDataURL(blob); + }) + .push(function (evt) { + return evt.target.result; + }); + } else if (convert === "array_buffer") { + result = new RSVP.Queue() + .push(function () { + return jIO.util.readBlobAsArrayBuffer(blob); + }) + .push(function (evt) { + return evt.target.result; + }); + } else if (convert === "text") { + result = new RSVP.Queue() + .push(function () { + return jIO.util.readBlobAsText(blob); + }) + .push(function (evt) { + return evt.target.result; + }); + } else if (convert === "json") { + result = new RSVP.Queue() + .push(function () { + return jIO.util.readBlobAsText(blob); + }) + .push(function (evt) { + return JSON.parse(evt.target.result); + }); + } else { + throw new jIO.util.jIOError( + this.__type + ".getAttachment format: '" + convert + + "' is not supported", + 400 + ); + } + return result; + }); + + JioProxyStorage.prototype.buildQuery = function () { + var storage_method = this.__storage.buildQuery, + context = this, + argument_list = arguments; + if (storage_method === undefined) { + throw new jIO.util.jIOError( + "Capacity 'buildQuery' is not implemented on '" + this.__type + "'", + 501 + ); + } + return new RSVP.Queue() + .push(function () { + return storage_method.apply( + context.__storage, + argument_list + ); + }); + }; + + JioProxyStorage.prototype.hasCapacity = function (name) { + var storage_method = this.__storage.hasCapacity, + capacity_method = this.__storage[name]; + if (capacity_method !== undefined) { + return true; + } + if ((storage_method === undefined) || + !storage_method.apply(this.__storage, arguments)) { + throw new jIO.util.jIOError( + "Capacity '" + name + "' is not implemented on '" + this.__type + "'", + 501 + ); + } + return true; + }; + + JioProxyStorage.prototype.allDocs = function (options) { + var context = this; + if (options === undefined) { + options = {}; + } + return new RSVP.Queue() + .push(function () { + if (context.hasCapacity("list") && + ((options.query === undefined) || context.hasCapacity("query")) && + ((options.sort_on === undefined) || context.hasCapacity("sort")) && + ((options.select_list === undefined) || + context.hasCapacity("select")) && + ((options.include_docs === undefined) || + context.hasCapacity("include")) && + ((options.limit === undefined) || context.hasCapacity("limit"))) { + return context.buildQuery(options); + } + }) + .push(function (result) { + return { + data: { + rows: result, + total_rows: result.length + } + }; + }); + }; + + declareMethod(JioProxyStorage, "allAttachments", checkId); + declareMethod(JioProxyStorage, "repair"); + + JioProxyStorage.prototype.repair = function () { + var context = this, + argument_list = arguments; + return new RSVP.Queue() + .push(function () { + var storage_method = context.__storage.repair; + if (storage_method !== undefined) { + return context.__storage.repair.apply(context.__storage, + argument_list); + } + }); + }; + + ///////////////////////////////////////////////////////////////// + // Storage builder + ///////////////////////////////////////////////////////////////// + function JioBuilder() { + if (!(this instanceof JioBuilder)) { + return new JioBuilder(); + } + this.__storage_types = {}; + } + + JioBuilder.prototype.createJIO = function (storage_spec, util) { + + if (typeof storage_spec.type !== 'string') { + throw new TypeError("Invalid storage description"); + } + if (!this.__storage_types[storage_spec.type]) { + throw new TypeError("Unknown storage '" + storage_spec.type + "'"); + } + + return new JioProxyStorage( + storage_spec.type, + new this.__storage_types[storage_spec.type](storage_spec, util) + ); + + }; + + JioBuilder.prototype.addStorage = function (type, Constructor) { + if (typeof type !== 'string') { + throw new TypeError( + "jIO.addStorage(): Argument 1 is not of type 'string'" + ); + } + if (typeof Constructor !== 'function') { + throw new TypeError("jIO.addStorage(): " + + "Argument 2 is not of type 'function'"); } if (this.__storage_types[type] !== undefined) { throw new TypeError("jIO.addStorage(): Storage type already exists"); @@ -6469,7 +8158,7 @@ if ((error_count = __NODEJS_parse(string, error_offsets, error_lookaheads)) > 0) var ceilHeapSize = function (v) { // The asm.js spec says: // The heap object's byteLength must be either - // 2^n for n in [12, 24) or 2^24 * n for n ≥ 1. + // 2^n for n in [12, 24) or 2^24 * n for n ≥ 1. // Also, byteLengths smaller than 2^16 are deprecated. var p; // If v is smaller than 2^16, the smallest possible solution @@ -6900,7 +8589,9 @@ if ((error_count = __NODEJS_parse(string, error_offsets, error_lookaheads)) > 0) id, options); } // Already exists on destination - throw new jIO.util.jIOError("Conflict on '" + id + "'", + throw new jIO.util.jIOError("Conflict on '" + id + "': " + + JSON.stringify(doc) + " !== " + + JSON.stringify(remote_doc), 409); }); } @@ -6971,11 +8662,12 @@ if ((error_count = __NODEJS_parse(string, error_offsets, error_lookaheads)) > 0) } function checkSignatureDifference(queue, source, destination, id, - conflict_force, conflict_ignore) { + conflict_force, conflict_ignore, + getMethod) { queue .push(function () { return RSVP.all([ - source.get(id), + getMethod(id), context._signature_sub_storage.get(id) ]); }) @@ -7004,7 +8696,9 @@ if ((error_count = __NODEJS_parse(string, error_offsets, error_lookaheads)) > 0) return; } if (conflict_force !== true) { - throw new jIO.util.jIOError("Conflict on '" + id + "'", + throw new jIO.util.jIOError("Conflict on '" + id + "': " + + JSON.stringify(doc) + " !== " + + JSON.stringify(remote_doc), 409); } } @@ -7023,6 +8717,35 @@ if ((error_count = __NODEJS_parse(string, error_offsets, error_lookaheads)) > 0) }); } + function checkBulkSignatureDifference(queue, source, destination, id_list, + conflict_force, conflict_ignore) { + queue + .push(function () { + return source.bulk(id_list); + }) + .push(function (result_list) { + var i, + sub_queue = new RSVP.Queue(); + + function getResult(j) { + return function (id) { + if (id !== id_list[j].parameter_list[0]) { + throw new Error("Does not access expected ID " + id); + } + return result_list[j]; + }; + } + + for (i = 0; i < result_list.length; i += 1) { + checkSignatureDifference(sub_queue, source, destination, + id_list[i].parameter_list[0], + conflict_force, conflict_ignore, + getResult(i)); + } + return sub_queue; + }); + } + function pushStorage(source, destination, options) { var queue = new RSVP.Queue(); if (!options.hasOwnProperty("use_post")) { @@ -7039,6 +8762,7 @@ if ((error_count = __NODEJS_parse(string, error_offsets, error_lookaheads)) > 0) var i, local_dict = {}, new_list = [], + change_list = [], signature_dict = {}, key; for (i = 0; i < result_list[0].data.total_rows; i += 1) { @@ -7081,9 +8805,17 @@ if ((error_count = __NODEJS_parse(string, error_offsets, error_lookaheads)) > 0) if (signature_dict.hasOwnProperty(key)) { if (local_dict.hasOwnProperty(key)) { if (options.check_modification === true) { - checkSignatureDifference(queue, source, destination, key, - options.conflict_force, - options.conflict_ignore); + if (options.use_bulk_get === true) { + change_list.push({ + method: "get", + parameter_list: [key] + }); + } else { + checkSignatureDifference(queue, source, destination, key, + options.conflict_force, + options.conflict_ignore, + source.get.bind(source)); + } } } else { if (options.check_deletion === true) { @@ -7092,6 +8824,12 @@ if ((error_count = __NODEJS_parse(string, error_offsets, error_lookaheads)) > 0) } } } + if ((options.use_bulk_get === true) && (change_list.length !== 0)) { + checkBulkSignatureDifference(queue, source, destination, + change_list, + options.conflict_force, + options.conflict_ignore); + } }); } @@ -7181,300 +8919,688 @@ if ((error_count = __NODEJS_parse(string, error_offsets, error_lookaheads)) > 0) }); }; - jIO.addStorage('replicate', ReplicateStorage); + jIO.addStorage('replicate', ReplicateStorage); + +}(jIO, RSVP, Rusha)); +;/* + * Copyright 2015, Nexedi SA + * Released under the LGPL license. + * http://www.gnu.org/licenses/lgpl.html + */ + +/*jslint nomen: true*/ +/*global Rusha*/ + +/** + * JIO Sha Storage. Type = 'sha'. + */ + +(function (Rusha) { + "use strict"; + + var rusha = new Rusha(); + + function ShaStorage(spec) { + this._sub_storage = jIO.createJIO(spec.sub_storage); + } + + ShaStorage.prototype.post = function (param) { + return this._sub_storage.put( + rusha.digestFromString(JSON.stringify(param)), + param + ); + }; + + ShaStorage.prototype.get = function () { + return this._sub_storage.get.apply(this._sub_storage, arguments); + }; + ShaStorage.prototype.remove = function () { + return this._sub_storage.remove.apply(this._sub_storage, arguments); + }; + ShaStorage.prototype.hasCapacity = function () { + return this._sub_storage.hasCapacity.apply(this._sub_storage, arguments); + }; + ShaStorage.prototype.buildQuery = function () { + return this._sub_storage.buildQuery.apply(this._sub_storage, arguments); + }; + ShaStorage.prototype.getAttachment = function () { + return this._sub_storage.getAttachment.apply(this._sub_storage, arguments); + }; + ShaStorage.prototype.putAttachment = function () { + return this._sub_storage.putAttachment.apply(this._sub_storage, arguments); + }; + ShaStorage.prototype.removeAttachment = function () { + return this._sub_storage.removeAttachment.apply(this._sub_storage, + arguments); + }; + ShaStorage.prototype.allAttachments = function () { + return this._sub_storage.allAttachments.apply(this._sub_storage, arguments); + }; + ShaStorage.prototype.repair = function () { + return this._sub_storage.repair.apply(this._sub_storage, arguments); + }; + + jIO.addStorage('sha', ShaStorage); + +}(Rusha)); +;/*jslint nomen: true*/ +(function (jIO) { + "use strict"; + + /** + * The jIO UUIDStorage extension + * + * @class UUIDStorage + * @constructor + */ + function UUIDStorage(spec) { + this._sub_storage = jIO.createJIO(spec.sub_storage); + } + + UUIDStorage.prototype.get = function () { + return this._sub_storage.get.apply(this._sub_storage, arguments); + }; + UUIDStorage.prototype.allAttachments = function () { + return this._sub_storage.allAttachments.apply(this._sub_storage, arguments); + }; + UUIDStorage.prototype.post = function (param) { + + function S4() { + return ('0000' + Math.floor( + Math.random() * 0x10000 /* 65536 */ + ).toString(16)).slice(-4); + } + + var id = S4() + S4() + "-" + + S4() + "-" + + S4() + "-" + + S4() + "-" + + S4() + S4() + S4(); + + return this.put(id, param); + }; + UUIDStorage.prototype.put = function () { + return this._sub_storage.put.apply(this._sub_storage, arguments); + }; + UUIDStorage.prototype.remove = function () { + return this._sub_storage.remove.apply(this._sub_storage, arguments); + }; + UUIDStorage.prototype.getAttachment = function () { + return this._sub_storage.getAttachment.apply(this._sub_storage, arguments); + }; + UUIDStorage.prototype.putAttachment = function () { + return this._sub_storage.putAttachment.apply(this._sub_storage, arguments); + }; + UUIDStorage.prototype.removeAttachment = function () { + return this._sub_storage.removeAttachment.apply(this._sub_storage, + arguments); + }; + UUIDStorage.prototype.repair = function () { + return this._sub_storage.repair.apply(this._sub_storage, arguments); + }; + UUIDStorage.prototype.hasCapacity = function (name) { + return this._sub_storage.hasCapacity(name); + }; + UUIDStorage.prototype.buildQuery = function () { + return this._sub_storage.buildQuery.apply(this._sub_storage, + arguments); + }; + + jIO.addStorage('uuid', UUIDStorage); -}(jIO, RSVP, Rusha)); +}(jIO)); ;/* - * Copyright 2015, Nexedi SA + * Copyright 2013, Nexedi SA * Released under the LGPL license. * http://www.gnu.org/licenses/lgpl.html */ /*jslint nomen: true*/ -/*global Rusha*/ +/*global jIO, RSVP*/ /** - * JIO Sha Storage. Type = 'sha'. + * JIO Memory Storage. Type = 'memory'. + * Memory browser "database" storage. + * + * Storage Description: + * + * { + * "type": "memory" + * } + * + * @class MemoryStorage */ -(function (Rusha) { +(function (jIO, JSON, RSVP) { "use strict"; - var rusha = new Rusha(); - - function ShaStorage(spec) { - this._sub_storage = jIO.createJIO(spec.sub_storage); + /** + * The JIO MemoryStorage extension + * + * @class MemoryStorage + * @constructor + */ + function MemoryStorage() { + this._database = {}; } - ShaStorage.prototype.post = function (param) { - return this._sub_storage.put( - rusha.digestFromString(JSON.stringify(param)), - param - ); + MemoryStorage.prototype.put = function (id, metadata) { + if (!this._database.hasOwnProperty(id)) { + this._database[id] = { + attachments: {} + }; + } + this._database[id].doc = JSON.stringify(metadata); + return id; }; - ShaStorage.prototype.get = function () { - return this._sub_storage.get.apply(this._sub_storage, arguments); - }; - ShaStorage.prototype.remove = function () { - return this._sub_storage.remove.apply(this._sub_storage, arguments); + MemoryStorage.prototype.get = function (id) { + try { + return JSON.parse(this._database[id].doc); + } catch (error) { + if (error instanceof TypeError) { + throw new jIO.util.jIOError( + "Cannot find document: " + id, + 404 + ); + } + throw error; + } }; - ShaStorage.prototype.hasCapacity = function () { - return this._sub_storage.hasCapacity.apply(this._sub_storage, arguments); + + MemoryStorage.prototype.allAttachments = function (id) { + var key, + attachments = {}; + try { + for (key in this._database[id].attachments) { + if (this._database[id].attachments.hasOwnProperty(key)) { + attachments[key] = {}; + } + } + } catch (error) { + if (error instanceof TypeError) { + throw new jIO.util.jIOError( + "Cannot find document: " + id, + 404 + ); + } + throw error; + } + return attachments; }; - ShaStorage.prototype.buildQuery = function () { - return this._sub_storage.buildQuery.apply(this._sub_storage, arguments); + + MemoryStorage.prototype.remove = function (id) { + delete this._database[id]; + return id; }; - ShaStorage.prototype.getAttachment = function () { - return this._sub_storage.getAttachment.apply(this._sub_storage, arguments); + + MemoryStorage.prototype.getAttachment = function (id, name) { + try { + var result = this._database[id].attachments[name]; + if (result === undefined) { + throw new jIO.util.jIOError( + "Cannot find attachment: " + id + " , " + name, + 404 + ); + } + return jIO.util.dataURItoBlob(result); + } catch (error) { + if (error instanceof TypeError) { + throw new jIO.util.jIOError( + "Cannot find attachment: " + id + " , " + name, + 404 + ); + } + throw error; + } }; - ShaStorage.prototype.putAttachment = function () { - return this._sub_storage.putAttachment.apply(this._sub_storage, arguments); + + MemoryStorage.prototype.putAttachment = function (id, name, blob) { + var attachment_dict; + try { + attachment_dict = this._database[id].attachments; + } catch (error) { + if (error instanceof TypeError) { + throw new jIO.util.jIOError("Cannot find document: " + id, 404); + } + throw error; + } + return new RSVP.Queue() + .push(function () { + return jIO.util.readBlobAsDataURL(blob); + }) + .push(function (evt) { + attachment_dict[name] = evt.target.result; + }); }; - ShaStorage.prototype.removeAttachment = function () { - return this._sub_storage.removeAttachment.apply(this._sub_storage, - arguments); + + MemoryStorage.prototype.removeAttachment = function (id, name) { + try { + delete this._database[id].attachments[name]; + } catch (error) { + if (error instanceof TypeError) { + throw new jIO.util.jIOError( + "Cannot find document: " + id, + 404 + ); + } + throw error; + } }; - ShaStorage.prototype.allAttachments = function () { - return this._sub_storage.allAttachments.apply(this._sub_storage, arguments); + + + MemoryStorage.prototype.hasCapacity = function (name) { + return ((name === "list") || (name === "include")); }; - ShaStorage.prototype.repair = function () { - return this._sub_storage.repair.apply(this._sub_storage, arguments); + + MemoryStorage.prototype.buildQuery = function (options) { + var rows = [], + i; + for (i in this._database) { + if (this._database.hasOwnProperty(i)) { + if (options.include_docs === true) { + rows.push({ + id: i, + value: {}, + doc: JSON.parse(this._database[i].doc) + }); + } else { + rows.push({ + id: i, + value: {} + }); + } + + } + } + return rows; }; - jIO.addStorage('sha', ShaStorage); + jIO.addStorage('memory', MemoryStorage); -}(Rusha)); +}(jIO, JSON, RSVP)); ;/*jslint nomen: true*/ -(function (jIO) { +/*global RSVP, Blob, LZString, DOMException*/ +(function (RSVP, Blob, LZString, DOMException) { "use strict"; /** - * The jIO UUIDStorage extension + * The jIO ZipStorage extension * - * @class UUIDStorage + * @class ZipStorage * @constructor */ - function UUIDStorage(spec) { + + var MIME_TYPE = "application/x-jio-utf16_lz_string"; + + function ZipStorage(spec) { this._sub_storage = jIO.createJIO(spec.sub_storage); } - UUIDStorage.prototype.get = function () { - return this._sub_storage.get.apply(this._sub_storage, arguments); + ZipStorage.prototype.get = function () { + return this._sub_storage.get.apply(this._sub_storage, + arguments); }; - UUIDStorage.prototype.allAttachments = function () { - return this._sub_storage.allAttachments.apply(this._sub_storage, arguments); + + ZipStorage.prototype.post = function () { + return this._sub_storage.post.apply(this._sub_storage, + arguments); + }; + + ZipStorage.prototype.put = function () { + return this._sub_storage.put.apply(this._sub_storage, + arguments); + }; + + ZipStorage.prototype.remove = function () { + return this._sub_storage.remove.apply(this._sub_storage, + arguments); + }; + + ZipStorage.prototype.hasCapacity = function () { + return this._sub_storage.hasCapacity.apply(this._sub_storage, + arguments); + }; + + ZipStorage.prototype.buildQuery = function () { + return this._sub_storage.buildQuery.apply(this._sub_storage, + arguments); + }; + + ZipStorage.prototype.getAttachment = function (id, name) { + var that = this; + return that._sub_storage.getAttachment(id, name) + .push(function (blob) { + if (blob.type !== MIME_TYPE) { + return blob; + } + return new RSVP.Queue() + .push(function () { + return jIO.util.readBlobAsText(blob, 'utf16'); + }) + .push(function (evt) { + var result = + LZString.decompressFromUTF16(evt.target.result); + if (result === '') { + return blob; + } + try { + return jIO.util.dataURItoBlob( + result + ); + } catch (error) { + if (error instanceof DOMException) { + return blob; + } + throw error; + } + }); + }); }; - UUIDStorage.prototype.post = function (param) { - - function S4() { - return ('0000' + Math.floor( - Math.random() * 0x10000 /* 65536 */ - ).toString(16)).slice(-4); - } - var id = S4() + S4() + "-" + - S4() + "-" + - S4() + "-" + - S4() + "-" + - S4() + S4() + S4(); + function myEndsWith(str, query) { + return (str.indexOf(query) === str.length - query.length); + } - return this.put(id, param); - }; - UUIDStorage.prototype.put = function () { - return this._sub_storage.put.apply(this._sub_storage, arguments); - }; - UUIDStorage.prototype.remove = function () { - return this._sub_storage.remove.apply(this._sub_storage, arguments); - }; - UUIDStorage.prototype.getAttachment = function () { - return this._sub_storage.getAttachment.apply(this._sub_storage, arguments); - }; - UUIDStorage.prototype.putAttachment = function () { - return this._sub_storage.putAttachment.apply(this._sub_storage, arguments); + ZipStorage.prototype.putAttachment = function (id, name, blob) { + var that = this; + if ((blob.type.indexOf("text/") === 0) || myEndsWith(blob.type, "xml") || + myEndsWith(blob.type, "json")) { + return new RSVP.Queue() + .push(function () { + return jIO.util.readBlobAsDataURL(blob); + }) + .push(function (data) { + var result = LZString.compressToUTF16(data.target.result); + blob = new Blob([result], + {type: MIME_TYPE}); + return that._sub_storage.putAttachment(id, name, blob); + }); + } + return this._sub_storage.putAttachment.apply(this._sub_storage, + arguments); }; - UUIDStorage.prototype.removeAttachment = function () { + + ZipStorage.prototype.removeAttachment = function () { return this._sub_storage.removeAttachment.apply(this._sub_storage, arguments); }; - UUIDStorage.prototype.repair = function () { - return this._sub_storage.repair.apply(this._sub_storage, arguments); - }; - UUIDStorage.prototype.hasCapacity = function (name) { - return this._sub_storage.hasCapacity(name); - }; - UUIDStorage.prototype.buildQuery = function () { - return this._sub_storage.buildQuery.apply(this._sub_storage, - arguments); - }; - jIO.addStorage('uuid', UUIDStorage); + ZipStorage.prototype.allAttachments = function () { + return this._sub_storage.allAttachments.apply(this._sub_storage, + arguments); + }; -}(jIO)); + jIO.addStorage('zip', ZipStorage); +}(RSVP, Blob, LZString, DOMException)); ;/* * Copyright 2013, Nexedi SA * Released under the LGPL license. * http://www.gnu.org/licenses/lgpl.html */ - -/*jslint nomen: true*/ -/*global jIO*/ - /** - * JIO Memory Storage. Type = 'memory'. - * Memory browser "database" storage. - * - * Storage Description: - * - * { - * "type": "memory" - * } - * - * @class MemoryStorage + * JIO Dropbox Storage. Type = "dropbox". + * Dropbox "database" storage. */ +/*global Blob, jIO, RSVP, UriTemplate*/ +/*jslint nomen: true*/ -(function (jIO) { +(function (jIO, RSVP, Blob, UriTemplate) { "use strict"; + var UPLOAD_URL = "https://content.dropboxapi.com/1/files_put/" + + "{+root}{+id}{+name}{?access_token}", + upload_template = UriTemplate.parse(UPLOAD_URL), + CREATE_DIR_URL = "https://api.dropboxapi.com/1/fileops/create_folder" + + "{?access_token,root,path}", + create_dir_template = UriTemplate.parse(CREATE_DIR_URL), + REMOVE_URL = "https://api.dropboxapi.com/1/fileops/delete/" + + "{?access_token,root,path}", + remote_template = UriTemplate.parse(REMOVE_URL), + GET_URL = "https://content.dropboxapi.com/1/files" + + "{/root,id}{+name}{?access_token}", + get_template = UriTemplate.parse(GET_URL), + //LIST_URL = 'https://api.dropboxapi.com/1/metadata/sandbox/'; + METADATA_URL = "https://api.dropboxapi.com/1/metadata" + + "{/root}{+id}{?access_token}", + metadata_template = UriTemplate.parse(METADATA_URL); + + function restrictDocumentId(id) { + if (id.indexOf("/") !== 0) { + throw new jIO.util.jIOError("id " + id + " is forbidden (no begin /)", + 400); + } + if (id.lastIndexOf("/") !== (id.length - 1)) { + throw new jIO.util.jIOError("id " + id + " is forbidden (no end /)", + 400); + } + return id; + } + + function restrictAttachmentId(id) { + if (id.indexOf("/") !== -1) { + throw new jIO.util.jIOError("attachment " + id + " is forbidden", + 400); + } + } /** - * The JIO MemoryStorage extension + * The JIO Dropbox Storage extension * - * @class MemoryStorage + * @class DropboxStorage * @constructor */ - function MemoryStorage() { - this._database = {}; + function DropboxStorage(spec) { + if (typeof spec.access_token !== 'string' || !spec.access_token) { + throw new TypeError("Access Token' must be a string " + + "which contains more than one character."); + } + if (typeof spec.root !== 'string' || !spec.root || + (spec.root !== "dropbox" && spec.root !== "sandbox")) { + throw new TypeError("root must be 'dropbox' or 'sandbox'"); + } + this._access_token = spec.access_token; + this._root = spec.root; } - MemoryStorage.prototype.put = function (id, metadata) { - if (!this._database.hasOwnProperty(id)) { - this._database[id] = { - attachments: {} - }; + DropboxStorage.prototype.put = function (id, param) { + var that = this; + id = restrictDocumentId(id); + if (Object.getOwnPropertyNames(param).length > 0) { + // Reject if param has some properties + throw new jIO.util.jIOError("Can not store properties: " + + Object.getOwnPropertyNames(param), 400); } - this._database[id].doc = metadata; - return id; + return new RSVP.Queue() + .push(function () { + return jIO.util.ajax({ + type: "POST", + url: create_dir_template.expand({ + access_token: that._access_token, + root: that._root, + path: id + }) + }); + }) + .push(undefined, function (err) { + if ((err.target !== undefined) && + (err.target.status === 405)) { + // Directory already exists, no need to fail + return; + } + throw err; + }); }; - MemoryStorage.prototype.get = function (id) { - try { - return this._database[id].doc; - } catch (error) { - if (error instanceof TypeError) { - throw new jIO.util.jIOError( - "Cannot find document: " + id, - 404 - ); - } - throw error; + DropboxStorage.prototype.remove = function (id) { + id = restrictDocumentId(id); + return jIO.util.ajax({ + type: "POST", + url: remote_template.expand({ + access_token: this._access_token, + root: this._root, + path: id + }) + }); + }; + + DropboxStorage.prototype.get = function (id) { + var that = this; + + if (id === "/") { + return {}; } + id = restrictDocumentId(id); + + return new RSVP.Queue() + .push(function () { + return jIO.util.ajax({ + type: "GET", + url: metadata_template.expand({ + access_token: that._access_token, + root: that._root, + id: id + }) + }); + }) + .push(function (evt) { + var obj = JSON.parse(evt.target.response || + evt.target.responseText); + if (obj.is_dir) { + return {}; + } + throw new jIO.util.jIOError("Not a directory: " + id, 404); + }, function (error) { + if (error.target !== undefined && error.target.status === 404) { + throw new jIO.util.jIOError("Cannot find document: " + id, 404); + } + throw error; + }); }; - MemoryStorage.prototype.allAttachments = function (id) { - var key, - attachments = {}; - try { - for (key in this._database[id].attachments) { - if (this._database[id].attachments.hasOwnProperty(key)) { - attachments[key] = {}; + DropboxStorage.prototype.allAttachments = function (id) { + + var that = this; + id = restrictDocumentId(id); + + return new RSVP.Queue() + .push(function () { + return jIO.util.ajax({ + type: "GET", + url: metadata_template.expand({ + access_token: that._access_token, + root: that._root, + id: id + }) + }); + }) + .push(function (evt) { + var obj = JSON.parse(evt.target.response || evt.target.responseText), + i, + result = {}; + if (!obj.is_dir) { + throw new jIO.util.jIOError("Not a directory: " + id, 404); } - } - } catch (error) { - if (error instanceof TypeError) { - throw new jIO.util.jIOError( - "Cannot find document: " + id, - 404 - ); - } - throw error; - } - return attachments; + for (i = 0; i < obj.contents.length; i += 1) { + if (!obj.contents[i].is_dir) { + result[obj.contents[i].path.split("/").pop()] = {}; + } + } + return result; + }, function (error) { + if (error.target !== undefined && error.target.status === 404) { + throw new jIO.util.jIOError("Cannot find document: " + id, 404); + } + throw error; + }); }; - MemoryStorage.prototype.remove = function (id) { - delete this._database[id]; - return id; - }; + //currently, putAttachment will fail with files larger than 150MB, + //due to the Dropbox API. the API provides the "chunked_upload" method + //to pass this limit, but upload process becomes more complex to implement. + // + //putAttachment will also create a folder if you try to put an attachment + //to an inexisting foler. - MemoryStorage.prototype.getAttachment = function (id, name) { - try { - var result = this._database[id].attachments[name]; - if (result === undefined) { - throw new jIO.util.jIOError( - "Cannot find attachment: " + id + " , " + name, - 404 - ); - } - return result; - } catch (error) { - if (error instanceof TypeError) { - throw new jIO.util.jIOError( - "Cannot find attachment: " + id + " , " + name, - 404 - ); - } - throw error; - } - }; + DropboxStorage.prototype.putAttachment = function (id, name, blob) { + id = restrictDocumentId(id); + restrictAttachmentId(name); - MemoryStorage.prototype.putAttachment = function (id, name, blob) { - var attachment_dict; - try { - attachment_dict = this._database[id].attachments; - } catch (error) { - if (error instanceof TypeError) { - throw new jIO.util.jIOError("Cannot find document: " + id, 404); - } - throw error; - } - attachment_dict[name] = blob; + return jIO.util.ajax({ + type: "PUT", + url: upload_template.expand({ + root: this._root, + id: id, + name: name, + access_token: this._access_token + }), + dataType: blob.type, + data: blob + }); }; - MemoryStorage.prototype.removeAttachment = function (id, name) { - try { - delete this._database[id].attachments[name]; - } catch (error) { - if (error instanceof TypeError) { - throw new jIO.util.jIOError( - "Cannot find document: " + id, - 404 + DropboxStorage.prototype.getAttachment = function (id, name) { + var that = this; + + id = restrictDocumentId(id); + restrictAttachmentId(name); + + return new RSVP.Queue() + .push(function () { + return jIO.util.ajax({ + type: "GET", + dataType: "blob", + url: get_template.expand({ + root: that._root, + id: id, + name: name, + access_token: that._access_token + }) + }); + }) + .push(function (evt) { + return new Blob( + [evt.target.response || evt.target.responseText], + {"type": evt.target.getResponseHeader('Content-Type') || + "application/octet-stream"} ); - } - throw error; - } + }, function (error) { + if (error.target !== undefined && error.target.status === 404) { + throw new jIO.util.jIOError("Cannot find attachment: " + + id + ", " + name, 404); + } + throw error; + }); }; + //removeAttachment removes also directories.(due to Dropbox API) - MemoryStorage.prototype.hasCapacity = function (name) { - return ((name === "list") || (name === "include")); - }; + DropboxStorage.prototype.removeAttachment = function (id, name) { + var that = this; + id = restrictDocumentId(id); + restrictAttachmentId(name); - MemoryStorage.prototype.buildQuery = function (options) { - var rows = [], - i; - for (i in this._database) { - if (this._database.hasOwnProperty(i)) { - if (options.include_docs === true) { - rows.push({ - id: i, - value: {}, - doc: this._database[i] - }); - } else { - rows.push({ - id: i, - value: {} - }); + return new RSVP.Queue() + .push(function () { + return jIO.util.ajax({ + type: "POST", + url: remote_template.expand({ + access_token: that._access_token, + root: that._root, + path: id + name + }) + }); + }).push(undefined, function (error) { + if (error.target !== undefined && error.target.status === 404) { + throw new jIO.util.jIOError("Cannot find attachment: " + + id + ", " + name, 404); } - - } - } - return rows; + throw error; + }); }; - jIO.addStorage('memory', MemoryStorage); + jIO.addStorage('dropbox', DropboxStorage); -}(jIO)); +}(jIO, RSVP, Blob, UriTemplate)); ;/* * Copyright 2013, Nexedi SA * Released under the LGPL license. @@ -7482,3143 +9608,3197 @@ if ((error_count = __NODEJS_parse(string, error_offsets, error_lookaheads)) > 0) */ /*jslint nomen: true*/ -/*global jIO, sessionStorage, localStorage, RSVP */ +/*global jIO, RSVP, DOMParser, Blob */ -/** - * JIO Local Storage. Type = 'local'. - * Local browser "database" storage. - * - * Storage Description: - * - * { - * "type": "local", - * "sessiononly": false - * } - * - * @class LocalStorage - */ +// JIO Dav Storage Description : +// { +// type: "dav", +// url: {string}, +// basic_login: {string} // Basic authentication +// } -(function (jIO, sessionStorage, localStorage, RSVP) { +// NOTE: to get the authentication type -> +// curl --verbose -X OPTION http://domain/ +// In the headers: "WWW-Authenticate: Basic realm="DAV-upload" + +(function (jIO, RSVP, DOMParser, Blob) { "use strict"; - function LocalStorage(spec) { - if (spec.sessiononly === true) { - this._storage = sessionStorage; - } else { - this._storage = localStorage; + function ajax(storage, options) { + if (options === undefined) { + options = {}; + } + if (storage._authorization !== undefined) { + if (options.headers === undefined) { + options.headers = {}; + } + options.headers.Authorization = storage._authorization; } + + if (storage._with_credentials !== undefined) { + if (options.xhrFields === undefined) { + options.xhrFields = {}; + } + options.xhrFields.withCredentials = storage._with_credentials; + } +// if (start !== undefined) { +// if (end !== undefined) { +// headers.Range = "bytes=" + start + "-" + end; +// } else { +// headers.Range = "bytes=" + start + "-"; +// } +// } + return new RSVP.Queue() + .push(function () { + return jIO.util.ajax(options); + }); } function restrictDocumentId(id) { - if (id !== "/") { - throw new jIO.util.jIOError("id " + id + " is forbidden (!== /)", + if (id.indexOf("/") !== 0) { + throw new jIO.util.jIOError("id " + id + " is forbidden (no begin /)", + 400); + } + if (id.lastIndexOf("/") !== (id.length - 1)) { + throw new jIO.util.jIOError("id " + id + " is forbidden (no end /)", + 400); + } + return id; + } + + function restrictAttachmentId(id) { + if (id.indexOf("/") !== -1) { + throw new jIO.util.jIOError("attachment " + id + " is forbidden", 400); } } - LocalStorage.prototype.get = function (id) { - restrictDocumentId(id); - return {}; - }; - - LocalStorage.prototype.allAttachments = function (id) { - restrictDocumentId(id); - - var attachments = {}, - key; - - for (key in this._storage) { - if (this._storage.hasOwnProperty(key)) { - attachments[key] = {}; - } + /** + * The JIO WebDAV Storage extension + * + * @class DavStorage + * @constructor + */ + function DavStorage(spec) { + if (typeof spec.url !== 'string') { + throw new TypeError("DavStorage 'url' is not of type string"); + } + this._url = spec.url; + // XXX digest login + if (typeof spec.basic_login === 'string') { + this._authorization = "Basic " + spec.basic_login; + } + this._with_credentials = spec.with_credentials; + } + + DavStorage.prototype.put = function (id, param) { + var that = this; + id = restrictDocumentId(id); + if (Object.getOwnPropertyNames(param).length > 0) { + // Reject if param has some properties + throw new jIO.util.jIOError("Can not store properties: " + + Object.getOwnPropertyNames(param), 400); } - return attachments; + return new RSVP.Queue() + .push(function () { + return ajax(that, { + type: "MKCOL", + url: that._url + id + }); + }) + .push(undefined, function (err) { + if ((err.target !== undefined) && + (err.target.status === 405)) { + return; + } + throw err; + }); }; - LocalStorage.prototype.getAttachment = function (id, name) { - restrictDocumentId(id); - - var textstring = this._storage.getItem(name); - - if (textstring === null) { - throw new jIO.util.jIOError( - "Cannot find attachment " + name, - 404 - ); - } - return jIO.util.dataURItoBlob(textstring); + DavStorage.prototype.remove = function (id) { + id = restrictDocumentId(id); + return ajax(this, { + type: "DELETE", + url: this._url + id + }); }; - LocalStorage.prototype.putAttachment = function (id, name, blob) { + DavStorage.prototype.get = function (id) { var context = this; - restrictDocumentId(id); + id = restrictDocumentId(id); - // the document already exists - // download data return new RSVP.Queue() .push(function () { - return jIO.util.readBlobAsDataURL(blob); + return ajax(context, { + type: "PROPFIND", + url: context._url + id, + dataType: "text", + headers: { + // Increasing this value is a performance killer + Depth: "1" + } + }); }) - .push(function (e) { - context._storage.setItem(name, e.target.result); + .push(function () { + return {}; + }, function (error) { + if ((error.target !== undefined) && + (error.target.status === 404)) { + throw new jIO.util.jIOError("Cannot find document", 404); + } + throw error; }); }; - LocalStorage.prototype.removeAttachment = function (id, name) { - restrictDocumentId(id); - return this._storage.removeItem(name); - }; - + DavStorage.prototype.allAttachments = function (id) { - LocalStorage.prototype.hasCapacity = function (name) { - return (name === "list"); - }; + var context = this; + id = restrictDocumentId(id); - LocalStorage.prototype.buildQuery = function () { - return [{ - id: "/", - value: {} - }]; - }; + return new RSVP.Queue() + .push(function () { + return ajax(context, { + type: "PROPFIND", + url: context._url + id, + dataType: "text", + headers: { + // Increasing this value is a performance killer + Depth: "1" + } + }); + }) - jIO.addStorage('local', LocalStorage); -}(jIO, sessionStorage, localStorage, RSVP)); -;/*jslint nomen: true*/ -/*global RSVP, Blob, LZString, DOMException*/ -(function (RSVP, Blob, LZString, DOMException) { - "use strict"; + .push(function (response) { + // Extract all meta informations and return them to JSON - /** - * The jIO ZipStorage extension - * - * @class ZipStorage - * @constructor - */ + var i, + attachment = {}, + id, + attachment_list = new DOMParser().parseFromString( + response.target.responseText, + "text/xml" + ).querySelectorAll( + "D\\:response, response" + ); - var MIME_TYPE = "application/x-jio-utf16_lz_string"; + // exclude parent folder and browse + for (i = 1; i < attachment_list.length; i += 1) { + // XXX Only get files for now + id = attachment_list[i].querySelector("D\\:href, href"). + textContent.split('/').slice(-1)[0]; + // XXX Ugly + if ((id !== undefined) && (id !== "")) { + attachment[id] = {}; + } + } + return attachment; - function ZipStorage(spec) { - this._sub_storage = jIO.createJIO(spec.sub_storage); - } + }, function (error) { + if ((error.target !== undefined) && + (error.target.status === 404)) { + throw new jIO.util.jIOError("Cannot find document", 404); + } + throw error; + }); - ZipStorage.prototype.get = function () { - return this._sub_storage.get.apply(this._sub_storage, - arguments); }; - ZipStorage.prototype.post = function () { - return this._sub_storage.post.apply(this._sub_storage, - arguments); - }; - ZipStorage.prototype.put = function () { - return this._sub_storage.put.apply(this._sub_storage, - arguments); - }; + DavStorage.prototype.putAttachment = function (id, name, blob) { + var that = this; + id = restrictDocumentId(id); + restrictAttachmentId(name); - ZipStorage.prototype.remove = function () { - return this._sub_storage.remove.apply(this._sub_storage, - arguments); + return new RSVP.Queue() + .push(function () { + return ajax(that, { + type: "PUT", + url: that._url + id + name, + data: blob + }); + }) + .push(undefined, function (error) { + if (error.target.status === 403 || error.target.status === 424) { + throw new jIO.util.jIOError("Cannot access subdocument", 404); + } + throw error; + }); }; - ZipStorage.prototype.hasCapacity = function () { - return this._sub_storage.hasCapacity.apply(this._sub_storage, - arguments); - }; + DavStorage.prototype.getAttachment = function (id, name) { + var context = this; + id = restrictDocumentId(id); + restrictAttachmentId(name); + + return new RSVP.Queue() + .push(function () { + return ajax(context, { + type: "GET", + url: context._url + id + name, + dataType: "blob" + }); + }) + .push(function (response) { + return new Blob( + [response.target.response || response.target.responseText], + {"type": response.target.getResponseHeader('Content-Type') || + "application/octet-stream"} + ); + }, function (error) { + if ((error.target !== undefined) && + (error.target.status === 404)) { + throw new jIO.util.jIOError("Cannot find attachment: " + + id + " , " + name, + 404); + } + throw error; + }); - ZipStorage.prototype.buildQuery = function () { - return this._sub_storage.buildQuery.apply(this._sub_storage, - arguments); }; - ZipStorage.prototype.getAttachment = function (id, name) { - var that = this; - return that._sub_storage.getAttachment(id, name) - .push(function (blob) { - if (blob.type !== MIME_TYPE) { - return blob; - } - return new RSVP.Queue() - .push(function () { - return jIO.util.readBlobAsText(blob, 'utf16'); - }) - .push(function (evt) { - var result = - LZString.decompressFromUTF16(evt.target.result); - if (result === '') { - return blob; - } - try { - return jIO.util.dataURItoBlob( - result - ); - } catch (error) { - if (error instanceof DOMException) { - return blob; - } - throw error; - } - }); + DavStorage.prototype.removeAttachment = function (id, name) { + var context = this; + id = restrictDocumentId(id); + restrictAttachmentId(name); + + return new RSVP.Queue() + .push(function () { + return ajax(context, { + type: "DELETE", + url: context._url + id + name + }); + }) + .push(undefined, function (error) { + if ((error.target !== undefined) && + (error.target.status === 404)) { + throw new jIO.util.jIOError("Cannot find attachment: " + + id + " , " + name, + 404); + } + throw error; }); }; - function myEndsWith(str, query) { - return (str.indexOf(query) === str.length - query.length); - } + // JIO COMMANDS // - ZipStorage.prototype.putAttachment = function (id, name, blob) { - var that = this; - if ((blob.type.indexOf("text/") === 0) || myEndsWith(blob.type, "xml") || - myEndsWith(blob.type, "json")) { - return new RSVP.Queue() - .push(function () { - return jIO.util.readBlobAsDataURL(blob); - }) - .push(function (data) { - var result = LZString.compressToUTF16(data.target.result); - blob = new Blob([result], - {type: MIME_TYPE}); - return that._sub_storage.putAttachment(id, name, blob); - }); - } - return this._sub_storage.putAttachment.apply(this._sub_storage, - arguments); - }; + // wedDav methods rfc4918 (short summary) + // COPY Reproduces single resources (files) and collections (directory + // trees). Will overwrite files (if specified by request) but will + // respond 209 (Conflict) if it would overwrite a tree + // DELETE deletes files and directory trees + // GET just the vanilla HTTP/1.1 behaviour + // HEAD ditto + // LOCK locks a resources + // MKCOL creates a directory + // MOVE Moves (rename or copy) a file or a directory tree. Will + // 'overwrite' files (if specified by the request) but will respond + // 209 (Conflict) if it would overwrite a tree. + // OPTIONS If WebDAV is enabled and available for the path this reports the + // WebDAV extension methods + // PROPFIND Retrieves the requested file characteristics, DAV lock status + // and 'dead' properties for individual files, a directory and its + // child files, or a directory tree + // PROPPATCHset and remove 'dead' meta-data properties + // PUT Update or create resource or collections + // UNLOCK unlocks a resource - ZipStorage.prototype.removeAttachment = function () { - return this._sub_storage.removeAttachment.apply(this._sub_storage, - arguments); - }; + // Notes: all Ajax requests should be CORS (cross-domain) + // adding custom headers triggers preflight OPTIONS request! + // http://remysharp.com/2011/04/21/getting-cors-working/ - ZipStorage.prototype.allAttachments = function () { - return this._sub_storage.allAttachments.apply(this._sub_storage, - arguments); - }; + jIO.addStorage('dav', DavStorage); - jIO.addStorage('zip', ZipStorage); -}(RSVP, Blob, LZString, DOMException)); +}(jIO, RSVP, DOMParser, Blob)); ;/* * Copyright 2015, Nexedi SA * Released under the LGPL license. * http://www.gnu.org/licenses/lgpl.html */ - +/** + * JIO Google Drive Storage. Type = "gdrive". + * Google Drive "database" storage. + */ +/*global jIO, Blob, RSVP, UriTemplate, JSON*/ /*jslint nomen: true*/ -/*global jIO, RSVP, DOMException, Blob, crypto, Uint8Array, ArrayBuffer*/ -(function (jIO, RSVP, DOMException, Blob, crypto, Uint8Array, ArrayBuffer) { +(function (jIO, Blob, RSVP, UriTemplate, JSON) { "use strict"; + var UPLOAD_URL = "https://www.googleapis.com{/upload}/drive/v2/files{/id}" + + "{?uploadType,access_token}", + upload_template = UriTemplate.parse(UPLOAD_URL), + REMOVE_URL = "https://www.googleapis.com/drive/v2/" + + "files{/id,trash}{?access_token}", + remove_template = UriTemplate.parse(REMOVE_URL), + LIST_URL = "https://www.googleapis.com/drive/v2/files" + + "?prettyPrint=false{&pageToken}&q=trashed=false" + + "&fields=nextPageToken,items(id){&access_token}", + list_template = UriTemplate.parse(LIST_URL), + GET_URL = "https://www.googleapis.com/drive/v2/files{/id}{?alt}", + get_template = UriTemplate.parse(GET_URL); - // you the cryptography system used by this storage is AES-GCM. - // here is an example of how to generate a key to the json format. + function handleError(error, id) { + if (error.target.status === 404) { + throw new jIO.util.jIOError( + "Cannot find document: " + id, + 404 + ); + } + throw error; + } - // var key, - // jsonKey; - // crypto.subtle.generateKey({name: "AES-GCM",length: 256}, - // (true), ["encrypt", "decrypt"]) - // .then(function(res){key = res;}); - // - // window.crypto.subtle.exportKey("jwk", key) - // .then(function(res){jsonKey = val}) - // - //var storage = jIO.createJIO({type: "crypt", key: jsonKey, - // sub_storage: {...}}); + function listPage(result, token) { + var i, + obj; + return new RSVP.Queue() + .push(function () { + return jIO.util.ajax({ + "type": "GET", + "url": list_template.expand({ + pageToken : (result.nextPageToken || ""), + access_token: token + }) + }); + }) + .push(function (data) { + obj = JSON.parse(data.target.response || data.target.responseText); + for (i = 0; i < obj.items.length; i += 1) { + obj.items[i].value = {}; + result.push(obj.items[i]); + } + result.nextPageToken = obj.nextPageToken; + return result; + }, handleError); + } - // find more informations about this cryptography system on - // https://github.com/diafygi/webcrypto-examples#aes-gcm + function checkName(name) { + if (name !== "enclosure") { + throw new jIO.util.jIOError("Only support 'enclosure' attachment", 400); + } + } /** - * The JIO Cryptography Storage extension + * The JIO Google Drive Storage extension * - * @class CryptStorage + * @class GdriveStorage * @constructor */ - - var MIME_TYPE = "application/x-jio-aes-gcm-encryption"; - - function CryptStorage(spec) { - this._key = spec.key; - this._jsonKey = true; - this._sub_storage = jIO.createJIO(spec.sub_storage); + function GdriveStorage(spec) { + if (spec === undefined || spec.access_token === undefined || + typeof spec.access_token !== 'string') { + throw new TypeError("Access Token must be a string " + + "which contains more than one character."); + } + if (spec.trashing !== undefined && + (spec.trashing !== true && spec.trashing !== false)) { + throw new TypeError("trashing parameter" + + " must be a boolean (true or false)"); + } + this._trashing = spec.trashing || true; + this._access_token = spec.access_token; + return; } - function convertKey(that) { + function recursiveAllDocs(result, accessToken) { return new RSVP.Queue() .push(function () { - return crypto.subtle.importKey("jwk", that._key, - "AES-GCM", false, - ["encrypt", "decrypt"]); + return listPage(result, accessToken); }) - .push(function (res) { - that._key = res; - that._jsonKey = false; - return; - }, function () { - throw new TypeError( - "'key' must be a CryptoKey to JSON Web Key format" - ); + .push(function () { + if (result.nextPageToken) { + return recursiveAllDocs(result, accessToken); + } + return result; }); } - CryptStorage.prototype.get = function () { - return this._sub_storage.get.apply(this._sub_storage, - arguments); + GdriveStorage.prototype.hasCapacity = function (name) { + return (name === "list"); + }; + + GdriveStorage.prototype.buildQuery = function () { + return recursiveAllDocs([], this._access_token); + }; + + function sendMetaData(id, param, token) { + var boundary = "-------314159265358979323846"; + + return new RSVP.Queue() + .push(function () { + return jIO.util.ajax({ + "type": id ? "PUT" : "POST", + "url": upload_template.expand({ + access_token: token, + id: id || [], + upload: id ? [] : "upload", + uploadType: "multipart" + }), + headers: { + "Content-Type" : 'multipart/related; boundary="' + boundary + '"' + }, + data: '--' + boundary + '\n' + + 'Content-Type: application/json; charset=UTF-8\n\n' + + JSON.stringify(param) + '\n\n--' + boundary + "--" + }); + }) + .push(function (result) { + var obj = JSON.parse(result.target.responseText); + + return obj.id; + }, + function (error) {handleError(error, id); }); + } + + GdriveStorage.prototype.put = function (id, param) { + return sendMetaData(id, param, this._access_token); + }; + + GdriveStorage.prototype.post = function (param) { + return sendMetaData(undefined, param, this._access_token); }; - CryptStorage.prototype.post = function () { - return this._sub_storage.post.apply(this._sub_storage, - arguments); + function sendData(id, blob, token) { + return new RSVP.Queue() + .push(function () { + return jIO.util.ajax({ + "type": "PUT", + "url": upload_template.expand({ + access_token: token, + upload: "upload", + id: id, + uploadType: "media" + }), + data: blob + }); + }) + .push(function (data) { + data = JSON.parse(data.target.responseText); + if (data.mimeType === "application/vnd.google-apps.folder") { + throw new jIO.util.jIOError("cannot put attachments to folder", 400); + } + return data; + }, function (error) {handleError(error, id); }); + } + + GdriveStorage.prototype.putAttachment = function (id, name, blob) { + checkName(name); + return sendData(id, blob, this._access_token); }; - CryptStorage.prototype.put = function () { - return this._sub_storage.put.apply(this._sub_storage, - arguments); + GdriveStorage.prototype.remove = function (id) { + var that = this; + return new RSVP.Queue() + .push(function () { + return jIO.util.ajax({ + type: that._trashing ? "POST" : "DELETE", + url: remove_template.expand({ + id : id, + access_token : that._access_token, + trash : that._trashing ? "trash" : [] + }) + }); + }) + .push(undefined, function (error) {handleError(error, id); }); }; - CryptStorage.prototype.remove = function () { - return this._sub_storage.remove.apply(this._sub_storage, - arguments); - }; + function getData(id, attach, token) { + return new RSVP.Queue() + .push(function () { + return jIO.util.ajax({ + type: "GET", + dataType: attach ? "blob" : "json", + url: get_template.expand({ + id: id, + alt: attach ? "media" : [], + access_token: token + }), + headers: { + "Authorization" : "Bearer " + token + } + }); + }) + .push(function (evt) { + return evt.target.response || + (attach ? new Blob([evt.target.responseText], + {"type" : + evt.target.responseHeaders["Content-Type"]}) : + JSON.parse(evt.target.responseText)); + }, function (error) {handleError(error, id); }); + } - CryptStorage.prototype.hasCapacity = function () { - return this._sub_storage.hasCapacity.apply(this._sub_storage, - arguments); + GdriveStorage.prototype.get = function (id) { + return getData(id, false, this._access_token); }; - CryptStorage.prototype.buildQuery = function () { - return this._sub_storage.buildQuery.apply(this._sub_storage, - arguments); + GdriveStorage.prototype.getAttachment = function (id, name) { + checkName(name); + return getData(id, true, this._access_token); }; - - CryptStorage.prototype.putAttachment = function (id, name, blob) { - var initializaton_vector = crypto.getRandomValues(new Uint8Array(12)), - that = this; + GdriveStorage.prototype.allAttachments = function (id) { + var token = this._access_token; return new RSVP.Queue() .push(function () { - if (that._jsonKey === true) { - return convertKey(that); - } - return; - }) - .push(function () { - return jIO.util.readBlobAsDataURL(blob); - }) - .push(function (dataURL) { - //string->arraybuffer - var strLen = dataURL.currentTarget.result.length, - buf = new ArrayBuffer(strLen), - bufView = new Uint8Array(buf), - i; - - dataURL = dataURL.currentTarget.result; - for (i = 0; i < strLen; i += 1) { - bufView[i] = dataURL.charCodeAt(i); - } - return crypto.subtle.encrypt({ - name : "AES-GCM", - iv : initializaton_vector - }, - that._key, buf); + return getData(id, false, token); }) - .push(function (coded) { - var blob = new Blob([initializaton_vector, coded], {type: MIME_TYPE}); - return that._sub_storage.putAttachment(id, name, blob); - }); - }; - - CryptStorage.prototype.getAttachment = function (id, name) { - var that = this; - - return that._sub_storage.getAttachment(id, name) - .push(function (blob) { - if (blob.type !== MIME_TYPE) { - return blob; + .push(function (data) { + if (data.mimeType === "application/vnd.google-apps.folder") { + return {}; } - return new RSVP.Queue() - .push(function () { - if (that._jsonKey === true) { - return convertKey(that); - } - return; - }) - .push(function () { - return jIO.util.readBlobAsArrayBuffer(blob); - }) - .push(function (coded) { - var initializaton_vector; - - coded = coded.currentTarget.result; - initializaton_vector = new Uint8Array(coded.slice(0, 12)); - return crypto.subtle.decrypt({ - name : "AES-GCM", - iv : initializaton_vector - }, - that._key, coded.slice(12)); - }) - .push(function (arr) { - //arraybuffer->string - arr = String.fromCharCode.apply(null, new Uint8Array(arr)); - try { - return jIO.util.dataURItoBlob(arr); - } catch (error) { - if (error instanceof DOMException) { - return blob; - } - throw error; - } - }, function () { return blob; }); + return {"enclosure": {}}; }); }; - CryptStorage.prototype.removeAttachment = function () { - return this._sub_storage.removeAttachment.apply(this._sub_storage, - arguments); - }; - - CryptStorage.prototype.allAttachments = function () { - return this._sub_storage.allAttachments.apply(this._sub_storage, - arguments); - }; + jIO.addStorage('gdrive', GdriveStorage); - jIO.addStorage('crypt', CryptStorage); +}(jIO, Blob, RSVP, UriTemplate, JSON)); +;/*jslint nomen: true */ +/*global RSVP*/ -}(jIO, RSVP, DOMException, Blob, crypto, Uint8Array, ArrayBuffer)); -;/* - * Copyright 2013, Nexedi SA - * Released under the LGPL license. - * http://www.gnu.org/licenses/lgpl.html - */ /** - * JIO Dropbox Storage. Type = "dropbox". - * Dropbox "database" storage. + * JIO Union Storage. Type = 'union'. + * This provide a unified access other multiple storage. + * New document are created in the first sub storage. + * Document are searched in each sub storage until it is found. + * + * + * Storage Description: + * + * { + * "type": "union", + * "storage_list": [ + * sub_storage_description_1, + * sub_storage_description_2, + * + * sub_storage_description_X, + * ] + * } + * + * @class UnionStorage */ -/*global Blob, jIO, RSVP, UriTemplate*/ -/*jslint nomen: true*/ -(function (jIO, RSVP, Blob, UriTemplate) { +(function (jIO, RSVP) { "use strict"; - var UPLOAD_URL = "https://content.dropboxapi.com/1/files_put/" + - "{+root}{+id}{+name}{?access_token}", - upload_template = UriTemplate.parse(UPLOAD_URL), - CREATE_DIR_URL = "https://api.dropboxapi.com/1/fileops/create_folder" + - "{?access_token,root,path}", - create_dir_template = UriTemplate.parse(CREATE_DIR_URL), - REMOVE_URL = "https://api.dropboxapi.com/1/fileops/delete/" + - "{?access_token,root,path}", - remote_template = UriTemplate.parse(REMOVE_URL), - GET_URL = "https://content.dropboxapi.com/1/files" + - "{/root,id}{+name}{?access_token}", - get_template = UriTemplate.parse(GET_URL), - //LIST_URL = 'https://api.dropboxapi.com/1/metadata/sandbox/'; - METADATA_URL = "https://api.dropboxapi.com/1/metadata" + - "{/root}{+id}{?access_token}", - metadata_template = UriTemplate.parse(METADATA_URL); - - function restrictDocumentId(id) { - if (id.indexOf("/") !== 0) { - throw new jIO.util.jIOError("id " + id + " is forbidden (no begin /)", - 400); - } - if (id.lastIndexOf("/") !== (id.length - 1)) { - throw new jIO.util.jIOError("id " + id + " is forbidden (no end /)", - 400); - } - return id; - } - - function restrictAttachmentId(id) { - if (id.indexOf("/") !== -1) { - throw new jIO.util.jIOError("attachment " + id + " is forbidden", - 400); - } - } /** - * The JIO Dropbox Storage extension + * The JIO UnionStorage extension * - * @class DropboxStorage + * @class UnionStorage * @constructor */ - function DropboxStorage(spec) { - if (typeof spec.access_token !== 'string' || !spec.access_token) { - throw new TypeError("Access Token' must be a string " + - "which contains more than one character."); + function UnionStorage(spec) { + if (!Array.isArray(spec.storage_list)) { + throw new jIO.util.jIOError("storage_list is not an Array", 400); } - if (typeof spec.root !== 'string' || !spec.root || - (spec.root !== "dropbox" && spec.root !== "sandbox")) { - throw new TypeError("root must be 'dropbox' or 'sandbox'"); + var i; + this._storage_list = []; + for (i = 0; i < spec.storage_list.length; i += 1) { + this._storage_list.push(jIO.createJIO(spec.storage_list[i])); + } + } + + UnionStorage.prototype._getWithStorageIndex = function () { + var i, + index = 0, + context = this, + arg = arguments, + result = this._storage_list[0].get.apply(this._storage_list[0], arg); + + function handle404(j) { + result + .push(undefined, function (error) { + if ((error instanceof jIO.util.jIOError) && + (error.status_code === 404)) { + return context._storage_list[j].get.apply(context._storage_list[j], + arg) + .push(function (doc) { + index = j; + return doc; + }); + } + throw error; + }); } - this._access_token = spec.access_token; - this._root = spec.root; - } - DropboxStorage.prototype.put = function (id, param) { - var that = this; - id = restrictDocumentId(id); - if (Object.getOwnPropertyNames(param).length > 0) { - // Reject if param has some properties - throw new jIO.util.jIOError("Can not store properties: " + - Object.getOwnPropertyNames(param), 400); + for (i = 1; i < this._storage_list.length; i += 1) { + handle404(i); } - return new RSVP.Queue() - .push(function () { - return jIO.util.ajax({ - type: "POST", - url: create_dir_template.expand({ - access_token: that._access_token, - root: that._root, - path: id - }) - }); - }) - .push(undefined, function (err) { - if ((err.target !== undefined) && - (err.target.status === 405)) { - // Directory already exists, no need to fail - return; - } - throw err; + return result + .push(function (doc) { + return [index, doc]; }); }; - DropboxStorage.prototype.remove = function (id) { - id = restrictDocumentId(id); - return jIO.util.ajax({ - type: "POST", - url: remote_template.expand({ - access_token: this._access_token, - root: this._root, - path: id - }) - }); + /* + * Get a document + * Try on each substorage on after the other + */ + UnionStorage.prototype.get = function () { + return this._getWithStorageIndex.apply(this, arguments) + .push(function (result) { + return result[1]; + }); }; - DropboxStorage.prototype.get = function (id) { - var that = this; + /* + * Get attachments list + * Try on each substorage on after the other + */ + UnionStorage.prototype.allAttachments = function () { + var argument_list = arguments, + context = this; + return this._getWithStorageIndex.apply(this, arguments) + .push(function (result) { + var sub_storage = context._storage_list[result[0]]; + return sub_storage.allAttachments.apply(sub_storage, argument_list); + }); + }; - if (id === "/") { - return {}; - } - id = restrictDocumentId(id); + /* + * Post a document + * Simply store on the first substorage + */ + UnionStorage.prototype.post = function () { + return this._storage_list[0].post.apply(this._storage_list[0], arguments); + }; - return new RSVP.Queue() - .push(function () { - return jIO.util.ajax({ - type: "GET", - url: metadata_template.expand({ - access_token: that._access_token, - root: that._root, - id: id - }) - }); - }) - .push(function (evt) { - var obj = JSON.parse(evt.target.response || - evt.target.responseText); - if (obj.is_dir) { - return {}; - } - throw new jIO.util.jIOError("Not a directory: " + id, 404); - }, function (error) { - if (error.target !== undefined && error.target.status === 404) { - throw new jIO.util.jIOError("Cannot find document: " + id, 404); + /* + * Put a document + * Search the document location, and modify it in its storage. + */ + UnionStorage.prototype.put = function () { + var arg = arguments, + context = this; + return this._getWithStorageIndex(arg[0]) + .push(undefined, function (error) { + if ((error instanceof jIO.util.jIOError) && + (error.status_code === 404)) { + // Document does not exist, create in first substorage + return [0]; } throw error; + }) + .push(function (result) { + // Storage found, modify in it directly + var sub_storage = context._storage_list[result[0]]; + return sub_storage.put.apply(sub_storage, arg); }); }; - DropboxStorage.prototype.allAttachments = function (id) { - - var that = this; - id = restrictDocumentId(id); + /* + * Remove a document + * Search the document location, and remove it from its storage. + */ + UnionStorage.prototype.remove = function () { + var arg = arguments, + context = this; + return this._getWithStorageIndex(arg[0]) + .push(function (result) { + // Storage found, remove from it directly + var sub_storage = context._storage_list[result[0]]; + return sub_storage.remove.apply(sub_storage, arg); + }); + }; + UnionStorage.prototype.buildQuery = function () { + var promise_list = [], + i, + id_dict = {}, + len = this._storage_list.length, + sub_storage; + for (i = 0; i < len; i += 1) { + sub_storage = this._storage_list[i]; + promise_list.push(sub_storage.buildQuery.apply(sub_storage, arguments)); + } return new RSVP.Queue() .push(function () { - return jIO.util.ajax({ - type: "GET", - url: metadata_template.expand({ - access_token: that._access_token, - root: that._root, - id: id - }) - }); + return RSVP.all(promise_list); }) - .push(function (evt) { - var obj = JSON.parse(evt.target.response || evt.target.responseText), - i, - result = {}; - if (!obj.is_dir) { - throw new jIO.util.jIOError("Not a directory: " + id, 404); - } - for (i = 0; i < obj.contents.length; i += 1) { - if (!obj.contents[i].is_dir) { - result[obj.contents[i].path.split("/").pop()] = {}; + .push(function (result_list) { + var result = [], + sub_result, + sub_result_len, + j; + len = result_list.length; + for (i = 0; i < len; i += 1) { + sub_result = result_list[i]; + sub_result_len = sub_result.length; + for (j = 0; j < sub_result_len; j += 1) { + if (!id_dict.hasOwnProperty(sub_result[j].id)) { + id_dict[sub_result[j].id] = null; + result.push(sub_result[j]); + } } } return result; - }, function (error) { - if (error.target !== undefined && error.target.status === 404) { - throw new jIO.util.jIOError("Cannot find document: " + id, 404); - } - throw error; }); }; - //currently, putAttachment will fail with files larger than 150MB, - //due to the Dropbox API. the API provides the "chunked_upload" method - //to pass this limit, but upload process becomes more complex to implement. - // - //putAttachment will also create a folder if you try to put an attachment - //to an inexisting foler. - - DropboxStorage.prototype.putAttachment = function (id, name, blob) { - id = restrictDocumentId(id); - restrictAttachmentId(name); - - return jIO.util.ajax({ - type: "PUT", - url: upload_template.expand({ - root: this._root, - id: id, - name: name, - access_token: this._access_token - }), - dataType: blob.type, - data: blob - }); + UnionStorage.prototype.hasCapacity = function (name) { + var i, + len, + result, + sub_storage; + if ((name === "list") || + (name === "query") || + (name === "select")) { + result = true; + len = this._storage_list.length; + for (i = 0; i < len; i += 1) { + sub_storage = this._storage_list[i]; + result = result && sub_storage.hasCapacity(name); + } + return result; + } + return false; }; - DropboxStorage.prototype.getAttachment = function (id, name) { - var that = this; - - id = restrictDocumentId(id); - restrictAttachmentId(name); - - return new RSVP.Queue() - .push(function () { - return jIO.util.ajax({ - type: "GET", - dataType: "blob", - url: get_template.expand({ - root: that._root, - id: id, - name: name, - access_token: that._access_token - }) - }); - }) - .push(function (evt) { - return new Blob( - [evt.target.response || evt.target.responseText], - {"type": evt.target.getResponseHeader('Content-Type') || - "application/octet-stream"} - ); - }, function (error) { - if (error.target !== undefined && error.target.status === 404) { - throw new jIO.util.jIOError("Cannot find attachment: " + - id + ", " + name, 404); - } - throw error; - }); + UnionStorage.prototype.repair = function () { + var i, + promise_list = []; + for (i = 0; i < this._storage_list.length; i += 1) { + promise_list.push(this._storage_list[i].repair.apply( + this._storage_list[i], + arguments + )); + } + return RSVP.all(promise_list); }; - //removeAttachment removes also directories.(due to Dropbox API) + UnionStorage.prototype.getAttachment = function () { + var argument_list = arguments, + context = this; + return this._getWithStorageIndex.apply(this, arguments) + .push(function (result) { + var sub_storage = context._storage_list[result[0]]; + return sub_storage.getAttachment.apply(sub_storage, argument_list); + }); + }; - DropboxStorage.prototype.removeAttachment = function (id, name) { - var that = this; - id = restrictDocumentId(id); - restrictAttachmentId(name); + UnionStorage.prototype.putAttachment = function () { + var argument_list = arguments, + context = this; + return this._getWithStorageIndex.apply(this, arguments) + .push(function (result) { + var sub_storage = context._storage_list[result[0]]; + return sub_storage.putAttachment.apply(sub_storage, argument_list); + }); + }; - return new RSVP.Queue() - .push(function () { - return jIO.util.ajax({ - type: "POST", - url: remote_template.expand({ - access_token: that._access_token, - root: that._root, - path: id + name - }) - }); - }).push(undefined, function (error) { - if (error.target !== undefined && error.target.status === 404) { - throw new jIO.util.jIOError("Cannot find attachment: " + - id + ", " + name, 404); - } - throw error; + UnionStorage.prototype.removeAttachment = function () { + var argument_list = arguments, + context = this; + return this._getWithStorageIndex.apply(this, arguments) + .push(function (result) { + var sub_storage = context._storage_list[result[0]]; + return sub_storage.removeAttachment.apply(sub_storage, argument_list); }); }; - jIO.addStorage('dropbox', DropboxStorage); + jIO.addStorage('union', UnionStorage); -}(jIO, RSVP, Blob, UriTemplate)); +}(jIO, RSVP)); ;/* * Copyright 2013, Nexedi SA * Released under the LGPL license. * http://www.gnu.org/licenses/lgpl.html */ - -/*jslint nomen: true*/ -/*global jIO, RSVP, DOMParser, Blob */ - -// JIO Dav Storage Description : +// JIO ERP5 Storage Description : // { -// type: "dav", -// url: {string}, -// basic_login: {string} // Basic authentication +// type: "erp5" +// url: {string} // } -// NOTE: to get the authentication type -> -// curl --verbose -X OPTION http://domain/ -// In the headers: "WWW-Authenticate: Basic realm="DAV-upload" +/*jslint nomen: true, unparam: true */ +/*global jIO, UriTemplate, FormData, RSVP, URI, Blob, + SimpleQuery, ComplexQuery*/ -(function (jIO, RSVP, DOMParser, Blob) { +(function (jIO, UriTemplate, FormData, RSVP, URI, Blob, + SimpleQuery, ComplexQuery) { "use strict"; - function ajax(storage, options) { - if (options === undefined) { - options = {}; - } - if (storage._authorization !== undefined) { - if (options.headers === undefined) { - options.headers = {}; - } - options.headers.Authorization = storage._authorization; - } -// if (start !== undefined) { -// if (end !== undefined) { -// headers.Range = "bytes=" + start + "-" + end; -// } else { -// headers.Range = "bytes=" + start + "-"; -// } -// } + function getSiteDocument(storage) { return new RSVP.Queue() .push(function () { - return jIO.util.ajax(options); + return jIO.util.ajax({ + "type": "GET", + "url": storage._url, + "xhrFields": { + withCredentials: true + } + }); + }) + .push(function (event) { + return JSON.parse(event.target.responseText); }); } - function restrictDocumentId(id) { - if (id.indexOf("/") !== 0) { - throw new jIO.util.jIOError("id " + id + " is forbidden (no begin /)", - 400); - } - if (id.lastIndexOf("/") !== (id.length - 1)) { - throw new jIO.util.jIOError("id " + id + " is forbidden (no end /)", - 400); + function getDocumentAndHateoas(storage, id, options) { + if (options === undefined) { + options = {}; } - return id; + return getSiteDocument(storage) + .push(function (site_hal) { + // XXX need to get modified metadata + return new RSVP.Queue() + .push(function () { + return jIO.util.ajax({ + "type": "GET", + "url": UriTemplate.parse(site_hal._links.traverse.href) + .expand({ + relative_url: id, + view: options._view + }), + "xhrFields": { + withCredentials: true + } + }); + }) + .push(undefined, function (error) { + if ((error.target !== undefined) && + (error.target.status === 404)) { + throw new jIO.util.jIOError("Cannot find document: " + id, 404); + } + throw error; + }); + }); } - function restrictAttachmentId(id) { - if (id.indexOf("/") !== -1) { - throw new jIO.util.jIOError("attachment " + id + " is forbidden", - 400); - } + var allowed_field_dict = { + "StringField": null, + "EmailField": null, + "IntegerField": null, + "FloatField": null, + "TextAreaField": null + }; + + function extractPropertyFromFormJSON(json) { + return new RSVP.Queue() + .push(function () { + var form = json._embedded._view, + converted_json = { + portal_type: json._links.type.name + }, + form_data_json = {}, + field, + key, + prefix_length, + result; + + if (json._links.hasOwnProperty('parent')) { + converted_json.parent_relative_url = + new URI(json._links.parent.href).segment(2); + } + + form_data_json.form_id = { + "key": [form.form_id.key], + "default": form.form_id["default"] + }; + // XXX How to store datetime + for (key in form) { + if (form.hasOwnProperty(key)) { + field = form[key]; + prefix_length = 0; + if (key.indexOf('my_') === 0 && field.editable) { + prefix_length = 3; + } + if (key.indexOf('your_') === 0) { + prefix_length = 5; + } + if ((prefix_length !== 0) && + (allowed_field_dict.hasOwnProperty(field.type))) { + form_data_json[key.substring(prefix_length)] = { + "default": field["default"], + "key": field.key + }; + converted_json[key.substring(prefix_length)] = field["default"]; + } + } + } + + result = { + data: converted_json, + form_data: form_data_json + }; + if (form.hasOwnProperty('_actions') && + form._actions.hasOwnProperty('put')) { + result.action_href = form._actions.put.href; + } + return result; + }); } - /** - * The JIO WebDAV Storage extension - * - * @class DavStorage - * @constructor - */ - function DavStorage(spec) { - if (typeof spec.url !== 'string') { - throw new TypeError("DavStorage 'url' is not of type string"); + function extractPropertyFromForm(context, id) { + return context.getAttachment(id, "view") + .push(function (blob) { + return jIO.util.readBlobAsText(blob); + }) + .push(function (evt) { + return JSON.parse(evt.target.result); + }) + .push(function (json) { + return extractPropertyFromFormJSON(json); + }); + } + + // XXX docstring + function ERP5Storage(spec) { + if (typeof spec.url !== "string" || !spec.url) { + throw new TypeError("ERP5 'url' must be a string " + + "which contains more than one character."); } this._url = spec.url; - // XXX digest login - if (typeof spec.basic_login === 'string') { - this._authorization = "Basic " + spec.basic_login; - } - + this._default_view_reference = spec.default_view_reference; } - DavStorage.prototype.put = function (id, param) { - var that = this; - id = restrictDocumentId(id); - if (Object.getOwnPropertyNames(param).length > 0) { - // Reject if param has some properties - throw new jIO.util.jIOError("Can not store properties: " + - Object.getOwnPropertyNames(param), 400); - } - return new RSVP.Queue() - .push(function () { - return ajax(that, { - type: "MKCOL", - url: that._url + id - }); - }) - .push(undefined, function (err) { - if ((err.target !== undefined) && - (err.target.status === 405)) { - return; + function convertJSONToGet(json) { + var key, + result = json.data; + // Remove all ERP5 hateoas links / convert them into jIO ID + for (key in result) { + if (result.hasOwnProperty(key)) { + if (!result[key]) { + delete result[key]; } - throw err; + } + } + return result; + } + + ERP5Storage.prototype.get = function (id) { + return extractPropertyFromForm(this, id) + .push(function (result) { + return convertJSONToGet(result); }); }; - DavStorage.prototype.remove = function (id) { - id = restrictDocumentId(id); - return ajax(this, { - type: "DELETE", - url: this._url + id - }); - }; + ERP5Storage.prototype.bulk = function (request_list) { + var i, + storage = this, + bulk_list = []; - DavStorage.prototype.get = function (id) { - var context = this; - id = restrictDocumentId(id); - return new RSVP.Queue() - .push(function () { - return ajax(context, { - type: "PROPFIND", - url: context._url + id, - dataType: "text", - headers: { - // Increasing this value is a performance killer - Depth: "1" + for (i = 0; i < request_list.length; i += 1) { + if (request_list[i].method !== "get") { + throw new Error("ERP5Storage: not supported " + + request_list[i].method + " in bulk"); + } + bulk_list.push({ + relative_url: request_list[i].parameter_list[0], + view: storage._default_view_reference + }); + } + return getSiteDocument(storage) + .push(function (site_hal) { + var form_data = new FormData(); + form_data.append("bulk_list", JSON.stringify(bulk_list)); + return jIO.util.ajax({ + "type": "POST", + "url": site_hal._actions.bulk.href, + "data": form_data, +// "headers": { +// "Content-Type": "application/json" +// }, + "xhrFields": { + withCredentials: true } }); }) - .push(function () { - return {}; - }, function (error) { - if ((error.target !== undefined) && - (error.target.status === 404)) { - throw new jIO.util.jIOError("Cannot find document", 404); + .push(function (response) { + var result_list = [], + hateoas = JSON.parse(response.target.responseText); + + function pushResult(json) { + return extractPropertyFromFormJSON(json) + .push(function (json2) { + return convertJSONToGet(json2); + }); } - throw error; + + for (i = 0; i < hateoas.result_list.length; i += 1) { + result_list.push(pushResult(hateoas.result_list[i])); + } + return RSVP.all(result_list); }); }; - DavStorage.prototype.allAttachments = function (id) { - - var context = this; - id = restrictDocumentId(id); + ERP5Storage.prototype.post = function (data) { + var context = this, + new_id; - return new RSVP.Queue() - .push(function () { - return ajax(context, { - type: "PROPFIND", - url: context._url + id, - dataType: "text", - headers: { - // Increasing this value is a performance killer - Depth: "1" + return getSiteDocument(this) + .push(function (site_hal) { + var form_data = new FormData(); + form_data.append("portal_type", data.portal_type); + form_data.append("parent_relative_url", data.parent_relative_url); + return jIO.util.ajax({ + type: "POST", + url: site_hal._actions.add.href, + data: form_data, + xhrFields: { + withCredentials: true } }); }) + .push(function (evt) { + var location = evt.target.getResponseHeader("X-Location"), + uri = new URI(location); + new_id = uri.segment(2); + return context.put(new_id, data); + }) + .push(function () { + return new_id; + }); + }; + ERP5Storage.prototype.put = function (id, data) { + var context = this; - .push(function (response) { - // Extract all meta informations and return them to JSON - - var i, - attachment = {}, - id, - attachment_list = new DOMParser().parseFromString( - response.target.responseText, - "text/xml" - ).querySelectorAll( - "D\\:response, response" - ); + return extractPropertyFromForm(context, id) + .push(function (result) { + var key, + json = result.form_data, + form_data = {}; + form_data[json.form_id.key] = json.form_id["default"]; - // exclude parent folder and browse - for (i = 1; i < attachment_list.length; i += 1) { - // XXX Only get files for now - id = attachment_list[i].querySelector("D\\:href, href"). - textContent.split('/').slice(-1)[0]; - // XXX Ugly - if ((id !== undefined) && (id !== "")) { - attachment[id] = {}; + // XXX How to store datetime:!!!!! + for (key in data) { + if (data.hasOwnProperty(key)) { + if (key === "form_id") { + throw new jIO.util.jIOError( + "ERP5: forbidden property: " + key, + 400 + ); + } + if ((key !== "portal_type") && (key !== "parent_relative_url")) { + if (!json.hasOwnProperty(key)) { + throw new jIO.util.jIOError( + "ERP5: can not store property: " + key, + 400 + ); + } + form_data[json[key].key] = data[key]; + } } } - return attachment; - - }, function (error) { - if ((error.target !== undefined) && - (error.target.status === 404)) { - throw new jIO.util.jIOError("Cannot find document", 404); + if (!result.hasOwnProperty('action_href')) { + throw new jIO.util.jIOError( + "ERP5: can not modify document: " + id, + 403 + ); } - throw error; + return context.putAttachment( + id, + result.action_href, + new Blob([JSON.stringify(form_data)], {type: "application/json"}) + ); }); - }; + ERP5Storage.prototype.allAttachments = function (id) { + var context = this; + return getDocumentAndHateoas(this, id) + .push(function () { + if (context._default_view_reference === undefined) { + return { + links: {} + }; + } + return { + view: {}, + links: {} + }; + }); + }; - DavStorage.prototype.putAttachment = function (id, name, blob) { - var that = this; - id = restrictDocumentId(id); - restrictAttachmentId(name); + ERP5Storage.prototype.getAttachment = function (id, action, options) { + if (options === undefined) { + options = {}; + } + if (action === "view") { + if (this._default_view_reference === undefined) { + throw new jIO.util.jIOError( + "Cannot find attachment view for: " + id, + 404 + ); + } + return getDocumentAndHateoas(this, id, + {"_view": this._default_view_reference}) + .push(function (response) { + var result = JSON.parse(response.target.responseText); + // Remove all ERP5 hateoas links / convert them into jIO ID - return new RSVP.Queue() - .push(function () { - return ajax(that, { - type: "PUT", - url: that._url + id + name, - data: blob + // XXX Change default action to an jio urn with attachment name inside + // if Base_edit, do put URN + // if others, do post URN (ie, unique new attachment name) + // XXX Except this attachment name should be generated when + return new Blob( + [JSON.stringify(result)], + {"type": 'application/hal+json'} + ); + }); + } + if (action === "links") { + return getDocumentAndHateoas(this, id) + .push(function (response) { + return new Blob( + [JSON.stringify(JSON.parse(response.target.responseText))], + {"type": 'application/hal+json'} + ); + }); + } + if (action.indexOf(this._url) === 0) { + return new RSVP.Queue() + .push(function () { + var start, + end, + range, + request_options = { + "type": "GET", + "dataType": "blob", + "url": action, + "xhrFields": { + withCredentials: true + } + }; + if (options.start !== undefined || options.end !== undefined) { + start = options.start || 0; + end = options.end; + if (end !== undefined && end < 0) { + throw new jIO.util.jIOError("end must be positive", + 400); + } + if (start < 0) { + range = "bytes=" + start; + } else if (end === undefined) { + range = "bytes=" + start + "-"; + } else { + if (start > end) { + throw new jIO.util.jIOError("start is greater than end", + 400); + } + range = "bytes=" + start + "-" + end; + } + request_options.headers = {Range: range}; + } + return jIO.util.ajax(request_options); + }) + .push(function (evt) { + if (evt.target.response === undefined) { + return new Blob( + [evt.target.responseText], + {"type": evt.target.getResponseHeader("Content-Type")} + ); + } + return evt.target.response; }); - }) - .push(undefined, function (error) { - if (error.target.status === 403 || error.target.status === 424) { - throw new jIO.util.jIOError("Cannot access subdocument", 404); - } - throw error; - }); + } + throw new jIO.util.jIOError("ERP5: not support get attachment: " + action, + 400); }; - DavStorage.prototype.getAttachment = function (id, name) { - var context = this; - id = restrictDocumentId(id); - restrictAttachmentId(name); + ERP5Storage.prototype.putAttachment = function (id, name, blob) { + // Assert we use a callable on a document from the ERP5 site + if (name.indexOf(this._url) !== 0) { + throw new jIO.util.jIOError("Can not store outside ERP5: " + + name, 400); + } return new RSVP.Queue() .push(function () { - return ajax(context, { - type: "GET", - url: context._url + id + name, - dataType: "blob" - }); + return jIO.util.readBlobAsText(blob); }) - .push(function (response) { - return new Blob( - [response.target.response || response.target.responseText], - {"type": response.target.getResponseHeader('Content-Type') || - "application/octet-stream"} - ); - }, function (error) { - if ((error.target !== undefined) && - (error.target.status === 404)) { - throw new jIO.util.jIOError("Cannot find attachment: " - + id + " , " + name, - 404); + .push(function (evt) { + var form_data = JSON.parse(evt.target.result), + data = new FormData(), + array, + i, + key, + value; + for (key in form_data) { + if (form_data.hasOwnProperty(key)) { + if (Array.isArray(form_data[key])) { + array = form_data[key]; + } else { + array = [form_data[key]]; + } + for (i = 0; i < array.length; i += 1) { + value = array[i]; + if (typeof value === "object") { + data.append(key, jIO.util.dataURItoBlob(value.url), + value.file_name); + } else { + data.append(key, value); + } + } + } } - throw error; + return jIO.util.ajax({ + "type": "POST", + "url": name, + "data": data, + "xhrFields": { + withCredentials: true + } + }); }); + }; + ERP5Storage.prototype.hasCapacity = function (name) { + return ((name === "list") || (name === "query") || + (name === "select") || (name === "limit") || + (name === "sort")); }; - DavStorage.prototype.removeAttachment = function (id, name) { - var context = this; - id = restrictDocumentId(id); - restrictAttachmentId(name); + function isSingleLocalRoles(parsed_query) { + if ((parsed_query instanceof SimpleQuery) && + (parsed_query.key === 'local_roles')) { + // local_roles:"Assignee" + return parsed_query.value; + } + } - return new RSVP.Queue() - .push(function () { - return ajax(context, { - type: "DELETE", - url: context._url + id + name - }); - }) - .push(undefined, function (error) { - if ((error.target !== undefined) && - (error.target.status === 404)) { - throw new jIO.util.jIOError("Cannot find attachment: " - + id + " , " + name, - 404); - } - throw error; - }); - }; + function isMultipleLocalRoles(parsed_query) { + var i, + sub_query, + is_multiple = true, + local_role_list = []; + if ((parsed_query instanceof ComplexQuery) && + (parsed_query.operator === 'OR')) { - // JIO COMMANDS // + for (i = 0; i < parsed_query.query_list.length; i += 1) { + sub_query = parsed_query.query_list[i]; + if ((sub_query instanceof SimpleQuery) && + (sub_query.key === 'local_roles')) { + local_role_list.push(sub_query.value); + } else { + is_multiple = false; + } + } + if (is_multiple) { + // local_roles:"Assignee" OR local_roles:"Assignor" + return local_role_list; + } + } + } - // wedDav methods rfc4918 (short summary) - // COPY Reproduces single resources (files) and collections (directory - // trees). Will overwrite files (if specified by request) but will - // respond 209 (Conflict) if it would overwrite a tree - // DELETE deletes files and directory trees - // GET just the vanilla HTTP/1.1 behaviour - // HEAD ditto - // LOCK locks a resources - // MKCOL creates a directory - // MOVE Moves (rename or copy) a file or a directory tree. Will - // 'overwrite' files (if specified by the request) but will respond - // 209 (Conflict) if it would overwrite a tree. - // OPTIONS If WebDAV is enabled and available for the path this reports the - // WebDAV extension methods - // PROPFIND Retrieves the requested file characteristics, DAV lock status - // and 'dead' properties for individual files, a directory and its - // child files, or a directory tree - // PROPPATCHset and remove 'dead' meta-data properties - // PUT Update or create resource or collections - // UNLOCK unlocks a resource + ERP5Storage.prototype.buildQuery = function (options) { +// if (typeof options.query !== "string") { +// options.query = (options.query ? +// jIO.Query.objectToSearchText(options.query) : +// undefined); +// } + return getSiteDocument(this) + .push(function (site_hal) { + var query = options.query, + i, + parsed_query, + sub_query, + result_list, + local_roles, + tmp_list = []; + if (options.query) { + parsed_query = jIO.QueryFactory.create(options.query); - // Notes: all Ajax requests should be CORS (cross-domain) - // adding custom headers triggers preflight OPTIONS request! - // http://remysharp.com/2011/04/21/getting-cors-working/ + result_list = isSingleLocalRoles(parsed_query); + if (result_list) { + query = undefined; + local_roles = result_list; + } else { - jIO.addStorage('dav', DavStorage); + result_list = isMultipleLocalRoles(parsed_query); + if (result_list) { + query = undefined; + local_roles = result_list; + } else if ((parsed_query instanceof ComplexQuery) && + (parsed_query.operator === 'AND')) { -}(jIO, RSVP, DOMParser, Blob)); -;/* - * Copyright 2015, Nexedi SA - * Released under the LGPL license. - * http://www.gnu.org/licenses/lgpl.html - */ -/** - * JIO Google Drive Storage. Type = "gdrive". - * Google Drive "database" storage. - */ -/*global jIO, Blob, RSVP, UriTemplate, JSON*/ -/*jslint nomen: true*/ + // portal_type:"Person" AND local_roles:"Assignee" + for (i = 0; i < parsed_query.query_list.length; i += 1) { + sub_query = parsed_query.query_list[i]; -(function (jIO, Blob, RSVP, UriTemplate, JSON) { - "use strict"; + result_list = isSingleLocalRoles(sub_query); + if (result_list) { + local_roles = result_list; + parsed_query.query_list.splice(i, 1); + query = jIO.Query.objectToSearchText(parsed_query); + i = parsed_query.query_list.length; + } else { + result_list = isMultipleLocalRoles(sub_query); + if (result_list) { + local_roles = result_list; + parsed_query.query_list.splice(i, 1); + query = jIO.Query.objectToSearchText(parsed_query); + i = parsed_query.query_list.length; + } + } + } + } - var UPLOAD_URL = "https://www.googleapis.com{/upload}/drive/v2/files{/id}" + - "{?uploadType,access_token}", - upload_template = UriTemplate.parse(UPLOAD_URL), - REMOVE_URL = "https://www.googleapis.com/drive/v2/" + - "files{/id,trash}{?access_token}", - remove_template = UriTemplate.parse(REMOVE_URL), - LIST_URL = "https://www.googleapis.com/drive/v2/files" + - "?prettyPrint=false{&pageToken}&q=trashed=false" + - "&fields=nextPageToken,items(id){&access_token}", - list_template = UriTemplate.parse(LIST_URL), - GET_URL = "https://www.googleapis.com/drive/v2/files{/id}{?alt}", - get_template = UriTemplate.parse(GET_URL); + } + } - function handleError(error, id) { - if (error.target.status === 404) { - throw new jIO.util.jIOError( - "Cannot find document: " + id, - 404 - ); - } - throw error; - } + if (options.sort_on) { + for (i = 0; i < options.sort_on.length; i += 1) { + tmp_list.push(JSON.stringify(options.sort_on[i])); + } + options.sort_on = tmp_list; + } - function listPage(result, token) { - var i, - obj; - return new RSVP.Queue() - .push(function () { return jIO.util.ajax({ "type": "GET", - "url": list_template.expand({ - pageToken : (result.nextPageToken || ""), - access_token: token - }) + "url": UriTemplate.parse(site_hal._links.raw_search.href) + .expand({ + query: query, + // XXX Force erp5 to return embedded document + select_list: options.select_list || ["title", "reference"], + limit: options.limit, + sort_on: options.sort_on, + local_roles: local_roles + }), + "xhrFields": { + withCredentials: true + } }); }) - .push(function (data) { - obj = JSON.parse(data.target.response || data.target.responseText); - for (i = 0; i < obj.items.length; i += 1) { - obj.items[i].value = {}; - result.push(obj.items[i]); + .push(function (response) { + return JSON.parse(response.target.responseText); + }) + .push(function (catalog_json) { + var data = catalog_json._embedded.contents, + count = data.length, + i, + uri, + item, + result = []; + for (i = 0; i < count; i += 1) { + item = data[i]; + uri = new URI(item._links.self.href); + delete item._links; + result.push({ + id: uri.segment(2), + value: item + }); } - result.nextPageToken = obj.nextPageToken; return result; - }, handleError); - } + }); + }; - function checkName(name) { - if (name !== "enclosure") { - throw new jIO.util.jIOError("Only support 'enclosure' attachment", 400); - } - } + jIO.addStorage("erp5", ERP5Storage); + +}(jIO, UriTemplate, FormData, RSVP, URI, Blob, + SimpleQuery, ComplexQuery)); +;/*jslint nomen: true*/ +/*global RSVP*/ +(function (jIO, RSVP) { + "use strict"; /** - * The JIO Google Drive Storage extension + * The jIO QueryStorage extension * - * @class GdriveStorage + * @class QueryStorage * @constructor */ - function GdriveStorage(spec) { - if (spec === undefined || spec.access_token === undefined || - typeof spec.access_token !== 'string') { - throw new TypeError("Access Token must be a string " + - "which contains more than one character."); - } - if (spec.trashing !== undefined && - (spec.trashing !== true && spec.trashing !== false)) { - throw new TypeError("trashing parameter" + - " must be a boolean (true or false)"); - } - this._trashing = spec.trashing || true; - this._access_token = spec.access_token; - return; - } - - function recursiveAllDocs(result, accessToken) { - return new RSVP.Queue() - .push(function () { - return listPage(result, accessToken); - }) - .push(function () { - if (result.nextPageToken) { - return recursiveAllDocs(result, accessToken); - } - return result; - }); + function QueryStorage(spec) { + this._sub_storage = jIO.createJIO(spec.sub_storage); + this._key_schema = spec.key_schema; } - GdriveStorage.prototype.hasCapacity = function (name) { - return (name === "list"); + QueryStorage.prototype.get = function () { + return this._sub_storage.get.apply(this._sub_storage, arguments); }; - - GdriveStorage.prototype.buildQuery = function () { - return recursiveAllDocs([], this._access_token); + QueryStorage.prototype.allAttachments = function () { + return this._sub_storage.allAttachments.apply(this._sub_storage, arguments); + }; + QueryStorage.prototype.post = function () { + return this._sub_storage.post.apply(this._sub_storage, arguments); + }; + QueryStorage.prototype.put = function () { + return this._sub_storage.put.apply(this._sub_storage, arguments); + }; + QueryStorage.prototype.remove = function () { + return this._sub_storage.remove.apply(this._sub_storage, arguments); + }; + QueryStorage.prototype.getAttachment = function () { + return this._sub_storage.getAttachment.apply(this._sub_storage, arguments); + }; + QueryStorage.prototype.putAttachment = function () { + return this._sub_storage.putAttachment.apply(this._sub_storage, arguments); + }; + QueryStorage.prototype.removeAttachment = function () { + return this._sub_storage.removeAttachment.apply(this._sub_storage, + arguments); + }; + QueryStorage.prototype.repair = function () { + return this._sub_storage.repair.apply(this._sub_storage, arguments); }; - function sendMetaData(id, param, token) { - var boundary = "-------314159265358979323846"; - - return new RSVP.Queue() - .push(function () { - return jIO.util.ajax({ - "type": id ? "PUT" : "POST", - "url": upload_template.expand({ - access_token: token, - id: id || [], - upload: id ? [] : "upload", - uploadType: "multipart" - }), - headers: { - "Content-Type" : 'multipart/related; boundary="' + boundary + '"' - }, - data: '--' + boundary + '\n' + - 'Content-Type: application/json; charset=UTF-8\n\n' + - JSON.stringify(param) + '\n\n--' + boundary + "--" - }); - }) - .push(function (result) { - var obj = JSON.parse(result.target.responseText); - - return obj.id; - }, - function (error) {handleError(error, id); }); - } + QueryStorage.prototype.hasCapacity = function (name) { + var this_storage_capacity_list = ["limit", + "sort", + "select", + "query"]; - GdriveStorage.prototype.put = function (id, param) { - return sendMetaData(id, param, this._access_token); + if (this_storage_capacity_list.indexOf(name) !== -1) { + return true; + } + if (name === "list") { + return this._sub_storage.hasCapacity(name); + } + return false; }; + QueryStorage.prototype.buildQuery = function (options) { + var substorage = this._sub_storage, + context = this, + sub_options = {}, + is_manual_query_needed = false, + is_manual_include_needed = false; - GdriveStorage.prototype.post = function (param) { - return sendMetaData(undefined, param, this._access_token); - }; + if (substorage.hasCapacity("list")) { - function sendData(id, blob, token) { - return new RSVP.Queue() - .push(function () { - return jIO.util.ajax({ - "type": "PUT", - "url": upload_template.expand({ - access_token: token, - upload: "upload", - id: id, - uploadType: "media" - }), - data: blob - }); - }) - .push(function (data) { - data = JSON.parse(data.target.responseText); - if (data.mimeType === "application/vnd.google-apps.folder") { - throw new jIO.util.jIOError("cannot put attachments to folder", 400); + // Can substorage handle the queries if needed? + try { + if (((options.query === undefined) || + (substorage.hasCapacity("query"))) && + ((options.sort_on === undefined) || + (substorage.hasCapacity("sort"))) && + ((options.select_list === undefined) || + (substorage.hasCapacity("select"))) && + ((options.limit === undefined) || + (substorage.hasCapacity("limit")))) { + sub_options.query = options.query; + sub_options.sort_on = options.sort_on; + sub_options.select_list = options.select_list; + sub_options.limit = options.limit; } - return data; - }, function (error) {handleError(error, id); }); - } + } catch (error) { + if ((error instanceof jIO.util.jIOError) && + (error.status_code === 501)) { + is_manual_query_needed = true; + } else { + throw error; + } + } - GdriveStorage.prototype.putAttachment = function (id, name, blob) { - checkName(name); - return sendData(id, blob, this._access_token); - }; + // Can substorage include the docs if needed? + try { + if ((is_manual_query_needed || + (options.include_docs === true)) && + (substorage.hasCapacity("include"))) { + sub_options.include_docs = true; + } + } catch (error) { + if ((error instanceof jIO.util.jIOError) && + (error.status_code === 501)) { + is_manual_include_needed = true; + } else { + throw error; + } + } - GdriveStorage.prototype.remove = function (id) { - var that = this; - return new RSVP.Queue() - .push(function () { - return jIO.util.ajax({ - type: that._trashing ? "POST" : "DELETE", - url: remove_template.expand({ - id : id, - access_token : that._access_token, - trash : that._trashing ? "trash" : [] - }) - }); - }) - .push(undefined, function (error) {handleError(error, id); }); - }; + return substorage.buildQuery(sub_options) - function getData(id, attach, token) { - return new RSVP.Queue() - .push(function () { - return jIO.util.ajax({ - type: "GET", - dataType: attach ? "blob" : "json", - url: get_template.expand({ - id: id, - alt: attach ? "media" : [], - access_token: token - }), - headers: { - "Authorization" : "Bearer " + token + // Include docs if needed + .push(function (result) { + var include_query_list = [result], + len, + i; + + function safeGet(j) { + var id = result[j].id; + return substorage.get(id) + .push(function (doc) { + // XXX Can delete user data! + doc._id = id; + return doc; + }, function (error) { + // Document may have been dropped after listing + if ((error instanceof jIO.util.jIOError) && + (error.status_code === 404)) { + return; + } + throw error; + }); } - }); - }) - .push(function (evt) { - return evt.target.response || - (attach ? new Blob([evt.target.responseText], - {"type" : - evt.target.responseHeaders["Content-Type"]}) : - JSON.parse(evt.target.responseText)); - }, function (error) {handleError(error, id); }); - } - - GdriveStorage.prototype.get = function (id) { - return getData(id, false, this._access_token); - }; - GdriveStorage.prototype.getAttachment = function (id, name) { - checkName(name); - return getData(id, true, this._access_token); - }; + if (is_manual_include_needed) { + len = result.length; + for (i = 0; i < len; i += 1) { + include_query_list.push(safeGet(i)); + } + result = RSVP.all(include_query_list); + } + return result; + }) + .push(function (result) { + var original_result, + len, + i; + if (is_manual_include_needed) { + original_result = result[0]; + len = original_result.length; + for (i = 0; i < len; i += 1) { + original_result[i].doc = result[i + 1]; + } + result = original_result; + } + return result; - GdriveStorage.prototype.allAttachments = function (id) { - var token = this._access_token; + }) - return new RSVP.Queue() - .push(function () { - return getData(id, false, token); - }) - .push(function (data) { - if (data.mimeType === "application/vnd.google-apps.folder") { - return {}; - } - return {"enclosure": {}}; - }); - }; + // Manual query if needed + .push(function (result) { + var data_rows = [], + len, + i; + if (is_manual_query_needed) { + len = result.length; + for (i = 0; i < len; i += 1) { + result[i].doc.__id = result[i].id; + data_rows.push(result[i].doc); + } + if (options.select_list) { + options.select_list.push("__id"); + } + result = jIO.QueryFactory.create(options.query || "", + context._key_schema). + exec(data_rows, options); + } + return result; + }) - jIO.addStorage('gdrive', GdriveStorage); + // reconstruct filtered rows, preserving the order from docs + .push(function (result) { + var new_result = [], + element, + len, + i; + if (is_manual_query_needed) { + len = result.length; + for (i = 0; i < len; i += 1) { + element = { + id: result[i].__id, + value: options.select_list ? result[i] : {}, + doc: {} + }; + if (options.select_list) { + // Does not work if user manually request __id + delete element.value.__id; + } + if (options.include_docs) { + // XXX To implement + throw new Error("QueryStorage does not support include docs"); + } + new_result.push(element); + } + result = new_result; + } + return result; + }); -}(jIO, Blob, RSVP, UriTemplate, JSON)); -;/*jslint nomen: true */ -/*global RSVP*/ + } + }; -/** - * JIO Union Storage. Type = 'union'. - * This provide a unified access other multiple storage. - * New document are created in the first sub storage. - * Document are searched in each sub storage until it is found. - * - * - * Storage Description: - * - * { - * "type": "union", - * "storage_list": [ - * sub_storage_description_1, - * sub_storage_description_2, - * - * sub_storage_description_X, - * ] - * } - * - * @class UnionStorage - */ + jIO.addStorage('query', QueryStorage); -(function (jIO, RSVP) { +}(jIO, RSVP)); +;/*jslint nomen: true*/ +/*global RSVP, Blob*/ +(function (jIO, RSVP, Blob) { "use strict"; /** - * The JIO UnionStorage extension + * The jIO FileSystemBridgeStorage extension * - * @class UnionStorage + * @class FileSystemBridgeStorage * @constructor */ - function UnionStorage(spec) { - if (!Array.isArray(spec.storage_list)) { - throw new jIO.util.jIOError("storage_list is not an Array", 400); - } - var i; - this._storage_list = []; - for (i = 0; i < spec.storage_list.length; i += 1) { - this._storage_list.push(jIO.createJIO(spec.storage_list[i])); - } + function FileSystemBridgeStorage(spec) { + this._sub_storage = jIO.createJIO(spec.sub_storage); } + var DOCUMENT_EXTENSION = ".json", + DOCUMENT_KEY = "/.jio_documents/", + ROOT = "/"; - UnionStorage.prototype._getWithStorageIndex = function () { - var i, - index = 0, - context = this, - arg = arguments, - result = this._storage_list[0].get.apply(this._storage_list[0], arg); + function endsWith(str, suffix) { + return str.indexOf(suffix, str.length - suffix.length) !== -1; + } - function handle404(j) { - result - .push(undefined, function (error) { - if ((error instanceof jIO.util.jIOError) && - (error.status_code === 404)) { - return context._storage_list[j].get.apply(context._storage_list[j], - arg) - .push(function (doc) { - index = j; - return doc; - }); - } - throw error; - }); - } + FileSystemBridgeStorage.prototype.get = function (id) { + var context = this; + return new RSVP.Queue() - for (i = 1; i < this._storage_list.length; i += 1) { - handle404(i); - } - return result - .push(function (doc) { - return [index, doc]; - }); - }; + // First, try to get explicit reference to the document - /* - * Get a document - * Try on each substorage on after the other - */ - UnionStorage.prototype.get = function () { - return this._getWithStorageIndex.apply(this, arguments) - .push(function (result) { - return result[1]; + .push(function () { + // First get the document itself if it exists + return context._sub_storage.getAttachment( + DOCUMENT_KEY, + id + DOCUMENT_EXTENSION, + {format: "json"} + ); + }) + .push(undefined, function (error) { + if ((error instanceof jIO.util.jIOError) && + (error.status_code === 404)) { + + // Second, try to get default attachment + return context._sub_storage.allAttachments(ROOT) + .push(function (attachment_dict) { + if (attachment_dict.hasOwnProperty(id)) { + return {}; + } + throw new jIO.util.jIOError("Cannot find document " + id, + 404); + }); + } + throw error; }); }; - /* - * Get attachments list - * Try on each substorage on after the other - */ - UnionStorage.prototype.allAttachments = function () { - var argument_list = arguments, - context = this; - return this._getWithStorageIndex.apply(this, arguments) - .push(function (result) { - var sub_storage = context._storage_list[result[0]]; - return sub_storage.allAttachments.apply(sub_storage, argument_list); + FileSystemBridgeStorage.prototype.allAttachments = function (id) { + var context = this; + return context._sub_storage.allAttachments(ROOT) + .push(function (attachment_dict) { + if (attachment_dict.hasOwnProperty(id)) { + return { + enclosure: {} + }; + } + // Second get the document itself if it exists + return context._sub_storage.getAttachment( + DOCUMENT_KEY, + id + DOCUMENT_EXTENSION + ) + .push(function () { + return {}; + }, function (error) { + if ((error instanceof jIO.util.jIOError) && + (error.status_code === 404)) { + throw new jIO.util.jIOError("Cannot find document " + id, + 404); + } + throw error; + }); }); - }; - /* - * Post a document - * Simply store on the first substorage - */ - UnionStorage.prototype.post = function () { - return this._storage_list[0].post.apply(this._storage_list[0], arguments); }; - /* - * Put a document - * Search the document location, and modify it in its storage. - */ - UnionStorage.prototype.put = function () { - var arg = arguments, - context = this; - return this._getWithStorageIndex(arg[0]) + FileSystemBridgeStorage.prototype.put = function (doc_id, param) { + var context = this; + // XXX Handle conflict! + + return context._sub_storage.putAttachment( + DOCUMENT_KEY, + doc_id + DOCUMENT_EXTENSION, + new Blob([JSON.stringify(param)], {type: "application/json"}) + ) .push(undefined, function (error) { if ((error instanceof jIO.util.jIOError) && (error.status_code === 404)) { - // Document does not exist, create in first substorage - return [0]; + return context._sub_storage.put(DOCUMENT_KEY, {}) + .push(function () { + return context._sub_storage.putAttachment( + DOCUMENT_KEY, + doc_id + DOCUMENT_EXTENSION, + new Blob([JSON.stringify(param)], + {type: "application/json"}) + ); + }); } throw error; }) - .push(function (result) { - // Storage found, modify in it directly - var sub_storage = context._storage_list[result[0]]; - return sub_storage.put.apply(sub_storage, arg); + .push(function () { + return doc_id; }); - }; - /* - * Remove a document - * Search the document location, and remove it from its storage. - */ - UnionStorage.prototype.remove = function () { - var arg = arguments, - context = this; - return this._getWithStorageIndex(arg[0]) - .push(function (result) { - // Storage found, remove from it directly - var sub_storage = context._storage_list[result[0]]; - return sub_storage.remove.apply(sub_storage, arg); - }); }; - UnionStorage.prototype.buildQuery = function () { - var promise_list = [], - i, - id_dict = {}, - len = this._storage_list.length, - sub_storage; - for (i = 0; i < len; i += 1) { - sub_storage = this._storage_list[i]; - promise_list.push(sub_storage.buildQuery.apply(sub_storage, arguments)); - } + FileSystemBridgeStorage.prototype.remove = function (doc_id) { + var context = this, + got_error = false; return new RSVP.Queue() + + // First, try to remove enclosure .push(function () { - return RSVP.all(promise_list); + return context._sub_storage.removeAttachment( + ROOT, + doc_id + ); }) - .push(function (result_list) { - var result = [], - sub_result, - sub_result_len, - j; - len = result_list.length; - for (i = 0; i < len; i += 1) { - sub_result = result_list[i]; - sub_result_len = sub_result.length; - for (j = 0; j < sub_result_len; j += 1) { - if (!id_dict.hasOwnProperty(sub_result[j].id)) { - id_dict[sub_result[j].id] = null; - result.push(sub_result[j]); - } - } - } - return result; - }); - }; - UnionStorage.prototype.hasCapacity = function (name) { - var i, - len, - result, - sub_storage; - if ((name === "list") || - (name === "query") || - (name === "select")) { - result = true; - len = this._storage_list.length; - for (i = 0; i < len; i += 1) { - sub_storage = this._storage_list[i]; - result = result && sub_storage.hasCapacity(name); - } - return result; - } - return false; - }; + .push(undefined, function (error) { + if ((error instanceof jIO.util.jIOError) && + (error.status_code === 404)) { + got_error = true; + return; + } + throw error; + }) - UnionStorage.prototype.repair = function () { - var i, - promise_list = []; - for (i = 0; i < this._storage_list.length; i += 1) { - promise_list.push(this._storage_list[i].repair.apply( - this._storage_list[i], - arguments - )); - } - return RSVP.all(promise_list); - }; + // Second, try to remove explicit doc + .push(function () { + return context._sub_storage.removeAttachment( + DOCUMENT_KEY, + doc_id + DOCUMENT_EXTENSION + ); + }) - UnionStorage.prototype.getAttachment = function () { - var argument_list = arguments, - context = this; - return this._getWithStorageIndex.apply(this, arguments) - .push(function (result) { - var sub_storage = context._storage_list[result[0]]; - return sub_storage.getAttachment.apply(sub_storage, argument_list); + .push(undefined, function (error) { + if ((!got_error) && (error instanceof jIO.util.jIOError) && + (error.status_code === 404)) { + return doc_id; + } + throw error; }); - }; - UnionStorage.prototype.putAttachment = function () { - var argument_list = arguments, - context = this; - return this._getWithStorageIndex.apply(this, arguments) - .push(function (result) { - var sub_storage = context._storage_list[result[0]]; - return sub_storage.putAttachment.apply(sub_storage, argument_list); - }); }; - UnionStorage.prototype.removeAttachment = function () { - var argument_list = arguments, - context = this; - return this._getWithStorageIndex.apply(this, arguments) - .push(function (result) { - var sub_storage = context._storage_list[result[0]]; - return sub_storage.removeAttachment.apply(sub_storage, argument_list); - }); + FileSystemBridgeStorage.prototype.hasCapacity = function (capacity) { + return (capacity === "list"); }; - jIO.addStorage('union', UnionStorage); - -}(jIO, RSVP)); -;/* - * Copyright 2013, Nexedi SA - * Released under the LGPL license. - * http://www.gnu.org/licenses/lgpl.html - */ -// JIO ERP5 Storage Description : -// { -// type: "erp5" -// url: {string} -// } - -/*jslint nomen: true, unparam: true */ -/*global jIO, UriTemplate, FormData, RSVP, URI, Blob, - SimpleQuery, ComplexQuery*/ - -(function (jIO, UriTemplate, FormData, RSVP, URI, Blob, - SimpleQuery, ComplexQuery) { - "use strict"; - - function getSiteDocument(storage) { + FileSystemBridgeStorage.prototype.buildQuery = function () { + var result_dict = {}, + context = this; return new RSVP.Queue() - .push(function () { - return jIO.util.ajax({ - "type": "GET", - "url": storage._url, - "xhrFields": { - withCredentials: true - } - }); - }) - .push(function (event) { - return JSON.parse(event.target.responseText); - }); - } - - function getDocumentAndHateoas(storage, id, options) { - if (options === undefined) { - options = {}; - } - return getSiteDocument(storage) - .push(function (site_hal) { - // XXX need to get modified metadata - return new RSVP.Queue() - .push(function () { - return jIO.util.ajax({ - "type": "GET", - "url": UriTemplate.parse(site_hal._links.traverse.href) - .expand({ - relative_url: id, - view: options._view - }), - "xhrFields": { - withCredentials: true - } - }); - }) - .push(undefined, function (error) { - if ((error.target !== undefined) && - (error.target.status === 404)) { - throw new jIO.util.jIOError("Cannot find document: " + id, 404); - } - throw error; - }); - }); - } - - var allowed_field_dict = { - "StringField": null, - "EmailField": null, - "IntegerField": null, - "FloatField": null, - "TextAreaField": null - }; - function extractPropertyFromFormJSON(json) { - return new RSVP.Queue() - .push(function () { - var form = json._embedded._view, - converted_json = { - portal_type: json.portal_type - }, - form_data_json = {}, - field, - key, - prefix_length; + // First, get list of explicit documents - form_data_json.form_id = { - "key": [form.form_id.key], - "default": form.form_id["default"] - }; - // XXX How to store datetime - for (key in form) { - if (form.hasOwnProperty(key)) { - field = form[key]; - prefix_length = 0; - if (key.indexOf('my_') === 0 && field.editable) { - prefix_length = 3; - } - if (key.indexOf('your_') === 0) { - prefix_length = 5; - } - if ((prefix_length !== 0) && - (allowed_field_dict.hasOwnProperty(field.type))) { - form_data_json[key.substring(prefix_length)] = { - "default": field["default"], - "key": field.key - }; - converted_json[key.substring(prefix_length)] = field["default"]; + .push(function () { + return context._sub_storage.allAttachments(DOCUMENT_KEY); + }) + .push(function (result) { + var key; + for (key in result) { + if (result.hasOwnProperty(key)) { + if (endsWith(key, DOCUMENT_EXTENSION)) { + result_dict[key.substring( + 0, + key.length - DOCUMENT_EXTENSION.length + )] = null; } } } + }, function (error) { + if ((error instanceof jIO.util.jIOError) && + (error.status_code === 404)) { + return; + } + throw error; + }) - return { - action_href: form._actions.put.href, - data: converted_json, - form_data: form_data_json - }; - }); - } + // Second, get list of enclosure - function extractPropertyFromForm(context, id) { - return context.getAttachment(id, "view") - .push(function (blob) { - return jIO.util.readBlobAsText(blob); + .push(function () { + return context._sub_storage.allAttachments(ROOT); }) - .push(function (evt) { - return JSON.parse(evt.target.result); + .push(function (result) { + var key; + for (key in result) { + if (result.hasOwnProperty(key)) { + result_dict[key] = null; + } + } }) - .push(function (json) { - return extractPropertyFromFormJSON(json); - }); - } - // XXX docstring - function ERP5Storage(spec) { - if (typeof spec.url !== "string" || !spec.url) { - throw new TypeError("ERP5 'url' must be a string " + - "which contains more than one character."); - } - this._url = spec.url; - this._default_view_reference = spec.default_view_reference; - } + // Finally, build the result - function convertJSONToGet(json) { - var key, - result = json.data; - // Remove all ERP5 hateoas links / convert them into jIO ID - for (key in result) { - if (result.hasOwnProperty(key)) { - if (!result[key]) { - delete result[key]; + .push(function () { + var result = [], + key; + for (key in result_dict) { + if (result_dict.hasOwnProperty(key)) { + result.push({ + id: key, + value: {} + }); + } } - } + return result; + }); + + }; + + FileSystemBridgeStorage.prototype.getAttachment = function (id, name) { + if (name !== "enclosure") { + throw new jIO.util.jIOError("Only support 'enclosure' attachment", + 400); } - return result; - } - ERP5Storage.prototype.get = function (id) { - return extractPropertyFromForm(this, id) - .push(function (result) { - return convertJSONToGet(result); - }); + return this._sub_storage.getAttachment(ROOT, id); }; - ERP5Storage.prototype.bulk = function (request_list) { - var i, - storage = this, - bulk_list = []; + FileSystemBridgeStorage.prototype.putAttachment = function (id, name, blob) { + if (name !== "enclosure") { + throw new jIO.util.jIOError("Only support 'enclosure' attachment", + 400); + } + return this._sub_storage.putAttachment( + ROOT, + id, + blob + ); + }; - for (i = 0; i < request_list.length; i += 1) { - if (request_list[i].method !== "get") { - throw new Error("ERP5Storage: not supported " + - request_list[i].method + " in bulk"); - } - bulk_list.push({ - relative_url: request_list[i].parameter_list[0], - view: storage._default_view_reference - }); + FileSystemBridgeStorage.prototype.removeAttachment = function (id, name) { + if (name !== "enclosure") { + throw new jIO.util.jIOError("Only support 'enclosure' attachment", + 400); } - return getSiteDocument(storage) - .push(function (site_hal) { - var form_data = new FormData(); - form_data.append("bulk_list", JSON.stringify(bulk_list)); - return jIO.util.ajax({ - "type": "POST", - "url": site_hal._actions.bulk.href, - "data": form_data, -// "headers": { -// "Content-Type": "application/json" -// }, - "xhrFields": { - withCredentials: true - } - }); - }) - .push(function (response) { - var result_list = [], - hateoas = JSON.parse(response.target.responseText); - function pushResult(json) { - json.portal_type = json._links.type.name; - return extractPropertyFromFormJSON(json) - .push(function (json2) { - return convertJSONToGet(json2); - }); - } + return this._sub_storage.removeAttachment(ROOT, id); + }; - for (i = 0; i < hateoas.result_list.length; i += 1) { - result_list.push(pushResult(hateoas.result_list[i])); - } - return RSVP.all(result_list); - }); + FileSystemBridgeStorage.prototype.repair = function () { + return this._sub_storage.repair.apply(this._sub_storage, arguments); }; - ERP5Storage.prototype.post = function (data) { - var context = this, - new_id; + jIO.addStorage('drivetojiomapping', FileSystemBridgeStorage); - return getSiteDocument(this) - .push(function (site_hal) { - var form_data = new FormData(); - form_data.append("portal_type", data.portal_type); - form_data.append("parent_relative_url", data.parent_relative_url); - return jIO.util.ajax({ - type: "POST", - url: site_hal._actions.add.href, - data: form_data, - xhrFields: { - withCredentials: true - } - }); - }) - .push(function (evt) { - var location = evt.target.getResponseHeader("X-Location"), - uri = new URI(location); - new_id = uri.segment(2); - return context.put(new_id, data); - }) +}(jIO, RSVP, Blob)); +;/*jslint nomen: true*/ +/*global Blob, atob, btoa, RSVP*/ +(function (jIO, Blob, atob, btoa, RSVP) { + "use strict"; + + /** + * The jIO DocumentStorage extension + * + * @class DocumentStorage + * @constructor + */ + function DocumentStorage(spec) { + this._sub_storage = jIO.createJIO(spec.sub_storage); + this._document_id = spec.document_id; + this._repair_attachment = spec.repair_attachment || false; + } + + var DOCUMENT_EXTENSION = ".json", + DOCUMENT_REGEXP = new RegExp("^jio_document/([\\w=]+)" + + DOCUMENT_EXTENSION + "$"), + ATTACHMENT_REGEXP = new RegExp("^jio_attachment/([\\w=]+)/([\\w=]+)$"); + + function getSubAttachmentIdFromParam(id, name) { + if (name === undefined) { + return 'jio_document/' + btoa(id) + DOCUMENT_EXTENSION; + } + return 'jio_attachment/' + btoa(id) + "/" + btoa(name); + } + + DocumentStorage.prototype.get = function (id) { + return this._sub_storage.getAttachment( + this._document_id, + getSubAttachmentIdFromParam(id), + {format: "json"} + ); + }; + + DocumentStorage.prototype.allAttachments = function (id) { + return this._sub_storage.allAttachments(this._document_id) + .push(function (result) { + var attachments = {}, + exec, + key; + for (key in result) { + if (result.hasOwnProperty(key)) { + if (ATTACHMENT_REGEXP.test(key)) { + exec = ATTACHMENT_REGEXP.exec(key); + try { + if (atob(exec[1]) === id) { + attachments[atob(exec[2])] = {}; + } + } catch (error) { + // Check if unable to decode base64 data + if (!error instanceof ReferenceError) { + throw error; + } + } + } + } + } + return attachments; + }); + }; + + DocumentStorage.prototype.put = function (doc_id, param) { + return this._sub_storage.putAttachment( + this._document_id, + getSubAttachmentIdFromParam(doc_id), + new Blob([JSON.stringify(param)], {type: "application/json"}) + ) .push(function () { - return new_id; + return doc_id; }); + }; - ERP5Storage.prototype.put = function (id, data) { + DocumentStorage.prototype.remove = function (id) { var context = this; - - return extractPropertyFromForm(context, id) + return this.allAttachments(id) .push(function (result) { var key, - json = result.form_data, - form_data = {}; - form_data[json.form_id.key] = json.form_id["default"]; - - // XXX How to store datetime:!!!!! - for (key in data) { - if (data.hasOwnProperty(key)) { - if (key === "form_id") { - throw new jIO.util.jIOError( - "ERP5: forbidden property: " + key, - 400 - ); - } - if ((key !== "portal_type") && (key !== "parent_relative_url")) { - if (!json.hasOwnProperty(key)) { - throw new jIO.util.jIOError( - "ERP5: can not store property: " + key, - 400 - ); - } - form_data[json[key].key] = data[key]; - } + promise_list = []; + for (key in result) { + if (result.hasOwnProperty(key)) { + promise_list.push(context.removeAttachment(id, key)); } } - return context.putAttachment( - id, - result.action_href, - new Blob([JSON.stringify(form_data)], {type: "application/json"}) + return RSVP.all(promise_list); + }) + .push(function () { + return context._sub_storage.removeAttachment( + context._document_id, + getSubAttachmentIdFromParam(id) ); + }) + .push(function () { + return id; }); }; - ERP5Storage.prototype.allAttachments = function (id) { + DocumentStorage.prototype.repair = function () { var context = this; - return getDocumentAndHateoas(this, id) - .push(function () { - if (context._default_view_reference === undefined) { - return { - links: {} - }; + return this._sub_storage.repair.apply(this._sub_storage, arguments) + .push(function (result) { + if (context._repair_attachment) { + return context._sub_storage.allAttachments(context._document_id) + .push(function (result_dict) { + var promise_list = [], + id_dict = {}, + attachment_dict = {}, + id, + attachment, + exec, + key; + for (key in result_dict) { + if (result_dict.hasOwnProperty(key)) { + id = undefined; + attachment = undefined; + if (DOCUMENT_REGEXP.test(key)) { + try { + id = atob(DOCUMENT_REGEXP.exec(key)[1]); + } catch (error) { + // Check if unable to decode base64 data + if (!error instanceof ReferenceError) { + throw error; + } + } + if (id !== undefined) { + id_dict[id] = null; + } + } else if (ATTACHMENT_REGEXP.test(key)) { + exec = ATTACHMENT_REGEXP.exec(key); + try { + id = atob(exec[1]); + attachment = atob(exec[2]); + } catch (error) { + // Check if unable to decode base64 data + if (!error instanceof ReferenceError) { + throw error; + } + } + if (attachment !== undefined) { + if (!id_dict.hasOwnProperty(id)) { + if (!attachment_dict.hasOwnProperty(id)) { + attachment_dict[id] = {}; + } + attachment_dict[id][attachment] = null; + } + } + } + } + } + for (id in attachment_dict) { + if (attachment_dict.hasOwnProperty(id)) { + if (!id_dict.hasOwnProperty(id)) { + for (attachment in attachment_dict[id]) { + if (attachment_dict[id].hasOwnProperty(attachment)) { + promise_list.push(context.removeAttachment( + id, + attachment + )); + } + } + } + } + } + return RSVP.all(promise_list); + }); } - return { - view: {}, - links: {} - }; + return result; }); }; - ERP5Storage.prototype.getAttachment = function (id, action) { - - if (action === "view") { - if (this._default_view_reference === undefined) { - throw new jIO.util.jIOError( - "Cannot find attachment view for: " + id, - 404 - ); - } - return getDocumentAndHateoas(this, id, - {"_view": this._default_view_reference}) - .push(function (response) { - var result = JSON.parse(response.target.responseText); - result._id = id; - result.portal_type = result._links.type.name; - // Remove all ERP5 hateoas links / convert them into jIO ID - - // XXX Change default action to an jio urn with attachment name inside - // if Base_edit, do put URN - // if others, do post URN (ie, unique new attachment name) - // XXX Except this attachment name should be generated when - return new Blob( - [JSON.stringify(result)], - {"type": 'application/hal+json'} - ); - }); - } - if (action === "links") { - return getDocumentAndHateoas(this, id) - .push(function (response) { - return new Blob( - [JSON.stringify(JSON.parse(response.target.responseText))], - {"type": 'application/hal+json'} - ); - }); - } - if (action.indexOf(this._url) === 0) { - return new RSVP.Queue() - .push(function () { - return jIO.util.ajax({ - "type": "GET", - "url": action, - "xhrFields": { - withCredentials: true - } - }); - }) - .push(function (evt) { - var result = JSON.parse(evt.target.responseText); - result._id = id; - return new Blob( - [JSON.stringify(result)], - {"type": evt.target.getResponseHeader("Content-Type")} - ); - }); - } - throw new jIO.util.jIOError("ERP5: not support get attachment: " + action, - 400); + DocumentStorage.prototype.hasCapacity = function (capacity) { + return (capacity === "list"); }; - ERP5Storage.prototype.putAttachment = function (id, name, blob) { - // Assert we use a callable on a document from the ERP5 site - if (name.indexOf(this._url) !== 0) { - throw new jIO.util.jIOError("Can not store outside ERP5: " + - name, 400); - } - - return new RSVP.Queue() - .push(function () { - return jIO.util.readBlobAsText(blob); - }) - .push(function (evt) { - var form_data = JSON.parse(evt.target.result), - data = new FormData(), - i, + DocumentStorage.prototype.buildQuery = function () { + return this._sub_storage.allAttachments(this._document_id) + .push(function (attachment_dict) { + var result = [], key; - for (key in form_data) { - if (form_data.hasOwnProperty(key)) { - if (Array.isArray(form_data[key])) { - for (i = 0; i < form_data[key].length; i += 1) { - data.append(key, form_data[key][i]); + for (key in attachment_dict) { + if (attachment_dict.hasOwnProperty(key)) { + if (DOCUMENT_REGEXP.test(key)) { + try { + result.push({ + id: atob(DOCUMENT_REGEXP.exec(key)[1]), + value: {} + }); + } catch (error) { + // Check if unable to decode base64 data + if (!error instanceof ReferenceError) { + throw error; + } } - } else { - data.append(key, form_data[key]); } } } - return jIO.util.ajax({ - "type": "POST", - "url": name, - "data": data, - "xhrFields": { - withCredentials: true - } - }); + return result; }); }; - ERP5Storage.prototype.hasCapacity = function (name) { - return ((name === "list") || (name === "query") || - (name === "select") || (name === "limit") || - (name === "sort")); + DocumentStorage.prototype.getAttachment = function (id, name) { + return this._sub_storage.getAttachment( + this._document_id, + getSubAttachmentIdFromParam(id, name) + ); + }; + + DocumentStorage.prototype.putAttachment = function (id, name, blob) { + return this._sub_storage.putAttachment( + this._document_id, + getSubAttachmentIdFromParam(id, name), + blob + ); + }; + + DocumentStorage.prototype.removeAttachment = function (id, name) { + return this._sub_storage.removeAttachment( + this._document_id, + getSubAttachmentIdFromParam(id, name) + ); }; - function isSingleLocalRoles(parsed_query) { - if ((parsed_query instanceof SimpleQuery) && - (parsed_query.key === 'local_roles')) { - // local_roles:"Assignee" - return parsed_query.value; + jIO.addStorage('document', DocumentStorage); + +}(jIO, Blob, atob, btoa, RSVP)); +;/* + * Copyright 2013, Nexedi SA + * Released under the LGPL license. + * http://www.gnu.org/licenses/lgpl.html + */ + +/*jslint nomen: true*/ +/*global jIO, sessionStorage, localStorage, RSVP */ + +/** + * JIO Local Storage. Type = 'local'. + * Local browser "database" storage. + * + * Storage Description: + * + * { + * "type": "local", + * "sessiononly": false + * } + * + * @class LocalStorage + */ + +(function (jIO, sessionStorage, localStorage, RSVP) { + "use strict"; + + function LocalStorage(spec) { + if (spec.sessiononly === true) { + this._storage = sessionStorage; + } else { + this._storage = localStorage; } } - function isMultipleLocalRoles(parsed_query) { - var i, - sub_query, - is_multiple = true, - local_role_list = []; - if ((parsed_query instanceof ComplexQuery) && - (parsed_query.operator === 'OR')) { - - for (i = 0; i < parsed_query.query_list.length; i += 1) { - sub_query = parsed_query.query_list[i]; - if ((sub_query instanceof SimpleQuery) && - (sub_query.key === 'local_roles')) { - local_role_list.push(sub_query.value); - } else { - is_multiple = false; - } - } - if (is_multiple) { - // local_roles:"Assignee" OR local_roles:"Assignor" - return local_role_list; - } + function restrictDocumentId(id) { + if (id !== "/") { + throw new jIO.util.jIOError("id " + id + " is forbidden (!== /)", + 400); } } - ERP5Storage.prototype.buildQuery = function (options) { -// if (typeof options.query !== "string") { -// options.query = (options.query ? -// jIO.Query.objectToSearchText(options.query) : -// undefined); -// } - return getSiteDocument(this) - .push(function (site_hal) { - var query = options.query, - i, - parsed_query, - sub_query, - result_list, - local_roles; - if (options.query) { - parsed_query = jIO.QueryFactory.create(options.query); + LocalStorage.prototype.get = function (id) { + restrictDocumentId(id); + return {}; + }; - result_list = isSingleLocalRoles(parsed_query); - if (result_list) { - query = undefined; - local_roles = result_list; - } else { + LocalStorage.prototype.allAttachments = function (id) { + restrictDocumentId(id); - result_list = isMultipleLocalRoles(parsed_query); - if (result_list) { - query = undefined; - local_roles = result_list; - } else if ((parsed_query instanceof ComplexQuery) && - (parsed_query.operator === 'AND')) { + var attachments = {}, + key; - // portal_type:"Person" AND local_roles:"Assignee" - for (i = 0; i < parsed_query.query_list.length; i += 1) { - sub_query = parsed_query.query_list[i]; + for (key in this._storage) { + if (this._storage.hasOwnProperty(key)) { + attachments[key] = {}; + } + } + return attachments; + }; - result_list = isSingleLocalRoles(sub_query); - if (result_list) { - local_roles = result_list; - parsed_query.query_list.splice(i, 1); - query = jIO.Query.objectToSearchText(parsed_query); - i = parsed_query.query_list.length; - } else { - result_list = isMultipleLocalRoles(sub_query); - if (result_list) { - local_roles = result_list; - parsed_query.query_list.splice(i, 1); - query = jIO.Query.objectToSearchText(parsed_query); - i = parsed_query.query_list.length; - } - } - } - } + LocalStorage.prototype.getAttachment = function (id, name) { + restrictDocumentId(id); - } - } + var textstring = this._storage.getItem(name); - return jIO.util.ajax({ - "type": "GET", - "url": UriTemplate.parse(site_hal._links.raw_search.href) - .expand({ - query: query, - // XXX Force erp5 to return embedded document - select_list: options.select_list || ["title", "reference"], - limit: options.limit, - sort_on: options.sort_on, - local_roles: local_roles - }), - "xhrFields": { - withCredentials: true - } - }); - }) - .push(function (response) { - return JSON.parse(response.target.responseText); + if (textstring === null) { + throw new jIO.util.jIOError( + "Cannot find attachment " + name, + 404 + ); + } + return jIO.util.dataURItoBlob(textstring); + }; + + LocalStorage.prototype.putAttachment = function (id, name, blob) { + var context = this; + restrictDocumentId(id); + + // the document already exists + // download data + return new RSVP.Queue() + .push(function () { + return jIO.util.readBlobAsDataURL(blob); }) - .push(function (catalog_json) { - var data = catalog_json._embedded.contents, - count = data.length, - i, - uri, - item, - result = []; - for (i = 0; i < count; i += 1) { - item = data[i]; - uri = new URI(item._links.self.href); - delete item._links; - result.push({ - id: uri.segment(2), - value: item - }); - } - return result; + .push(function (e) { + context._storage.setItem(name, e.target.result); }); }; - jIO.addStorage("erp5", ERP5Storage); + LocalStorage.prototype.removeAttachment = function (id, name) { + restrictDocumentId(id); + return this._storage.removeItem(name); + }; -}(jIO, UriTemplate, FormData, RSVP, URI, Blob, - SimpleQuery, ComplexQuery)); -;/*jslint nomen: true*/ -/*global RSVP*/ -(function (jIO, RSVP) { - "use strict"; - /** - * The jIO QueryStorage extension - * - * @class QueryStorage - * @constructor - */ - function QueryStorage(spec) { - this._sub_storage = jIO.createJIO(spec.sub_storage); - this._key_schema = spec.key_schema; - } + LocalStorage.prototype.hasCapacity = function (name) { + return (name === "list"); + }; + + LocalStorage.prototype.buildQuery = function () { + return [{ + id: "/", + value: {} + }]; + }; + + jIO.addStorage('local', LocalStorage); + +}(jIO, sessionStorage, localStorage, RSVP)); +;/* + * Copyright 2014, Nexedi SA + * Released under the LGPL license. + * http://www.gnu.org/licenses/lgpl.html + */ + +/** + * JIO Indexed Database Storage. + * + * A local browser "database" storage greatly more powerful than localStorage. + * + * Description: + * + * { + * "type": "indexeddb", + * "database": <string> + * } + * + * The database name will be prefixed by "jio:", so if the database property is + * "hello", then you can manually reach this database with + * `indexedDB.open("jio:hello");`. (Or + * `indexedDB.deleteDatabase("jio:hello");`.) + * + * For more informations: + * + * - http://www.w3.org/TR/IndexedDB/ + * - https://developer.mozilla.org/en-US/docs/IndexedDB/Using_IndexedDB + */ + +/*jslint nomen: true */ +/*global indexedDB, jIO, RSVP, Blob, Math, IDBKeyRange*/ + +(function (indexedDB, jIO, RSVP, Blob, Math, IDBKeyRange) { + "use strict"; - QueryStorage.prototype.get = function () { - return this._sub_storage.get.apply(this._sub_storage, arguments); - }; - QueryStorage.prototype.allAttachments = function () { - return this._sub_storage.allAttachments.apply(this._sub_storage, arguments); - }; - QueryStorage.prototype.post = function () { - return this._sub_storage.post.apply(this._sub_storage, arguments); - }; - QueryStorage.prototype.put = function () { - return this._sub_storage.put.apply(this._sub_storage, arguments); - }; - QueryStorage.prototype.remove = function () { - return this._sub_storage.remove.apply(this._sub_storage, arguments); - }; - QueryStorage.prototype.getAttachment = function () { - return this._sub_storage.getAttachment.apply(this._sub_storage, arguments); - }; - QueryStorage.prototype.putAttachment = function () { - return this._sub_storage.putAttachment.apply(this._sub_storage, arguments); - }; - QueryStorage.prototype.removeAttachment = function () { - return this._sub_storage.removeAttachment.apply(this._sub_storage, - arguments); - }; - QueryStorage.prototype.repair = function () { - return this._sub_storage.repair.apply(this._sub_storage, arguments); - }; + // Read only as changing it can lead to data corruption + var UNITE = 2000000; - QueryStorage.prototype.hasCapacity = function (name) { - if (name === "list") { - return this._sub_storage.hasCapacity(name); + function IndexedDBStorage(description) { + if (typeof description.database !== "string" || + description.database === "") { + throw new TypeError("IndexedDBStorage 'database' description property " + + "must be a non-empty string"); } - return true; + this._database_name = "jio:" + description.database; + } + + IndexedDBStorage.prototype.hasCapacity = function (name) { + return ((name === "list") || (name === "include")); }; - QueryStorage.prototype.buildQuery = function (options) { - var substorage = this._sub_storage, - context = this, - sub_options = {}, - is_manual_query_needed = false, - is_manual_include_needed = false; - if (substorage.hasCapacity("list")) { + function buildKeyPath(key_list) { + return key_list.join("_"); + } - // Can substorage handle the queries if needed? - try { - if (((options.query === undefined) || - (substorage.hasCapacity("query"))) && - ((options.sort_on === undefined) || - (substorage.hasCapacity("sort"))) && - ((options.select_list === undefined) || - (substorage.hasCapacity("select"))) && - ((options.limit === undefined) || - (substorage.hasCapacity("limit")))) { - sub_options.query = options.query; - sub_options.sort_on = options.sort_on; - sub_options.select_list = options.select_list; - sub_options.limit = options.limit; - } - } catch (error) { - if ((error instanceof jIO.util.jIOError) && - (error.status_code === 501)) { - is_manual_query_needed = true; - } else { - throw error; - } - } + function handleUpgradeNeeded(evt) { + var db = evt.target.result, + store; - // Can substorage include the docs if needed? - try { - if ((is_manual_query_needed || - (options.include_docs === true)) && - (substorage.hasCapacity("include"))) { - sub_options.include_docs = true; - } - } catch (error) { - if ((error instanceof jIO.util.jIOError) && - (error.status_code === 501)) { - is_manual_include_needed = true; - } else { - throw error; - } - } + store = db.createObjectStore("metadata", { + keyPath: "_id", + autoIncrement: false + }); + // It is not possible to use openKeyCursor on keypath directly + // https://www.w3.org/Bugs/Public/show_bug.cgi?id=19955 + store.createIndex("_id", "_id", {unique: true}); - return substorage.buildQuery(sub_options) + store = db.createObjectStore("attachment", { + keyPath: "_key_path", + autoIncrement: false + }); + store.createIndex("_id", "_id", {unique: false}); - // Include docs if needed - .push(function (result) { - var include_query_list = [result], - len, - i; + store = db.createObjectStore("blob", { + keyPath: "_key_path", + autoIncrement: false + }); + store.createIndex("_id_attachment", + ["_id", "_attachment"], {unique: false}); + store.createIndex("_id", "_id", {unique: false}); + } - function safeGet(j) { - var id = result[j].id; - return substorage.get(id) - .push(function (doc) { - // XXX Can delete user data! - doc._id = id; - return doc; - }, function (error) { - // Document may have been dropped after listing - if ((error instanceof jIO.util.jIOError) && - (error.status_code === 404)) { - return; - } - throw error; - }); - } + function openIndexedDB(jio_storage) { + var db_name = jio_storage._database_name; + function resolver(resolve, reject) { + // Open DB // + var request = indexedDB.open(db_name); + request.onerror = function (error) { + if (request.result) { + request.result.close(); + } + reject(error); + }; - if (is_manual_include_needed) { - len = result.length; - for (i = 0; i < len; i += 1) { - include_query_list.push(safeGet(i)); - } - result = RSVP.all(include_query_list); - } - return result; - }) - .push(function (result) { - var original_result, - len, - i; - if (is_manual_include_needed) { - original_result = result[0]; - len = original_result.length; - for (i = 0; i < len; i += 1) { - original_result[i].doc = result[i + 1]; - } - result = original_result; - } - return result; + request.onabort = function () { + request.result.close(); + reject("Aborting connection to: " + db_name); + }; - }) + request.ontimeout = function () { + request.result.close(); + reject("Connection to: " + db_name + " timeout"); + }; - // Manual query if needed - .push(function (result) { - var data_rows = [], - len, - i; - if (is_manual_query_needed) { - len = result.length; - for (i = 0; i < len; i += 1) { - result[i].doc.__id = result[i].id; - data_rows.push(result[i].doc); - } - if (options.select_list) { - options.select_list.push("__id"); - } - result = jIO.QueryFactory.create(options.query || "", - context._key_schema). - exec(data_rows, options); - } - return result; - }) + request.onblocked = function () { + request.result.close(); + reject("Connection to: " + db_name + " was blocked"); + }; - // reconstruct filtered rows, preserving the order from docs - .push(function (result) { - var new_result = [], - element, - len, - i; - if (is_manual_query_needed) { - len = result.length; - for (i = 0; i < len; i += 1) { - element = { - id: result[i].__id, - value: options.select_list ? result[i] : {}, - doc: {} - }; - if (options.select_list) { - // Does not work if user manually request __id - delete element.value.__id; - } - if (options.include_docs) { - // XXX To implement - throw new Error("QueryStorage does not support include docs"); - } - new_result.push(element); - } - result = new_result; - } - return result; - }); + // Create DB if necessary // + request.onupgradeneeded = handleUpgradeNeeded; + + request.onversionchange = function () { + request.result.close(); + reject(db_name + " was upgraded"); + }; + + request.onsuccess = function () { + resolve(request.result); + }; + } + // XXX Canceller??? + return new RSVP.Queue() + .push(function () { + return new RSVP.Promise(resolver); + }); + } + function openTransaction(db, stores, flag, autoclosedb) { + var tx = db.transaction(stores, flag); + if (autoclosedb !== false) { + tx.oncomplete = function () { + db.close(); + }; } - }; - - jIO.addStorage('query', QueryStorage); + tx.onabort = function () { + db.close(); + }; + return tx; + } -}(jIO, RSVP)); -;/*jslint nomen: true*/ -/*global RSVP, Blob*/ -(function (jIO, RSVP, Blob) { - "use strict"; + function handleCursor(request, callback) { + function resolver(resolve, reject) { + // Open DB // + request.onerror = function (error) { + if (request.transaction) { + request.transaction.abort(); + } + reject(error); + }; - /** - * The jIO FileSystemBridgeStorage extension - * - * @class FileSystemBridgeStorage - * @constructor - */ - function FileSystemBridgeStorage(spec) { - this._sub_storage = jIO.createJIO(spec.sub_storage); - } - var DOCUMENT_EXTENSION = ".json", - DOCUMENT_KEY = "/.jio_documents/", - ROOT = "/"; + request.onsuccess = function (evt) { + var cursor = evt.target.result; + if (cursor) { + // XXX Wait for result + try { + callback(cursor); + } catch (error) { + reject(error); + } - function endsWith(str, suffix) { - return str.indexOf(suffix, str.length - suffix.length) !== -1; + // continue to next iteration + cursor["continue"](); + } else { + resolve(); + } + }; + } + // XXX Canceller??? + return new RSVP.Promise(resolver); } - FileSystemBridgeStorage.prototype.get = function (id) { - var context = this; - return new RSVP.Queue() - - // First, try to get explicit reference to the document + IndexedDBStorage.prototype.buildQuery = function (options) { + var result_list = []; - .push(function () { - // First get the document itself if it exists - return context._sub_storage.getAttachment( - DOCUMENT_KEY, - id + DOCUMENT_EXTENSION, - {format: "json"} - ); - }) - .push(undefined, function (error) { - if ((error instanceof jIO.util.jIOError) && - (error.status_code === 404)) { + function pushIncludedMetadata(cursor) { + result_list.push({ + "id": cursor.key, + "value": {}, + "doc": cursor.value.doc + }); + } - // Second, try to get default attachment - return context._sub_storage.allAttachments(ROOT) - .push(function (attachment_dict) { - if (attachment_dict.hasOwnProperty(id)) { - return {}; - } - throw new jIO.util.jIOError("Cannot find document " + id, - 404); - }); + function pushMetadata(cursor) { + result_list.push({ + "id": cursor.key, + "value": {} + }); + } + return openIndexedDB(this) + .push(function (db) { + var tx = openTransaction(db, ["metadata"], "readonly"); + if (options.include_docs === true) { + return handleCursor(tx.objectStore("metadata").index("_id") + .openCursor(), pushIncludedMetadata); } - throw error; + return handleCursor(tx.objectStore("metadata").index("_id") + .openKeyCursor(), pushMetadata); + }) + .push(function () { + return result_list; }); + }; - FileSystemBridgeStorage.prototype.allAttachments = function (id) { - var context = this; - return context._sub_storage.allAttachments(ROOT) - .push(function (attachment_dict) { - if (attachment_dict.hasOwnProperty(id)) { - return { - enclosure: {} - }; + function handleGet(request) { + function resolver(resolve, reject) { + request.onerror = reject; + request.onsuccess = function () { + if (request.result) { + resolve(request.result); } - // Second get the document itself if it exists - return context._sub_storage.getAttachment( - DOCUMENT_KEY, - id + DOCUMENT_EXTENSION - ) - .push(function () { - return {}; - }, function (error) { - if ((error instanceof jIO.util.jIOError) && - (error.status_code === 404)) { - throw new jIO.util.jIOError("Cannot find document " + id, - 404); - } - throw error; - }); - }); + // XXX How to get ID + reject(new jIO.util.jIOError("Cannot find document", 404)); + }; + } + return new RSVP.Promise(resolver); + } + IndexedDBStorage.prototype.get = function (id) { + return openIndexedDB(this) + .push(function (db) { + var transaction = openTransaction(db, ["metadata"], + "readonly"); + return handleGet(transaction.objectStore("metadata").get(id)); + }) + .push(function (result) { + return result.doc; + }); }; - FileSystemBridgeStorage.prototype.put = function (doc_id, param) { - var context = this; - // XXX Handle conflict! + IndexedDBStorage.prototype.allAttachments = function (id) { + var attachment_dict = {}; - return context._sub_storage.putAttachment( - DOCUMENT_KEY, - doc_id + DOCUMENT_EXTENSION, - new Blob([JSON.stringify(param)], {type: "application/json"}) - ) - .push(undefined, function (error) { - if ((error instanceof jIO.util.jIOError) && - (error.status_code === 404)) { - return context._sub_storage.put(DOCUMENT_KEY, {}) - .push(function () { - return context._sub_storage.putAttachment( - DOCUMENT_KEY, - doc_id + DOCUMENT_EXTENSION, - new Blob([JSON.stringify(param)], - {type: "application/json"}) - ); - }); - } - throw error; + function addEntry(cursor) { + attachment_dict[cursor.value._attachment] = {}; + } + + return openIndexedDB(this) + .push(function (db) { + var transaction = openTransaction(db, ["metadata", "attachment"], + "readonly"); + return RSVP.all([ + handleGet(transaction.objectStore("metadata").get(id)), + handleCursor(transaction.objectStore("attachment").index("_id") + .openCursor(IDBKeyRange.only(id)), addEntry) + ]); }) .push(function () { - return doc_id; + return attachment_dict; }); + }; + + function handleRequest(request) { + function resolver(resolve, reject) { + request.onerror = reject; + request.onsuccess = function () { + resolve(request.result); + }; + } + return new RSVP.Promise(resolver); + } + + IndexedDBStorage.prototype.put = function (id, metadata) { + return openIndexedDB(this) + .push(function (db) { + var transaction = openTransaction(db, ["metadata"], "readwrite"); + return handleRequest(transaction.objectStore("metadata").put({ + "_id": id, + "doc": metadata + })); + }); + }; + + function deleteEntry(cursor) { + cursor["delete"](); + } + IndexedDBStorage.prototype.remove = function (id) { + return openIndexedDB(this) + .push(function (db) { + var transaction = openTransaction(db, ["metadata", "attachment", + "blob"], "readwrite"); + return RSVP.all([ + handleRequest(transaction + .objectStore("metadata")["delete"](id)), + // XXX Why not possible to delete with KeyCursor? + handleCursor(transaction.objectStore("attachment").index("_id") + .openCursor(IDBKeyRange.only(id)), deleteEntry), + handleCursor(transaction.objectStore("blob").index("_id") + .openCursor(IDBKeyRange.only(id)), deleteEntry) + ]); + }); }; - FileSystemBridgeStorage.prototype.remove = function (doc_id) { - var context = this, - got_error = false; - return new RSVP.Queue() + IndexedDBStorage.prototype.getAttachment = function (id, name, options) { + var transaction, + type, + start, + end; + if (options === undefined) { + options = {}; + } + return openIndexedDB(this) + .push(function (db) { + transaction = openTransaction(db, ["attachment", "blob"], "readonly"); + // XXX Should raise if key is not good + return handleGet(transaction.objectStore("attachment") + .get(buildKeyPath([id, name]))); + }) + .push(function (attachment) { + var total_length = attachment.info.length, + i, + promise_list = [], + store = transaction.objectStore("blob"), + start_index, + end_index; + + type = attachment.info.content_type; + start = options.start || 0; + end = options.end || total_length; + if (end > total_length) { + end = total_length; + } - // First, try to remove enclosure - .push(function () { - return context._sub_storage.removeAttachment( - ROOT, - doc_id - ); - }) + if (start < 0 || end < 0) { + throw new jIO.util.jIOError("_start and _end must be positive", + 400); + } + if (start > end) { + throw new jIO.util.jIOError("_start is greater than _end", + 400); + } - .push(undefined, function (error) { - if ((error instanceof jIO.util.jIOError) && - (error.status_code === 404)) { - got_error = true; - return; + start_index = Math.floor(start / UNITE); + end_index = Math.floor(end / UNITE); + if (end % UNITE === 0) { + end_index -= 1; } - throw error; - }) - // Second, try to remove explicit doc - .push(function () { - return context._sub_storage.removeAttachment( - DOCUMENT_KEY, - doc_id + DOCUMENT_EXTENSION - ); + for (i = start_index; i <= end_index; i += 1) { + promise_list.push( + handleGet(store.get(buildKeyPath([id, + name, i]))) + ); + } + return RSVP.all(promise_list); }) - - .push(undefined, function (error) { - if ((!got_error) && (error instanceof jIO.util.jIOError) && - (error.status_code === 404)) { - return doc_id; + .push(function (result_list) { + var array_buffer_list = [], + blob, + i, + index, + len = result_list.length; + for (i = 0; i < len; i += 1) { + array_buffer_list.push(result_list[i].blob); } - throw error; + if ((options.start === undefined) && (options.end === undefined)) { + return new Blob(array_buffer_list, {type: type}); + } + index = Math.floor(start / UNITE) * UNITE; + blob = new Blob(array_buffer_list, {type: "application/octet-stream"}); + return blob.slice(start - index, end - index, + "application/octet-stream"); }); - }; - FileSystemBridgeStorage.prototype.hasCapacity = function (capacity) { - return (capacity === "list"); - }; + function removeAttachment(transaction, id, name) { + return RSVP.all([ + // XXX How to get the right attachment + handleRequest(transaction.objectStore("attachment")["delete"]( + buildKeyPath([id, name]) + )), + handleCursor(transaction.objectStore("blob").index("_id_attachment") + .openCursor(IDBKeyRange.only( + [id, name] + )), + deleteEntry + ) + ]); + } - FileSystemBridgeStorage.prototype.buildQuery = function () { - var result_dict = {}, - context = this; - return new RSVP.Queue() + IndexedDBStorage.prototype.putAttachment = function (id, name, blob) { + var blob_part = [], + transaction, + db; - // First, get list of explicit documents + return openIndexedDB(this) + .push(function (database) { + db = database; - .push(function () { - return context._sub_storage.allAttachments(DOCUMENT_KEY); - }) - .push(function (result) { - var key; - for (key in result) { - if (result.hasOwnProperty(key)) { - if (endsWith(key, DOCUMENT_EXTENSION)) { - result_dict[key.substring( - 0, - key.length - DOCUMENT_EXTENSION.length - )] = null; - } - } - } - }, function (error) { - if ((error instanceof jIO.util.jIOError) && - (error.status_code === 404)) { - return; - } - throw error; + // Split the blob first + return jIO.util.readBlobAsArrayBuffer(blob); }) + .push(function (event) { + var array_buffer = event.target.result, + total_size = blob.size, + handled_size = 0; - // Second, get list of enclosure - - .push(function () { - return context._sub_storage.allAttachments(ROOT); - }) - .push(function (result) { - var key; - for (key in result) { - if (result.hasOwnProperty(key)) { - result_dict[key] = null; - } + while (handled_size < total_size) { + blob_part.push(array_buffer.slice(handled_size, + handled_size + UNITE)); + handled_size += UNITE; } - }) - - // Finally, build the result + // Remove previous attachment + transaction = openTransaction(db, ["attachment", "blob"], "readwrite"); + return removeAttachment(transaction, id, name); + }) .push(function () { - var result = [], - key; - for (key in result_dict) { - if (result_dict.hasOwnProperty(key)) { - result.push({ - id: key, - value: {} - }); - } + + var promise_list = [ + handleRequest(transaction.objectStore("attachment").put({ + "_key_path": buildKeyPath([id, name]), + "_id": id, + "_attachment": name, + "info": { + "content_type": blob.type, + "length": blob.size + } + })) + ], + len = blob_part.length, + blob_store = transaction.objectStore("blob"), + i; + for (i = 0; i < len; i += 1) { + promise_list.push( + handleRequest(blob_store.put({ + "_key_path": buildKeyPath([id, name, + i]), + "_id" : id, + "_attachment" : name, + "_part" : i, + "blob": blob_part[i] + })) + ); } - return result; + // Store all new data + return RSVP.all(promise_list); }); - }; - FileSystemBridgeStorage.prototype.getAttachment = function (id, name) { - if (name !== "enclosure") { - throw new jIO.util.jIOError("Only support 'enclosure' attachment", - 400); - } - - return this._sub_storage.getAttachment(ROOT, id); + IndexedDBStorage.prototype.removeAttachment = function (id, name) { + return openIndexedDB(this) + .push(function (db) { + var transaction = openTransaction(db, ["attachment", "blob"], + "readwrite"); + return removeAttachment(transaction, id, name); + }); }; - FileSystemBridgeStorage.prototype.putAttachment = function (id, name, blob) { - if (name !== "enclosure") { - throw new jIO.util.jIOError("Only support 'enclosure' attachment", - 400); - } + jIO.addStorage("indexeddb", IndexedDBStorage); +}(indexedDB, jIO, RSVP, Blob, Math, IDBKeyRange)); +;/* + * Copyright 2015, Nexedi SA + * Released under the LGPL license. + * http://www.gnu.org/licenses/lgpl.html + */ - return this._sub_storage.putAttachment( - ROOT, - id, - blob - ); - }; +/*jslint nomen: true*/ +/*global jIO, RSVP, DOMException, Blob, crypto, Uint8Array, ArrayBuffer*/ - FileSystemBridgeStorage.prototype.removeAttachment = function (id, name) { - if (name !== "enclosure") { - throw new jIO.util.jIOError("Only support 'enclosure' attachment", - 400); - } +(function (jIO, RSVP, DOMException, Blob, crypto, Uint8Array, ArrayBuffer) { + "use strict"; - return this._sub_storage.removeAttachment(ROOT, id); - }; - FileSystemBridgeStorage.prototype.repair = function () { - return this._sub_storage.repair.apply(this._sub_storage, arguments); - }; + // you the cryptography system used by this storage is AES-GCM. + // here is an example of how to generate a key to the json format. - jIO.addStorage('drivetojiomapping', FileSystemBridgeStorage); + // var key, + // jsonKey; + // crypto.subtle.generateKey({name: "AES-GCM",length: 256}, + // (true), ["encrypt", "decrypt"]) + // .then(function(res){key = res;}); + // + // window.crypto.subtle.exportKey("jwk", key) + // .then(function(res){jsonKey = val}) + // + //var storage = jIO.createJIO({type: "crypt", key: jsonKey, + // sub_storage: {...}}); -}(jIO, RSVP, Blob)); -;/*jslint nomen: true*/ -/*global Blob, atob, btoa, RSVP*/ -(function (jIO, Blob, atob, btoa, RSVP) { - "use strict"; + // find more informations about this cryptography system on + // https://github.com/diafygi/webcrypto-examples#aes-gcm /** - * The jIO DocumentStorage extension + * The JIO Cryptography Storage extension * - * @class DocumentStorage + * @class CryptStorage * @constructor */ - function DocumentStorage(spec) { + + var MIME_TYPE = "application/x-jio-aes-gcm-encryption"; + + function CryptStorage(spec) { + this._key = spec.key; + this._jsonKey = true; this._sub_storage = jIO.createJIO(spec.sub_storage); - this._document_id = spec.document_id; - this._repair_attachment = spec.repair_attachment || false; } - var DOCUMENT_EXTENSION = ".json", - DOCUMENT_REGEXP = new RegExp("^jio_document/([\\w=]+)" + - DOCUMENT_EXTENSION + "$"), - ATTACHMENT_REGEXP = new RegExp("^jio_attachment/([\\w=]+)/([\\w=]+)$"); - - function getSubAttachmentIdFromParam(id, name) { - if (name === undefined) { - return 'jio_document/' + btoa(id) + DOCUMENT_EXTENSION; - } - return 'jio_attachment/' + btoa(id) + "/" + btoa(name); + function convertKey(that) { + return new RSVP.Queue() + .push(function () { + return crypto.subtle.importKey("jwk", that._key, + "AES-GCM", false, + ["encrypt", "decrypt"]); + }) + .push(function (res) { + that._key = res; + that._jsonKey = false; + return; + }); } - DocumentStorage.prototype.get = function (id) { - return this._sub_storage.getAttachment( - this._document_id, - getSubAttachmentIdFromParam(id), - {format: "json"} - ); + CryptStorage.prototype.get = function () { + return this._sub_storage.get.apply(this._sub_storage, + arguments); }; - DocumentStorage.prototype.allAttachments = function (id) { - return this._sub_storage.allAttachments(this._document_id) - .push(function (result) { - var attachments = {}, - exec, - key; - for (key in result) { - if (result.hasOwnProperty(key)) { - if (ATTACHMENT_REGEXP.test(key)) { - exec = ATTACHMENT_REGEXP.exec(key); - try { - if (atob(exec[1]) === id) { - attachments[atob(exec[2])] = {}; - } - } catch (error) { - // Check if unable to decode base64 data - if (!error instanceof ReferenceError) { - throw error; - } - } - } - } - } - return attachments; - }); + CryptStorage.prototype.post = function () { + return this._sub_storage.post.apply(this._sub_storage, + arguments); }; - DocumentStorage.prototype.put = function (doc_id, param) { - return this._sub_storage.putAttachment( - this._document_id, - getSubAttachmentIdFromParam(doc_id), - new Blob([JSON.stringify(param)], {type: "application/json"}) - ) - .push(function () { - return doc_id; - }); + CryptStorage.prototype.put = function () { + return this._sub_storage.put.apply(this._sub_storage, + arguments); + }; + CryptStorage.prototype.remove = function () { + return this._sub_storage.remove.apply(this._sub_storage, + arguments); }; - DocumentStorage.prototype.remove = function (id) { - var context = this; - return this.allAttachments(id) - .push(function (result) { - var key, - promise_list = []; - for (key in result) { - if (result.hasOwnProperty(key)) { - promise_list.push(context.removeAttachment(id, key)); - } + CryptStorage.prototype.hasCapacity = function () { + return this._sub_storage.hasCapacity.apply(this._sub_storage, + arguments); + }; + + CryptStorage.prototype.buildQuery = function () { + return this._sub_storage.buildQuery.apply(this._sub_storage, + arguments); + }; + + + CryptStorage.prototype.putAttachment = function (id, name, blob) { + var initializaton_vector = crypto.getRandomValues(new Uint8Array(12)), + that = this; + + return new RSVP.Queue() + .push(function () { + if (that._jsonKey === true) { + return convertKey(that); } - return RSVP.all(promise_list); + return; }) .push(function () { - return context._sub_storage.removeAttachment( - context._document_id, - getSubAttachmentIdFromParam(id) - ); + return jIO.util.readBlobAsDataURL(blob); }) - .push(function () { - return id; - }); - }; + .push(function (dataURL) { + //string->arraybuffer + var strLen = dataURL.currentTarget.result.length, + buf = new ArrayBuffer(strLen), + bufView = new Uint8Array(buf), + i; - DocumentStorage.prototype.repair = function () { - var context = this; - return this._sub_storage.repair.apply(this._sub_storage, arguments) - .push(function (result) { - if (context._repair_attachment) { - return context._sub_storage.allAttachments(context._document_id) - .push(function (result_dict) { - var promise_list = [], - id_dict = {}, - attachment_dict = {}, - id, - attachment, - exec, - key; - for (key in result_dict) { - if (result_dict.hasOwnProperty(key)) { - id = undefined; - attachment = undefined; - if (DOCUMENT_REGEXP.test(key)) { - try { - id = atob(DOCUMENT_REGEXP.exec(key)[1]); - } catch (error) { - // Check if unable to decode base64 data - if (!error instanceof ReferenceError) { - throw error; - } - } - if (id !== undefined) { - id_dict[id] = null; - } - } else if (ATTACHMENT_REGEXP.test(key)) { - exec = ATTACHMENT_REGEXP.exec(key); - try { - id = atob(exec[1]); - attachment = atob(exec[2]); - } catch (error) { - // Check if unable to decode base64 data - if (!error instanceof ReferenceError) { - throw error; - } - } - if (attachment !== undefined) { - if (!id_dict.hasOwnProperty(id)) { - if (!attachment_dict.hasOwnProperty(id)) { - attachment_dict[id] = {}; - } - attachment_dict[id][attachment] = null; - } - } - } - } - } - for (id in attachment_dict) { - if (attachment_dict.hasOwnProperty(id)) { - if (!id_dict.hasOwnProperty(id)) { - for (attachment in attachment_dict[id]) { - if (attachment_dict[id].hasOwnProperty(attachment)) { - promise_list.push(context.removeAttachment( - id, - attachment - )); - } - } - } - } - } - return RSVP.all(promise_list); - }); + dataURL = dataURL.currentTarget.result; + for (i = 0; i < strLen; i += 1) { + bufView[i] = dataURL.charCodeAt(i); } - return result; + return crypto.subtle.encrypt({ + name : "AES-GCM", + iv : initializaton_vector + }, + that._key, buf); + }) + .push(function (coded) { + var blob = new Blob([initializaton_vector, coded], {type: MIME_TYPE}); + return that._sub_storage.putAttachment(id, name, blob); }); }; - DocumentStorage.prototype.hasCapacity = function (capacity) { - return (capacity === "list"); - }; + CryptStorage.prototype.getAttachment = function (id, name) { + var that = this; - DocumentStorage.prototype.buildQuery = function () { - return this._sub_storage.allAttachments(this._document_id) - .push(function (attachment_dict) { - var result = [], - key; - for (key in attachment_dict) { - if (attachment_dict.hasOwnProperty(key)) { - if (DOCUMENT_REGEXP.test(key)) { - try { - result.push({ - id: atob(DOCUMENT_REGEXP.exec(key)[1]), - value: {} - }); - } catch (error) { - // Check if unable to decode base64 data - if (!error instanceof ReferenceError) { - throw error; + return that._sub_storage.getAttachment(id, name) + .push(function (blob) { + if (blob.type !== MIME_TYPE) { + return blob; + } + return new RSVP.Queue() + .push(function () { + if (that._jsonKey === true) { + return convertKey(that); + } + return; + }) + .push(function () { + return jIO.util.readBlobAsArrayBuffer(blob); + }) + .push(function (coded) { + var initializaton_vector; + + coded = coded.currentTarget.result; + initializaton_vector = new Uint8Array(coded.slice(0, 12)); + return new RSVP.Queue() + .push(function () { + return crypto.subtle.decrypt({ + name : "AES-GCM", + iv : initializaton_vector + }, + that._key, coded.slice(12)); + }) + .push(function (arr) { + //arraybuffer->string + arr = String.fromCharCode.apply(null, new Uint8Array(arr)); + return jIO.util.dataURItoBlob(arr); + }) + .push(undefined, function (error) { + if (error instanceof DOMException) { + return blob; } - } - } - } - } - return result; + throw error; + }); + }); }); }; - DocumentStorage.prototype.getAttachment = function (id, name) { - return this._sub_storage.getAttachment( - this._document_id, - getSubAttachmentIdFromParam(id, name) - ); - }; - - DocumentStorage.prototype.putAttachment = function (id, name, blob) { - return this._sub_storage.putAttachment( - this._document_id, - getSubAttachmentIdFromParam(id, name), - blob - ); + CryptStorage.prototype.removeAttachment = function () { + return this._sub_storage.removeAttachment.apply(this._sub_storage, + arguments); }; - DocumentStorage.prototype.removeAttachment = function (id, name) { - return this._sub_storage.removeAttachment( - this._document_id, - getSubAttachmentIdFromParam(id, name) - ); + CryptStorage.prototype.allAttachments = function () { + return this._sub_storage.allAttachments.apply(this._sub_storage, + arguments); }; - jIO.addStorage('document', DocumentStorage); + jIO.addStorage('crypt', CryptStorage); -}(jIO, Blob, atob, btoa, RSVP)); +}(jIO, RSVP, DOMException, Blob, crypto, Uint8Array, ArrayBuffer)); ;/* - * Copyright 2014, Nexedi SA + * Copyright 2013, Nexedi SA * Released under the LGPL license. * http://www.gnu.org/licenses/lgpl.html */ - /** - * JIO Indexed Database Storage. - * - * A local browser "database" storage greatly more powerful than localStorage. - * - * Description: - * - * { - * "type": "indexeddb", - * "database": <string> - * } - * - * The database name will be prefixed by "jio:", so if the database property is - * "hello", then you can manually reach this database with - * `indexedDB.open("jio:hello");`. (Or - * `indexedDB.deleteDatabase("jio:hello");`.) - * - * For more informations: - * - * - http://www.w3.org/TR/IndexedDB/ - * - https://developer.mozilla.org/en-US/docs/IndexedDB/Using_IndexedDB + * JIO Websql Storage. Type = "websql". + * websql "database" storage. */ +/*global Blob, jIO, RSVP, openDatabase*/ +/*jslint nomen: true*/ -/*jslint nomen: true */ -/*global indexedDB, jIO, RSVP, Blob, Math, IDBKeyRange*/ +(function (jIO, RSVP, Blob, openDatabase) { -(function (indexedDB, jIO, RSVP, Blob, Math, IDBKeyRange) { "use strict"; - // Read only as changing it can lead to data corruption - var UNITE = 2000000; - - function IndexedDBStorage(description) { - if (typeof description.database !== "string" || - description.database === "") { - throw new TypeError("IndexedDBStorage 'database' description property " + - "must be a non-empty string"); - } - this._database_name = "jio:" + description.database; - } - - IndexedDBStorage.prototype.hasCapacity = function (name) { - return ((name === "list") || (name === "include")); - }; - - function buildKeyPath(key_list) { - return key_list.join("_"); - } - - function handleUpgradeNeeded(evt) { - var db = evt.target.result, - store; - - store = db.createObjectStore("metadata", { - keyPath: "_id", - autoIncrement: false - }); - // It is not possible to use openKeyCursor on keypath directly - // https://www.w3.org/Bugs/Public/show_bug.cgi?id=19955 - store.createIndex("_id", "_id", {unique: true}); - - store = db.createObjectStore("attachment", { - keyPath: "_key_path", - autoIncrement: false - }); - store.createIndex("_id", "_id", {unique: false}); + /** + * The JIO Websql Storage extension + * + * @class WebSQLStorage + * @constructor + */ - store = db.createObjectStore("blob", { - keyPath: "_key_path", - autoIncrement: false - }); - store.createIndex("_id_attachment", - ["_id", "_attachment"], {unique: false}); - store.createIndex("_id", "_id", {unique: false}); - } + function queueSql(db, query_list, argument_list) { + return new RSVP.Promise(function (resolve, reject) { + /*jslint unparam: true*/ + db.transaction(function (tx) { + var len = query_list.length, + result_list = [], + i; - function openIndexedDB(jio_storage) { - var db_name = jio_storage._database_name; - function resolver(resolve, reject) { - // Open DB // - var request = indexedDB.open(db_name); - request.onerror = function (error) { - if (request.result) { - request.result.close(); + function resolveTransaction(tx, result) { + result_list.push(result); + if (result_list.length === len) { + resolve(result_list); + } + } + function rejectTransaction(tx, error) { + reject(error); + return true; + } + for (i = 0; i < len; i += 1) { + tx.executeSql(query_list[i], argument_list[i], resolveTransaction, + rejectTransaction); } + }, function (tx, error) { reject(error); - }; - - request.onabort = function () { - request.result.close(); - reject("Aborting connection to: " + db_name); - }; - - request.ontimeout = function () { - request.result.close(); - reject("Connection to: " + db_name + " timeout"); - }; - - request.onblocked = function () { - request.result.close(); - reject("Connection to: " + db_name + " was blocked"); - }; - - // Create DB if necessary // - request.onupgradeneeded = handleUpgradeNeeded; - - request.onversionchange = function () { - request.result.close(); - reject(db_name + " was upgraded"); - }; + }); + /*jslint unparam: false*/ + }); + } - request.onsuccess = function () { - resolve(request.result); - }; - } - // XXX Canceller??? + function initDatabase(db) { + var query_list = [ + "CREATE TABLE IF NOT EXISTS document" + + "(id VARCHAR PRIMARY KEY NOT NULL, data TEXT)", + "CREATE TABLE IF NOT EXISTS attachment" + + "(id VARCHAR, attachment VARCHAR, part INT, blob TEXT)", + "CREATE TRIGGER IF NOT EXISTS removeAttachment " + + "BEFORE DELETE ON document FOR EACH ROW " + + "BEGIN DELETE from attachment WHERE id = OLD.id;END;", + "CREATE INDEX IF NOT EXISTS index_document ON document (id);", + "CREATE INDEX IF NOT EXISTS index_attachment " + + "ON attachment (id, attachment);" + ]; return new RSVP.Queue() .push(function () { - return new RSVP.Promise(resolver); + return queueSql(db, query_list, []); }); } - function openTransaction(db, stores, flag, autoclosedb) { - var tx = db.transaction(stores, flag); - if (autoclosedb !== false) { - tx.oncomplete = function () { - db.close(); - }; + function WebSQLStorage(spec) { + if (typeof spec.database !== 'string' || !spec.database) { + throw new TypeError("database must be a string " + + "which contains more than one character."); } - tx.onabort = function () { - db.close(); - }; - return tx; - } - - function handleCursor(request, callback) { - function resolver(resolve, reject) { - // Open DB // - request.onerror = function (error) { - if (request.transaction) { - request.transaction.abort(); - } - reject(error); - }; - - request.onsuccess = function (evt) { - var cursor = evt.target.result; - if (cursor) { - // XXX Wait for result - try { - callback(cursor); - } catch (error) { - reject(error); - } - - // continue to next iteration - cursor["continue"](); - } else { - resolve(); - } - }; + this._database = openDatabase("jio:" + spec.database, + '1.0', '', 2 * 1024 * 1024); + if (spec.blob_length && + (typeof spec.blob_length !== "number" || + spec.blob_length < 20)) { + throw new TypeError("blob_len parameter must be a number >= 20"); } - // XXX Canceller??? - return new RSVP.Promise(resolver); + this._blob_length = spec.blob_length || 2000000; + this._init_db_promise = initDatabase(this._database); } - IndexedDBStorage.prototype.buildQuery = function (options) { - var result_list = []; - - function pushIncludedMetadata(cursor) { - result_list.push({ - "id": cursor.key, - "value": {}, - "doc": cursor.value.doc - }); - } + WebSQLStorage.prototype.put = function (id, param) { + var db = this._database, + that = this, + data_string = JSON.stringify(param); - function pushMetadata(cursor) { - result_list.push({ - "id": cursor.key, - "value": {} - }); - } - return openIndexedDB(this) - .push(function (db) { - var tx = openTransaction(db, ["metadata"], "readonly"); - if (options.include_docs === true) { - return handleCursor(tx.objectStore("metadata").index("_id") - .openCursor(), pushIncludedMetadata); - } - return handleCursor(tx.objectStore("metadata").index("_id") - .openKeyCursor(), pushMetadata); + return new RSVP.Queue() + .push(function () { + return that._init_db_promise; }) .push(function () { - return result_list; + return queueSql(db, ["INSERT OR REPLACE INTO " + + "document(id, data) VALUES(?,?)"], + [[id, data_string]]); + }) + .push(function () { + return id; }); - }; - function handleGet(request) { - function resolver(resolve, reject) { - request.onerror = reject; - request.onsuccess = function () { - if (request.result) { - resolve(request.result); - } - // XXX How to get ID - reject(new jIO.util.jIOError("Cannot find document", 404)); - }; - } - return new RSVP.Promise(resolver); - } + WebSQLStorage.prototype.remove = function (id) { + var db = this._database, + that = this; - IndexedDBStorage.prototype.get = function (id) { - return openIndexedDB(this) - .push(function (db) { - var transaction = openTransaction(db, ["metadata"], - "readonly"); - return handleGet(transaction.objectStore("metadata").get(id)); + return new RSVP.Queue() + .push(function () { + return that._init_db_promise; }) - .push(function (result) { - return result.doc; + .push(function () { + return queueSql(db, ["DELETE FROM document WHERE id = ?"], [[id]]); + }) + .push(function (result_list) { + if (result_list[0].rowsAffected === 0) { + throw new jIO.util.jIOError("Cannot find document", 404); + } + return id; }); - }; - IndexedDBStorage.prototype.allAttachments = function (id) { - var attachment_dict = {}; + }; - function addEntry(cursor) { - attachment_dict[cursor.value._attachment] = {}; - } + WebSQLStorage.prototype.get = function (id) { + var db = this._database, + that = this; - return openIndexedDB(this) - .push(function (db) { - var transaction = openTransaction(db, ["metadata", "attachment"], - "readonly"); - return RSVP.all([ - handleGet(transaction.objectStore("metadata").get(id)), - handleCursor(transaction.objectStore("attachment").index("_id") - .openCursor(IDBKeyRange.only(id)), addEntry) - ]); + return new RSVP.Queue() + .push(function () { + return that._init_db_promise; }) .push(function () { - return attachment_dict; + return queueSql(db, ["SELECT data FROM document WHERE id = ?"], + [[id]]); + }) + .push(function (result_list) { + if (result_list[0].rows.length === 0) { + throw new jIO.util.jIOError("Cannot find document", 404); + } + return JSON.parse(result_list[0].rows[0].data); }); }; - function handleRequest(request) { - function resolver(resolve, reject) { - request.onerror = reject; - request.onsuccess = function () { - resolve(request.result); - }; - } - return new RSVP.Promise(resolver); - } + WebSQLStorage.prototype.allAttachments = function (id) { + var db = this._database, + that = this; - IndexedDBStorage.prototype.put = function (id, metadata) { - return openIndexedDB(this) - .push(function (db) { - var transaction = openTransaction(db, ["metadata"], "readwrite"); - return handleRequest(transaction.objectStore("metadata").put({ - "_id": id, - "doc": metadata - })); + return new RSVP.Queue() + .push(function () { + return that._init_db_promise; + }) + .push(function () { + return queueSql(db, [ + "SELECT id FROM document WHERE id = ?", + "SELECT DISTINCT attachment FROM attachment WHERE id = ?" + ], [[id], [id]]); + }) + .push(function (result_list) { + if (result_list[0].rows.length === 0) { + throw new jIO.util.jIOError("Cannot find document", 404); + } + + var len = result_list[1].rows.length, + obj = {}, + i; + + for (i = 0; i < len; i += 1) { + obj[result_list[1].rows[i].attachment] = {}; + } + return obj; }); }; - function deleteEntry(cursor) { - cursor["delete"](); + function sendBlobPart(blob, argument_list, index, queue) { + queue.push(function () { + return jIO.util.readBlobAsDataURL(blob); + }) + .push(function (strBlob) { + argument_list[index + 2].push(strBlob.currentTarget.result); + return; + }); } - IndexedDBStorage.prototype.remove = function (id) { - return openIndexedDB(this) - .push(function (db) { - var transaction = openTransaction(db, ["metadata", "attachment", - "blob"], "readwrite"); - return RSVP.all([ - handleRequest(transaction - .objectStore("metadata")["delete"](id)), - // XXX Why not possible to delete with KeyCursor? - handleCursor(transaction.objectStore("attachment").index("_id") - .openCursor(IDBKeyRange.only(id)), deleteEntry), - handleCursor(transaction.objectStore("blob").index("_id") - .openCursor(IDBKeyRange.only(id)), deleteEntry) - ]); - }); - }; + WebSQLStorage.prototype.putAttachment = function (id, name, blob) { + var db = this._database, + that = this, + part_size = this._blob_length; - IndexedDBStorage.prototype.getAttachment = function (id, name, options) { - var transaction, - type, - start, - end; - if (options === undefined) { - options = {}; - } - return openIndexedDB(this) - .push(function (db) { - transaction = openTransaction(db, ["attachment", "blob"], "readonly"); - // XXX Should raise if key is not good - return handleGet(transaction.objectStore("attachment") - .get(buildKeyPath([id, name]))); + return new RSVP.Queue() + .push(function () { + return that._init_db_promise; }) - .push(function (attachment) { - var total_length = attachment.info.length, + .push(function () { + return queueSql(db, ["SELECT id FROM document WHERE id = ?"], [[id]]); + }) + .push(function (result) { + var query_list = [], + argument_list = [], + blob_size = blob.size, + queue = new RSVP.Queue(), i, - promise_list = [], - store = transaction.objectStore("blob"), - start_index, - end_index; + index; - type = attachment.info.content_type; - start = options.start || 0; - end = options.end || total_length; - if (end > total_length) { - end = total_length; + if (result[0].rows.length === 0) { + throw new jIO.util.jIOError("Cannot access subdocument", 404); } + query_list.push("DELETE FROM attachment WHERE id = ? " + + "AND attachment = ?"); + argument_list.push([id, name]); + query_list.push("INSERT INTO attachment(id, attachment, part, blob)" + + "VALUES(?, ?, ?, ?)"); + argument_list.push([id, name, -1, + blob.type || "application/octet-stream"]); + + for (i = 0, index = 0; i < blob_size; i += part_size, index += 1) { + query_list.push("INSERT INTO attachment(id, attachment, part, blob)" + + "VALUES(?, ?, ?, ?)"); + argument_list.push([id, name, index]); + sendBlobPart(blob.slice(i, i + part_size), argument_list, index, + queue); + } + queue.push(function () { + return queueSql(db, query_list, argument_list); + }); + return queue; + }); + }; - if (start < 0 || end < 0) { - throw new jIO.util.jIOError("_start and _end must be positive", - 400); - } - if (start > end) { - throw new jIO.util.jIOError("_start is greater than _end", - 400); - } + WebSQLStorage.prototype.getAttachment = function (id, name, options) { + var db = this._database, + that = this, + part_size = this._blob_length, + start, + end, + start_index, + end_index; - start_index = Math.floor(start / UNITE); - end_index = Math.floor(end / UNITE); - if (end % UNITE === 0) { - end_index -= 1; - } + if (options === undefined) { options = {}; } + start = options.start || 0; + end = options.end || -1; - for (i = start_index; i <= end_index; i += 1) { - promise_list.push( - handleGet(store.get(buildKeyPath([id, - name, i]))) - ); + if (start < 0 || (options.end !== undefined && options.end < 0)) { + throw new jIO.util.jIOError("_start and _end must be positive", + 400); + } + if (start > end && end !== -1) { + throw new jIO.util.jIOError("_start is greater than _end", + 400); + } + + start_index = Math.floor(start / part_size); + if (start === 0) { start_index -= 1; } + end_index = Math.floor(end / part_size); + if (end % part_size === 0) { + end_index -= 1; + } + + return new RSVP.Queue() + .push(function () { + return that._init_db_promise; + }) + .push(function () { + var command = "SELECT part, blob FROM attachment WHERE id = ? AND " + + "attachment = ? AND part >= ?", + argument_list = [id, name, start_index]; + + if (end !== -1) { + command += " AND part <= ?"; + argument_list.push(end_index); } - return RSVP.all(promise_list); + return queueSql(db, [command], [argument_list]); }) - .push(function (result_list) { - var array_buffer_list = [], + .push(function (response_list) { + var i, + response, + blob_array = [], blob, - i, - index, - len = result_list.length; - for (i = 0; i < len; i += 1) { - array_buffer_list.push(result_list[i].blob); + type; + + response = response_list[0].rows; + if (response.length === 0) { + throw new jIO.util.jIOError("Cannot find document", 404); } - if ((options.start === undefined) && (options.end === undefined)) { - return new Blob(array_buffer_list, {type: type}); + for (i = 0; i < response.length; i += 1) { + if (response[i].part === -1) { + type = response[i].blob; + start_index += 1; + } else { + blob_array.push(jIO.util.dataURItoBlob(response[i].blob)); + } } - index = Math.floor(start / UNITE) * UNITE; - blob = new Blob(array_buffer_list, {type: "application/octet-stream"}); - return blob.slice(start - index, end - index, + if ((start === 0) && (options.end === undefined)) { + return new Blob(blob_array, {type: type}); + } + blob = new Blob(blob_array, {}); + return blob.slice(start - (start_index * part_size), + end === -1 ? blob.size : + end - (start_index * part_size), "application/octet-stream"); }); }; - function removeAttachment(transaction, id, name) { - return RSVP.all([ - // XXX How to get the right attachment - handleRequest(transaction.objectStore("attachment")["delete"]( - buildKeyPath([id, name]) - )), - handleCursor(transaction.objectStore("blob").index("_id_attachment") - .openCursor(IDBKeyRange.only( - [id, name] - )), - deleteEntry - ) - ]); - } - - IndexedDBStorage.prototype.putAttachment = function (id, name, blob) { - var blob_part = [], - transaction, - db; - - return openIndexedDB(this) - .push(function (database) { - db = database; + WebSQLStorage.prototype.removeAttachment = function (id, name) { + var db = this._database, + that = this; - // Split the blob first - return jIO.util.readBlobAsArrayBuffer(blob); + return new RSVP.Queue() + .push(function () { + return that._init_db_promise; }) - .push(function (event) { - var array_buffer = event.target.result, - total_size = blob.size, - handled_size = 0; - - while (handled_size < total_size) { - blob_part.push(array_buffer.slice(handled_size, - handled_size + UNITE)); - handled_size += UNITE; + .push(function () { + return queueSql(db, ["DELETE FROM attachment WHERE " + + "id = ? AND attachment = ?"], [[id, name]]); + }) + .push(function (result) { + if (result[0].rowsAffected === 0) { + throw new jIO.util.jIOError("Cannot find document", 404); } + return name; + }); + }; - // Remove previous attachment - transaction = openTransaction(db, ["attachment", "blob"], "readwrite"); - return removeAttachment(transaction, id, name); + WebSQLStorage.prototype.hasCapacity = function (name) { + return (name === "list" || (name === "include")); + }; + + WebSQLStorage.prototype.buildQuery = function (options) { + var db = this._database, + that = this, + query = "SELECT id"; + + return new RSVP.Queue() + .push(function () { + return that._init_db_promise; }) .push(function () { - - var promise_list = [ - handleRequest(transaction.objectStore("attachment").put({ - "_key_path": buildKeyPath([id, name]), - "_id": id, - "_attachment": name, - "info": { - "content_type": blob.type, - "length": blob.size - } - })) - ], - len = blob_part.length, - blob_store = transaction.objectStore("blob"), + if (options === undefined) { options = {}; } + if (options.include_docs === true) { + query += ", data AS doc"; + } + query += " FROM document"; + return queueSql(db, [query], [[]]); + }) + .push(function (result) { + var array = [], + len = result[0].rows.length, i; + for (i = 0; i < len; i += 1) { - promise_list.push( - handleRequest(blob_store.put({ - "_key_path": buildKeyPath([id, name, - i]), - "_id" : id, - "_attachment" : name, - "_part" : i, - "blob": blob_part[i] - })) - ); + array.push(result[0].rows[i]); + array[i].value = {}; + if (array[i].doc !== undefined) { + array[i].doc = JSON.parse(array[i].doc); + } } - // Store all new data - return RSVP.all(promise_list); + return array; }); }; - IndexedDBStorage.prototype.removeAttachment = function (id, name) { - return openIndexedDB(this) - .push(function (db) { - var transaction = openTransaction(db, ["attachment", "blob"], - "readwrite"); - return removeAttachment(transaction, id, name); - }); - }; + jIO.addStorage('websql', WebSQLStorage); - jIO.addStorage("indexeddb", IndexedDBStorage); -}(indexedDB, jIO, RSVP, Blob, Math, IDBKeyRange)); \ No newline at end of file +}(jIO, RSVP, Blob, openDatabase)); \ No newline at end of file -- 2.30.9