///|
/// MoonCron CLI tool.
/// Usage:
/// mooncron check ""
/// mooncron explain ""
/// mooncron describe "" [--cn]
/// mooncron next "" [--count N] [--time "YYYY-MM-DD HH:MM[:SS]"]
/// mooncron matches "" --time "YYYY-MM-DD HH:MM[:SS]"
/// mooncron help
fn print_usage() -> Unit {
println("MoonCron - Cron Expression Parser and Schedule Calculator")
println("")
println("Usage:")
println(" mooncron check Validate a cron expression")
println(" mooncron explain Show English description")
println(" mooncron describe --cn Show Chinese description")
println(" mooncron next Show next 5 execution times")
println(" [--count N] [--time \"YYYY-MM-DD HH:MM\"]")
println(
" mooncron matches Check if expression matches a time",
)
println(" --time \"YYYY-MM-DD HH:MM\"")
println(" mooncron help Show this help message")
println("")
println("Examples:")
println(" mooncron check \"*/15 9-18 * * 1-5\"")
println(" mooncron explain \"0 9 * * 1-5\"")
println(" mooncron next \"0 8 * * *\" --count 3")
println(" mooncron matches \"0 9 * * 1-5\" --time \"2026-07-27 09:00\"")
}
///|
fn[T] fail(message : String) -> T {
println("error: " + message)
@sys.exit(1)
abort(message)
}
///|
fn command_check(args : Array[String]) -> Unit {
let expr = get_expr_arg(args, 2)
match @lib.validate_expression(expr) {
Ok(_) => {
println("Valid cron expression")
println("")
println("Field details:")
match @lib.parse_cron_expression(expr) {
Ok(fields) =>
for i = 0; i < fields.length(); i = i + 1 {
let field = fields[i]
let name = @lib.field_key(field.field_type)
let vals = field_values_to_string(field)
println(" " + name + ": " + vals)
}
Err(_) => ()
}
}
Err(err) => println(@lib.cron_error_to_string(err))
}
}
///|
fn command_explain(args : Array[String]) -> Unit {
let expr = get_expr_arg(args, 2)
match @lib.parse_cron_expression(expr) {
Ok(fields) => {
let desc = @lib.describe_en(fields)
println(desc)
}
Err(err) => println(@lib.cron_error_to_string(err))
}
}
///|
fn command_describe(args : Array[String]) -> Unit {
let expr = get_expr_arg(args, 2)
let use_cn = has_flag(args, "--cn")
match @lib.parse_cron_expression(expr) {
Ok(fields) => {
let desc = if use_cn {
@lib.describe_cn(fields)
} else {
@lib.describe_en(fields)
}
println(desc)
}
Err(err) => println(@lib.cron_error_to_string(err))
}
}
///|
fn command_next(args : Array[String]) -> Unit {
let expr = get_expr_arg(args, 2)
let count = get_int_flag(args, "--count", 5)
let time_str = get_string_flag(args, "--time", "")
let now = match time_str {
"" => @lib.DateTime::new(2026, 7, 25, 12, 0)
s =>
match @lib.parse_datetime(s) {
Some(dt) => dt
None => fail("invalid time format, expected 'YYYY-MM-DD HH:MM[:SS]'")
}
}
match @lib.parse_cron_expression(expr) {
Ok(fields) => {
let times = @lib.next_n(fields, now, count)
let show_seconds = @lib.is_six_field(fields)
if times.length() == 0 {
println("No upcoming execution times found")
} else {
println("Next " + times.length().to_string() + " execution times:")
for i = 0; i < times.length(); i = i + 1 {
let display = if show_seconds {
times[i].to_string_with_seconds()
} else {
times[i].to_string()
}
println(" " + display)
}
}
}
Err(err) => println(@lib.cron_error_to_string(err))
}
}
///|
fn command_matches(args : Array[String]) -> Unit {
let expr = get_expr_arg(args, 2)
let time_str = get_string_flag(args, "--time", "")
if time_str == "" {
fail("--time flag is required for matches command")
}
let dt = match @lib.parse_datetime(time_str) {
Some(dt) => dt
None => fail("invalid time format, expected 'YYYY-MM-DD HH:MM[:SS]'")
}
match @lib.parse_cron_expression(expr) {
Ok(fields) => {
let matched = @lib.matches_time(fields, dt)
let display = if @lib.is_six_field(fields) {
dt.to_string_with_seconds()
} else {
dt.to_string()
}
if matched {
println("true - " + display + " matches the cron expression")
} else {
println("false - " + display + " does not match the cron expression")
}
}
Err(err) => println(@lib.cron_error_to_string(err))
}
}
///|
fn get_expr_arg(args : Array[String], index : Int) -> String {
if index >= args.length() {
println("error: missing cron expression argument")
print_usage()
@sys.exit(1)
}
args[index]
}
///|
fn has_flag(args : Array[String], flag : String) -> Bool {
for i = 0; i < args.length(); i = i + 1 {
if args[i] == flag {
return true
}
}
false
}
///|
fn get_int_flag(args : Array[String], flag : String, default : Int) -> Int {
for i = 0; i < args.length(); i = i + 1 {
if args[i] == flag && i + 1 < args.length() {
match parse_int_arg(args[i + 1]) {
Some(n) => return n
None => fail("invalid value for " + flag)
}
}
}
default
}
///|
fn get_string_flag(
args : Array[String],
flag : String,
default : String,
) -> String {
for i = 0; i < args.length(); i = i + 1 {
if args[i] == flag && i + 1 < args.length() {
return args[i + 1]
}
}
default
}
///|
fn parse_int_arg(s : String) -> Int? {
if s == "" {
return None
}
let mut value = 0
for i = 0; i < s.length(); i = i + 1 {
let code = s[i].to_int()
if code < 48 || code > 57 {
return None
}
value = value * 10 + (code - 48)
}
Some(value)
}
///|
fn field_values_to_string(field : @lib.CronField) -> String {
if field.values.length() == 0 {
return "(none)"
}
let (min, max) = @lib.field_range(field.field_type)
if field.values.length() == max - min + 1 {
let mut all = true
for i = 0; i < field.values.length(); i = i + 1 {
if field.values[i] != min + i {
all = false
break
}
}
if all {
return "*"
}
}
let mut s = ""
for i = 0; i < field.values.length(); i = i + 1 {
if i > 0 {
s = s + ","
}
if i > 0 && i % 20 == 0 {
s = s + "..."
break
}
s = s + field.values[i].to_string()
}
s
}
///|
/// Entry point.
fn main {
let args = @sys.get_cli_args()
if args.length() < 2 {
print_usage()
@sys.exit(1)
}
match args[1] {
"check" => command_check(args)
"explain" => command_explain(args)
"describe" => command_describe(args)
"next" => command_next(args)
"matches" => command_matches(args)
"help" | "--help" | "-h" => print_usage()
_ => {
println("error: unknown command '" + args[1] + "'")
println("")
print_usage()
@sys.exit(1)
}
}
}