Skip to content

Commit

Permalink
feat: add no specific value #25
Browse files Browse the repository at this point in the history
  • Loading branch information
abichinger committed Nov 4, 2023
1 parent 7b09c1e commit 3beb67f
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 46 deletions.
32 changes: 17 additions & 15 deletions core/src/components/__tests__/cron-core.spec.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,29 @@
import { describe, expect, it } from 'vitest';
import { describe, expect, it } from 'vitest'

import { useCron, type CronFormat } from '../cron-core';
import { useCron, type CronFormat } from '../cron-core'

type UseCronReturn = ReturnType<typeof useCron>

function cronToString({selected: { value: selected }, period, }:UseCronReturn): string {
const fields = selected.map((seg) => {
const prefix = seg.prefix.value ? seg.prefix.value+' ' : '';
const suffix = seg.suffix.value ? ' '+seg.suffix.value : '';
return prefix+seg.text.value+suffix
}).join(' ')
function cronToString({ selected: { value: selected }, period }: UseCronReturn): string {
const fields = selected
.map((seg) => {
const prefix = seg.prefix.value ? seg.prefix.value + ' ' : ''
const suffix = seg.suffix.value ? ' ' + seg.suffix.value : ''
return prefix + seg.text.value + suffix
})
.join(' ')

const prefix = period.prefix.value ? period.prefix.value+' ' : '';
const suffix = period.suffix.value ? ' '+period.suffix.value : '';
const prefix = period.prefix.value ? period.prefix.value + ' ' : ''
const suffix = period.suffix.value ? ' ' + period.suffix.value : ''
return `${prefix}${period.selected.value.text}${suffix} ${fields}`
}

describe('useCron', () => {
it('renders properly', () => {
const tests: {
format: CronFormat,
value: string,
expected: string,
format: CronFormat
value: string
expected: string
}[] = [
{
format: 'crontab',
Expand All @@ -35,8 +37,8 @@ describe('useCron', () => {
}
]

for(const t of tests) {
const cron = useCron({format: t.format, initialValue: t.value})
for (const t of tests) {
const cron = useCron({ format: t.format, initialValue: t.value })
expect(cronToString(cron)).toEqual(t.expected)
}
})
Expand Down
67 changes: 58 additions & 9 deletions core/src/components/cron-core.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { AnySegment, EverySegment, NoSpecificSegment, RangeSegment, ValueSegment } from '@/cron'
import type { Localization } from '@/locale/types'
import { computed, defineComponent, ref, watch, type PropType } from 'vue'
import { getLocale } from '../locale'
import { FieldWrapper, TextPosition, type Field, type Period } from '../types'
import { defaultItems } from '../util'
import { useCronSegment } from './cron-segment'
import { useCronSegment, type UseCronSegmentReturn } from './cron-segment'

export type CronFormat = 'crontab' | 'quartz'

interface CronOptions {
export interface CronOptions {
initialValue?: string
locale?: string
fields?: Field[]
Expand All @@ -16,6 +17,10 @@ interface CronOptions {
format?: CronFormat
}

export interface CronContext {
segmentMap: Map<string, UseCronSegmentReturn>
}

function createCron(len: number, seg: string = '*') {
return new Array(len).fill(seg).join(' ')
}
Expand All @@ -33,24 +38,65 @@ class DefaultCronOptions {
return createCron(len, seg)
}

fields(format: CronFormat, locale: string) {
fields(format: CronFormat, locale: string): Field[] {
const isQuartz = format == 'quartz'
const items = defaultItems(locale)

const setNoSpecific = (fieldId: string) => {
return (value: UseCronSegmentReturn, { segmentMap }: CronContext) => {
if (value.cron.value == '?') {
return
}

const segment = segmentMap.get(fieldId)
if (!segment) {
return
}
segment.cron.value = '?'
}
}

return [
...(format == 'quartz' ? [{ id: 'second', items: items.secondItems }] : []),
...(isQuartz ? [{ id: 'second', items: items.secondItems }] : []),
{ id: 'minute', items: items.minuteItems },
{ id: 'hour', items: items.hourItems },
{ id: 'day', items: items.dayItems },
{
id: 'day',
items: items.dayItems,
onChange: isQuartz ? setNoSpecific('dayOfWeek') : undefined,
segmentFactories: isQuartz
? [
AnySegment.fromString,
NoSpecificSegment.fromString,
EverySegment.fromString,
RangeSegment.fromString,
ValueSegment.fromString
]
: undefined
},
{ id: 'month', items: items.monthItems },
{ id: 'dayOfWeek', items: items.dayOfWeekItems }
{
id: 'dayOfWeek',
items: items.dayOfWeekItems,
onChange: isQuartz ? setNoSpecific('day') : undefined,
segmentFactories: isQuartz
? [
AnySegment.fromString,
NoSpecificSegment.fromString,
EverySegment.fromString,
RangeSegment.fromString,
ValueSegment.fromString
]
: undefined
}
]
}

periods(format: CronFormat) {
const isQuartz = format == 'quartz';
const isQuartz = format == 'quartz'
const second = isQuartz ? [{ id: 'q-second', value: [] }] : []
const secondField = isQuartz ? ['second'] : []
const prefix = isQuartz ? 'q-' : '';
const prefix = isQuartz ? 'q-' : ''

return [
...second,
Expand Down Expand Up @@ -143,7 +189,10 @@ export function useCron(options: CronOptions) {
})

segments.forEach((s) => {
watch(s.cron, toCron)
watch(s.cron, () => {
s.onChange?.(s, { segmentMap })
toCron()
})
watch(s.error, (value) => {
error.value = value
})
Expand Down
11 changes: 9 additions & 2 deletions core/src/components/cron-segment.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { CombinedSegment, arrayToSegment, cronToSegment, type CronSegment } from '@/cron'
import { CombinedSegment, arrayToSegment, cronToSegment } from '@/cron'
import type { Locale } from '@/locale'
import { TextPosition, type FieldWrapper, type Period } from '@/types'
import { TextPosition, type CronSegment, type FieldWrapper, type Period } from '@/types'
import { ref, watch, type Ref } from 'vue'

export interface FieldOptions {
Expand Down Expand Up @@ -48,6 +48,10 @@ export function useCronSegment(options: FieldOptions) {
}

const toCron = (value: number[]) => {
if (cron.value == '?' && value.length == 0) {
return
}

const seg = arrayToSegment(value, field)
if (seg != null) {
cron.value = seg.toCron()
Expand Down Expand Up @@ -84,6 +88,7 @@ export function useCronSegment(options: FieldOptions) {
return {
id: field.id,
items: field.items,
onChange: field.onChange,
cron,
selected,
error,
Expand All @@ -93,3 +98,5 @@ export function useCronSegment(options: FieldOptions) {
suffix
}
}

export type UseCronSegmentReturn = ReturnType<typeof useCronSegment>
39 changes: 29 additions & 10 deletions core/src/cron.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,32 @@
import { CronType, FieldWrapper, type FieldItem } from './types'
import { CronType, FieldWrapper, type CronSegment, type SegmentFromString } from './types'
import { isSquence, range, unimplemented } from './util'

type SegmentFromArray = (arr: number[], field: FieldWrapper) => CronSegment | null
type SegmentFromString = (str: string, field: FieldWrapper) => CronSegment | null

export interface CronSegment {
class NoSpecificSegment implements CronSegment {
field: FieldWrapper
type: CronType
toCron: () => string
toArray: () => number[]
items: Record<string, FieldItem>
type: CronType = CronType.NoSpecific

constructor(field: FieldWrapper) {
this.field = field
}

toCron() {
return '?'
}

toArray() {
return []
}

get items() {
return {}
}

static fromString(str: string, field: FieldWrapper) {
if (str !== '?') {
return null
}
return new NoSpecificSegment(field)
}
}

class AnySegment implements CronSegment {
Expand Down Expand Up @@ -284,6 +301,7 @@ class CombinedSegment implements CronSegment {
}

static fromString(str: string, field: FieldWrapper) {
const factories = field.segmentFactories ?? CombinedSegment.segmentFactories
let segments: CronSegment[] = []
for (const strSeg of str.split(',')) {
if (strSeg === '*') {
Expand All @@ -292,7 +310,7 @@ class CombinedSegment implements CronSegment {
}

let segment = null
for (const fromString of CombinedSegment.segmentFactories) {
for (const fromString of factories) {
segment = fromString(strSeg, field)
if (segment !== null) {
break
Expand Down Expand Up @@ -358,6 +376,7 @@ export {
AnySegment,
CombinedSegment,
EverySegment,
NoSpecificSegment,
RangeSegment,
ValueSegment,
arrayToSegment,
Expand Down
16 changes: 12 additions & 4 deletions core/src/locale/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ const locale: Localization = {
everyX: {
prefix: '',
text: 'alle {{every.value}} Tage'
},
noSpecific: {
prefix: 'an',
text: 'keinem bestimmten Tag'
}
},
dayOfWeek: {
Expand All @@ -37,7 +41,11 @@ const locale: Localization = {
text: 'jedem Wochentag'
},
value: { text: '{{value.alt}}' },
range: { text: '{{start.alt}}-{{end.alt}}' }
range: { text: '{{start.alt}}-{{end.alt}}' },
noSpecific: {
prefix: 'und',
text: 'keinem bestimmten Wochentag'
}
},
hour: {
'*': { prefix: 'um' },
Expand Down Expand Up @@ -104,8 +112,8 @@ const locale: Localization = {
text: 'Minute',
second: {
'*': {
prefix: 'und',
},
prefix: 'und'
}
}
},
'q-hour': {
Expand All @@ -120,7 +128,7 @@ const locale: Localization = {
prefix: 'und'
}
}
},
}
}

export default locale
12 changes: 9 additions & 3 deletions core/src/locale/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,19 @@ const locale: Localization = {
range: { text: '{{start.alt}}-{{end.alt}}' }
},
day: {
'*': { prefix: 'on' }
'*': { prefix: 'on' },
noSpecific: {
text: 'no specific day'
}
},
dayOfWeek: {
'*': { prefix: 'on' },
empty: { text: 'every day of the week' },
value: { text: '{{value.alt}}' },
range: { text: '{{start.alt}}-{{end.alt}}' }
range: { text: '{{start.alt}}-{{end.alt}}' },
noSpecific: {
text: 'no specific day of the week'
}
},
hour: {
'*': { prefix: 'at' }
Expand Down Expand Up @@ -88,7 +94,7 @@ const locale: Localization = {
prefix: 'at'
}
}
},
}
}

export default locale
25 changes: 24 additions & 1 deletion core/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,24 @@
import type { CronContext } from './components/cron-core'
import type { UseCronSegmentReturn } from './components/cron-segment'

export interface CronSegment {
field: FieldWrapper
type: CronType
toCron: () => string
toArray: () => number[]
items: Record<string, FieldItem>
}

export type SegmentFromArray = (arr: number[], field: FieldWrapper) => CronSegment | null
export type SegmentFromString = (str: string, field: FieldWrapper) => CronSegment | null

export enum CronType {
Empty = 'empty',
Value = 'value',
Range = 'range',
EveryX = 'everyX',
Combined = 'combined'
Combined = 'combined',
NoSpecific = 'noSpecific'
}

export enum TextPosition {
Expand All @@ -21,6 +36,8 @@ export interface FieldItem {
export interface Field {
id: string
items: FieldItem[]
onChange?: (segment: UseCronSegmentReturn, ctx: CronContext) => void
segmentFactories?: SegmentFromString[]
}

export interface Period {
Expand Down Expand Up @@ -50,6 +67,12 @@ export class FieldWrapper {
get items() {
return this.field.items
}
get onChange() {
return this.field.onChange
}
get segmentFactories() {
return this.field.segmentFactories
}

get min() {
return this.items[0].value
Expand Down
Loading

0 comments on commit 3beb67f

Please sign in to comment.