From 27f8a69fcf43a84eee23e74f4f7ed9f898ad89bc Mon Sep 17 00:00:00 2001 From: 7000pctAUTO Date: Sun, 1 Feb 2026 15:06:54 +0000 Subject: [PATCH] Add source code files --- src/cronparse/cli.py | 166 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 src/cronparse/cli.py diff --git a/src/cronparse/cli.py b/src/cronparse/cli.py new file mode 100644 index 0000000..c11991d --- /dev/null +++ b/src/cronparse/cli.py @@ -0,0 +1,166 @@ +"""Command-line interface for Cron Parser CLI.""" + +import sys + +import click + +from cronparse import __version__ +from cronparse.parser import parse_cron, validate_cron +from cronparse.nlp import text_to_cron +from cronparse.scheduler import get_next_executions, format_timeline +from cronparse.generator import generate_cron_interactive +from cronparse.describer import describe_cron + + +@click.group() +@click.version_option(version=__version__, prog_name="cronparse") +def main(): + """Cron Parser CLI - Parse, validate, generate, and explain cron expressions.""" + pass + + +@main.command(name="parse") +@click.argument("expression", type=str) +@click.option("--json", "output_json", is_flag=True, help="Output as JSON format") +def parse_command(expression: str, output_json: bool): + """Parse and validate a cron expression, showing field breakdown.""" + try: + is_valid, result = parse_cron(expression) + if not is_valid: + click.echo(click.style("Error: Invalid cron expression", fg="red")) + sys.exit(1) + + if output_json: + import json + click.echo(json.dumps(result, indent=2)) + else: + click.echo(click.style(f"Valid cron expression: {expression}", fg="green")) + click.echo() + fields = ["minute", "hour", "day", "month", "day_of_week"] + for field in fields: + value = result.get(field, "N/A") + click.echo(f" {field:15}: {value}") + if result.get("command"): + click.echo(f" {'command':15}: {result['command']}") + except Exception as e: + click.echo(click.style(f"Error: {e}", fg="red")) + sys.exit(1) + + +@main.command(name="from-text") +@click.argument("text", type=str, nargs=-1) +@click.option("--json", "output_json", is_flag=True, help="Output as JSON format") +def from_text_command(text: tuple, output_json: bool): + """Convert natural language to cron expression. + + Examples: + \"every Monday at 9am\" + \"daily at 14:30\" + \"every 5 minutes\" + \"on the 1st of every month at midnight\" + """ + text_str = " ".join(text) + try: + result = text_to_cron(text_str) + if output_json: + import json + click.echo(json.dumps(result, indent=2)) + else: + click.echo(click.style("Natural Language:", fg="cyan") + f" {text_str}") + click.echo(click.style("Cron Expression:", fg="green") + f" {result['cron']}") + if result.get("description"): + click.echo(click.style("Description:", fg="yellow") + f" {result['description']}") + except ValueError as e: + click.echo(click.style(f"Error: {e}", fg="red")) + sys.exit(1) + except Exception as e: + click.echo(click.style(f"Error parsing text: {e}", fg="red")) + sys.exit(1) + + +@main.command(name="next") +@click.argument("expression", type=str) +@click.option("--count", "-c", default=5, show_default=True, help="Number of executions to show") +@click.option("--json", "output_json", is_flag=True, help="Output as JSON format") +@click.option("--timeline/--no-timeline", default=True, help="Show ASCII timeline") +def next_command(expression: str, count: int, output_json: bool, timeline: bool): + """Show next execution times for a cron expression.""" + try: + is_valid, _ = validate_cron(expression) + if not is_valid: + click.echo(click.style("Error: Invalid cron expression", fg="red")) + sys.exit(1) + + executions = get_next_executions(expression, count) + if output_json: + import json + click.echo(json.dumps(executions, indent=2, default=str)) + else: + click.echo(click.style(f"Next {count} executions for: {expression}", fg="cyan")) + click.echo() + for i, exec_time in enumerate(executions, 1): + click.echo(f" {i}. {exec_time.strftime('%Y-%m-%d %H:%M:%S %A')}") + + if timeline: + click.echo() + timeline_output = format_timeline(executions) + click.echo(timeline_output) + except Exception as e: + click.echo(click.style(f"Error: {e}", fg="red")) + sys.exit(1) + + +@main.command(name="generate") +@click.option("--json", "output_json", is_flag=True, help="Output as JSON format") +def generate_command(output_json: bool): + """Interactive wizard to generate a cron expression.""" + try: + result = generate_cron_interactive() + if output_json: + import json + click.echo(json.dumps(result, indent=2)) + else: + click.echo(click.style("Generated Cron Expression:", fg="green", bold=True)) + click.echo(f" {result['cron']}") + click.echo() + click.echo("Field breakdown:") + for field, value in result["fields"].items(): + click.echo(f" {field:15}: {value}") + except click.Abort: + click.echo(click.style("\nGeneration cancelled.", fg="yellow")) + sys.exit(0) + except Exception as e: + click.echo(click.style(f"Error: {e}", fg="red")) + sys.exit(1) + + +@main.command(name="explain") +@click.argument("expression", type=str) +@click.option("--json", "output_json", is_flag=True, help="Output as JSON format") +@click.option("--24h", "use_24h", is_flag=True, help="Use 24-hour time format") +def explain_command(expression: str, output_json: bool, use_24h: bool): + """Explain a cron expression in human-readable language.""" + try: + is_valid, _ = validate_cron(expression) + if not is_valid: + click.echo(click.style("Error: Invalid cron expression", fg="red")) + sys.exit(1) + + description = describe_cron(expression, use_24h=use_24h) + if output_json: + import json + click.echo(json.dumps({ + "expression": expression, + "description": description + }, indent=2)) + else: + click.echo(click.style(f"Cron: {expression}", fg="cyan")) + click.echo(click.style("Description:", fg="green")) + click.echo(f" {description}") + except Exception as e: + click.echo(click.style(f"Error: {e}", fg="red")) + sys.exit(1) + + +if __name__ == "__main__": + main()