[FIX] update py.js for operators &al, implement basic crummy version of relativedelta
bzr revid: xmo@openerp.com-20120305085532-0vcz6j0m985gjuz1
This commit is contained in:
parent
12358af73b
commit
f8a93bc284
|
@ -1,5 +1,5 @@
|
|||
repo: 076b192d0d8ab2b92d1dbcfa3da055382f30eaea
|
||||
node: 7be96c381d9302b4aafaf5eb5381083b0731a9aa
|
||||
node: 5adb2d9c89e53a6445e3799f9c4dc9110458c149
|
||||
branch: default
|
||||
latesttag: 0.4
|
||||
latesttagdistance: 7
|
||||
latesttag: 0.5
|
||||
latesttagdistance: 9
|
||||
|
|
|
@ -10,6 +10,17 @@ specification document is the `Python 2.7 Expressions spec
|
|||
<http://docs.python.org/reference/expressions.html>`_ (along with the
|
||||
lexical analysis part).
|
||||
|
||||
Syntax
|
||||
------
|
||||
|
||||
* Lambdas and ternaries should be parsed but are not implemented (in
|
||||
the evaluator)
|
||||
* Only floats are implemented, ``int`` literals are parsed as floats.
|
||||
* Octal and hexadecimal literals are not implemented
|
||||
* Srings are backed by JavaScript strings and probably behave like
|
||||
``unicode`` more than like ``str``
|
||||
* Slices don't work
|
||||
|
||||
Builtins
|
||||
--------
|
||||
|
||||
|
@ -89,9 +100,10 @@ Collections Abstract Base Classes
|
|||
Hashable are kind-of implemented as well)
|
||||
|
||||
Numeric type emulation
|
||||
Basically not implemented, the only part of it which is
|
||||
implemented is the unary ``-`` (because it's used to create
|
||||
negative floats, they're parsed as a negated positive number)
|
||||
Operators are implemented (but not tested), ``abs``, ``divmod``
|
||||
and ``pow`` builtins are not implemented yet. Neither are ``oct``
|
||||
and ``hex`` but I'm not sure we care (I'm not sure we care about
|
||||
``pow`` or even ``divmod`` either, for that matter)
|
||||
|
||||
Utilities
|
||||
---------
|
||||
|
|
|
@ -34,15 +34,14 @@ Base methods requirement
|
|||
************************
|
||||
|
||||
* ``__getattr__``
|
||||
* ? ``__getitem``
|
||||
* ``__call__``
|
||||
* ``or``
|
||||
* ``toJS`` / ``toJSON``
|
||||
* ``dict.get``
|
||||
* ``datetime.time.today``
|
||||
* ``datetime.time.strftime``
|
||||
* ``time.strftime``
|
||||
* ``__add__`` / ``__radd__``
|
||||
* ``__sub__`` / ``__rsub__``
|
||||
* ``__len__``
|
||||
* ``__nonzero__``
|
||||
|
||||
In datamodel, not implemented in any type, untested
|
||||
***************************************************
|
||||
|
||||
* a[b]
|
||||
|
||||
* a + b, a - b, a * b, ...
|
||||
|
||||
* +a, ~a
|
||||
|
|
|
@ -104,6 +104,15 @@ var py = {};
|
|||
symbol(':'); symbol(')'); symbol(']'); symbol('}'); symbol(',');
|
||||
symbol('else');
|
||||
|
||||
infix('=', 10, function (left) {
|
||||
if (left.id !== '(name)') {
|
||||
throw new Error("Expected keyword argument name, got " + token.id);
|
||||
}
|
||||
this.first = left;
|
||||
this.second = expression();
|
||||
return this;
|
||||
});
|
||||
|
||||
symbol('lambda', 20).nud = function () {
|
||||
this.first = [];
|
||||
if (token.id !== ':') {
|
||||
|
@ -362,7 +371,7 @@ var py = {};
|
|||
|
||||
if (val instanceof py.object
|
||||
|| val === py.object
|
||||
|| py.issubclass.__call__(val, py.object) === py.True) {
|
||||
|| py.issubclass.__call__([val, py.object]) === py.True) {
|
||||
return val;
|
||||
}
|
||||
|
||||
|
@ -443,7 +452,7 @@ var py = {};
|
|||
// TODO: second argument should be class
|
||||
return val.__get__(this);
|
||||
}
|
||||
if (typeof val === 'function' && !this.hasOwnProperty(val)) {
|
||||
if (typeof val === 'function' && !this.hasOwnProperty(name)) {
|
||||
// val is a method from the class
|
||||
return new PY_instancemethod(val, this);
|
||||
}
|
||||
|
@ -476,6 +485,7 @@ var py = {};
|
|||
py.NotImplemented = new NotImplementedType();
|
||||
var booleans_initialized = false;
|
||||
py.bool = py.type(function bool(value) {
|
||||
value = (value instanceof Array) ? value[0] : value;
|
||||
// The only actual instance of py.bool should be py.True
|
||||
// and py.False. Return the new instance of py.bool if we
|
||||
// are initializing py.True and py.False, otherwise always
|
||||
|
@ -493,7 +503,26 @@ var py = {};
|
|||
py.False = new py.bool();
|
||||
booleans_initialized = true;
|
||||
py.float = py.type(function float(value) {
|
||||
this._value = value;
|
||||
value = (value instanceof Array) ? value[0] : value;
|
||||
if (value === undefined) { this._value = 0; return; }
|
||||
if (value instanceof py.float) { return value; }
|
||||
if (typeof value === 'number' || value instanceof Number) {
|
||||
this._value = value;
|
||||
return;
|
||||
}
|
||||
if (typeof value === 'string' || value instanceof String) {
|
||||
this._value = parseFloat(value);
|
||||
return;
|
||||
}
|
||||
if (value instanceof py.object && '__float__' in value) {
|
||||
var res = value.__float__();
|
||||
if (res instanceof py.float) {
|
||||
return res;
|
||||
}
|
||||
throw new Error('TypeError: __float__ returned non-float (type ' +
|
||||
res.constructor.name + ')');
|
||||
}
|
||||
throw new Error('TypeError: float() argument must be a string or a number');
|
||||
}, py.object, {
|
||||
__eq__: function (other) {
|
||||
return this._value === other._value ? py.True : py.False;
|
||||
|
@ -514,9 +543,25 @@ var py = {};
|
|||
if (!(other instanceof py.float)) { return py.NotImplemented; }
|
||||
return this._value >= other._value ? py.True : py.False;
|
||||
},
|
||||
__add__: function (other) {
|
||||
if (!(other instanceof py.float)) { return py.NotImplemented; }
|
||||
return new py.float(this._value + other._value);
|
||||
},
|
||||
__neg__: function () {
|
||||
return new py.float(-this._value);
|
||||
},
|
||||
__sub__: function (other) {
|
||||
if (!(other instanceof py.float)) { return py.NotImplemented; }
|
||||
return new py.float(this._value - other._value);
|
||||
},
|
||||
__mul__: function (other) {
|
||||
if (!(other instanceof py.float)) { return py.NotImplemented; }
|
||||
return new py.float(this._value * other._value);
|
||||
},
|
||||
__div__: function (other) {
|
||||
if (!(other instanceof py.float)) { return py.NotImplemented; }
|
||||
return new py.float(this._value / other._value);
|
||||
},
|
||||
__nonzero__: function () {
|
||||
return this._value ? py.True : py.False;
|
||||
},
|
||||
|
@ -525,7 +570,17 @@ var py = {};
|
|||
}
|
||||
});
|
||||
py.str = py.type(function str(s) {
|
||||
this._value = s;
|
||||
s = (s instanceof Array) ? s[0] : s;
|
||||
if (s === undefined) { this._value = ''; return; }
|
||||
if (s instanceof py.str) { return s; }
|
||||
if (typeof s === 'string' || s instanceof String) {
|
||||
this._value = s;
|
||||
return;
|
||||
}
|
||||
var v = s.__str__();
|
||||
if (v instanceof py.str) { return v; }
|
||||
throw new Error('TypeError: __str__ returned non-string (type ' +
|
||||
v.constructor.name + ')');
|
||||
}, py.object, {
|
||||
__eq__: function (other) {
|
||||
if (other instanceof py.str && this._value === other._value) {
|
||||
|
@ -549,6 +604,10 @@ var py = {};
|
|||
if (!(other instanceof py.str)) { return py.NotImplemented; }
|
||||
return this._value >= other._value ? py.True : py.False;
|
||||
},
|
||||
__add__: function (other) {
|
||||
if (!(other instanceof py.str)) { return py.NotImplemented; }
|
||||
return new py.str(this._value + other._value);
|
||||
},
|
||||
__nonzero__: function () {
|
||||
return this._value.length ? py.True : py.False;
|
||||
},
|
||||
|
@ -596,9 +655,9 @@ var py = {};
|
|||
this._inst = null;
|
||||
this._func = nativefunc;
|
||||
}, py.object, {
|
||||
__call__: function () {
|
||||
__call__: function (args, kwargs) {
|
||||
// don't want to rewrite __call__ for instancemethod
|
||||
return this._func.apply(this._inst, arguments);
|
||||
return this._func.call(this._inst, args, kwargs);
|
||||
},
|
||||
toJSON: function () {
|
||||
return this._func;
|
||||
|
@ -610,13 +669,69 @@ var py = {};
|
|||
this._func = nativefunc;
|
||||
}, py.def, {});
|
||||
|
||||
py.issubclass = new py.def(function issubclass(derived, parent) {
|
||||
py.issubclass = new py.def(function issubclass(args) {
|
||||
var derived = args[0], parent = args[1];
|
||||
// still hurts my brain that this can work
|
||||
return derived.prototype instanceof py.object
|
||||
? py.True
|
||||
: py.False;
|
||||
});
|
||||
|
||||
|
||||
// All binary operators with fallbacks, so they can be applied generically
|
||||
var PY_operators = {
|
||||
'==': ['eq', 'eq', function (a, b) { return a === b; }],
|
||||
'!=': ['ne', 'ne', function (a, b) { return a !== b; }],
|
||||
'<': ['lt', 'gt', function (a, b) {return a.constructor.name < b.constructor.name;}],
|
||||
'<=': ['le', 'ge', function (a, b) {return a.constructor.name <= b.constructor.name;}],
|
||||
'>': ['gt', 'lt', function (a, b) {return a.constructor.name > b.constructor.name;}],
|
||||
'>=': ['ge', 'le', function (a, b) {return a.constructor.name >= b.constructor.name;}],
|
||||
|
||||
'+': ['add', 'radd'],
|
||||
'-': ['sub', 'rsub'],
|
||||
'*': ['mul', 'rmul'],
|
||||
'/': ['div', 'rdiv'],
|
||||
'//': ['floordiv', 'rfloordiv'],
|
||||
'%': ['mod', 'rmod'],
|
||||
'**': ['pow', 'rpow'],
|
||||
'<<': ['lshift', 'rlshift'],
|
||||
'>>': ['rshift', 'rrshift'],
|
||||
'&': ['and', 'rand'],
|
||||
'^': ['xor', 'rxor'],
|
||||
'|': ['or', 'ror']
|
||||
};
|
||||
/**
|
||||
* Implements operator fallback/reflection.
|
||||
*
|
||||
* First two arguments are the objects to apply the operator on,
|
||||
* in their actual order (ltr).
|
||||
*
|
||||
* Third argument is the actual operator.
|
||||
*
|
||||
* If the operator methods raise exceptions, those exceptions are
|
||||
* not intercepted.
|
||||
*/
|
||||
var PY_op = function (o1, o2, op) {
|
||||
var r;
|
||||
var methods = PY_operators[op];
|
||||
var forward = '__' + methods[0] + '__', reverse = '__' + methods[1] + '__';
|
||||
var otherwise = methods[2];
|
||||
|
||||
if (forward in o1 && (r = o1[forward](o2)) !== py.NotImplemented) {
|
||||
return r;
|
||||
}
|
||||
if (reverse in o2 && (r = o2[reverse](o1)) !== py.NotImplemented) {
|
||||
return r;
|
||||
}
|
||||
if (otherwise) {
|
||||
return PY_ensurepy(otherwise(o1, o2));
|
||||
}
|
||||
throw new Error(
|
||||
"TypeError: unsupported operand type(s) for " + op + ": '"
|
||||
+ o1.constructor.name + "' and '"
|
||||
+ o2.constructor.name + "'");
|
||||
};
|
||||
|
||||
var PY_builtins = {
|
||||
type: py.type,
|
||||
|
||||
|
@ -643,30 +758,16 @@ var py = {};
|
|||
var evaluate_operator = function (operator, a, b) {
|
||||
var v;
|
||||
switch (operator) {
|
||||
case '==': return a.__eq__(b);
|
||||
case 'is': return a === b ? py.True : py.False;
|
||||
case '!=': return a.__ne__(b);
|
||||
case 'is not': return a !== b ? py.True : py.False;
|
||||
case '<':
|
||||
v = a.__lt__(b);
|
||||
if (v !== py.NotImplemented) { return v; }
|
||||
return PY_ensurepy(a.constructor.name < b.constructor.name);
|
||||
case '<=':
|
||||
v = a.__le__(b);
|
||||
if (v !== py.NotImplemented) { return v; }
|
||||
return PY_ensurepy(a.constructor.name <= b.constructor.name);
|
||||
case '>':
|
||||
v = a.__gt__(b);
|
||||
if (v !== py.NotImplemented) { return v; }
|
||||
return PY_ensurepy(a.constructor.name > b.constructor.name);
|
||||
case '>=':
|
||||
v = a.__ge__(b);
|
||||
if (v !== py.NotImplemented) { return v; }
|
||||
return PY_ensurepy(a.constructor.name >= b.constructor.name);
|
||||
case 'in':
|
||||
return b.__contains__(a);
|
||||
case 'not in':
|
||||
return b.__contains__(a) === py.True ? py.False : py.True;
|
||||
case '==': case '!=':
|
||||
case '<': case '<=':
|
||||
case '>': case '>=':
|
||||
return PY_op(a, b, operator);
|
||||
}
|
||||
throw new Error('SyntaxError: unknown comparator [[' + operator + ']]');
|
||||
};
|
||||
|
@ -700,11 +801,6 @@ var py = {};
|
|||
if (result === py.False) { return py.False; }
|
||||
}
|
||||
return py.True;
|
||||
case '-':
|
||||
if (expr.second) {
|
||||
throw new Error('SyntaxError: binary [-] not implemented yet');
|
||||
}
|
||||
return (py.evaluate(expr.first, context)).__neg__();
|
||||
case 'not':
|
||||
return py.evaluate(expr.first, context).__nonzero__() === py.True
|
||||
? py.False
|
||||
|
@ -723,12 +819,20 @@ var py = {};
|
|||
return py.evaluate(expr.second, context);
|
||||
case '(':
|
||||
if (expr.second) {
|
||||
var callable = py.evaluate(expr.first, context), args=[];
|
||||
var callable = py.evaluate(expr.first, context);
|
||||
var args = [], kwargs = {};
|
||||
for (var jj=0; jj<expr.second.length; ++jj) {
|
||||
args.push(py.evaluate(
|
||||
expr.second[jj], context));
|
||||
var arg = expr.second[jj];
|
||||
if (arg.id !== '=') {
|
||||
// arg
|
||||
args.push(py.evaluate(arg, context));
|
||||
} else {
|
||||
// kwarg
|
||||
kwargs[arg.first.value] =
|
||||
py.evaluate(arg.second, context);
|
||||
}
|
||||
}
|
||||
return callable.__call__.apply(callable, args);
|
||||
return callable.__call__(args, kwargs);
|
||||
}
|
||||
var tuple_exprs = expr.first,
|
||||
tuple_values = [];
|
||||
|
@ -741,7 +845,8 @@ var py = {};
|
|||
return t;
|
||||
case '[':
|
||||
if (expr.second) {
|
||||
throw new Error('SyntaxError: indexing not implemented yet');
|
||||
return py.evaluate(expr.first, context)
|
||||
.__getitem__(expr.evaluate(expr.second, context));
|
||||
}
|
||||
var list_exprs = expr.first, list_values = [];
|
||||
for (var k=0; k<list_exprs.length; ++k) {
|
||||
|
@ -765,6 +870,27 @@ var py = {};
|
|||
}
|
||||
return py.evaluate(expr.first, context)
|
||||
.__getattribute__(expr.second.value);
|
||||
// numerical operators
|
||||
case '~':
|
||||
return (py.evaluate(expr.first, context)).__invert__();
|
||||
case '+':
|
||||
if (!expr.second) {
|
||||
return (py.evaluate(expr.first, context)).__pos__();
|
||||
}
|
||||
case '-':
|
||||
if (!expr.second) {
|
||||
return (py.evaluate(expr.first, context)).__neg__();
|
||||
}
|
||||
case '*': case '/': case '//':
|
||||
case '%':
|
||||
case '**':
|
||||
case '<<': case '>>':
|
||||
case '&': case '^': case '|':
|
||||
return PY_op(
|
||||
py.evaluate(expr.first, context),
|
||||
py.evaluate(expr.second, context),
|
||||
expr.id);
|
||||
|
||||
default:
|
||||
throw new Error('SyntaxError: Unknown node [[' + expr.id + ']]');
|
||||
}
|
||||
|
|
|
@ -11,6 +11,14 @@ expect.Assertion.prototype.tokens = function (n) {
|
|||
'expected ' + this.obj + ' to not have an end token');
|
||||
};
|
||||
|
||||
expect.Assertion.prototype.named = function (value) {
|
||||
this.assert(this.obj.id === '(name)',
|
||||
'expected ' + this.obj + ' to be a name token',
|
||||
'expected ' + this.obj + ' not to be a name token');
|
||||
this.assert(this.obj.value === value,
|
||||
'expected ' + this.obj + ' to have tokenized ' + value,
|
||||
'expected ' + this.obj + ' not to have tokenized ' + value);
|
||||
};
|
||||
expect.Assertion.prototype.constant = function (value) {
|
||||
this.assert(this.obj.id === '(constant)',
|
||||
'expected ' + this.obj + ' to be a constant token',
|
||||
|
@ -98,4 +106,27 @@ describe('Tokenizer', function () {
|
|||
expect(toks[1].id).to.be(')');
|
||||
});
|
||||
});
|
||||
describe('functions', function () {
|
||||
it('tokenizes kwargs', function () {
|
||||
var toks = py.tokenize('foo(bar=3, qux=4)');
|
||||
expect(toks).to.have.tokens(10);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Parser', function () {
|
||||
describe('functions', function () {
|
||||
var ast = py.parse(py.tokenize('foo(bar=3, qux=4)'));
|
||||
expect(ast.id).to.be('(');
|
||||
expect(ast.first).to.be.named('foo');
|
||||
|
||||
args = ast.second;
|
||||
expect(args[0].id).to.be('=');
|
||||
expect(args[0].first).to.be.named('bar');
|
||||
expect(args[0].second).to.be.number(3);
|
||||
|
||||
expect(args[1].id).to.be('=');
|
||||
expect(args[1].first).to.be.named('qux');
|
||||
expect(args[1].second).to.be.number(4);
|
||||
});
|
||||
});
|
|
@ -262,6 +262,15 @@ describe('Attribute access', function () {
|
|||
});
|
||||
expect(py.eval('foo.bar()', {foo: o})).to.be('ok');
|
||||
});
|
||||
it('should not convert function attributes into methods', function () {
|
||||
var o = new py.object();
|
||||
o.bar = new py.type(function bar() {});
|
||||
o.bar.__getattribute__ = function () {
|
||||
return o.bar.baz;
|
||||
}
|
||||
o.bar.baz = py.True;
|
||||
expect(py.eval('foo.bar.baz', {foo: o})).to.be(true);
|
||||
});
|
||||
it('should work on instance attributes', function () {
|
||||
var typ = py.type(function MyType() {
|
||||
this.attr = new py.float(3);
|
||||
|
@ -296,16 +305,37 @@ describe('Callables', function () {
|
|||
});
|
||||
expect(py.eval('MyType()', {MyType: typ})).to.be(true);
|
||||
});
|
||||
it('should accept kwargs', function () {
|
||||
expect(py.eval('foo(ok=True)', {
|
||||
foo: function foo() { return py.True; }
|
||||
})).to.be(true);
|
||||
});
|
||||
it('should be able to get its kwargs', function () {
|
||||
expect(py.eval('foo(ok=True)', {
|
||||
foo: function foo(args, kwargs) { return kwargs.ok; }
|
||||
})).to.be(true);
|
||||
});
|
||||
it('should be able to have both args and kwargs', function () {
|
||||
expect(py.eval('foo(1, 2, 3, ok=True, nok=False)', {
|
||||
foo: function (args, kwargs) {
|
||||
expect(args).to.have.length(3);
|
||||
expect(args[0].toJSON()).to.be(1);
|
||||
expect(kwargs).to.only.have.keys('ok', 'nok')
|
||||
expect(kwargs.nok.toJSON()).to.be(false);
|
||||
return kwargs.ok;
|
||||
}
|
||||
})).to.be(true);
|
||||
});
|
||||
});
|
||||
describe('issubclass', function () {
|
||||
it('should say a type is its own subclass', function () {
|
||||
expect(py.issubclass.__call__(py.dict, py.dict).toJSON())
|
||||
expect(py.issubclass.__call__([py.dict, py.dict]).toJSON())
|
||||
.to.be(true);
|
||||
expect(py.eval('issubclass(dict, dict)'))
|
||||
.to.be(true);
|
||||
});
|
||||
it('should work with subtypes', function () {
|
||||
expect(py.issubclass.__call__(py.bool, py.object).toJSON())
|
||||
expect(py.issubclass.__call__([py.bool, py.object]).toJSON())
|
||||
.to.be(true);
|
||||
});
|
||||
});
|
||||
|
@ -313,4 +343,37 @@ describe('builtins', function () {
|
|||
it('should aways be available', function () {
|
||||
expect(py.eval('bool("foo")')).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('numerical protocols', function () {
|
||||
describe('True numbers (float)', function () {
|
||||
describe('Basic arithmetic', function () {
|
||||
it('can be added', function () {
|
||||
expect(py.eval('1 + 1')).to.be(2);
|
||||
expect(py.eval('1.5 + 2')).to.be(3.5);
|
||||
expect(py.eval('1 + -1')).to.be(0);
|
||||
});
|
||||
it('can be subtracted', function () {
|
||||
expect(py.eval('1 - 1')).to.be(0);
|
||||
expect(py.eval('1.5 - 2')).to.be(-0.5);
|
||||
expect(py.eval('2 - 1.5')).to.be(0.5);
|
||||
});
|
||||
it('can be multiplied', function () {
|
||||
expect(py.eval('1 * 3')).to.be(3);
|
||||
expect(py.eval('0 * 5')).to.be(0);
|
||||
expect(py.eval('42 * -2')).to.be(-84);
|
||||
});
|
||||
it('can be divided', function () {
|
||||
expect(py.eval('1 / 2')).to.be(0.5);
|
||||
expect(py.eval('2 / 1')).to.be(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('Strings', function () {
|
||||
describe('Basic arithmetics operators', function () {
|
||||
it('can be added (concatenation)', function () {
|
||||
expect(py.eval('"foo" + "bar"')).to.be('foobar');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -322,19 +322,34 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp.
|
|||
return this.session_init();
|
||||
},
|
||||
test_eval_get_context: function () {
|
||||
var asJS = function (arg) {
|
||||
if (arg instanceof py.object) {
|
||||
return arg.toJSON();
|
||||
}
|
||||
return arg;
|
||||
};
|
||||
|
||||
var datetime = new py.object();
|
||||
datetime.datetime = new py.type(function datetime() {
|
||||
throw new Error('datetime.datetime not implemented');
|
||||
});
|
||||
var date = datetime.date = new py.type(function date(y, m, d) {
|
||||
this._year = y;
|
||||
this._month = m;
|
||||
this._day = d;
|
||||
if (y instanceof Array) {
|
||||
d = y[2];
|
||||
m = y[1];
|
||||
y = y[0];
|
||||
}
|
||||
this.year = asJS(y);
|
||||
this.month = asJS(m);
|
||||
this.day = asJS(d);
|
||||
}, py.object, {
|
||||
strftime: function (format) {
|
||||
var f = format.toJSON(), self = this;
|
||||
strftime: function (args) {
|
||||
var f = asJS(args[0]), self = this;
|
||||
return new py.str(f.replace(/%([A-Za-z])/g, function (m, c) {
|
||||
switch (c) {
|
||||
case 'Y': return self._year;
|
||||
case 'm': return _.str.sprintf('%02d', self._month);
|
||||
case 'd': return _.str.sprintf('%02d', self._day);
|
||||
case 'Y': return self.year;
|
||||
case 'm': return _.str.sprintf('%02d', self.month);
|
||||
case 'd': return _.str.sprintf('%02d', self.day);
|
||||
}
|
||||
throw new Error('ValueError: No known conversion for ' + m);
|
||||
}));
|
||||
|
@ -350,16 +365,107 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp.
|
|||
var d = new Date();
|
||||
return new date(d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate());
|
||||
});
|
||||
datetime.time = new py.type(function time() {
|
||||
throw new Error('datetime.time not implemented');
|
||||
});
|
||||
|
||||
var time = new py.object();
|
||||
time.strftime = new py.def(function (format) {
|
||||
return date.today.__call__().strftime(format);
|
||||
time.strftime = new py.def(function (args) {
|
||||
return date.today.__call__().strftime(args);
|
||||
});
|
||||
|
||||
var relativedelta = new py.type(function relativedelta(args, kwargs) {
|
||||
if (!_.isEmpty(args)) {
|
||||
throw new Error('Extraction of relative deltas from existing datetimes not supported');
|
||||
}
|
||||
this.ops = kwargs;
|
||||
}, py.object, {
|
||||
__add__: function (other) {
|
||||
if (!(other instanceof datetime.date)) {
|
||||
return py.NotImplemented;
|
||||
}
|
||||
// TODO: test this whole mess
|
||||
var year = asJS(this.ops.year) || asJS(other.year);
|
||||
if (asJS(this.ops.years)) {
|
||||
year += asJS(this.ops.years);
|
||||
}
|
||||
|
||||
var month = asJS(this.ops.month) || asJS(other.month);
|
||||
if (asJS(this.ops.months)) {
|
||||
month += asJS(this.ops.months);
|
||||
// FIXME: no divmod in JS?
|
||||
while (month < 1) {
|
||||
year -= 1;
|
||||
month += 12;
|
||||
}
|
||||
while (month > 12) {
|
||||
year += 1;
|
||||
month -= 12;
|
||||
}
|
||||
}
|
||||
|
||||
var lastMonthDay = new Date(year, month, 0).getDate();
|
||||
var day = asJS(this.ops.day) || asJS(other.day);
|
||||
if (day > lastMonthDay) { day = lastMonthDay; }
|
||||
var days_offset = ((asJS(this.ops.weeks) || 0) * 7) + (asJS(this.ops.days) || 0);
|
||||
if (days_offset) {
|
||||
day = new Date(year, month-1, day + days_offset).getDate();
|
||||
}
|
||||
// TODO: leapdays?
|
||||
// TODO: hours, minutes, seconds? Not used in XML domains
|
||||
// TODO: weekday?
|
||||
return new datetime.date(year, month, day);
|
||||
},
|
||||
__radd__: function (other) {
|
||||
return this.__add__(other);
|
||||
},
|
||||
|
||||
__sub__: function (other) {
|
||||
if (!(other instanceof datetime.date)) {
|
||||
return py.NotImplemented;
|
||||
}
|
||||
// TODO: test this whole mess
|
||||
var year = asJS(this.ops.year) || asJS(other.year);
|
||||
if (asJS(this.ops.years)) {
|
||||
year -= asJS(this.ops.years);
|
||||
}
|
||||
|
||||
var month = asJS(this.ops.month) || asJS(other.month);
|
||||
if (asJS(this.ops.months)) {
|
||||
month -= asJS(this.ops.months);
|
||||
// FIXME: no divmod in JS?
|
||||
while (month < 1) {
|
||||
year -= 1;
|
||||
month += 12;
|
||||
}
|
||||
while (month > 12) {
|
||||
year += 1;
|
||||
month -= 12;
|
||||
}
|
||||
}
|
||||
|
||||
var lastMonthDay = new Date(year, month, 0).getDate();
|
||||
var day = asJS(this.ops.day) || asJS(other.day);
|
||||
if (day > lastMonthDay) { day = lastMonthDay; }
|
||||
var days_offset = ((asJS(this.ops.weeks) || 0) * 7) + (asJS(this.ops.days) || 0);
|
||||
if (days_offset) {
|
||||
day = new Date(year, month-1, day - days_offset).getDate();
|
||||
}
|
||||
// TODO: leapdays?
|
||||
// TODO: hours, minutes, seconds? Not used in XML domains
|
||||
// TODO: weekday?
|
||||
return new datetime.date(year, month, day);
|
||||
},
|
||||
__rsub__: function (other) {
|
||||
return this.__sub__(other);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
uid: new py.float(this.uid),
|
||||
datetime: datetime,
|
||||
time: time
|
||||
time: time,
|
||||
relativedelta: relativedelta
|
||||
};
|
||||
},
|
||||
test_eval: function (source, expected) {
|
||||
|
|
Loading…
Reference in New Issue