UNPKG

20 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6
7var _numeral = require('numeral');
8
9var _numeral2 = _interopRequireDefault(_numeral);
10
11var _convertUnits = require('convert-units');
12
13var _convertUnits2 = _interopRequireDefault(_convertUnits);
14
15var _alinexUtil = require('alinex-util');
16
17var _alinexUtil2 = _interopRequireDefault(_alinexUtil);
18
19var _Any = require('./Any');
20
21var _Any2 = _interopRequireDefault(_Any);
22
23var _Error = require('../Error');
24
25var _Error2 = _interopRequireDefault(_Error);
26
27var _Reference = require('../Reference');
28
29var _Reference2 = _interopRequireDefault(_Reference);
30
31function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
32
33const INTTYPE = {
34 byte: 8,
35 short: 16,
36 long: 32,
37 safe: 53,
38 quad: 64
39};
40
41class 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
52class 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
552exports.default = NumberSchema;
\No newline at end of file