UNPKG

timed-silky

Version:

Timed-Silky 是基于一款基于 TypeScript 的定时任务调度器。得益于 TypeScript,Timed-Silky 可以用符合特定句式的自然语言描述任务的调度规则,提供丝滑的链式调用接口。

354 lines (353 loc) 11.6 kB
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, };