UNPKG

@vue-js-cron/core

Version:
1 lines 150 kB
{"version":3,"file":"core.umd.cjs","sources":["../src/types.ts","../src/util.ts","../src/cron.ts","../../node_modules/mustache/mustache.mjs","../src/locale/index.ts","../src/locale/en.ts","../src/locale/de.ts","../src/locale/pt.ts","../src/locale/es.ts","../src/locale/da.ts","../src/locale/cn.ts","../src/locale/he.ts","../src/locale/ru.ts","../src/locale/fr.ts","../src/locale/hi.ts","../src/locale/ja.ts","../src/locale/ko.ts","../src/locale/it.ts","../src/components/cron-segment.ts","../src/components/cron-core.ts","../src/components/select.ts","../src/index.ts"],"sourcesContent":["import type { CronContext } from './components/cron-core'\nimport type { UseCronSegmentReturn } from './components/cron-segment'\n\nexport type CronFormat = 'crontab' | 'quartz' | 'spring'\n\nexport interface CronSegment {\n field: FieldWrapper\n type: FieldPattern\n toCron: () => string\n toArray: () => number[]\n items: Record<string, FieldItem>\n}\n\nexport type SegmentFromArray = (arr: number[], field: FieldWrapper) => CronSegment | null\nexport type SegmentFromString = (str: string, field: FieldWrapper) => CronSegment | null\n\nexport enum FieldPattern {\n Any = 'any', // *\n Value = 'value', // a\n Range = 'range', // a-b\n Step = 'step', // */x\n StepFrom = `stepFrom`, // a/x\n RangeStep = 'rangeStep', // a-b/x\n Combined = 'combined',\n NoSpecific = 'noSpecific', // ?\n}\n\nexport enum TextPosition {\n Prefix = 'prefix',\n Suffix = 'suffix',\n Text = 'text',\n}\n\nexport interface FieldItem {\n value: number\n text: string\n alt: string\n}\n\nexport interface Field {\n id: string\n items: FieldItem[]\n onChange?: (segment: UseCronSegmentReturn, ctx: CronContext) => void\n segmentFactories?: SegmentFromString[]\n default?: string\n}\n\nexport interface Period {\n /**\n * The id of the period\n */\n id: string\n\n /**\n * The value determines which fields are visible\n */\n value: string[]\n\n /**\n * The display name of the period\n */\n text?: string\n}\n\ninterface FieldContext {\n format: CronFormat\n}\nexport class FieldWrapper {\n field: Field\n itemMap: Record<number, FieldItem>\n ctx: FieldContext\n\n constructor(field: Field, ctx: FieldContext) {\n this.field = field\n this.ctx = ctx\n\n this.itemMap = this.field.items.reduce(\n (acc, item) => {\n acc[item.value] = item\n return acc\n },\n {} as Record<number, FieldItem>,\n )\n }\n\n get id() {\n return this.field.id\n }\n get items() {\n return this.field.items\n }\n get onChange() {\n return this.field.onChange\n }\n get segmentFactories() {\n return this.field.segmentFactories\n }\n\n get min() {\n return this.items[0].value\n }\n\n get max() {\n return this.items[this.items.length - 1].value\n }\n\n getItem(value: number) {\n return this.itemMap[value]\n }\n}\n","import type { CronFormat, FieldItem } from './types'\n\nfunction range(start: number, end: number, step = 1) {\n const r = []\n for (let i = start; i <= end; i += step) {\n r.push(i)\n }\n return r\n}\n\nclass Range {\n [name: string]: number\n start: number\n end: number\n step: number\n\n constructor(start: number, end: number, step = 1) {\n this.start = start\n this.end = end\n this.step = step\n\n return new Proxy(this, {\n get: function (target, prop) {\n const i = typeof prop === 'string' ? parseInt(prop) : prop\n if (typeof i === 'number' && i >= 0 && i <= target.length) {\n return target.start + target.step * i\n }\n return Reflect.get(target, prop)\n },\n })\n }\n\n get length() {\n return (this.end - this.start) / this.step + 1\n }\n\n [Symbol.iterator]() {\n let index = -1\n return {\n next: () => {\n return { value: this[++index], done: !(this[index + 1] !== undefined) }\n },\n }\n }\n}\n\nexport type toText = (value: number) => string\n\n/**\n * generate items for fields\n * @param min - first value\n * @param max - last value\n * @param genText - returns a string representation of value\n * @param genAltText - returns an alternative string representation of value\n * @returns array of items\n */\nfunction genItems(\n min: number,\n max: number,\n genText: toText = (value) => {\n return value + ''\n },\n genAltText: toText = (value) => {\n return value + ''\n },\n): FieldItem[] {\n const res = []\n for (const i of new Range(min, max)) {\n res.push({\n text: genText(i),\n alt: genAltText(i),\n value: i,\n })\n }\n return res\n}\n\n/**\n *\n * @param locale - locale code, e.g.: en, en-GB de-DE\n * @param [format='crontab'] format of cron expression\n * @returns items for minute, hour, day, month and day of week\n */\nfunction defaultItems(localeCode: string, format: CronFormat = 'crontab') {\n const monthName = (month: number, short: boolean = false) => {\n return new Date(2021, month - 1, 1).toLocaleDateString(localeCode, {\n month: short ? 'short' : 'long',\n })\n }\n\n const weekdayName = (weekday: number, short: boolean = false) => {\n // if weekday is 0, this is the first sunday in 2021\n return new Date(2021, 0, 3 + weekday).toLocaleDateString(localeCode, {\n weekday: short ? 'short' : 'long',\n })\n }\n\n return {\n secondItems: genItems(0, 59, (value) => pad(value, 2)),\n minuteItems: genItems(0, 59, (value) => pad(value, 2)),\n hourItems: genItems(0, 23, (value) => pad(value, 2)),\n dayItems: genItems(1, 31),\n monthItems: genItems(1, 12, monthName, (value) => monthName(value, true)),\n dayOfWeekItems:\n format === 'quartz'\n ? genItems(\n 1,\n 7,\n (value) => weekdayName(value - 1),\n (value) => weekdayName(value - 1, true),\n )\n : genItems(0, 6, weekdayName, (value) => weekdayName(value, true)),\n }\n}\n\n/**\n * pads numbers\n * @param n - number to pad\n * @param width - length of final string\n * @example\n * ```\n * //returns \"001\"\n * util.pad(1,3)\n * ```\n * @returns the padded number\n */\nfunction pad(n: number, width: number) {\n const s = n + ''\n return s.length < width ? new Array(width - s.length).fill('0').join('') + n : s\n}\n\n/**\n * determines whether the passed value is an object\n * @returns true if value is an object, otherwise false\n */\nfunction isObject(value: any): value is { [key: string]: any } {\n return value && typeof value === 'object' && !Array.isArray(value)\n}\n\n/**\n * copies (deep copy) all properties from each source to target\n */\nfunction deepMerge(target: Object, ...sources: { [key: string]: any }[]) {\n if (!isObject(target) || sources.length === 0) return\n const source = sources.shift()\n\n if (isObject(source)) {\n for (const [key, value] of Object.entries(source)) {\n if (isObject(value)) {\n if (!isObject(target[key])) {\n target[key] = {}\n }\n deepMerge(target[key], source[key])\n } else {\n target[key] = source[key]\n }\n }\n }\n\n if (sources.length > 0) deepMerge(target, ...sources)\n return target\n}\n\nfunction traverse(obj: { [key: string]: any }, ...keys: string[][]): any {\n if (keys.length === 0) {\n return obj\n }\n\n for (const key of keys[0]) {\n if (key in obj) {\n const res = traverse(obj[key], ...keys.slice(1))\n if (res !== undefined) {\n return res\n }\n }\n }\n}\n\nfunction isSquence(numbers: number[]) {\n for (let i = 1; i < numbers.length; i++) {\n if (numbers[i - 1] + 1 !== numbers[i]) {\n return false\n }\n }\n return true\n}\n\nfunction unimplemented(): never {\n throw new Error('not implemented')\n}\n\nfunction splitArray<T>(arr: T[], chunkSize: number, fill: boolean = true): (T | null)[][] {\n const res = []\n\n for (let i = 0; i < arr.length; i += chunkSize) {\n const slice: (T | null)[] = arr.slice(i, i + chunkSize)\n while (fill && slice.length < chunkSize) {\n slice.push(null)\n }\n res.push(slice)\n }\n\n return res\n}\n\nexport {\n deepMerge,\n defaultItems,\n genItems,\n isObject,\n isSquence,\n pad,\n Range,\n range,\n splitArray,\n traverse,\n unimplemented,\n}\n","import { FieldPattern, FieldWrapper, type CronSegment, type SegmentFromString } from './types'\nimport { isSquence, range, unimplemented } from './util'\n\nclass NoSpecificSegment implements CronSegment {\n field: FieldWrapper\n type: FieldPattern = FieldPattern.NoSpecific\n\n constructor(field: FieldWrapper) {\n this.field = field\n }\n\n toCron() {\n return '?'\n }\n\n toArray() {\n return []\n }\n\n get items() {\n return {}\n }\n\n static fromString(str: string, field: FieldWrapper) {\n if (str !== '?') {\n return null\n }\n return new NoSpecificSegment(field)\n }\n}\n\nclass AnySegment implements CronSegment {\n field: FieldWrapper\n type: FieldPattern = FieldPattern.Any\n\n constructor(field: FieldWrapper) {\n this.field = field\n }\n\n toCron() {\n return '*'\n }\n\n toArray() {\n return []\n }\n\n get items() {\n return {}\n }\n\n static fromString(str: string, field: FieldWrapper) {\n if (str !== '*') {\n return null\n }\n return new AnySegment(field)\n }\n\n static fromArray(arr: number[], field: FieldWrapper) {\n const { items } = field\n\n if (arr.length === 0) {\n return new AnySegment(field)\n }\n if (arr.length !== items.length) {\n return null\n }\n\n for (const item of items) {\n if (!arr.includes(item.value)) {\n return null\n }\n }\n if (!isSquence(items.map((item) => item.value))) {\n return null\n }\n return new AnySegment(field)\n }\n}\n\nclass RangeSegment implements CronSegment {\n static re = /^\\d+-\\d+$/\n\n field: FieldWrapper\n type: FieldPattern = FieldPattern.Range\n start: number\n end: number\n\n constructor(field: FieldWrapper, start: number, end: number) {\n this.field = field\n this.start = start\n this.end = end\n }\n\n toCron() {\n return `${this.start}-${this.end}`\n }\n\n toArray() {\n const start = this.start\n const end = this.end\n\n return range(start, end)\n }\n\n get items() {\n return {\n start: this.field.itemMap[this.start],\n end: this.field.itemMap[this.end],\n }\n }\n\n static fromString(str: string, field: FieldWrapper) {\n if (!RangeSegment.re.test(str)) {\n return null\n }\n\n const { min, max } = field\n const range = str.split('-')\n const start = parseInt(range[0])\n const end = parseInt(range[1])\n\n if (start > end || start < min || end > max) {\n return null\n }\n\n return new RangeSegment(field, start, end)\n }\n}\n\nfunction _rangeWithStep(n: number, min: number, max: number) {\n const res = []\n for (let i = min; i <= max; i += n) {\n res.push(i)\n }\n return res\n}\n\nfunction parseRange(s: string, field: FieldWrapper): [number, number] {\n if (s === '*') {\n return [field.min, field.max]\n }\n const range = s.split('-').map((s) => parseInt(s))\n if (range.length === 1 && field.ctx.format !== 'crontab') {\n return [range[0], field.max]\n }\n return [range[0], range[1]]\n}\n\nclass StepSegment implements CronSegment {\n static re = /^(\\*|\\d+(-\\d+)?)\\/\\d+$/\n\n field: FieldWrapper\n step: number\n start: number\n end: number\n\n constructor(field: FieldWrapper, step: number, start?: number, end?: number) {\n this.field = field\n this.step = step\n this.start = start ?? field.min\n this.end = end ?? field.max\n }\n\n get type() {\n const { min, max } = this.field\n if (this.field.ctx.format !== 'crontab' && this.start !== min && max - this.end < this.step) {\n return FieldPattern.StepFrom\n }\n if (this.start !== min || max - this.end >= this.step) {\n return FieldPattern.RangeStep\n }\n return FieldPattern.Step\n }\n\n toCron() {\n if (this.type === FieldPattern.StepFrom) {\n return `${this.start}/${this.step}`\n }\n if (this.type === FieldPattern.RangeStep) {\n return `${this.start}-${this.end}/${this.step}`\n }\n return `*/${this.step}`\n }\n\n toArray() {\n return _rangeWithStep(this.step, this.start, this.end)\n }\n\n get items() {\n return {\n step: this.field.itemMap[this.step],\n start: this.field.itemMap[this.start],\n end: this.field.itemMap[this.end],\n }\n }\n\n static fromString(str: string, field: FieldWrapper) {\n if (!StepSegment.re.test(str)) {\n return null\n }\n\n const [rangeStr, stepStr] = str.split('/')\n const step = parseInt(stepStr)\n\n if (step > field.items.length) {\n return null\n }\n\n const [min, max] = parseRange(rangeStr, field)\n\n if (_rangeWithStep(step, min, max).length == 0) {\n return null\n }\n\n return new StepSegment(field, step, min, max)\n }\n\n static fromArray(arr: number[], field: FieldWrapper) {\n if (arr.length < 3) {\n return null\n }\n\n const step = arr[1] - arr[0]\n if (step <= 1) {\n return null\n }\n\n for (let i = 2; i < arr.length; i++) {\n if (arr[i] - arr[i - 1] != step) {\n return null\n }\n }\n\n return new StepSegment(field, step, arr[0], arr[arr.length - 1])\n }\n}\n\nclass ValueSegment implements CronSegment {\n field: FieldWrapper\n type: FieldPattern = FieldPattern.Value\n value: number\n\n constructor(field: FieldWrapper, value: number) {\n this.field = field\n this.value = value\n }\n\n toCron() {\n return `${this.value}`\n }\n\n toArray() {\n return [this.value]\n }\n\n get items() {\n return {\n value: this.field.itemMap[this.value],\n }\n }\n\n static fromString(str: string, field: FieldWrapper) {\n const { min, max } = field\n const value = parseInt(str)\n return String(value) === str && value >= min && value <= max\n ? new ValueSegment(field, value)\n : null\n }\n\n static fromArray(arr: number[], field: FieldWrapper) {\n const { min, max } = field\n\n if (arr.length != 1) {\n return null\n }\n\n const value = arr[0]\n if (value < min || value > max) {\n return null\n }\n\n return value\n }\n}\n\nclass CombinedSegment implements CronSegment {\n static segmentFactories: SegmentFromString[] = [\n AnySegment.fromString,\n StepSegment.fromString,\n RangeSegment.fromString,\n ValueSegment.fromString,\n ]\n\n field: FieldWrapper\n segments: CronSegment[]\n\n constructor(field: FieldWrapper, segments: CronSegment[] = []) {\n this.field = field\n this.segments = segments\n }\n\n get type() {\n if (this.segments.length == 1) {\n return this.segments[0].type\n }\n return FieldPattern.Range\n }\n\n addSegment(segment: CronSegment) {\n this.segments.push(segment)\n }\n\n toCron() {\n return this.segments.map((c) => c.toCron()).join(',')\n }\n\n toArray() {\n const values = new Set<number>()\n for (const seg of this.segments) {\n seg.toArray().forEach((value) => values.add(value))\n }\n return Array.from(values)\n }\n\n get items() {\n return unimplemented()\n }\n\n static fromString(str: string, field: FieldWrapper) {\n const factories = field.segmentFactories ?? CombinedSegment.segmentFactories\n let segments: CronSegment[] = []\n for (const strSeg of str.split(',')) {\n if (strSeg === '*') {\n segments = [new AnySegment(field)]\n break\n }\n\n let segment = null\n for (const fromString of factories) {\n segment = fromString(strSeg, field)\n if (segment !== null) {\n break\n }\n }\n if (segment === null) {\n return null\n }\n segments.push(segment)\n }\n return new CombinedSegment(field, segments)\n }\n\n static fromArray(arr: number[], field: FieldWrapper) {\n const { min, max } = field\n\n const minValue = arr[0]\n const maxValue = arr[arr.length - 1]\n\n if (minValue < min) {\n return null\n }\n if (maxValue > max) {\n return null\n }\n\n const ranges: CronSegment[] = []\n let start = 0\n for (let i = 0; i < arr.length; i++) {\n if (arr[i + 1] === undefined || arr[i + 1] - arr[i] > 1) {\n if (i === start) {\n ranges.push(new ValueSegment(field, arr[start]))\n } else {\n ranges.push(new RangeSegment(field, arr[start], arr[i]))\n }\n start = i + 1\n }\n }\n\n return new CombinedSegment(field, ranges)\n }\n}\n\nfunction cronToSegment(cron: string, field: FieldWrapper) {\n return CombinedSegment.fromString(cron, field)\n}\n\nfunction arrayToSegment(arr: number[], field: FieldWrapper) {\n for (const fromArray of [\n AnySegment.fromArray,\n StepSegment.fromArray,\n CombinedSegment.fromArray,\n ]) {\n const seg = fromArray(arr, field)\n if (seg != null) {\n return seg\n }\n }\n return null\n}\n\nexport {\n AnySegment,\n arrayToSegment,\n CombinedSegment,\n cronToSegment,\n NoSpecificSegment,\n RangeSegment,\n StepSegment,\n ValueSegment,\n}\n","/*!\n * mustache.js - Logic-less {{mustache}} templates with JavaScript\n * http://github.com/janl/mustache.js\n */\n\nvar objectToString = Object.prototype.toString;\nvar isArray = Array.isArray || function isArrayPolyfill (object) {\n return objectToString.call(object) === '[object Array]';\n};\n\nfunction isFunction (object) {\n return typeof object === 'function';\n}\n\n/**\n * More correct typeof string handling array\n * which normally returns typeof 'object'\n */\nfunction typeStr (obj) {\n return isArray(obj) ? 'array' : typeof obj;\n}\n\nfunction escapeRegExp (string) {\n return string.replace(/[\\-\\[\\]{}()*+?.,\\\\\\^$|#\\s]/g, '\\\\$&');\n}\n\n/**\n * Null safe way of checking whether or not an object,\n * including its prototype, has a given property\n */\nfunction hasProperty (obj, propName) {\n return obj != null && typeof obj === 'object' && (propName in obj);\n}\n\n/**\n * Safe way of detecting whether or not the given thing is a primitive and\n * whether it has the given property\n */\nfunction primitiveHasOwnProperty (primitive, propName) {\n return (\n primitive != null\n && typeof primitive !== 'object'\n && primitive.hasOwnProperty\n && primitive.hasOwnProperty(propName)\n );\n}\n\n// Workaround for https://issues.apache.org/jira/browse/COUCHDB-577\n// See https://github.com/janl/mustache.js/issues/189\nvar regExpTest = RegExp.prototype.test;\nfunction testRegExp (re, string) {\n return regExpTest.call(re, string);\n}\n\nvar nonSpaceRe = /\\S/;\nfunction isWhitespace (string) {\n return !testRegExp(nonSpaceRe, string);\n}\n\nvar entityMap = {\n '&': '&amp;',\n '<': '&lt;',\n '>': '&gt;',\n '\"': '&quot;',\n \"'\": '&#39;',\n '/': '&#x2F;',\n '`': '&#x60;',\n '=': '&#x3D;'\n};\n\nfunction escapeHtml (string) {\n return String(string).replace(/[&<>\"'`=\\/]/g, function fromEntityMap (s) {\n return entityMap[s];\n });\n}\n\nvar whiteRe = /\\s*/;\nvar spaceRe = /\\s+/;\nvar equalsRe = /\\s*=/;\nvar curlyRe = /\\s*\\}/;\nvar tagRe = /#|\\^|\\/|>|\\{|&|=|!/;\n\n/**\n * Breaks up the given `template` string into a tree of tokens. If the `tags`\n * argument is given here it must be an array with two string values: the\n * opening and closing tags used in the template (e.g. [ \"<%\", \"%>\" ]). Of\n * course, the default is to use mustaches (i.e. mustache.tags).\n *\n * A token is an array with at least 4 elements. The first element is the\n * mustache symbol that was used inside the tag, e.g. \"#\" or \"&\". If the tag\n * did not contain a symbol (i.e. {{myValue}}) this element is \"name\". For\n * all text that appears outside a symbol this element is \"text\".\n *\n * The second element of a token is its \"value\". For mustache tags this is\n * whatever else was inside the tag besides the opening symbol. For text tokens\n * this is the text itself.\n *\n * The third and fourth elements of the token are the start and end indices,\n * respectively, of the token in the original template.\n *\n * Tokens that are the root node of a subtree contain two more elements: 1) an\n * array of tokens in the subtree and 2) the index in the original template at\n * which the closing tag for that section begins.\n *\n * Tokens for partials also contain two more elements: 1) a string value of\n * indendation prior to that tag and 2) the index of that tag on that line -\n * eg a value of 2 indicates the partial is the third tag on this line.\n */\nfunction parseTemplate (template, tags) {\n if (!template)\n return [];\n var lineHasNonSpace = false;\n var sections = []; // Stack to hold section tokens\n var tokens = []; // Buffer to hold the tokens\n var spaces = []; // Indices of whitespace tokens on the current line\n var hasTag = false; // Is there a {{tag}} on the current line?\n var nonSpace = false; // Is there a non-space char on the current line?\n var indentation = ''; // Tracks indentation for tags that use it\n var tagIndex = 0; // Stores a count of number of tags encountered on a line\n\n // Strips all whitespace tokens array for the current line\n // if there was a {{#tag}} on it and otherwise only space.\n function stripSpace () {\n if (hasTag && !nonSpace) {\n while (spaces.length)\n delete tokens[spaces.pop()];\n } else {\n spaces = [];\n }\n\n hasTag = false;\n nonSpace = false;\n }\n\n var openingTagRe, closingTagRe, closingCurlyRe;\n function compileTags (tagsToCompile) {\n if (typeof tagsToCompile === 'string')\n tagsToCompile = tagsToCompile.split(spaceRe, 2);\n\n if (!isArray(tagsToCompile) || tagsToCompile.length !== 2)\n throw new Error('Invalid tags: ' + tagsToCompile);\n\n openingTagRe = new RegExp(escapeRegExp(tagsToCompile[0]) + '\\\\s*');\n closingTagRe = new RegExp('\\\\s*' + escapeRegExp(tagsToCompile[1]));\n closingCurlyRe = new RegExp('\\\\s*' + escapeRegExp('}' + tagsToCompile[1]));\n }\n\n compileTags(tags || mustache.tags);\n\n var scanner = new Scanner(template);\n\n var start, type, value, chr, token, openSection;\n while (!scanner.eos()) {\n start = scanner.pos;\n\n // Match any text between tags.\n value = scanner.scanUntil(openingTagRe);\n\n if (value) {\n for (var i = 0, valueLength = value.length; i < valueLength; ++i) {\n chr = value.charAt(i);\n\n if (isWhitespace(chr)) {\n spaces.push(tokens.length);\n indentation += chr;\n } else {\n nonSpace = true;\n lineHasNonSpace = true;\n indentation += ' ';\n }\n\n tokens.push([ 'text', chr, start, start + 1 ]);\n start += 1;\n\n // Check for whitespace on the current line.\n if (chr === '\\n') {\n stripSpace();\n indentation = '';\n tagIndex = 0;\n lineHasNonSpace = false;\n }\n }\n }\n\n // Match the opening tag.\n if (!scanner.scan(openingTagRe))\n break;\n\n hasTag = true;\n\n // Get the tag type.\n type = scanner.scan(tagRe) || 'name';\n scanner.scan(whiteRe);\n\n // Get the tag value.\n if (type === '=') {\n value = scanner.scanUntil(equalsRe);\n scanner.scan(equalsRe);\n scanner.scanUntil(closingTagRe);\n } else if (type === '{') {\n value = scanner.scanUntil(closingCurlyRe);\n scanner.scan(curlyRe);\n scanner.scanUntil(closingTagRe);\n type = '&';\n } else {\n value = scanner.scanUntil(closingTagRe);\n }\n\n // Match the closing tag.\n if (!scanner.scan(closingTagRe))\n throw new Error('Unclosed tag at ' + scanner.pos);\n\n if (type == '>') {\n token = [ type, value, start, scanner.pos, indentation, tagIndex, lineHasNonSpace ];\n } else {\n token = [ type, value, start, scanner.pos ];\n }\n tagIndex++;\n tokens.push(token);\n\n if (type === '#' || type === '^') {\n sections.push(token);\n } else if (type === '/') {\n // Check section nesting.\n openSection = sections.pop();\n\n if (!openSection)\n throw new Error('Unopened section \"' + value + '\" at ' + start);\n\n if (openSection[1] !== value)\n throw new Error('Unclosed section \"' + openSection[1] + '\" at ' + start);\n } else if (type === 'name' || type === '{' || type === '&') {\n nonSpace = true;\n } else if (type === '=') {\n // Set the tags for the next time around.\n compileTags(value);\n }\n }\n\n stripSpace();\n\n // Make sure there are no open sections when we're done.\n openSection = sections.pop();\n\n if (openSection)\n throw new Error('Unclosed section \"' + openSection[1] + '\" at ' + scanner.pos);\n\n return nestTokens(squashTokens(tokens));\n}\n\n/**\n * Combines the values of consecutive text tokens in the given `tokens` array\n * to a single token.\n */\nfunction squashTokens (tokens) {\n var squashedTokens = [];\n\n var token, lastToken;\n for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) {\n token = tokens[i];\n\n if (token) {\n if (token[0] === 'text' && lastToken && lastToken[0] === 'text') {\n lastToken[1] += token[1];\n lastToken[3] = token[3];\n } else {\n squashedTokens.push(token);\n lastToken = token;\n }\n }\n }\n\n return squashedTokens;\n}\n\n/**\n * Forms the given array of `tokens` into a nested tree structure where\n * tokens that represent a section have two additional items: 1) an array of\n * all tokens that appear in that section and 2) the index in the original\n * template that represents the end of that section.\n */\nfunction nestTokens (tokens) {\n var nestedTokens = [];\n var collector = nestedTokens;\n var sections = [];\n\n var token, section;\n for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) {\n token = tokens[i];\n\n switch (token[0]) {\n case '#':\n case '^':\n collector.push(token);\n sections.push(token);\n collector = token[4] = [];\n break;\n case '/':\n section = sections.pop();\n section[5] = token[2];\n collector = sections.length > 0 ? sections[sections.length - 1][4] : nestedTokens;\n break;\n default:\n collector.push(token);\n }\n }\n\n return nestedTokens;\n}\n\n/**\n * A simple string scanner that is used by the template parser to find\n * tokens in template strings.\n */\nfunction Scanner (string) {\n this.string = string;\n this.tail = string;\n this.pos = 0;\n}\n\n/**\n * Returns `true` if the tail is empty (end of string).\n */\nScanner.prototype.eos = function eos () {\n return this.tail === '';\n};\n\n/**\n * Tries to match the given regular expression at the current position.\n * Returns the matched text if it can match, the empty string otherwise.\n */\nScanner.prototype.scan = function scan (re) {\n var match = this.tail.match(re);\n\n if (!match || match.index !== 0)\n return '';\n\n var string = match[0];\n\n this.tail = this.tail.substring(string.length);\n this.pos += string.length;\n\n return string;\n};\n\n/**\n * Skips all text until the given regular expression can be matched. Returns\n * the skipped string, which is the entire tail if no match can be made.\n */\nScanner.prototype.scanUntil = function scanUntil (re) {\n var index = this.tail.search(re), match;\n\n switch (index) {\n case -1:\n match = this.tail;\n this.tail = '';\n break;\n case 0:\n match = '';\n break;\n default:\n match = this.tail.substring(0, index);\n this.tail = this.tail.substring(index);\n }\n\n this.pos += match.length;\n\n return match;\n};\n\n/**\n * Represents a rendering context by wrapping a view object and\n * maintaining a reference to the parent context.\n */\nfunction Context (view, parentContext) {\n this.view = view;\n this.cache = { '.': this.view };\n this.parent = parentContext;\n}\n\n/**\n * Creates a new context using the given view with this context\n * as the parent.\n */\nContext.prototype.push = function push (view) {\n return new Context(view, this);\n};\n\n/**\n * Returns the value of the given name in this context, traversing\n * up the context hierarchy if the value is absent in this context's view.\n */\nContext.prototype.lookup = function lookup (name) {\n var cache = this.cache;\n\n var value;\n if (cache.hasOwnProperty(name)) {\n value = cache[name];\n } else {\n var context = this, intermediateValue, names, index, lookupHit = false;\n\n while (context) {\n if (name.indexOf('.') > 0) {\n intermediateValue = context.view;\n names = name.split('.');\n index = 0;\n\n /**\n * Using the dot notion path in `name`, we descend through the\n * nested objects.\n *\n * To be certain that the lookup has been successful, we have to\n * check if the last object in the path actually has the property\n * we are looking for. We store the result in `lookupHit`.\n *\n * This is specially necessary for when the value has been set to\n * `undefined` and we want to avoid looking up parent contexts.\n *\n * In the case where dot notation is used, we consider the lookup\n * to be successful even if the last \"object\" in the path is\n * not actually an object but a primitive (e.g., a string, or an\n * integer), because it is sometimes useful to access a property\n * of an autoboxed primitive, such as the length of a string.\n **/\n while (intermediateValue != null && index < names.length) {\n if (index === names.length - 1)\n lookupHit = (\n hasProperty(intermediateValue, names[index])\n || primitiveHasOwnProperty(intermediateValue, names[index])\n );\n\n intermediateValue = intermediateValue[names[index++]];\n }\n } else {\n intermediateValue = context.view[name];\n\n /**\n * Only checking against `hasProperty`, which always returns `false` if\n * `context.view` is not an object. Deliberately omitting the check\n * against `primitiveHasOwnProperty` if dot notation is not used.\n *\n * Consider this example:\n * ```\n * Mustache.render(\"The length of a football field is {{#length}}{{length}}{{/length}}.\", {length: \"100 yards\"})\n * ```\n *\n * If we were to check also against `primitiveHasOwnProperty`, as we do\n * in the dot notation case, then render call would return:\n *\n * \"The length of a football field is 9.\"\n *\n * rather than the expected:\n *\n * \"The length of a football field is 100 yards.\"\n **/\n lookupHit = hasProperty(context.view, name);\n }\n\n if (lookupHit) {\n value = intermediateValue;\n break;\n }\n\n context = context.parent;\n }\n\n cache[name] = value;\n }\n\n if (isFunction(value))\n value = value.call(this.view);\n\n return value;\n};\n\n/**\n * A Writer knows how to take a stream of tokens and render them to a\n * string, given a context. It also maintains a cache of templates to\n * avoid the need to parse the same template twice.\n */\nfunction Writer () {\n this.templateCache = {\n _cache: {},\n set: function set (key, value) {\n this._cache[key] = value;\n },\n get: function get (key) {\n return this._cache[key];\n },\n clear: function clear () {\n this._cache = {};\n }\n };\n}\n\n/**\n * Clears all cached templates in this writer.\n */\nWriter.prototype.clearCache = function clearCache () {\n if (typeof this.templateCache !== 'undefined') {\n this.templateCache.clear();\n }\n};\n\n/**\n * Parses and caches the given `template` according to the given `tags` or\n * `mustache.tags` if `tags` is omitted, and returns the array of tokens\n * that is generated from the parse.\n */\nWriter.prototype.parse = function parse (template, tags) {\n var cache = this.templateCache;\n var cacheKey = template + ':' + (tags || mustache.tags).join(':');\n var isCacheEnabled = typeof cache !== 'undefined';\n var tokens = isCacheEnabled ? cache.get(cacheKey) : undefined;\n\n if (tokens == undefined) {\n tokens = parseTemplate(template, tags);\n isCacheEnabled && cache.set(cacheKey, tokens);\n }\n return tokens;\n};\n\n/**\n * High-level method that is used to render the given `template` with\n * the given `view`.\n *\n * The optional `partials` argument may be an object that contains the\n * names and templates of partials that are used in the template. It may\n * also be a function that is used to load partial templates on the fly\n * that takes a single argument: the name of the partial.\n *\n * If the optional `config` argument is given here, then it should be an\n * object with a `tags` attribute or an `escape` attribute or both.\n * If an array is passed, then it will be interpreted the same way as\n * a `tags` attribute on a `config` object.\n *\n * The `tags` attribute of a `config` object must be an array with two\n * string values: the opening and closing tags used in the template (e.g.\n * [ \"<%\", \"%>\" ]). The default is to mustache.tags.\n *\n * The `escape` attribute of a `config` object must be a function which\n * accepts a string as input and outputs a safely escaped string.\n * If an `escape` function is not provided, then an HTML-safe string\n * escaping function is used as the default.\n */\nWriter.prototype.render = function render (template, view, partials, config) {\n var tags = this.getConfigTags(config);\n var tokens = this.parse(template, tags);\n var context = (view instanceof Context) ? view : new Context(view, undefined);\n return this.renderTokens(tokens, context, partials, template, config);\n};\n\n/**\n * Low-level method that renders the given array of `tokens` using\n * the given `context` and `partials`.\n *\n * Note: The `originalTemplate` is only ever used to extract the portion\n * of the original template that was contained in a higher-order section.\n * If the template doesn't use higher-order sections, this argument may\n * be omitted.\n */\nWriter.prototype.renderTokens = function renderTokens (tokens, context, partials, originalTemplate, config) {\n var buffer = '';\n\n var token, symbol, value;\n for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) {\n value = undefined;\n token = tokens[i];\n symbol = token[0];\n\n if (symbol === '#') value = this.renderSection(token, context, partials, originalTemplate, config);\n else if (symbol === '^') value = this.renderInverted(token, context, partials, originalTemplate, config);\n else if (symbol === '>') value = this.renderPartial(token, context, partials, config);\n else if (symbol === '&') value = this.unescapedValue(token, context);\n else if (symbol === 'name') value = this.escapedValue(token, context, config);\n else if (symbol === 'text') value = this.rawValue(token);\n\n if (value !== undefined)\n buffer += value;\n }\n\n return buffer;\n};\n\nWriter.prototype.renderSection = function renderSection (token, context, partials, originalTemplate, config) {\n var self = this;\n var buffer = '';\n var value = context.lookup(token[1]);\n\n // This function is used to render an arbitrary template\n // in the current context by higher-order sections.\n function subRender (template) {\n return self.render(template, context, partials, config);\n }\n\n if (!value) return;\n\n if (isArray(value)) {\n for (var j = 0, valueLength = value.length; j < valueLength; ++j) {\n buffer += this.renderTokens(token[4], context.push(value[j]), partials, originalTemplate, config);\n }\n } else if (typeof value === 'object' || typeof value === 'string' || typeof value === 'number') {\n buffer += this.renderTokens(token[4], context.push(value), partials, originalTemplate, config);\n } else if (isFunction(value)) {\n if (typeof originalTemplate !== 'string')\n throw new Error('Cannot use higher-order sections without the original template');\n\n // Extract the portion of the original template that the section contains.\n value = value.call(context.view, originalTemplate.slice(token[3], token[5]), subRender);\n\n if (value != null)\n buffer += value;\n } else {\n buffer += this.renderTokens(token[4], context, partials, originalTemplate, config);\n }\n return buffer;\n};\n\nWriter.prototype.renderInverted = function renderInverted (token, context, partials, originalTemplate, config) {\n var value = context.lookup(token[1]);\n\n // Use JavaScript's definition of falsy. Include empty arrays.\n // See https://github.com/janl/mustache.js/issues/186\n if (!value || (isArray(value) && value.length === 0))\n return this.renderTokens(token[4], context, partials, originalTemplate, config);\n};\n\nWriter.prototype.indentPartial = function indentPartial (partial, indentation, lineHasNonSpace) {\n var filteredIndentation = indentation.replace(/[^ \\t]/g, '');\n var partialByNl = partial.split('\\n');\n for (var i = 0; i < partialByNl.length; i++) {\n if (partialByNl[i].length && (i > 0 || !lineHasNonSpace)) {\n partialByNl[i] = filteredIndentation + partialByNl[i];\n }\n }\n return partialByNl.join('\\n');\n};\n\nWriter.prototype.renderPartial = function renderPartial (token, context, partials, config) {\n if (!partials) return;\n var tags = this.getConfigTags(config);\n\n var value = isFunction(partials) ? partials(token[1]) : partials[token[1]];\n if (value != null) {\n var lineHasNonSpace = token[6];\n var tagIndex = token[5];\n var indentation = token[4];\n var indentedValue = value;\n if (tagIndex == 0 && indentation) {\n indentedValue = this.indentPartial(value, indentation, lineHasNonSpace);\n }\n var tokens = this.parse(indentedValue, tags);\n return this.renderTokens(tokens, context, partials, indentedValue, config);\n }\n};\n\nWriter.prototype.unescapedValue = function unescapedValue (token, context) {\n var value = context.lookup(token[1]);\n if (value != null)\n return value;\n};\n\nWriter.prototype.escapedValue = function escapedValue (token, context, config) {\n var escape = this.getConfigEscape(config) || mustache.escape;\n var value = context.lookup(token[1]);\n if (value != null)\n return (typeof value === 'number' && escape === mustache.escape) ? String(value) : escape(value);\n};\n\nWriter.prototype.rawValue = function rawValue (token) {\n return token[1];\n};\n\nWriter.prototype.getConfigTags = function getConfigTags (config) {\n if (isArray(config)) {\n return config;\n }\n else if (config && typeof config === 'object') {\n return config.tags;\n }\n else {\n return undefined;\n }\n};\n\nWriter.prototype.getConfigEscape = function getConfigEscape (config) {\n if (config && typeof config === 'object' && !isArray(config)) {\n return config.escape;\n }\n else {\n return undefined;\n }\n};\n\nvar mustache = {\n name: 'mustache.js',\n version: '4.2.0',\n tags: [ '{{', '}}' ],\n clearCache: undefined,\n escape: undefined,\n parse: undefined,\n render: undefined,\n Scanner: undefined,\n Context: undefined,\n Writer: undefined,\n /**\n * Allows a user to override the default caching strategy, by providing an\n * object with set, get and clear methods. This can also be used to disable\n * the cache by setting it to the literal `undefined`.\n */\n set templateCache (cache) {\n defaultWriter.templateCache = cache;\n },\n /**\n * Gets the default or overridden caching object from the default writer.\n */\n get templateCache () {\n return defaultWriter.templateCache;\n }\n};\n\n// All high-level mustache.* functions use this writer.\nvar defaultWriter = new Writer();\n\n/**\n * Clears all cached templates in the default writer.\n */\nmustache.clearCache = function clearCache () {\n return defaultWriter.clearCache();\n};\n\n/**\n * Parses and caches the given template in the default writer and returns the\n * array of tokens it contains. Doing this ahead of time avoids the need to\n * parse templates on the fly as they are rendered.\n */\nmustache.parse = function parse (template, tags) {\n return defaultWriter.parse(template, tags);\n};\n\n/**\n * Renders the `template` with the given `view`, `partials`, and `config`\n * using the default writer.\n */\nmustache.render = function render (template, view, partials, config) {\n if (typeof template !== 'string') {\n throw new TypeError('Invalid template! Template should be a \"string\" ' +\n 'but \"' + typeStr(template) + '\" was given as the first ' +\n 'argument for mustache#render(template, view, partials)');\n }\n\n return defaultWriter.render(template, view, partials, config);\n};\n\n// Export the escaping function so that the user may override it.\n// See https://github.com/janl/mustache.js/issues/244\nmustache.escape = escapeHtml;\n\n// Export these mainly for testing, but also for advanced usage.\nmustache.Scanner = Scanner;\nmustache.Context = Context;\nmustache.Writer = Writer;\n\nexport default mustache;\n","import type { FieldPattern, TextPosition } from '@/types'\nimport Mustache from 'mustache'\nimport { deepMerge, traverse } from '../util'\nimport cn from './cn'\nimport da from './da'\nimport de from './de'\nimport en from './en'\nimport es from './es'\nimport fr from './fr'\nimport he from './he'\nimport hi from './hi'\nimport it from './it'\nimport ja from './ja'\nimport ko from './ko'\nimport pt from './pt'\nimport ru from './ru'\nimport type { Localization } from './types'\n\nconst locales: Record<string, Localization> = {\n empty: {},\n en,\n de,\n pt,\n es,\n da,\n zh: cn,\n he,\n ru,\n fr,\n hi,\n ja,\n ko,\n it,\n}\n\nclass L10nEngine {\n dict: Localization\n\n constructor(dict: Localization) {\n this.dict = dict\n }\n\n /**\n * Gets a localization template by traversing the dictionary using provided keys.\n * @param keys - Array of keys to traverse through the localization dictionary\n * @returns The found template string or empty string if not found\n */\n getTemplate(...keys: string[]) {\n const k = keys.map((key) => [key, '*'])\n return traverse(this.dict, ...k) || ''\n }\n\n /**\n * Renders a localization template with the provided parameters using Mustache.\n * @param periodId - The period identifier (e.g. 'year', 'month')\n * @param fieldId - The field identifier (e.g. 'hour', 'minute')\n * @param fieldPattern - The pattern type of the field\n * @param position - The text position\n * @param params - Parameters to be interpolated into the template\n * @returns The rendered localization string\n */\n render(\n periodId: string,\n fieldId: string,\n fieldPattern: FieldPattern,\n position: TextPosition,\n params: any,\n ) {\n const template = this.getTemplate(periodId, fieldId, fieldPattern, position)\n return Mustache.render(template, params || {})\n }\n}\n\n/**\n * Creates a localization engine for the specified locale.\n * @param localeCode - Locale code (e.g. 'en', 'en-GB', 'de-DE')\n * @param mixin - Optional dictionary to override default locale strings\n * @returns A new L10nEngine instance for the specified locale with English as fallback\n */\nfunction createL10n(localeCode: string, mixin?: Localization) {\n const [language] = localeCode.split('-')\n const l = locales[localeCode.toLowerCase()] || locales[language.toLowerCase()] || locales.en\n // Note: always use an empty object as target\n const dict = deepMerge({}, locales.en, l, mixin || {}) as Localization\n return new L10nEngine(dict)\n}\n\nexport { createL10n, L10nEngine }\n\n// The following prompt was used for localizations translated with GPT-4:\n//\n// You are a service that translates user requests into JSON objects of type \"Localizations\" according to the following TypeScript definitions:\n// ```typescript\n// export type PartialRecord<K extends keyof any, T> = Partial<Record<K, T>>\n\n// export type PositionedLocalization = PartialRecord<TextPosition, string>\n// export type CronLocalization = PartialRecord<FieldPattern | '*', PositionedLocalization>\n\n// export interface FieldLocalization {\n// // Allow custom field ids\n// [fieldId: string]: unknown\n\n// /** `*` is used as a wildcard and matches any field id */\n// '*'?: CronLocalization\n// second?: CronLocalization\n// minute?: CronLocalization\n// hour?: CronLocalization\n// day?: CronLocalization\n// month?: CronLocalization\n// dayOfWeek?: CronLocalization\n// }\n\n// export type PeriodLocalization = PositionedLocalization | FieldLocalization\n\n// /**\n// * Interface to define localizations for vue-js-cron\n// * The localization strings are accessed by {periodId}.{fieldId}.{fieldPattern}.{position}\n// */\n// export interface Localization {\n// /** Allows custom period ids */\n// [periodId: string]: unknown\n\n// /** `*` is used as a wildcard and matches any period id */\n// '*'?: PeriodLocalization\n// minute?: PeriodLocalization\n// hour?: PeriodLocalization\n// day?: PeriodLocalization\n// week?: PeriodLocalization\n// month?: PeriodLocalization\n// year?: PeriodLocalization\n// }\n// ```\n// The following is a user request:\n// \"\"\"\n// Translate the following Javascript object from German into Russian:\n// {\n// '*': {\n// prefix: 'Jede',\n// suffix: '',\n// text: 'Unknown',\n// '*': {\n// value: { text: '{{value.text}}' },\n// range: { text: '{{start.text}}-{{end.text}}' },\n// step: { text: 'alle {{step.value}}' },\n// },\n// month: {\n// '*': { prefix: 'im' },\n// any: {\n// prefix: 'in',\n// text: 'jedem Monat',\n// },\n// value: { text: '{{value.alt}}' },\n// range: { text: '{{start.alt}}-{{end.alt}}' },\n// },\n// day: {\n// '*': { prefix: 'den' },\n// any: {\n// prefix: 'an',\n// text: 'jedem Tag',\n// },\n// step: {\n// prefix: '',\n// text: 'alle {{step.value}} Tage',\n// },\n// noSpecific: {\n// prefix: 'an',\n// text: 'keinem bestimmten Tag',\n// },\n// },\n// dayOfWeek: {\n// '*': { prefix: 'am' },\n// any: {\n// prefix: 'an',\n// text: 'jedem Wochentag',\n// },\n// value: { text: '{{value.alt}}' },\n// range: { text: '{{start.alt}}-{{end.alt}}' },\n// noSpecific: {\n// prefix: 'und',\n// text: 'keinem bestimmten Wochentag',\n// },\n// },\n// hour: {\n// '*': { prefix: 'um' },\n// any: {\n// prefix: 'zu',\n// text: 'jeder Stunde',\n// },\n// step: {\n// prefix: '',\n// text: 'alle {{step.value}} Stunden',\n// },\n// },\n// minute: {\n// '*': { prefix: ':' },\n// any: { text: 'jede Minute' },\n// step: {\n// prefix: '',\n// text: 'alle {{step.value}} Minuten',\n// },\n// },\n// second: {\n// '*': { prefix: ':' },\n// any: { text: 'jede Sekunde' },\n// step: {\n// prefix: '',\n// text: 'alle {{step.value}} Sekunden',\n// },\n// },\n// },\n// minute: {\n// text: 'Minute',\n// },\n// hour: {\n// text: 'Stunde',\n// minute: {\n// '*': {\n// prefix: 'zu',\n// suffix: 'Minute(n)',\n// },\n// any: { text: 'jeder' },\n// },\n// },\n// day: {\n// prefix: 'Jeden',\n// text: 'Tag',\n// },\n// week: {\n// text: 'Woche',\n// },\n// month: {\n// prefix: 'Jedes',\n// text: 'Monat',\n// },\n// year: {\n// prefix: 'Jedes',\n// text: 'Jahr',\n// },\n\n// //quartz format\n// 'q-second': {\n// text: 'Sekunde',\n// },\n// 'q-minute': {\n// text: 'Minute',\n// second: {\n// '*': {\n// prefix: 'und',\n// },\n// },\n// },\n// 'q-hour': {\n// text: 'Stunde',\n// minute: {\n// '*': {\n// prefix: 'und',\n// },\n// },\n// second: {\n// '*': {\n// prefix: 'und',\n// },\n// },\n// },\n// }\n// \"\"\"\n// The following is the user request translated into a JSON object with 2 spaces of indentation and no properties with the value undefined:\n","import type { Localization } from './types'\n\nconst locale: Localization = {\n '*': {\n prefix: 'Every',\n suffix: '',\n text: 'Unknown',\n '*': {\n value: { text: '{{value.text}}' },\n range: { text: '{{start.text}}-{{end.text}}' },\n step: { text: 'every {{step.value}}' },\n rangeStep: { text: '{{start.text}}-{{end.text}}/{{step.value}}' },\n stepFrom: { text: '{{start.text}}/{{step.value}}' },\n },\n month: {\n '*': { prefix: 'in' },\n any: { text: 'every month' },\n value: { text: '{{value.alt}}' },\n range: { text: '{{start.alt}}-{{end.alt}}' },\n rangeStep: { text: '{{start.alt}}-{{end.alt}}/{{step.value}}' },\n stepFrom: { text: '{{start.alt}}/{{step.value}}' },\n },\n day: {\n '*': { prefix: 'on' },\n any: { text: 'every day' },\n noSpecific: {\n text: 'no specific day',\n },\n },\n dayOfWeek: {\n '*': { prefix: 'on' },\n any: { text: 'every day of the week' },\n value: { text: '{{value.alt}}' },\n range: { text: '{{start.alt}}-{{end.alt}}' },\n range