1 | 'use strict';
|
2 |
|
3 | Object.defineProperty(exports, "__esModule", {
|
4 | value: true
|
5 | });
|
6 |
|
7 | var _numeral = require('numeral');
|
8 |
|
9 | var _numeral2 = _interopRequireDefault(_numeral);
|
10 |
|
11 | var _convertUnits = require('convert-units');
|
12 |
|
13 | var _convertUnits2 = _interopRequireDefault(_convertUnits);
|
14 |
|
15 | var _alinexUtil = require('alinex-util');
|
16 |
|
17 | var _alinexUtil2 = _interopRequireDefault(_alinexUtil);
|
18 |
|
19 | var _Any = require('./Any');
|
20 |
|
21 | var _Any2 = _interopRequireDefault(_Any);
|
22 |
|
23 | var _Error = require('../Error');
|
24 |
|
25 | var _Error2 = _interopRequireDefault(_Error);
|
26 |
|
27 | var _Reference = require('../Reference');
|
28 |
|
29 | var _Reference2 = _interopRequireDefault(_Reference);
|
30 |
|
31 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
32 |
|
33 | const INTTYPE = {
|
34 | byte: 8,
|
35 | short: 16,
|
36 | long: 32,
|
37 | safe: 53,
|
38 | quad: 64
|
39 | };
|
40 |
|
41 | class Round {
|
42 |
|
43 | constructor(precision = 0, method = 'arithmetic') {
|
44 | if (precision < 0) {
|
45 | throw new Error('Precision for round should be 0 or greater.');
|
46 | }
|
47 | this.precision = precision;
|
48 | this.method = method;
|
49 | }
|
50 | }
|
51 |
|
52 | class NumberSchema extends _Any2.default {
|
53 | constructor(base) {
|
54 | super(base);
|
55 |
|
56 | let raw = this._rules.descriptor.pop();
|
57 | let allow = this._rules.descriptor.pop();
|
58 | this._rules.descriptor.push(this._unitDescriptor, this._sanitizeDescriptor, this._roundDescriptor, this._minmaxDescriptor, this._multipleDescriptor, allow, this._formatDescriptor, raw);
|
59 | raw = this._rules.validator.pop();
|
60 | allow = this._rules.validator.pop();
|
61 | this._rules.validator.push(this._unitValidator, this._sanitizeValidator, this._roundValidator, this._minmaxValidator, this._multipleValidator, allow, this._formatValidator, raw);
|
62 | }
|
63 |
|
64 | sanitize(flag) {
|
65 | return this._setFlag('sanitize', flag);
|
66 | }
|
67 |
|
68 | _sanitizeDescriptor() {
|
69 | const set = this._setting;
|
70 | let msg = 'A numerical value is needed. ';
|
71 | if (set.sanitize instanceof _Reference2.default) {
|
72 | msg += `Strings are sanitized depending on ${set.sanitize.description}. `;
|
73 | } else if (set.sanitize) {
|
74 | msg += 'Strings are sanitized to get the first numerical value out of it. ';
|
75 | }
|
76 | return msg.replace(/ $/, '\n');
|
77 | }
|
78 |
|
79 | _sanitizeValidator(data) {
|
80 | const check = this._check;
|
81 | try {
|
82 | this._checkBoolean('sanitize');
|
83 | } catch (err) {
|
84 | return Promise.reject(new _Error2.default(this, data, err.message));
|
85 | }
|
86 |
|
87 | const orig = data.value;
|
88 | if (typeof data.value === 'string') {
|
89 | if (check.sanitize) data.value = data.value.replace(/^.*?([-+]?\d+\.?\d*).*?$/, '$1');
|
90 | data.value = Number(data.value);
|
91 | }
|
92 | if (typeof data.value !== 'number') {
|
93 | return Promise.reject(new _Error2.default(this, data, `The given value is of type ${typeof data.value} but a number is needed.`));
|
94 | } else if (Number.isNaN(data.value)) {
|
95 | return Promise.reject(new _Error2.default(this, data, `The given element \`${_alinexUtil2.default.inspect(orig)}\` is no valid number.`));
|
96 | }
|
97 | return Promise.resolve();
|
98 | }
|
99 |
|
100 | unit(unit) {
|
101 | const set = this._setting;
|
102 | if (unit instanceof _Reference2.default) set.unit = unit;else if (unit) {
|
103 | try {
|
104 | (0, _convertUnits2.default)().from(unit);
|
105 | set.unit = unit;
|
106 | } catch (e) {
|
107 | throw new Error(`Unit ${unit} not recognized`);
|
108 | }
|
109 | } else delete set.unit;
|
110 | return this;
|
111 | }
|
112 |
|
113 | toUnit(unit) {
|
114 | const set = this._setting;
|
115 | if (!set.unit) throw new Error('First define the input `unit()` before converting further');
|
116 | if (unit instanceof _Reference2.default) set.toUnit = unit;else if (unit) {
|
117 | try {
|
118 | (0, _convertUnits2.default)().from(unit);
|
119 | set.toUnit = unit;
|
120 | } catch (e) {
|
121 | throw new Error(`Unit ${unit} not recognized`);
|
122 | }
|
123 | } else delete set.toUnit;
|
124 | return this;
|
125 | }
|
126 |
|
127 | _unitDescriptor() {
|
128 | const set = this._setting;
|
129 | let msg = '';
|
130 | if (set.unit) {
|
131 | if (set.unit instanceof _Reference2.default) {
|
132 | msg += `The value is given in unit specified in ${set.unit.description}. `;
|
133 | } else msg += `Give the values in \`${set.unit}\`. `;
|
134 | if (set.toUnit instanceof _Reference2.default) {
|
135 | msg += `The value converted to unit specified in ${set.toUnit.description}. `;
|
136 | } else if (set.toUnit) {
|
137 | msg = msg.replace(/\. $/, ` and onvert the values to \`${set.toUnit}\`. `);
|
138 | }
|
139 | }
|
140 | return msg.length ? msg.replace(/ $/, '\n') : '';
|
141 | }
|
142 |
|
143 | _unitValidator(data) {
|
144 | const check = this._check;
|
145 | if (check.unit) {
|
146 | try {
|
147 | this._checkString('unit');
|
148 | (0, _convertUnits2.default)().from(check.unit);
|
149 | } catch (e) {
|
150 | throw new Error(`Unit ${check.unit} not recognized`);
|
151 | }
|
152 | }
|
153 | if (check.toUnit) {
|
154 | try {
|
155 | this._checkString('unit');
|
156 | (0, _convertUnits2.default)().from(check.toUnit);
|
157 | } catch (e) {
|
158 | throw new Error(`Unit ${check.toUnit} not recognized`);
|
159 | }
|
160 | }
|
161 |
|
162 | if (check.unit && typeof data.value === 'string') {
|
163 | if (check.sanitize) data.value = data.value.replace(/^.*?([-+]?\d+\.?\d*\s*\S*).*?$/, '$1');
|
164 | let quantity;
|
165 | try {
|
166 | const match = data.value.match(/(^[-+]?\d+\.?\d*)\s*(\S*)/);
|
167 | quantity = (0, _convertUnits2.default)(match[1]).from(match[2]);
|
168 | } catch (e) {
|
169 | return Promise.reject(new _Error2.default(this, data, `Could not parse the unit of ${data.value}: ${e.message}`));
|
170 | }
|
171 | try {
|
172 | data.value = quantity.to(check.unit);
|
173 | } catch (e) {
|
174 | return Promise.reject(new _Error2.default(this, data, `Could not convert to ${check.unit}: ${e.message}`));
|
175 | }
|
176 | }
|
177 | if (check.unit && check.toUnit && typeof data.value === 'number') {
|
178 | try {
|
179 | data.value = (0, _convertUnits2.default)(data.value).from(check.unit).to(check.toUnit);
|
180 | } catch (e) {
|
181 | return Promise.reject(new _Error2.default(this, data, `Could not convert ${check.unit} to ${check.toUnit}: ${e.message}`));
|
182 | }
|
183 | }
|
184 | return Promise.resolve();
|
185 | }
|
186 |
|
187 | min(value) {
|
188 | const set = this._setting;
|
189 | if (value) {
|
190 | if (!(value instanceof _Reference2.default)) {
|
191 | if (set.max && !this._isReference('max') && value > set.max) {
|
192 | throw new Error('Min can´t be greater than max value');
|
193 | }
|
194 | if (set.less && !this._isReference('less') && value >= set.less) {
|
195 | throw new Error('Min can´t be greater or equal less value');
|
196 | }
|
197 | if (set.negative && !this._isReference('negative') && value > 0) {
|
198 | throw new Error('Min can´t be positive, because defined as negative');
|
199 | }
|
200 | }
|
201 | set.min = value;
|
202 | } else delete set.min;
|
203 | return this;
|
204 | }
|
205 |
|
206 | max(value) {
|
207 | const set = this._setting;
|
208 | if (value) {
|
209 | if (!(value instanceof _Reference2.default)) {
|
210 | if (set.min && !this._isReference('min') && value < set.min) {
|
211 | throw new Error('Max can´t be less than min value');
|
212 | }
|
213 | if (set.greater && !this._isReference('greater') && value >= set.greater) {
|
214 | throw new Error('Max can´t be less or equal greater value');
|
215 | }
|
216 | if (set.positive && !this._isReference('positive') && value < 0) {
|
217 | throw new Error('Max can´t be negative, because defined as positive');
|
218 | }
|
219 | }
|
220 | set.max = value;
|
221 | } else delete set.max;
|
222 | return this;
|
223 | }
|
224 |
|
225 | less(value) {
|
226 | const set = this._setting;
|
227 | if (value) {
|
228 | if (!(value instanceof _Reference2.default)) {
|
229 | if (set.min && !this._isReference('min') && value <= set.min) {
|
230 | throw new Error('Less can´t be less than min value');
|
231 | }
|
232 | if (set.greater && !this._isReference('greater') && value <= set.greater) {
|
233 | throw new Error('Less can´t be less or equal greater value');
|
234 | }
|
235 | if (set.positive && !this._isReference('positive') && value <= 0) {
|
236 | throw new Error('Less can´t be negative, because defined as positive');
|
237 | }
|
238 | }
|
239 | set.less = value;
|
240 | } else delete set.less;
|
241 | return this;
|
242 | }
|
243 |
|
244 | greater(value) {
|
245 | const set = this._setting;
|
246 | if (value) {
|
247 | if (!(value instanceof _Reference2.default)) {
|
248 | if (set.max && !this._isReference('max') && value >= set.max) {
|
249 | throw new Error('Greater can´t be greater than max value');
|
250 | }
|
251 | if (set.less && !this._isReference('less') && value >= set.less) {
|
252 | throw new Error('Greater can´t be greater or equal less value');
|
253 | }
|
254 | if (set.negative && !this._isReference('negative') && value >= 0) {
|
255 | throw new Error('Greater can´t be positive, because defined as negative');
|
256 | }
|
257 | }
|
258 | set.greater = value;
|
259 | } else delete set.greater;
|
260 | return this;
|
261 | }
|
262 |
|
263 | positive(flag = true) {
|
264 | const set = this._setting;
|
265 | if (flag === false) delete set.positive;else if (flag instanceof _Reference2.default) set.positive = flag;else {
|
266 | if (!this._isReference('max') && set.max < 0) {
|
267 | throw new Error('Positive can´t be set because max value is negative');
|
268 | }
|
269 | if (!this._isReference('less') && set.less <= 0) {
|
270 | throw new Error('Positive can´t be set because less value is negative');
|
271 | }
|
272 | if (!this._isReference('negative')) set.negative = false;
|
273 | set.positive = true;
|
274 | }
|
275 | return this;
|
276 | }
|
277 |
|
278 | negative(flag = true) {
|
279 | const set = this._setting;
|
280 | if (flag === false) delete set.negative;else if (flag instanceof _Reference2.default) set.negative = flag;else {
|
281 | if (!this._isReference('min') && set.min > 0) {
|
282 | throw new Error('Negative can´t be set because min value is positive');
|
283 | }
|
284 | if (!this._isReference('greater') && set.greater >= 0) {
|
285 | throw new Error('Negative can´t be set because greater value is positive');
|
286 | }
|
287 | if (!this._isReference('positive')) set.positive = false;
|
288 | set.negative = true;
|
289 | }
|
290 | return this;
|
291 | }
|
292 |
|
293 | integer(flag) {
|
294 | return this._setFlag('integer', flag);
|
295 | }
|
296 |
|
297 | integerType(type) {
|
298 | const set = this._setting;
|
299 | if (type) {
|
300 | if (!(type instanceof _Reference2.default)) {
|
301 | set.integer = true;
|
302 | if (INTTYPE[type]) set.integerType = INTTYPE[type];else if (typeof type === 'number') {
|
303 | if (Object.values(INTTYPE).includes(type)) set.integerType = type;
|
304 | } else throw new Error(`Undefined type ${type} for integer.`);
|
305 | } else {
|
306 | set.integerType = type;
|
307 | }
|
308 | } else delete set.integerType;
|
309 | return this;
|
310 | }
|
311 |
|
312 | _minmaxDescriptor() {
|
313 | const set = this._setting;
|
314 |
|
315 | let max;
|
316 | let min;
|
317 | if (set.integer && !this._isReference('integer') && set.integerType) {
|
318 | const unsigned = set.positive ? 1 : 0;
|
319 | max = Math.pow(2, set.integerType - 1 + unsigned) - 1;
|
320 | min = (unsigned - 1) * max - 1 + unsigned;
|
321 | }
|
322 | if (min === undefined || !(!this._isReference('min') && set.min <= min)) min = set.min;
|
323 | if (max === undefined || !(!this._isReference('max') && set.max >= max)) max = set.max;
|
324 | if (min !== undefined && !this._isReference('greater') && set.greater !== undefined) {
|
325 | if (set.greater >= min) min = undefined;else delete set.greater;
|
326 | }
|
327 | if (max !== undefined && !this._isReference('less') && set.less !== undefined) {
|
328 | if (set.less <= max) max = undefined;else delete set.less;
|
329 | }
|
330 |
|
331 | let msg = '';
|
332 | if (this._isReference('integer')) {
|
333 | msg += `The value has to be an integer if specified in ${set.integer.description}. `;
|
334 | } else if (set.integer && set.integerType) {
|
335 | msg += `It has to be an ${set.positive ? 'unsigned ' : ''}\
|
336 | ${set.integerType}-bit integer. `;
|
337 | }
|
338 | if (this._isReference('positive')) {
|
339 | msg += `The value has to be positive if specified in ${set.positive.description}. `;
|
340 | } else if (set.positive) msg += 'The number should be positive. ';
|
341 | if (this._isReference('negative')) {
|
342 | msg += `The value has to be negative if specified in ${set.negative.description}. `;
|
343 | } else if (set.negative) msg += 'The number should be negative. ';
|
344 | if (this._isReference('min')) {
|
345 | msg += `The value has to be at least the number given in ${set.min.description}. `;
|
346 | } else if (min !== undefined) msg += `The value has to be at least \`${set.min}\`. `;
|
347 | if (this._isReference('greater')) {
|
348 | msg += `The value has to be higher than given in ${set.greater.description}. `;
|
349 | } else if (set.greater !== undefined) {
|
350 | msg += `The value has to be greater than \`${set.greater}\`. `;
|
351 | }
|
352 | if (this._isReference('less')) {
|
353 | msg += `The value has to be at lower than given in ${set.less.description}. `;
|
354 | } else if (set.less !== undefined) msg += `The value has to be less than \`${set.less}\`. `;
|
355 | if (this._isReference('max')) {
|
356 | msg += `The value has to be at least the number given in ${set.max.description}. `;
|
357 | } else if (max !== undefined) msg += `The value has to be at most \`${set.max}\`. `;
|
358 | if ((min !== undefined || set.greater !== undefined) && (max !== undefined || set.less !== undefined)) {
|
359 | msg = msg.replace(/(.*)\. The value has to be/, '$1 and');
|
360 | }
|
361 | return msg.replace(/ $/, '\n');
|
362 | }
|
363 |
|
364 | _minmaxValidator(data) {
|
365 | const check = this._check;
|
366 | try {
|
367 | this._checkNumber('min');
|
368 | this._checkNumber('max');
|
369 | this._checkNumber('greater');
|
370 | this._checkNumber('less');
|
371 | this._checkBoolean('positive');
|
372 | this._checkBoolean('negative');
|
373 | if (check.integerType) {
|
374 | if (INTTYPE[check.integerType]) check.integerType = INTTYPE[check.integerType];
|
375 | this._checkNumber('integerType');
|
376 | if (!Object.values(INTTYPE).includes(check.integerType)) {
|
377 | throw new Error(`Impossible to use ${check.integerType}-bit for integer.`);
|
378 | }
|
379 | }
|
380 | } catch (err) {
|
381 | return Promise.reject(new _Error2.default(this, data, err.message));
|
382 | }
|
383 |
|
384 | let max;
|
385 | let min;
|
386 | if (check.integer && check.integerType) {
|
387 | const unsigned = check.positive ? 1 : 0;
|
388 | max = Math.pow(2, check.integerType - 1 + unsigned) - 1;
|
389 | min = (unsigned - 1) * max - 1 + unsigned;
|
390 | }
|
391 | if (min === undefined || check.min !== undefined && !(check.min <= min)) min = check.min;
|
392 | if (max === undefined || check.max !== undefined && !(check.max >= max)) max = check.max;
|
393 | if (min !== undefined && check.greater !== undefined) {
|
394 | if (check.greater >= min) min = undefined;else delete check.greater;
|
395 | }
|
396 | if (max !== undefined && check.less !== undefined) {
|
397 | if (check.less <= max) max = undefined;else delete check.less;
|
398 | }
|
399 |
|
400 | if (check.positive && data.value < 0) {
|
401 | return Promise.reject(new _Error2.default(this, data, 'The number should be positive.'));
|
402 | }
|
403 | if (check.negative && data.value > 0) {
|
404 | return Promise.reject(new _Error2.default(this, data, 'The number should be negative.'));
|
405 | }
|
406 | if (min !== undefined && data.value < min) {
|
407 | return Promise.reject(new _Error2.default(this, data, `The value has to be at least ${min}.`));
|
408 | }
|
409 | if (check.greater !== undefined && data.value <= check.greater) {
|
410 | return Promise.reject(new _Error2.default(this, data, `The value has to be greater than ${check.greater}.`));
|
411 | }
|
412 | if (check.less !== undefined && data.value >= check.less) {
|
413 | return Promise.reject(new _Error2.default(this, data, `The value has to be less than ${check.less}.`));
|
414 | }
|
415 | if (max !== undefined && data.value > max) {
|
416 | return Promise.reject(new _Error2.default(this, data, `The value has to be at most ${max}.`));
|
417 | }
|
418 | return Promise.resolve();
|
419 | }
|
420 |
|
421 | round(precision = 0, method) {
|
422 | const set = this._setting;
|
423 | if (typeof precision === 'boolean' && !precision) delete set.round;else {
|
424 | if (set.integer && !(set.integer instanceof _Reference2.default)) {
|
425 | throw new Error('Rounding not possible because defined as integer');
|
426 | }
|
427 | set.round = new Round(typeof precision === 'boolean' ? 0 : precision, method);
|
428 | }
|
429 | return this;
|
430 | }
|
431 |
|
432 | _roundDescriptor() {
|
433 | const set = this._setting;
|
434 | if (set.integer && set.sanitize) return 'The value is rounded to an integer.\n';
|
435 | if (set.round && set.integer) {
|
436 | return `The value is rounded to \`${set.round.method}\` to get an integer.\n`;
|
437 | }
|
438 | if (set.round) {
|
439 | return `The value is rounded to \`${set.round.method}\` with \
|
440 | ${set.round.precision} digits precision.\n`;
|
441 | }
|
442 | if (set.integer && !set.integerType) return 'An integer value is needed.\n';
|
443 | return '';
|
444 | }
|
445 |
|
446 | _roundValidator(data) {
|
447 | const check = this._check;
|
448 | try {
|
449 | this._checkBoolean('integer');
|
450 | } catch (err) {
|
451 | return Promise.reject(new _Error2.default(this, data, err.message));
|
452 | }
|
453 |
|
454 | if (check.integer && !Number.isInteger(data.value)) {
|
455 | if (check.sanitize) {
|
456 | const method = check.round ? check.round.method : 'arithmetic';
|
457 | if (method === 'ceil') data.value = Math.ceil(data.value);else if (method === 'floor') data.value = Math.floor(data.value);else data.value = Math.round(data.value);
|
458 | } else {
|
459 | return Promise.reject(new _Error2.default(this, data, 'The value has to be an integer number.'));
|
460 | }
|
461 | } else if (check.round) {
|
462 | const exp = check.integer ? 1 : Math.pow(10, check.round.precision);
|
463 | let value = data.value * exp;
|
464 | if (check.round.method === 'ceil') value = Math.ceil(value);else if (check.round.method === 'floor') value = Math.floor(value);else value = Math.round(value);
|
465 | data.value = value / exp;
|
466 | }
|
467 | return Promise.resolve();
|
468 | }
|
469 |
|
470 | multiple(value) {
|
471 | const set = this._setting;
|
472 | if (value) {
|
473 | if (!(value instanceof _Reference2.default)) {
|
474 | if (set.negative && !this._isReference('negative') && value > 0) {
|
475 | throw new Error('Multiplicator has to be negative, too.');
|
476 | }
|
477 | if (set.positive && !this._isReference('positive') && value < 0) {
|
478 | throw new Error('Multiplicator has to be positive, too.');
|
479 | }
|
480 | set.multiple = value;
|
481 | } else {
|
482 | set.multiple = value;
|
483 | }
|
484 | } else delete set.multiple;
|
485 | return this;
|
486 | }
|
487 |
|
488 | _multipleDescriptor() {
|
489 | const set = this._setting;
|
490 | if (set.multiple) {
|
491 | return `The value has to be multiple of \
|
492 | ${this._isReference('multiple') ? set.multiple.description : set.multiple}.\n`;
|
493 | }
|
494 | return '';
|
495 | }
|
496 |
|
497 | _multipleValidator(data) {
|
498 | const check = this._check;
|
499 | try {
|
500 | this._checkNumber('multiple');
|
501 | } catch (err) {
|
502 | return Promise.reject(new _Error2.default(this, data, err.message));
|
503 | }
|
504 |
|
505 | if (check.multiple && data.value % check.multiple) {
|
506 | return Promise.reject(new _Error2.default(this, data, `The value has to be a multiple of ${check.multiple}.`));
|
507 | }
|
508 | return Promise.resolve();
|
509 | }
|
510 |
|
511 | format(format) {
|
512 | return this._setAny('format', format);
|
513 | }
|
514 |
|
515 | _formatDescriptor() {
|
516 | const set = this._setting;
|
517 | if (set.format) {
|
518 | return `The value will be formatted as string in the form of \
|
519 | ${this._isReference('format') ? set.format.description : set.format}.\n`;
|
520 | }
|
521 | return '';
|
522 | }
|
523 |
|
524 | _formatValidator(data) {
|
525 | const check = this._check;
|
526 | try {
|
527 | this._checkString('format');
|
528 | } catch (err) {
|
529 | return Promise.reject(new _Error2.default(this, data, err.message));
|
530 | }
|
531 |
|
532 | if (check.format) {
|
533 | const match = check.format.match(/(^.*?)(\s*\$(?:unit|best))/);
|
534 | let unit = match ? match[2] : '';
|
535 | if (unit.includes('$best')) {
|
536 | const quantity = (0, _convertUnits2.default)(data.value).from(check.toUnit || check.unit).toBest();
|
537 | data.value = quantity.val;
|
538 | unit = unit.replace('$best', quantity.unit);
|
539 | }
|
540 | if (unit.includes('$unit')) unit = unit.replace('$unit', check.toUnit || check.unit || '');
|
541 | const format = match ? match[1] : check.format;
|
542 | try {
|
543 | data.value = `${(0, _numeral2.default)(data.value).format(format)}${unit}`;
|
544 | } catch (e) {
|
545 | return Promise.reject(new _Error2.default(this, data, `Could not format value: ${e.message}`));
|
546 | }
|
547 | }
|
548 | return Promise.resolve();
|
549 | }
|
550 | }
|
551 |
|
552 | exports.default = NumberSchema; |
\ | No newline at end of file |