@getpipher/armory-fleet 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,79 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Date class extension methods
5
+ */
6
+ var extensions = {
7
+ addYear: function addYear() {
8
+ this.setFullYear(this.getFullYear() + 1);
9
+ },
10
+
11
+ addMonth: function addMonth() {
12
+ this.setDate(1);
13
+ this.setHours(0);
14
+ this.setMinutes(0);
15
+ this.setSeconds(0);
16
+ this.setMonth(this.getMonth() + 1);
17
+ },
18
+
19
+ addDay: function addDay() {
20
+ var day = this.getDate();
21
+ this.setDate(day + 1);
22
+
23
+ this.setHours(0);
24
+ this.setMinutes(0);
25
+ this.setSeconds(0);
26
+
27
+ if (this.getDate() === day) {
28
+ this.setDate(day + 2);
29
+ }
30
+ },
31
+
32
+ addHour: function addHour() {
33
+ var hours = this.getHours();
34
+ this.setHours(hours + 1);
35
+
36
+ if (this.getHours() === hours) {
37
+ this.setHours(hours + 2);
38
+ }
39
+
40
+ this.setMinutes(0);
41
+ this.setSeconds(0);
42
+ },
43
+
44
+ addMinute: function addMinute() {
45
+ this.setMinutes(this.getMinutes() + 1);
46
+ this.setSeconds(0);
47
+ },
48
+
49
+ addSecond: function addSecond() {
50
+ this.setSeconds(this.getSeconds() + 1);
51
+ },
52
+
53
+ toUTC: function toUTC() {
54
+ var to = new CronDate(this);
55
+ var ms = to.getTime() + (to.getTimezoneOffset() * 60000);
56
+ to.setTime(ms);
57
+ return to;
58
+ }
59
+ };
60
+
61
+ /**
62
+ * Extends Javascript Date class by adding
63
+ * utility methods for basic date incrementation
64
+ */
65
+
66
+ function CronDate (timestamp) {
67
+ var date = timestamp ? new Date(timestamp) : new Date();
68
+
69
+ // Attach extensions
70
+ var methods = Object.keys(extensions);
71
+ for (var i = 0, c = methods.length; i < c; i++) {
72
+ var method = methods[i];
73
+ date[method] = extensions[method].bind(date);
74
+ }
75
+
76
+ return date;
77
+ }
78
+
79
+ module.exports = CronDate;
@@ -0,0 +1,614 @@
1
+ 'use strict';
2
+
3
+ // Load Date class extensions
4
+ var CronDate = require('./date');
5
+
6
+ // Load fix for isNaN (IE)
7
+ require('./number');
8
+
9
+ /**
10
+ * Construct a new expression parser
11
+ *
12
+ * Options:
13
+ * currentDate: iterator start date
14
+ * endDate: iterator end date
15
+ *
16
+ * @constructor
17
+ * @private
18
+ * @param {Object} fields Expression fields parsed values
19
+ * @param {Object} options Parser options
20
+ */
21
+ function CronExpression (fields, options) {
22
+ this._options = options;
23
+ this._currentDate = new CronDate(options.currentDate);
24
+ this._endDate = options.endDate ? new CronDate(options.endDate) : null;
25
+ this._fields = {};
26
+ this._isIterator = options.iterator || false;
27
+ this._hasIterated = false;
28
+ this._utc = options.utc || false;
29
+
30
+ // Map fields
31
+ for (var i = 0, c = CronExpression.map.length; i < c; i++) {
32
+ var key = CronExpression.map[i];
33
+ this._fields[key] = fields[i];
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Field mappings
39
+ * @type {Array}
40
+ */
41
+ CronExpression.map = [ 'second', 'minute', 'hour', 'dayOfMonth', 'month', 'dayOfWeek' ];
42
+
43
+ /**
44
+ * Prefined intervals
45
+ * @type {Object}
46
+ */
47
+ CronExpression.predefined = {
48
+ '@yearly': '0 0 1 1 *',
49
+ '@monthly': '0 0 1 * *',
50
+ '@weekly': '0 0 * * 0',
51
+ '@daily': '0 0 * * *',
52
+ '@hourly': '0 * * * *'
53
+ };
54
+
55
+ /**
56
+ * Fields constraints
57
+ * @type {Array}
58
+ */
59
+ CronExpression.constraints = [
60
+ [ 0, 59 ], // Second
61
+ [ 0, 59 ], // Minute
62
+ [ 0, 23 ], // Hour
63
+ [ 1, 31 ], // Day of month
64
+ [ 1, 12 ], // Month
65
+ [ 0, 7 ] // Day of week
66
+ ];
67
+
68
+ /**
69
+ * Days in month
70
+ * @type {number[]}
71
+ */
72
+ CronExpression.daysInMonth = [
73
+ 31,
74
+ 28,
75
+ 31,
76
+ 30,
77
+ 31,
78
+ 30,
79
+ 31,
80
+ 31,
81
+ 30,
82
+ 31,
83
+ 30,
84
+ 31
85
+ ];
86
+
87
+ /**
88
+ * Field aliases
89
+ * @type {Object}
90
+ */
91
+ CronExpression.aliases = {
92
+ month: {
93
+ jan: 1,
94
+ feb: 2,
95
+ mar: 3,
96
+ apr: 4,
97
+ may: 5,
98
+ jun: 6,
99
+ jul: 7,
100
+ aug: 8,
101
+ sep: 9,
102
+ oct: 10,
103
+ nov: 11,
104
+ dec: 12
105
+ },
106
+
107
+ dayOfWeek: {
108
+ sun: 0,
109
+ mon: 1,
110
+ tue: 2,
111
+ wed: 3,
112
+ thu: 4,
113
+ fri: 5,
114
+ sat: 6
115
+ }
116
+ };
117
+
118
+ /**
119
+ * Field defaults
120
+ * @type {Array}
121
+ */
122
+ CronExpression.parseDefaults = [ '0', '*', '*', '*', '*', '*' ];
123
+
124
+ /**
125
+ * Parse input interval
126
+ *
127
+ * @param {String} field Field symbolic name
128
+ * @param {String} value Field value
129
+ * @param {Array} constraints Range upper and lower constraints
130
+ * @return {Array} Sequence of sorted values
131
+ * @private
132
+ */
133
+ CronExpression._parseField = function _parseField (field, value, constraints) {
134
+ // Replace aliases
135
+ switch (field) {
136
+ case 'month':
137
+ case 'dayOfWeek':
138
+ var aliases = CronExpression.aliases[field];
139
+
140
+ value = value.replace(/[a-z]{1,3}/gi, function(match) {
141
+ match = match.toLowerCase();
142
+
143
+ if (typeof aliases[match] !== undefined) {
144
+ return aliases[match];
145
+ } else {
146
+ throw new Error('Cannot resolve alias "' + match + '"')
147
+ }
148
+ });
149
+ break;
150
+ }
151
+
152
+ // Check for valid characters.
153
+ if (!(/^[\d|/|*|\-|,]+$/.test(value))) {
154
+ throw new Error('Invalid characters, got value: ' + value)
155
+ }
156
+
157
+ // Replace '*'
158
+ if (value.indexOf('*') !== -1) {
159
+ value = value.replace(/\*/g, constraints.join('-'));
160
+ }
161
+
162
+ //
163
+ // Inline parsing functions
164
+ //
165
+ // Parser path:
166
+ // - parseSequence
167
+ // - parseRepeat
168
+ // - parseRange
169
+
170
+ /**
171
+ * Parse sequence
172
+ *
173
+ * @param {String} val
174
+ * @return {Array}
175
+ * @private
176
+ */
177
+ function parseSequence (val) {
178
+ var stack = [];
179
+
180
+ function handleResult (result) {
181
+ var max = stack.length > 0 ? Math.max.apply(Math, stack) : -1;
182
+
183
+ if (result instanceof Array) { // Make sequence linear
184
+ for (var i = 0, c = result.length; i < c; i++) {
185
+ var value = result[i];
186
+
187
+ // Check constraints
188
+ if (value < constraints[0] || value > constraints[1]) {
189
+ throw new Error(
190
+ 'Constraint error, got value ' + value + ' expected range ' +
191
+ constraints[0] + '-' + constraints[1]
192
+ );
193
+ }
194
+
195
+ if (value > max) {
196
+ stack.push(value);
197
+ }
198
+
199
+ max = Math.max.apply(Math, stack);
200
+ }
201
+ } else { // Scalar value
202
+ result = +result;
203
+
204
+ // Check constraints
205
+ if (result < constraints[0] || result > constraints[1]) {
206
+ throw new Error(
207
+ 'Constraint error, got value ' + result + ' expected range ' +
208
+ constraints[0] + '-' + constraints[1]
209
+ );
210
+ }
211
+
212
+ if (field == 'dayOfWeek') {
213
+ result = result % 7;
214
+ }
215
+
216
+ if (result > max) {
217
+ stack.push(result);
218
+ }
219
+ }
220
+ }
221
+
222
+ var atoms = val.split(',');
223
+ if (atoms.length > 1) {
224
+ for (var i = 0, c = atoms.length; i < c; i++) {
225
+ handleResult(parseRepeat(atoms[i]));
226
+ }
227
+ } else {
228
+ handleResult(parseRepeat(val));
229
+ }
230
+
231
+ return stack;
232
+ }
233
+
234
+ /**
235
+ * Parse repetition interval
236
+ *
237
+ * @param {String} val
238
+ * @return {Array}
239
+ */
240
+ function parseRepeat (val) {
241
+ var repeatInterval = 1;
242
+ var atoms = val.split('/');
243
+
244
+ if (atoms.length > 1) {
245
+ return parseRange(atoms[0], atoms[atoms.length - 1]);
246
+ }
247
+
248
+ return parseRange(val, repeatInterval);
249
+ }
250
+
251
+ /**
252
+ * Parse range
253
+ *
254
+ * @param {String} val
255
+ * @param {Number} repeatInterval Repetition interval
256
+ * @return {Array}
257
+ * @private
258
+ */
259
+ function parseRange (val, repeatInterval) {
260
+ var stack = [];
261
+ var atoms = val.split('-');
262
+
263
+ if (atoms.length > 1 ) {
264
+ // Invalid range, return value
265
+ if (atoms.length < 2 || !atoms[0].length) {
266
+ return +val;
267
+ }
268
+
269
+ // Validate range
270
+ var min = +atoms[0];
271
+ var max = +atoms[1];
272
+
273
+ if (Number.isNaN(min) || Number.isNaN(max) ||
274
+ min < constraints[0] || max > constraints[1]) {
275
+ throw new Error(
276
+ 'Constraint error, got range ' +
277
+ min + '-' + max +
278
+ ' expected range ' +
279
+ constraints[0] + '-' + constraints[1]
280
+ );
281
+ } else if (min >= max) {
282
+ throw new Error('Invalid range: ' + val);
283
+ }
284
+
285
+ // Create range
286
+ var repeatIndex = +repeatInterval;
287
+
288
+ if (Number.isNaN(repeatIndex) || repeatIndex <= 0) {
289
+ throw new Error('Constraint error, cannot repeat at every ' + repeatIndex + ' time.');
290
+ }
291
+
292
+ for (var index = min, count = max; index <= count; index++) {
293
+ if (repeatIndex > 0 && (repeatIndex % repeatInterval) === 0) {
294
+ repeatIndex = 1;
295
+ stack.push(index);
296
+ } else {
297
+ repeatIndex++;
298
+ }
299
+ }
300
+
301
+ return stack;
302
+ }
303
+
304
+ return +val;
305
+ }
306
+
307
+ return parseSequence(value);
308
+ };
309
+
310
+ /**
311
+ * Find next matching schedule date
312
+ *
313
+ * @return {CronDate}
314
+ * @private
315
+ */
316
+ CronExpression.prototype._findSchedule = function _findSchedule () {
317
+ /**
318
+ * Match field value
319
+ *
320
+ * @param {String} value
321
+ * @param {Array} sequence
322
+ * @return {Boolean}
323
+ * @private
324
+ */
325
+ function matchSchedule (value, sequence) {
326
+ for (var i = 0, c = sequence.length; i < c; i++) {
327
+ if (sequence[i] >= value) {
328
+ return sequence[i] === value;
329
+ }
330
+ }
331
+
332
+ return sequence[0] === value;
333
+ }
334
+
335
+ /**
336
+ * Detect if input range fully matches constraint bounds
337
+ * @param {Array} range Input range
338
+ * @param {Array} constraints Input constraints
339
+ * @returns {Boolean}
340
+ * @private
341
+ */
342
+ function isWildcardRange (range, constraints) {
343
+ if (range instanceof Array && !range.length) {
344
+ return false;
345
+ }
346
+
347
+ if (constraints.length !== 2) {
348
+ return false;
349
+ }
350
+
351
+ return range.length === (constraints[1] - (constraints[0] < 1 ? - 1 : 0));
352
+ }
353
+
354
+ var method = function(name) {
355
+ return !this._utc ? name : ('getUTC' + name.slice(3));
356
+ }.bind(this);
357
+
358
+ var currentDate = new CronDate(this._currentDate);
359
+ var endDate = this._endDate;
360
+
361
+ // TODO: Improve this part
362
+ // Always increment second value when second part is present
363
+ if (this._fields.second.length > 1 && !this._hasIterated) {
364
+ currentDate.addSecond();
365
+ }
366
+
367
+ // Find matching schedule
368
+ while (true) {
369
+ // Validate timespan
370
+ if (endDate && (endDate.getTime() - currentDate.getTime()) < 0) {
371
+ throw new Error('Out of the timespan range');
372
+ }
373
+
374
+ // Day of month and week matching:
375
+ //
376
+ // "The day of a command's execution can be specified by two fields --
377
+ // day of month, and day of week. If both fields are restricted (ie,
378
+ // aren't *), the command will be run when either field matches the cur-
379
+ // rent time. For example, "30 4 1,15 * 5" would cause a command to be
380
+ // run at 4:30 am on the 1st and 15th of each month, plus every Friday."
381
+ //
382
+ // http://unixhelp.ed.ac.uk/CGI/man-cgi?crontab+5
383
+ //
384
+
385
+ var dayOfMonthMatch = matchSchedule(currentDate[method('getDate')](), this._fields.dayOfMonth);
386
+ var dayOfWeekMatch = matchSchedule(currentDate[method('getDay')](), this._fields.dayOfWeek);
387
+
388
+ var isDayOfMonthWildcardMatch = isWildcardRange(this._fields.dayOfMonth, CronExpression.constraints[3]);
389
+ var isMonthWildcardMatch = isWildcardRange(this._fields.month, CronExpression.constraints[4]);
390
+ var isDayOfWeekWildcardMatch = isWildcardRange(this._fields.dayOfWeek, CronExpression.constraints[5]);
391
+
392
+ // Validate days in month if explicit value is given
393
+ if (!isMonthWildcardMatch) {
394
+ var currentYear = currentDate[method('getFullYear')]();
395
+ var currentMonth = currentDate[method('getMonth')]() + 1;
396
+ var previousMonth = currentMonth === 1 ? 11 : currentMonth - 1;
397
+ var daysInPreviousMonth = CronExpression.daysInMonth[previousMonth - 1];
398
+ var daysOfMontRangeMax = this._fields.dayOfMonth[this._fields.dayOfMonth.length - 1];
399
+
400
+ var _daysInPreviousMonth = daysInPreviousMonth;
401
+ var _daysOfMontRangeMax = daysOfMontRangeMax;
402
+
403
+ // Handle leap year
404
+ var isLeap = !((currentYear % 4) || (!(currentYear % 100) && (currentYear % 400)));
405
+ if (isLeap) {
406
+ _daysInPreviousMonth = 29;
407
+ _daysOfMontRangeMax = 29;
408
+ }
409
+
410
+ if (this._fields.month[0] === previousMonth && _daysInPreviousMonth < _daysOfMontRangeMax) {
411
+ throw new Error('Invalid explicit day of month definition');
412
+ }
413
+ }
414
+
415
+ // Add day if select day not match with month (according to calendar)
416
+ if (!dayOfMonthMatch || !dayOfWeekMatch) {
417
+ currentDate.addDay();
418
+ continue;
419
+ }
420
+
421
+ // Add day if not day of month is set (and no match) and day of week is wildcard
422
+ if (!isDayOfMonthWildcardMatch && isDayOfWeekWildcardMatch && !dayOfMonthMatch) {
423
+ currentDate.addDay();
424
+ continue;
425
+ }
426
+
427
+ // Add day if not day of week is set (and no match) and day of month is wildcard
428
+ if (isDayOfMonthWildcardMatch && !isDayOfWeekWildcardMatch && !dayOfWeekMatch) {
429
+ currentDate.addDay();
430
+ continue;
431
+ }
432
+
433
+ // Add day if day of mont and week are non-wildcard values and both doesn't match
434
+ if (!(isDayOfMonthWildcardMatch && isDayOfWeekWildcardMatch) &&
435
+ !dayOfMonthMatch && !dayOfWeekMatch) {
436
+ currentDate.addDay();
437
+ continue;
438
+ }
439
+
440
+ // Match month
441
+ if (!matchSchedule(currentDate[method('getMonth')]() + 1, this._fields.month)) {
442
+ currentDate.addMonth();
443
+ continue;
444
+ }
445
+
446
+ // Match hour
447
+ if (!matchSchedule(currentDate[method('getHours')](), this._fields.hour)) {
448
+ currentDate.addHour();
449
+ continue;
450
+ }
451
+
452
+ // Match minute
453
+ if (!matchSchedule(currentDate[method('getMinutes')](), this._fields.minute)) {
454
+ currentDate.addMinute();
455
+ continue;
456
+ }
457
+
458
+ // Match second
459
+ if (!matchSchedule(currentDate[method('getSeconds')](), this._fields.second)) {
460
+ currentDate.addSecond();
461
+ continue;
462
+ }
463
+
464
+ break;
465
+ }
466
+
467
+ // When internal date is not mutated, append one second as a padding
468
+ var nextDate = new CronDate(currentDate);
469
+ if (this._currentDate !== currentDate) {
470
+ nextDate.addSecond();
471
+ }
472
+
473
+ this._currentDate = nextDate;
474
+ this._hasIterated = true;
475
+
476
+ return currentDate;
477
+ };
478
+
479
+ /**
480
+ * Find next suitable date
481
+ *
482
+ * @public
483
+ * @return {CronDate|Object}
484
+ */
485
+ CronExpression.prototype.next = function next () {
486
+ var schedule = this._findSchedule();
487
+
488
+ // Try to return ES6 compatible iterator
489
+ if (this._isIterator) {
490
+ return {
491
+ value: schedule,
492
+ done: !this.hasNext()
493
+ };
494
+ }
495
+
496
+ return schedule;
497
+ };
498
+
499
+ /**
500
+ * Check if next suitable date exists
501
+ *
502
+ * @public
503
+ * @return {Boolean}
504
+ */
505
+ CronExpression.prototype.hasNext = function() {
506
+ var current = this._currentDate;
507
+
508
+ try {
509
+ this.next();
510
+ return true;
511
+ } catch (err) {
512
+ return false;
513
+ } finally {
514
+ this._currentDate = current;
515
+ }
516
+ };
517
+
518
+ /**
519
+ * Iterate over expression iterator
520
+ *
521
+ * @public
522
+ * @param {Number} steps Numbers of steps to iterate
523
+ * @param {Function} callback Optional callback
524
+ * @return {Array} Array of the iterated results
525
+ */
526
+ CronExpression.prototype.iterate = function iterate (steps, callback) {
527
+ var dates = [];
528
+
529
+ for (var i = 0, c = steps; i < c; i++) {
530
+ try {
531
+ var item = this.next();
532
+ dates.push(item);
533
+
534
+ // Fire the callback
535
+ if (callback) {
536
+ callback(item, i);
537
+ }
538
+ } catch (err) {
539
+ break;
540
+ }
541
+ }
542
+
543
+ return dates;
544
+ };
545
+
546
+ /**
547
+ * Reset expression iterator state
548
+ *
549
+ * @public
550
+ */
551
+ CronExpression.prototype.reset = function reset () {
552
+ this._currentDate = new CronDate(this._options.currentDate);
553
+ };
554
+
555
+ /**
556
+ * Parse input expression (async)
557
+ *
558
+ * @public
559
+ * @param {String} expression Input expression
560
+ * @param {Object} [options] Parsing options
561
+ * @param {Function} [callback]
562
+ */
563
+ CronExpression.parse = function parse (expression, options, callback) {
564
+ if (typeof options === 'function') {
565
+ callback = options;
566
+ options = {};
567
+ }
568
+
569
+ function parse (expression, options) {
570
+ if (!options) {
571
+ options = {};
572
+ }
573
+
574
+ if (!options.currentDate) {
575
+ options.currentDate = new CronDate();
576
+ }
577
+
578
+ // Is input expression predefined?
579
+ if (CronExpression.predefined[expression]) {
580
+ expression = CronExpression.predefined[expression];
581
+ }
582
+
583
+ // Split fields
584
+ var fields = [];
585
+ var atoms = expression.split(' ');
586
+
587
+ // Resolve fields
588
+ var start = (CronExpression.map.length - atoms.length);
589
+ for (var i = 0, c = CronExpression.map.length; i < c; ++i) {
590
+ var field = CronExpression.map[i]; // Field name
591
+ var value = atoms[atoms.length > c ? i : i - start]; // Field value
592
+
593
+ if (i < start || !value) {
594
+ fields.push(CronExpression._parseField(
595
+ field,
596
+ CronExpression.parseDefaults[i],
597
+ CronExpression.constraints[i])
598
+ );
599
+ } else { // Use default value
600
+ fields.push(CronExpression._parseField(
601
+ field,
602
+ value,
603
+ CronExpression.constraints[i])
604
+ );
605
+ }
606
+ }
607
+
608
+ return new CronExpression(fields, options);
609
+ }
610
+
611
+ return parse(expression, options);
612
+ };
613
+
614
+ module.exports = CronExpression;
@@ -0,0 +1,8 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Polyfill. IE Number.isNaN does not support method 'isNaN'.
5
+ */
6
+ Number.isNaN = Number.isNaN || function(value) {
7
+ return typeof value === 'number' && isNaN(value);
8
+ }