From fe41c092f631c108d2078e53900499738c638f9a Mon Sep 17 00:00:00 2001 From: AshAnand34 Date: Mon, 7 Jul 2025 21:27:32 -0700 Subject: [PATCH 1/4] feat: add Crontab Guru tool for parsing and validating crontab expressions --- package-lock.json | 2 + package.json | 2 + src/components/input/ToolTextInput.tsx | 5 +- .../crontab-guru/crontab-guru.service.test.ts | 26 +++++ src/pages/tools/time/crontab-guru/index.tsx | 108 ++++++++++++++++++ src/pages/tools/time/crontab-guru/meta.ts | 24 ++++ src/pages/tools/time/crontab-guru/service.ts | 23 ++++ src/pages/tools/time/crontab-guru/types.ts | 4 + src/pages/tools/time/index.ts | 4 +- 9 files changed, 196 insertions(+), 2 deletions(-) create mode 100644 src/pages/tools/time/crontab-guru/crontab-guru.service.test.ts create mode 100644 src/pages/tools/time/crontab-guru/index.tsx create mode 100644 src/pages/tools/time/crontab-guru/meta.ts create mode 100644 src/pages/tools/time/crontab-guru/service.ts create mode 100644 src/pages/tools/time/crontab-guru/types.ts diff --git a/package-lock.json b/package-lock.json index 173cc4e..4600001 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,8 @@ "browser-image-compression": "^2.0.2", "buffer": "^6.0.3", "color": "^4.2.3", + "cron-validator": "^1.3.1", + "cronstrue": "^3.0.0", "dayjs": "^1.11.13", "formik": "^2.4.6", "jimp": "^0.22.12", diff --git a/package.json b/package.json index ed7ebcf..86276e5 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,8 @@ "browser-image-compression": "^2.0.2", "buffer": "^6.0.3", "color": "^4.2.3", + "cron-validator": "^1.3.1", + "cronstrue": "^3.0.0", "dayjs": "^1.11.13", "formik": "^2.4.6", "jimp": "^0.22.12", diff --git a/src/components/input/ToolTextInput.tsx b/src/components/input/ToolTextInput.tsx index e5741f6..793e0d7 100644 --- a/src/components/input/ToolTextInput.tsx +++ b/src/components/input/ToolTextInput.tsx @@ -7,11 +7,13 @@ import InputFooter from './InputFooter'; export default function ToolTextInput({ value, onChange, - title = 'Input text' + title = 'Input text', + placeholder }: { title?: string; value: string; onChange: (value: string) => void; + placeholder?: string; }) { const { showSnackBar } = useContext(CustomSnackBarContext); const fileInputRef = useRef(null); @@ -50,6 +52,7 @@ export default function ToolTextInput({ fullWidth multiline rows={10} + placeholder={placeholder} sx={{ '&.MuiTextField-root': { backgroundColor: 'background.paper' diff --git a/src/pages/tools/time/crontab-guru/crontab-guru.service.test.ts b/src/pages/tools/time/crontab-guru/crontab-guru.service.test.ts new file mode 100644 index 0000000..bce7fd9 --- /dev/null +++ b/src/pages/tools/time/crontab-guru/crontab-guru.service.test.ts @@ -0,0 +1,26 @@ +import { expect, describe, it } from 'vitest'; +import { validateCrontab, explainCrontab } from './service'; + +describe('crontab-guru service', () => { + it('validates correct crontab expressions', () => { + expect(validateCrontab('35 16 * * 0-5')).toBe(true); + expect(validateCrontab('* * * * *')).toBe(true); + expect(validateCrontab('0 12 1 * *')).toBe(true); + }); + + it('invalidates incorrect crontab expressions', () => { + expect(validateCrontab('invalid expression')).toBe(false); + expect(validateCrontab('61 24 * * *')).toBe(false); + }); + + it('explains valid crontab expressions', () => { + expect(explainCrontab('35 16 * * 0-5')).toMatch(/At 04:35 PM/); + expect(explainCrontab('* * * * *')).toMatch(/Every minute/); + }); + + it('returns error for invalid crontab explanation', () => { + expect(explainCrontab('invalid expression')).toMatch( + /Invalid crontab expression/ + ); + }); +}); diff --git a/src/pages/tools/time/crontab-guru/index.tsx b/src/pages/tools/time/crontab-guru/index.tsx new file mode 100644 index 0000000..4d39ed2 --- /dev/null +++ b/src/pages/tools/time/crontab-guru/index.tsx @@ -0,0 +1,108 @@ +import { Box, Typography, Alert, Button, Stack } from '@mui/material'; +import React, { useState } from 'react'; +import ToolContent from '@components/ToolContent'; +import { ToolComponentProps } from '@tools/defineTool'; +import ToolTextInput from '@components/input/ToolTextInput'; +import ToolTextResult from '@components/result/ToolTextResult'; +import { GetGroupsType } from '@components/options/ToolOptions'; +import { CardExampleType } from '@components/examples/ToolExamples'; +import { main, validateCrontab, explainCrontab } from './service'; +import { InitialValuesType } from './types'; + +const initialValues: InitialValuesType = {}; + +const exampleCards: CardExampleType[] = [ + { + title: 'Every day at 16:35, Sunday to Friday', + description: 'At 16:35 on every day-of-week from Sunday through Friday.', + sampleText: '35 16 * * 0-5', + sampleResult: 'At 04:35 PM, Sunday through Friday', + sampleOptions: {} + }, + { + title: 'Every minute', + description: 'Runs every minute.', + sampleText: '* * * * *', + sampleResult: 'Every minute', + sampleOptions: {} + }, + { + title: 'Every 5 minutes', + description: 'Runs every 5 minutes.', + sampleText: '*/5 * * * *', + sampleResult: 'Every 5 minutes', + sampleOptions: {} + }, + { + title: 'At 12:00 PM on the 1st of every month', + description: 'Runs at noon on the first day of each month.', + sampleText: '0 12 1 * *', + sampleResult: 'At 12:00 PM, on day 1 of the month', + sampleOptions: {} + } +]; + +export default function CrontabGuru({ + title, + longDescription +}: ToolComponentProps) { + const [input, setInput] = useState(''); + const [result, setResult] = useState(''); + const [isValid, setIsValid] = useState(null); + + const compute = (values: InitialValuesType, input: string) => { + setIsValid(validateCrontab(input)); + setResult(main(input, values)); + }; + + const handleExample = (expr: string) => { + setInput(expr); + setIsValid(validateCrontab(expr)); + setResult(main(expr, initialValues)); + }; + + const getGroups: GetGroupsType | null = () => []; + + return ( + + + + {exampleCards.map((ex, i) => ( + + ))} + + + } + resultComponent={ + <> + {isValid === false && ( + Invalid crontab expression. + )} + + + } + initialValues={initialValues} + exampleCards={exampleCards} + getGroups={getGroups} + setInput={setInput} + compute={compute} + toolInfo={{ title: `What is a ${title}?`, description: longDescription }} + /> + ); +} diff --git a/src/pages/tools/time/crontab-guru/meta.ts b/src/pages/tools/time/crontab-guru/meta.ts new file mode 100644 index 0000000..7ae5994 --- /dev/null +++ b/src/pages/tools/time/crontab-guru/meta.ts @@ -0,0 +1,24 @@ +import { defineTool } from '@tools/defineTool'; +import { lazy } from 'react'; + +export const tool = defineTool('time', { + name: 'Crontab Guru', + path: 'crontab-guru', + icon: 'mdi:calendar-clock', + description: + 'Parse, validate, and explain crontab expressions in plain English.', + shortDescription: 'Crontab expression parser and explainer', + keywords: [ + 'crontab', + 'cron', + 'schedule', + 'guru', + 'time', + 'expression', + 'parser', + 'explain' + ], + longDescription: + 'Enter a crontab expression (like "35 16 * * 0-5") to get a human-readable explanation and validation. Useful for understanding and debugging cron schedules. Inspired by crontab.guru.', + component: lazy(() => import('./index')) +}); diff --git a/src/pages/tools/time/crontab-guru/service.ts b/src/pages/tools/time/crontab-guru/service.ts new file mode 100644 index 0000000..1c81ba8 --- /dev/null +++ b/src/pages/tools/time/crontab-guru/service.ts @@ -0,0 +1,23 @@ +import { InitialValuesType } from './types'; +import cronstrue from 'cronstrue'; +import { isValidCron } from 'cron-validator'; + +export function explainCrontab(expr: string): string { + try { + return cronstrue.toString(expr); + } catch (e: any) { + return `Invalid crontab expression: ${e.message}`; + } +} + +export function validateCrontab(expr: string): boolean { + return isValidCron(expr, { seconds: false, allowBlankDay: true }); +} + +export function main(input: string, options: InitialValuesType): string { + if (!input.trim()) return ''; + if (!validateCrontab(input)) { + return 'Invalid crontab expression.'; + } + return explainCrontab(input); +} diff --git a/src/pages/tools/time/crontab-guru/types.ts b/src/pages/tools/time/crontab-guru/types.ts new file mode 100644 index 0000000..54b3d7e --- /dev/null +++ b/src/pages/tools/time/crontab-guru/types.ts @@ -0,0 +1,4 @@ +// Options for crontab-guru tool. Currently empty, but can be extended for advanced features. +export type InitialValuesType = { + // Add future options here +}; diff --git a/src/pages/tools/time/index.ts b/src/pages/tools/time/index.ts index 9b80e65..1e9145d 100644 --- a/src/pages/tools/time/index.ts +++ b/src/pages/tools/time/index.ts @@ -1,3 +1,4 @@ +import { tool as timeCrontabGuru } from './crontab-guru/meta'; import { tool as timeBetweenDates } from './time-between-dates/meta'; import { tool as daysDoHours } from './convert-days-to-hours/meta'; import { tool as hoursToDays } from './convert-hours-to-days/meta'; @@ -11,5 +12,6 @@ export const timeTools = [ convertSecondsToTime, convertTimetoSeconds, truncateClockTime, - timeBetweenDates + timeBetweenDates, + timeCrontabGuru ]; From a613bdb4c54e86541d5ccdec85758a91a6982aa2 Mon Sep 17 00:00:00 2001 From: AshAnand34 Date: Mon, 7 Jul 2025 21:49:02 -0700 Subject: [PATCH 2/4] feat: enhance Crontab Guru tool with interaction tracking and improved validation feedback --- package-lock.json | 15 ++++++ src/pages/tools/time/crontab-guru/index.tsx | 58 ++++++++++++++++++--- 2 files changed, 66 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4600001..5cefa39 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4821,6 +4821,21 @@ "node": ">= 6" } }, + "node_modules/cron-validator": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/cron-validator/-/cron-validator-1.3.1.tgz", + "integrity": "sha512-C1HsxuPCY/5opR55G5/WNzyEGDWFVG+6GLrA+fW/sCTcP6A6NTjUP2AK7B8n2PyFs90kDG2qzwm8LMheADku6A==", + "license": "MIT" + }, + "node_modules/cronstrue": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cronstrue/-/cronstrue-3.0.0.tgz", + "integrity": "sha512-acwNTPzndJUmfDmcUN2cpBH4EgVn30rg5BYDAP8n5ENPP8A3IH2Z0UbxaNjvCkKxccjtfsTVhF6d+eHhv/GK5g==", + "license": "MIT", + "bin": { + "cronstrue": "bin/cli.js" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", diff --git a/src/pages/tools/time/crontab-guru/index.tsx b/src/pages/tools/time/crontab-guru/index.tsx index 4d39ed2..20033c3 100644 --- a/src/pages/tools/time/crontab-guru/index.tsx +++ b/src/pages/tools/time/crontab-guru/index.tsx @@ -49,18 +49,27 @@ export default function CrontabGuru({ const [input, setInput] = useState(''); const [result, setResult] = useState(''); const [isValid, setIsValid] = useState(null); + const [hasInteracted, setHasInteracted] = useState(false); const compute = (values: InitialValuesType, input: string) => { - setIsValid(validateCrontab(input)); + if (hasInteracted) { + setIsValid(validateCrontab(input)); + } setResult(main(input, values)); }; const handleExample = (expr: string) => { setInput(expr); + setHasInteracted(true); setIsValid(validateCrontab(expr)); setResult(main(expr, initialValues)); }; + const handleInputChange = (val: string) => { + if (!hasInteracted) setHasInteracted(true); + setInput(val); + }; + const getGroups: GetGroupsType | null = () => []; return ( @@ -71,7 +80,7 @@ export default function CrontabGuru({ <> @@ -90,12 +99,47 @@ export default function CrontabGuru({ } resultComponent={ - <> - {isValid === false && ( - Invalid crontab expression. +
+ {hasInteracted && isValid === false && ( +
+ + Invalid crontab expression. + +
)} - - +
+ +
+
} initialValues={initialValues} exampleCards={exampleCards} From fb6dd816a1ae7df69482107a867903ac842dfd4a Mon Sep 17 00:00:00 2001 From: AshAnand34 Date: Mon, 7 Jul 2025 22:07:46 -0700 Subject: [PATCH 3/4] refactor: simplify initial values handling and remove unused types in Crontab Guru tool --- src/pages/tools/time/crontab-guru/index.tsx | 5 +++-- src/pages/tools/time/crontab-guru/service.ts | 3 +-- src/pages/tools/time/crontab-guru/types.ts | 4 ---- 3 files changed, 4 insertions(+), 8 deletions(-) delete mode 100644 src/pages/tools/time/crontab-guru/types.ts diff --git a/src/pages/tools/time/crontab-guru/index.tsx b/src/pages/tools/time/crontab-guru/index.tsx index 20033c3..d5785db 100644 --- a/src/pages/tools/time/crontab-guru/index.tsx +++ b/src/pages/tools/time/crontab-guru/index.tsx @@ -7,9 +7,10 @@ import ToolTextResult from '@components/result/ToolTextResult'; import { GetGroupsType } from '@components/options/ToolOptions'; import { CardExampleType } from '@components/examples/ToolExamples'; import { main, validateCrontab, explainCrontab } from './service'; -import { InitialValuesType } from './types'; -const initialValues: InitialValuesType = {}; +const initialValues = {}; + +type InitialValuesType = typeof initialValues; const exampleCards: CardExampleType[] = [ { diff --git a/src/pages/tools/time/crontab-guru/service.ts b/src/pages/tools/time/crontab-guru/service.ts index 1c81ba8..638636c 100644 --- a/src/pages/tools/time/crontab-guru/service.ts +++ b/src/pages/tools/time/crontab-guru/service.ts @@ -1,4 +1,3 @@ -import { InitialValuesType } from './types'; import cronstrue from 'cronstrue'; import { isValidCron } from 'cron-validator'; @@ -14,7 +13,7 @@ export function validateCrontab(expr: string): boolean { return isValidCron(expr, { seconds: false, allowBlankDay: true }); } -export function main(input: string, options: InitialValuesType): string { +export function main(input: string, _options: any): string { if (!input.trim()) return ''; if (!validateCrontab(input)) { return 'Invalid crontab expression.'; diff --git a/src/pages/tools/time/crontab-guru/types.ts b/src/pages/tools/time/crontab-guru/types.ts deleted file mode 100644 index 54b3d7e..0000000 --- a/src/pages/tools/time/crontab-guru/types.ts +++ /dev/null @@ -1,4 +0,0 @@ -// Options for crontab-guru tool. Currently empty, but can be extended for advanced features. -export type InitialValuesType = { - // Add future options here -}; From 5133d0dce2973f67d87e8e6037e7be69b226a235 Mon Sep 17 00:00:00 2001 From: "Ibrahima G. Coulibaly" Date: Tue, 8 Jul 2025 18:00:30 +0100 Subject: [PATCH 4/4] fix: misc --- .idea/workspace.xml | 303 ++++++++++---------- src/pages/tools/time/crontab-guru/index.tsx | 41 +-- src/pages/tools/time/crontab-guru/meta.ts | 2 +- 3 files changed, 160 insertions(+), 186 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 5d8fccb..4a8295d 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -4,11 +4,10 @@