Skip to content

Commit

Permalink
Adds meta data for whether the schedule should repeat
Browse files Browse the repository at this point in the history
  • Loading branch information
rautio committed Oct 20, 2023
1 parent 57f1d97 commit c8c966d
Show file tree
Hide file tree
Showing 13 changed files with 101 additions and 62 deletions.
7 changes: 2 additions & 5 deletions __tests__/nextDate/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,8 @@ type Case = {
output: string;
};

const envSuite = process.env.TEST_SUITE;
let suites = readdirSync(path.join(__dirname, './suites'));
if (envSuite && envSuite !== 'undefined') {
suites = [envSuite + '.txt'];
}
// No flag filtering since only smoke tests exist
const suites = readdirSync(path.join(__dirname, './suites'));
suites.forEach(name => {
describe('nextDate test suite: ' + name, () => {
const content = readFileSync(
Expand Down
9 changes: 7 additions & 2 deletions __tests__/parseText/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { readFileSync, readdirSync } from 'fs';
import { parseText } from '../../src/index';
import { parseText, crontext } from '../../src/index';

const currentDate = new Date('2023-3-4');

Expand All @@ -16,7 +16,7 @@ type Case = {
const envSuite = process.env.TEST_SUITE;
let suites = readdirSync(path.join(__dirname, './suites'));
if (envSuite && envSuite !== 'undefined') {
suites = [envSuite + '.txt'];
suites = envSuite.split(',').map(suite => `${suite}.txt`);
}
suites.forEach(name => {
describe('Crontext test suite: ' + name, () => {
Expand Down Expand Up @@ -46,6 +46,11 @@ suites.forEach(name => {
cases.forEach(({ input, output }) => {
test(input, () => {
expect(parseText(input)).toEqual(output);
if (name === 'repeat.txt') {
expect(crontext(input)).toEqual(
expect.objectContaining({ repeat: true }),
);
}
});
});
});
Expand Down
14 changes: 13 additions & 1 deletion __tests__/parseText/suites/compound.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,14 @@
Every 5 minutes, every other hour, on every day excluding Friday, and also on the 13th of the month, in April and August.
*/5 */2 13 4,8 0,1,2,3,4,6
*/5 */2 13 4,8 0,1,2,3,4,6

At 04:00 on every day-of-month from 8 through 14.
0 4 8-14 * *

At 9am every 10th and 15th of the month
0 9 10,15 * *

At 9AM on the first day of every second month
0 9 1 */2 *

Every Monday, Tuesday and Thursday at noon
0 12 * * 1,2,4
32 changes: 13 additions & 19 deletions __tests__/parseText/suites/repeat.txt
Original file line number Diff line number Diff line change
@@ -1,22 +1,19 @@
At 04:00 on every day-of-month from 8 through 14.
0 4 8-14 * *
At every 9am
0 9 * * *

Hourly at 45 minutes past the hour
45 * * * *
On Fridays
0 9 * * 5

At 9am every 10th and 15th of the month
0 9 10,15 * *
On Wednesdays
0 9 * * 3

At every minute in April
* * * 4 *

At 9AM on the first day of every second month
0 9 1 */2 *
On weekdays
0 9 * * 1-5

Twice a day at 9am and 9pm
0 9,21 * * *
On Weekends
0 9 * * 0,6

Every 15th minute
Every 15 minutes
*/15 * * * *

Every hour
Expand All @@ -32,10 +29,7 @@ Every weekday at 9am
0 9 * * 1-5

Every weekend at 9am
0 9 * * 6,0
0 9 * * 0,6

Every Saturday
0 0 * * SAT

Every Monday, Tuesday and Thursday at noon
0 12 * * 1,2,4
0 9 * * 6
11 changes: 1 addition & 10 deletions __tests__/parseText/suites/smoke.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ At 9am
0 9 * * *

At midnight
0 24 * * *
0 0 * * *

At noon
0 12 * * *
Expand All @@ -40,15 +40,6 @@ At every minute on monday
On Friday
0 9 * * 5

On Wednesdays
0 9 * * 3

On weekdays
0 9 * * 1-5

On Weekends
0 9 * * 0,6

On Wednesday at 2:13pm
13 14 * * 3

Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "crontext",
"version": "0.2.9",
"version": "0.2.10",
"description": "Simple utility for parsing human text into a cron schedule.",
"files": [
"lib"
Expand All @@ -9,9 +9,9 @@
"types": "lib/index.d.ts",
"scripts": {
"test": "node ./scripts/jest-runner.js --config jest.config.js --watch",
"test:ci": "node ./scripts/jest-runner.js --config jest.config.js src __tests__ --suite=smoke",
"test:ci": "node ./scripts/jest-runner.js --config jest.config.js src __tests__ --suite=smoke,repeat",
"coverage": "yarn test:ci -- --coverage",
"test:smoke": "node ./scripts/jest-runner.js --config jest.config.js --suite=smoke __tests__",
"test:smoke": "node ./scripts/jest-runner.js --config jest.config.js --suite=smoke,repeat __tests__",
"build": "rm -rf lib && tsc",
"lint": "eslint --ignore-path .eslintignore --ext .js,.ts",
"format": "prettier --ignore-path .gitignore --write \"**/*.+(js|ts|json)\"",
Expand Down
4 changes: 2 additions & 2 deletions src/generate.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Parsed, INIT, DEFAULT } from './parser';
import { type Crontext, INIT, DEFAULT } from './parser';

/**
* CRON FORMAT:
Expand All @@ -19,7 +19,7 @@ const getValue = (str: string): string => {
return str;
};

export const generate = (parsed: Parsed): string => {
export const generate = (parsed: Crontext): string => {
return `${getValue(parsed.minutes)} ${getValue(parsed.hour)} ${getValue(
parsed.dayOfMonth,
)} ${getValue(parsed.month)} ${getValue(parsed.dayOfWeek)}`;
Expand Down
10 changes: 8 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import tokenize from './tokenize';
import parse from './parser';
import parse, { type Crontext } from './parser';
import { generate } from './generate';
import * as next from './next';
import { parseOptions } from './options';
Expand All @@ -14,6 +14,12 @@ export const parseText = (input: string, options?: InputOptions): string => {
return cron;
};

export const crontext = (input: string, options?: InputOptions): Crontext => {
const tokens = tokenize(input);
const parsed = parse(tokens, parseOptions(options));
return parsed;
};

export const nextDate = next.nextDate;

export const version = pJson.version;
Expand All @@ -25,4 +31,4 @@ export const version = pJson.version;
* - repeat: bool
*/

export default parseText;
export default crontext;
60 changes: 45 additions & 15 deletions src/parser.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import type { Token } from './tokenize';
import { TokenType } from './tokens';
import { testRepeat as freqTestRepeat } from './tokens/frequency';
import { getNumber } from './tokens/number';
import { getTime } from './tokens/clock';
import { getDayOfWeek } from './tokens/day';
import { getDayOfWeek, pluralDayRegexOptions } from './tokens/day';
import type { Options } from './options';

export type Parsed = {
export type Crontext = {
minutes: string;
hour: string;
dayOfMonth: string;
dayOfWeek: string;
month: string;
repeat: boolean;
};

export const DEFAULT = '*';
Expand All @@ -30,32 +32,42 @@ const {
RELATIVE_DAY,
} = TokenType;

const defaultParsed: Parsed = {
const defaultParsed: Crontext = {
minutes: INIT,
hour: INIT,
dayOfMonth: INIT,
dayOfWeek: INIT,
month: INIT,
repeat: false,
};

export const updateDay = (
crontext: Parsed,
crontext: Crontext,
tokens: Token[],
options: Options,
): Parsed => {
): Crontext => {
// If there are no minutes or hour set we use defaults
// 'On monday' -> 9am Monday
if (crontext.minutes === INIT) crontext.minutes = options.defaultMinute;
if (crontext.hour === INIT) crontext.hour = options.defaultHour;
const re = new RegExp(pluralDayRegexOptions);
// Plural 'on mondays' means its repeating
if (
tokens.length >= 2 &&
tokens[0].value === 'on' &&
re.test(tokens[1].value)
) {
crontext.repeat = true;
}
const dayOfWeek = getDayOfWeek(tokens[1].value);
return { ...crontext, dayOfWeek };
};

export const updateDays = (
crontext: Parsed,
crontext: Crontext,
tokens: Token[],
options: Options,
): Parsed => {
): Crontext => {
// Default like 'next week'
let delta = 1;
let setDate = true;
Expand Down Expand Up @@ -104,32 +116,42 @@ export const updateDays = (

// The grammar
export const rules = [
{
match: [FREQUENCY],
update: (crontext: Crontext, tokens: Token[]): Crontext => {
const re = new RegExp(freqTestRepeat);
if (re.test(tokens[0].value)) {
crontext.repeat = true;
}
return crontext;
},
},
{
match: [FREQUENCY, NUMBER, MINUTE],
update: (crontext: Parsed, tokens: Token[]): Parsed => {
update: (crontext: Crontext, tokens: Token[]): Crontext => {
crontext.minutes = '*/' + getNumber(tokens[1].value);
return crontext;
},
},
{
match: [FREQUENCY, MINUTE],
update: (crontext: Parsed): Parsed => {
update: (crontext: Crontext): Crontext => {
crontext.minutes = DEFAULT;
crontext.hour = DEFAULT;
return crontext;
},
},
{
match: [FREQUENCY, NUMBER, HOUR],
update: (crontext: Parsed, tokens: Token[]): Parsed => {
update: (crontext: Crontext, tokens: Token[]): Crontext => {
if (crontext.minutes === INIT) crontext.minutes = '0';
crontext.hour = '*/' + getNumber(tokens[1].value);
return crontext;
},
},
{
match: [FREQUENCY, HOUR],
update: (crontext: Parsed): Parsed => {
update: (crontext: Crontext): Crontext => {
crontext.minutes = '0';
crontext.hour = DEFAULT;
return crontext;
Expand All @@ -153,7 +175,11 @@ export const rules = [
},
{
match: [FREQUENCY, DAYS],
update: (crontext: Parsed, tokens: Token[], options: Options): Parsed => {
update: (
crontext: Crontext,
tokens: Token[],
options: Options,
): Crontext => {
if (crontext.minutes === INIT) crontext.minutes = options.defaultMinute;
if (crontext.hour === INIT) crontext.hour = options.defaultHour;
if (tokens[1].value.indexOf('month') > -1) {
Expand All @@ -167,7 +193,11 @@ export const rules = [
},
{
match: [RELATIVE_DAY],
update: (crontext: Parsed, tokens: Token[], options: Options): Parsed => {
update: (
crontext: Crontext,
tokens: Token[],
options: Options,
): Crontext => {
if (tokens[0].value === 'tomorrow') {
const { startDate } = options;
const tomorrow = new Date(startDate.getTime());
Expand All @@ -181,7 +211,7 @@ export const rules = [
},
{
match: [FREQUENCY, CLOCK],
update: (crontext: Parsed, tokens: Token[]): Parsed => {
update: (crontext: Crontext, tokens: Token[]): Crontext => {
const [hour, minute] = getTime(tokens[1].value);
crontext.minutes = minute.toString();
crontext.hour = hour.toString();
Expand All @@ -190,7 +220,7 @@ export const rules = [
},
];

export const parser = (tokens: Token[], options: Options): Parsed => {
export const parser = (tokens: Token[], options: Options): Crontext => {
let crontext = { ...defaultParsed };
// Iterate all tokens
for (let t = 0; t < tokens.length; t++) {
Expand Down
2 changes: 1 addition & 1 deletion src/tokens/clock.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ describe('Clock token getTime() should', () => {
expect(getTime('17:00')).toEqual([17, 0]);
});
test('return the correct clock time for special names', () => {
expect(getTime('midnight')).toEqual([24, 0]);
expect(getTime('midnight')).toEqual([0, 0]);
expect(getTime('noon')).toEqual([12, 0]);
});
});
2 changes: 1 addition & 1 deletion src/tokens/clock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export type Time = {
* @returns [hour, minute]
*/
export const getTime = (str: string): [number, number] => {
if (str === 'midnight') return [24, 0];
if (str === 'midnight') return [0, 0];
if (str === 'noon') return [12, 0];
const re = new RegExp('([0-9]+)[:]?([0-9]+)?');
const match = re.exec(str);
Expand Down
5 changes: 4 additions & 1 deletion src/tokens/day.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
export const dayRegexOptions =
'^(mon|tues|tue|wed|wednes|thurs|thur|fri|sat|sun)(day|days)?$|weekday|weekend|weekdays|weekends$';
'^(mon|tues|tue|wed|wednes|thurs|thur|fri|sat|satur|sun)(day|days)?$|weekday|weekend|weekdays|weekends$';

export const pluralDayRegexOptions =
'^(mon|tues|tue|wed|wednes|thurs|thur|fri|sat|satur|sun)(days)?$|weekdays|weekends$';

const weekMap: Record<string, string> = {
sun: '0',
Expand Down
1 change: 1 addition & 0 deletions src/tokens/frequency.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const testRepeat = '^(every|each|every other)$';

0 comments on commit c8c966d

Please sign in to comment.