commit 40304172679f6534e1862dd08334a6aee7f05e69
parent 33bbb244393b5384f10fa5ab6a42fb5d21880f55
Author: Bruno <b-coimbra@hotmail.com>
Date: Wed, 27 May 2020 21:35:31 -0300
Merge remote-tracking branch 'origin/features/cron'
Diffstat:
M | lib/commands/cron.ts | | | 300 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------ |
1 file changed, 210 insertions(+), 90 deletions(-)
diff --git a/lib/commands/cron.ts b/lib/commands/cron.ts
@@ -4,17 +4,46 @@ import DEFAULT_GUILD_CONFIG from '../default';
type Greenwich = 'pm' | 'am';
type Ordinal = 'st' | 'nd' | 'rd' | 'th';
+type AnyValue = '*';
+
+interface ListValue {
+ values: string[];
+}
+
+interface RangeValue {
+ range: string[];
+ step?: string;
+}
+
+interface PlaceValue {
+ [place: number]: object;
+}
+
+type Expr = string
+ | RangeValue
+ | ListValue
+ | AnyValue;
+
+enum Place {
+ MINUTE,
+ HOUR,
+ MONTHDAY,
+ MONTH,
+ WEEKDAY
+}
interface Schedule {
- hours?: string;
- minutes?: string;
- dayOfMonth?: string;
- month?: string;
- dayOfWeek?: string;
- greenwich?: Greenwich;
- ordinal?: Ordinal;
+ hours: Expr;
+ minutes: Expr;
+ dayOfMonth: Expr;
+ month: Expr;
+ dayOfWeek: Expr;
+ greenwich: Greenwich;
+ ordinal: Ordinal;
}
+type Defaults = keyof Omit<Schedule, 'greenwich' | 'ordinal'>;
+
interface Command {
name: string;
args?: string[];
@@ -22,22 +51,24 @@ interface Command {
interface Cron {
id: number;
- schedule?: Schedule;
+ schedule?: Partial<Schedule>;
command?: Command;
executed_at?: number;
}
const MATCHERS = {
- hour_mins:
- /^(((0|1)[0-9])|2[0-3]):[0-5][0-9]\s?(pm|am)?$/i,
- day_of_month:
- /(?:\b)(([1-9]|0[1-9]|[1-2][0-9]|3[0-1])(st|nd|rd|th)?)(?:\b|\/)/i,
+ hour_mins: '^(((0|1)[0-9])|2[0-3]):[0-5][0-9]\\s?(pm|am)?$',
+ day_of_month: '^(?:\\b)(([1-9]|0[1-9]|[1-2][0-9]|3[0-1])(st|nd|rd|th)?)(?:\\b|\\/)$',
+ any_value: '^\\*$',
+ numerics: '(^(\\d\\.?)+$)',
+ range: '(^\\d+\\-\\d+|\\*\\/\\d+)(\\/\\d+)?$',
+ list: '^(\\d+\\,\\d+)$',
weekdays: 'sun mon tue wed thu fri sat'.split(' '),
months: 'jan feb mar apr may jun jul aug sep oct nov dec'
.split(' ').map(month => month.capitalize()),
- prefix: (x: string) => new RegExp("^\\" + x),
- ordinals: /(st|nd|rd|th)/,
- greenwich: /(pm|am)/
+ prefix: (x: string) => "^\\" + x,
+ ordinals: '(st|nd|rd|th)',
+ greenwich: '(pm|am)'
};
const RESPONSES = {
@@ -49,10 +80,8 @@ const RESPONSES = {
},
empty: ":warning: There are no cron jobs being executed.",
clear: "Cleared all executed cron jobs.",
- removed: (id: number) =>
- `Removed cron job #${id.toString().format(FORMATS.bold)}.`,
- added: (cron: Cron) =>
- `New cron (#${cron.id.toString().format(FORMATS.bold)}) has been added.`,
+ removed: (id: number) => `Removed cron job #${id.toString().format(FORMATS.bold)}.`,
+ added: (cron: Cron) => `New cron (#${cron.id.toString().format(FORMATS.bold)}) has been added.`,
list: (cron: Cron) => {
const { schedule } = cron;
let result: string = "";
@@ -64,7 +93,7 @@ const RESPONSES = {
if (schedule?.hours && schedule?.minutes) {
result += `: ${schedule.hours}:${schedule.minutes}`;
if (schedule?.greenwich)
- result += `${schedule.greenwich.toUpperCase()}`;
+ result += schedule.greenwich.toUpperCase();
result += ' :clock3: ';
}
@@ -101,38 +130,38 @@ export class Timer {
this.homescope = homescope;
}
- get defaultDate() {
+ get now(): number {
const now = new Date();
- const default_values = {
- month: now.getMonth() - 1
- };
- return default_values;
- }
-
- compare(job: Cron): void {
- const current = new Date();
- current.setDate(current.getDate());
- current.setUTCHours(current.getHours() % 12);
- current.setSeconds(0);
- current.setMilliseconds(0);
-
- if (current.getTime() === this.timestamp(job))
- this.dispatch(job, current.getTime());
+ now.toLocaleString('en-US', { timeZone: 'America/New_York' });
+ now.setDate(now.getDate());
+ now.setUTCHours(now.getHours() % 12);
+ now.setSeconds(0);
+ now.setMilliseconds(0);
+ return now.getTime();
}
timestamp(job: Cron): number {
const date = new Date();
const { hours, minutes, month, dayOfMonth } = job.schedule;
+ date.toLocaleString('en-US', { timeZone: 'America/New_York' });
date.setUTCHours(Number(hours), Number(minutes), 0);
- date.setMonth(Number(month) - 1);
+ date.setMonth(Number(month));
date.setMilliseconds(0);
- date.setDate(Number(dayOfMonth));
+ date.setSeconds(0);
+ date.setDate(Number(dayOfMonth) - 1);
return date.getTime();
}
- dispatch(job: Cron, timespan: number) {
+ compare(job: Cron): void {
+ if (this.now === this.timestamp(job))
+ this.dispatch(job, this.now);
+ else
+ console.log('SKIPPED', this.now, this.timestamp(job));
+ }
+
+ dispatch(job: Cron, timespan: number): void {
if (job.executed_at === timespan)
return;
@@ -143,6 +172,8 @@ export class Timer {
job.executed_at = timespan;
+ this.homescope.message.answer("Ran cron #" + job.id);
+
this.homescope.main.process_command(
this.homescope.message,
true
@@ -150,7 +181,7 @@ export class Timer {
}
verify(jobs: Cron[]): void {
- jobs.forEach((job: Cron) => this.compare(job));
+ jobs.forEach(job => this.compare(job));
}
}
@@ -180,8 +211,8 @@ export default (home_scope: HomeScope) => {
const submit = () =>
CONFIG.cron_jobs = crons;
- const matches = (value: string, regex: RegExp): string | undefined =>
- (value.match(regex) || {})?.input;
+ const matches = (value: string, regex: string): string | undefined =>
+ (value.match(new RegExp(regex)) || {})?.input;
const rm = (job: number) => {
delete crons[crons.map(x => x.id).indexOf(job)];
@@ -208,60 +239,149 @@ export default (home_scope: HomeScope) => {
);
};
- const parse = (argm: string[]): Cron => {
- const cron: Cron = {
+ const tokenize = (args: string[]): Cron => {
+ const { prefix } = CONFIG.commands;
+
+ let cron: Cron = {
id: crons.slice(-1)[0]?.id + 1 || 0
};
- argm.some((argument, i) => {
- argument = argument.trim();
-
- switch (argument) {
- case (
- matches(argument, MATCHERS.prefix(CONFIG.commands.prefix))
- ): cron.command = {
- name: argument.split(CONFIG.commands.prefix)[1],
- args: argm.slice(i + 1)
- };
- break;
- case (matches(argument, MATCHERS.hour_mins)):
- const [hour, mins] = argument.split(':');
- const [min, greenwich] = mins.split(MATCHERS.greenwich);
-
- cron.schedule = {
- hours: hour,
- minutes: min,
- greenwich: greenwich as Greenwich
- };
- break;
- case (matches(argument, MATCHERS.day_of_month)):
- const [dayOfMonth, ordinal] =
- argument.split(MATCHERS.ordinals);
-
- const date =
- matches(argument, MATCHERS.ordinals) === undefined
- ? { month: argument }
- : { dayOfMonth, ordinal: ordinal as Ordinal };
-
- cron.schedule = {
- ...cron.schedule,
- ...date
- };
- break;
- }
+ const add = (schedule: Partial<Schedule>): void => {
+ cron.schedule = {
+ ...cron.schedule,
+ ...schedule
+ };
+ };
+
+ const populate = (value: Expr, position: number): void => {
+ add(({
+ [Place.HOUR]: { hours: value },
+ [Place.MINUTE]: { minutes: value },
+ [Place.MONTHDAY]: { dayOfMonth: value },
+ [Place.WEEKDAY]: { dayOfWeek: value },
+ [Place.MONTH]: { month: value }
+ } as PlaceValue)[position]);
+ };
+
+ const set_command = (expr: string, index: number): void => {
+ cron.command = {
+ name: expr,
+ args: args.slice(index + 1)
+ };
+ };
+
+ const set_hour_mins = (expr: string): void => {
+ const [hour, mins] = expr.split(':');
+ const [min, greenwich] = mins.split(new RegExp(MATCHERS.greenwich));
+
+ add({
+ hours: hour,
+ minutes: min,
+ greenwich: greenwich as Greenwich
+ });
+ };
+
+ const set_day_of_month = (expr: string): void => {
+ const [dayOfMonth, ordinal] =
+ expr.split(new RegExp(MATCHERS.ordinals));
+
+ const date =
+ matches(expr, MATCHERS.ordinals) === undefined
+ ? { month: expr }
+ : { dayOfMonth, ordinal: ordinal as Ordinal };
+
+ add(date);
+ };
+
+ const set_day_of_week = (expr: string): void => {
+ add({
+ dayOfWeek: (
+ MATCHERS.weekdays.indexOf(expr) + 1
+ ).toString()
+ });
+ };
+
+ const set_numerics = (expr: Expr, position: number): void => {
+ populate(expr, position);
+ };
+
+ const set_any_value = (position: number): void => {
+ if (cron.schedule?.hours &&
+ cron.schedule?.minutes &&
+ cron.schedule?.hours !== '*' &&
+ cron.schedule?.minutes !== '*' &&
+ cron.schedule?.greenwich) position += 1;
+
+ args[position] = '';
+ populate('*', position);
+ };
+
+ const set_range = (expr: string, position: number): void => {
+ let [from, to, step] = expr.split(/[\-\/]/);
+ const values: RangeValue = { range: [from, to] };
+
+ const has_step = (range: string[]) =>
+ range[0] == '*' && Number(range[1]) != NaN;
+
+ if (has_step(values.range))
+ step = values.range[1];
+
+ if (step) values.step = step;
+
+ populate(values, position);
+ };
+
+ const set_list = (expr: string, position: number): void => {
+ const values = expr.split(',');
+ const list: ListValue = { values };
+
+ populate(list, position);
+ };
+
+ const get_defaults = (key: Defaults): string => {
+ const now = new Date();
+
+ return {
+ hours: now.getHours(),
+ minutes: now.getMinutes(),
+ month: now.getMonth(),
+ dayOfMonth: now.getDate(),
+ dayOfWeek: now.getDay()
+ }[key].toString();
+ };
+
+ args.some((argument, i) => {
+ let position = args.indexOf(argument);
+
+ const MAPPINGS: Record<string, Function> = {
+ [MATCHERS.prefix(prefix)]: () => set_command(argument, i),
+ [MATCHERS.hour_mins]: () => set_hour_mins(argument),
+ [MATCHERS.range]: () => set_range(argument, position),
+ [MATCHERS.list]: () => set_list(argument, position),
+ [MATCHERS.numerics]: () => set_numerics(argument, position),
+ [MATCHERS.day_of_month]: () => set_day_of_month(argument),
+ [MATCHERS.any_value]: () => set_any_value(position)
+ };
+
+ for (let matcher in MAPPINGS)
+ if (matches(argument, matcher))
+ MAPPINGS[matcher]();
argument = argument.toLowerCase();
- if (MATCHERS.weekdays.includes(argument)) {
- cron.schedule = {
- ...cron.schedule,
- dayOfWeek: (
- MATCHERS.weekdays.indexOf(argument) + 1
- ).toString()
- };
- }
+ if (MATCHERS.weekdays.includes(argument))
+ set_day_of_week(argument);
});
+ Object
+ .keys(cron.schedule as object)
+ .forEach((value: string) => {
+ let key = <Defaults>value;
+
+ if (cron.schedule && cron.schedule[key] == '*')
+ cron!.schedule[key] = get_defaults(key);
+ });
+
return cron;
};
@@ -270,7 +390,7 @@ export default (home_scope: HomeScope) => {
else if (args[0] === 'rm') {
const job: number = Number(args[1]);
- (isNaN(job))
+ isNaN(job)
? message.answer(RESPONSES.help.rm)
: rm(job);
}
@@ -278,7 +398,7 @@ export default (home_scope: HomeScope) => {
clear();
}
else {
- const cron: Cron = parse(args);
+ const cron: Cron = tokenize(args);
if (!cron?.command)
message.answer(RESPONSES.help.command);