timed-silky
Version:
Timed-Silky 是基于一款基于 TypeScript 的定时任务调度器。得益于 TypeScript,Timed-Silky 可以用符合特定句式的自然语言描述任务的调度规则,提供丝滑的链式调用接口。
354 lines (353 loc) • 11.6 kB
JavaScript
import { DayFrequency, DayRangeRule, HourFrequency, HourRangeRule, MinuteFrequency, RuleChain, SecondFrequency, TimePointRule, } from "./rule";
import { day2num, dayRange, TimePoint, timePointReg, } from "./time-type";
function TimedRuleString2Words(rule) {
return rule.trim().replace(/,|,/g, " , ").toLowerCase().split(/\s+/);
}
class RuleBuilder {
constructor() {
this._timeRuleWords = [];
}
get timeRuleWords() {
return this._timeRuleWords;
}
timed(rule) {
this._timeRuleWords = TimedRuleString2Words(rule);
}
at(timePoint, ...timePoints) {
this._timeRuleWords.push("at", timePoint, ...timePoints);
return this;
}
every(x) {
this._timeRuleWords.push("every", String(x));
return this;
}
seconds() {
this._timeRuleWords.push("seconds");
return this;
}
minutes() {
this._timeRuleWords.push("minutes");
return this;
}
hours() {
this._timeRuleWords.push("hours");
return this;
}
days() {
this._timeRuleWords.push("days");
return this;
}
every_second() {
this._timeRuleWords.push("every", "second");
return this;
}
every_minute() {
this._timeRuleWords.push("every", "minute");
return this;
}
every_hour() {
this._timeRuleWords.push("every", "hour");
return this;
}
every_day() {
this._timeRuleWords.push("every", "day");
return this;
}
and(dayOrPoint) {
this._timeRuleWords.push("and", dayOrPoint);
return this;
}
on(day, day2, ...days) {
this._timeRuleWords.push("on", day);
if (day2 != undefined) {
this._timeRuleWords.push(day2, ...days);
}
return this;
}
from(timePoint) {
this._timeRuleWords.push("from", timePoint);
return this;
}
except(dayOrPoint, day2, ...days) {
this._timeRuleWords.push("except", dayOrPoint);
if (day2 != undefined) {
this._timeRuleWords.push(day2, ...days);
}
return this;
}
to(dayOrPoint) {
this._timeRuleWords.push("to", dayOrPoint);
return this;
}
}
function createRuleBuilder() {
return new RuleBuilder();
}
/**
* 解析自然语言描述的时间规则,并返回 RuleChain 对象描述的时间规则链
* @param ruleWords 自然语言字符串或单词列表描述的时间规则
* @returns RuleChain 对象表示的时间规则链,RuleChain 当前可能包括一个规则,以后可能会包括多个
*/
function parseFrom(ruleWords) {
let headRule;
let i = 0;
// 解析出时间点或天级、小时级、分钟级频率
switch (ruleWords[i]) {
case "at":
[i, headRule] = parseTimePointsRuleFromWords(ruleWords, i + 1);
break;
case "every":
[i, headRule] = parseFrequencyRuleFromWords(ruleWords, i + 1);
break;
default:
throw new Error("解析错误,无法解析出合法的固定时间点或频率");
}
let chain = new RuleChain(headRule);
if (i >= ruleWords.length) {
return chain;
}
// 继续解析所在/排除时间范围
let tailRule;
let isExclude = false;
if (ruleWords[i] === "except") {
isExclude = true;
i += 1;
}
if (ruleWords[i] === "from" || ruleWords[i] === "on") {
i += 1;
}
if (ruleWords[i] in day2num) {
tailRule = parseDayRangeRuleFromWords(ruleWords, i, isExclude);
}
else if (timePointReg.test(ruleWords[i])) {
tailRule = parseHourRangeRuleFromWords(ruleWords, i, isExclude);
}
else {
throw new Error("尝试解析时间范围出错");
}
chain.setTailRule(tailRule);
return chain;
}
/**
* 从单词列表中解析出固定时间点规则,解析过程从下标 i 开始,直到列表末尾或遇到不能解析的词时结束
* @param words 单词列表
* @param i 解析过程的起始下标
* @returns 解析结束下标和固定时间点规则对象组成的二元列表
*/
function parseTimePointsRuleFromWords(words, i) {
let timePoints = [];
while (i < words.length) {
if (words[i] === "and" || words[i] === ",") {
i += 1;
continue;
}
else if (timePointReg.test(words[i])) {
let [h, m, s] = words[i].split(":");
if (s === undefined) {
s = "0";
}
timePoints.push(new TimePoint(+h, +m, +s));
}
else {
break;
}
i++;
}
return [i, new TimePointRule(timePoints)];
}
/**
* 从单词列表解析出频率规则,解析过程从下标 i 开始
* @param words 单词列表
* @param i 解析过程的起始下标
* @returns 解析结束时的下标和频率规则对象组成二元列表
*/
function parseFrequencyRuleFromWords(words, i) {
let rule;
if (/^\d+$/.test(words[i])) {
let count = +words[i];
i += 1;
switch (words[i]) {
case "days":
rule = new DayFrequency(count);
break;
case "hours":
rule = new HourFrequency(count);
break;
case "minutes":
rule = new MinuteFrequency(count);
break;
case "seconds":
rule = new SecondFrequency(count);
break;
default:
throw new Error("解析频率规则错误");
}
}
else {
switch (words[i]) {
case "day":
rule = new DayFrequency(1);
break;
case "hour":
rule = new HourFrequency(1);
break;
case "minute":
rule = new MinuteFrequency(1);
break;
case "second":
rule = new SecondFrequency(1);
default:
throw new Error("解析频率规则出错");
}
}
return [i + 1, rule];
}
/**
* 从单词列表解析出天级时间范围规则,解析过程从下标 i 开始
* @param words 单词列表
* @param i 解析过程的起始下标
* @param isExclude 如果为 true,表示排除时间范围;否则,为所在时间范围
* @returns 解析结束时的下标和天级时间范围规则对象组成二元列表
*/
function parseDayRangeRuleFromWords(words, i, isExclude = false) {
let days = [];
while (i < words.length) {
if (words[i] in day2num) {
if (i + 1 < words.length && words[i + 1] === "to") {
days.splice(days.length, 0, ...dayRange(words[i], words[i + 2]));
i += 3;
}
else {
days.push(day2num[words[i]]);
i += 1;
}
}
else if (words[i] === "and" || words[i] === ",") {
i += 1;
continue;
}
}
let dayRangeRule;
if (isExclude) {
dayRangeRule = new DayRangeRule(true);
dayRangeRule.excludeDays(...days);
}
else {
dayRangeRule = new DayRangeRule(false);
dayRangeRule.addDays(...days);
}
return dayRangeRule;
}
/**
* 从单词列表解析出小级时间范围规则,解析过程从下标 i 开始
* @param words 单词列表
* @param i 解析过程的起始下标
* @param isExclude 如果为 true,表示排除时间范围;否则,为所在时间范围
* @returns 解析结束时的下标和小时级时间范围规则对象组成二元列表
*/
function parseHourRangeRuleFromWords(words, i, isExclude = false) {
let rule = new HourRangeRule(!isExclude);
while (i < words.length) {
let hourRanges;
[i, hourRanges] = parseTimeRangeFromWords(words, i);
let days;
[i, days] = parseDayRangeFromWords(words, i);
for (let hourRange of hourRanges) {
rule.addRange(hourRange, ...days);
}
}
return rule;
}
/**
* 从单词列表解析出间范围列表
* @param words 单词列表
* @param i 解析过程的起始下标
* @returns 解析结束时的下标和时间范围列表组成二元列表,时间范围是两个 TimePoint 组成二元列表,
* 第一个 TimePoint 表示时间范围起点,第二个 TimePoint 表示时间范围结束点
*/
function parseTimeRangeFromWords(words, i) {
let ranges = [];
while (i < words.length) {
// 解析出时间范围
if (timePointReg.test(words[i])) {
// 解析start时间点
let [h, m, s] = words[i].split(":");
if (s === undefined) {
s = "0";
}
let startPoint = new TimePoint(+h, +m, +s);
i += 1;
if (words[i] != "to") {
throw new Error(`解析时间点是期待出现一个\'to\',但出现的是${words[i]}`);
}
i += 1;
if (i >= words.length) {
throw new Error("期待解析出to后的时间点,但是单词已结束");
}
if (timePointReg.test(words[i])) {
// 解析end 时间点
let [h, m, s] = words[i].split(":");
if (s === undefined) {
s = "0";
}
let endPoint = new TimePoint(+h, +m, +s);
ranges.push([startPoint, endPoint]);
i += 1;
}
else {
throw new Error(`期待解析出to后的时间点,但是遇到的是${words[i]}`);
}
}
else if (words[i] == "and" ||
words[i] == "," ||
words[i] == "," ||
words[i] == "from") {
i += 1;
continue;
}
else if (words[i] == "on" || words[i] in day2num) {
break;
}
else {
throw Error(`解析时间点范围时遇到无法理解的词${words[i]}`);
}
}
return [i, ranges];
}
/**
* 从单词列表解析出天极时间列表
* @param words 单词列表
* @param i 解析过程的起始下标
* @returns 解析结束时的下标和天极时间列表组成二元列表
*/
function parseDayRangeFromWords(words, i) {
let days = [];
while (i < words.length) {
if (words[i] === "and" ||
words[i] === "on" ||
words[i] === "from" ||
words[i] === ",") {
i += 1;
continue;
}
if (words[i] in day2num) {
let day = day2num[words[i]];
if (i + 1 < words.length && words[i + 1] === "to") {
days.splice(days.length, 0, ...dayRange(words[i], words[i + 2]));
i += 3;
}
else {
days.push(day);
i += 1;
}
}
else if (timePointReg.test(words[i])) {
break;
}
else {
throw new Error(`解析天级时间范围时遇到不能理解的词:${words[i]}`);
}
}
return [i, days];
}
export { createRuleBuilder, parseFrom, RuleBuilder, };