Skip to content

Instantly share code, notes, and snippets.

@JosiahParry
Created July 30, 2024 17:21
Show Gist options
  • Save JosiahParry/4734f8b92934ea98ddbb0223a9e1546c to your computer and use it in GitHub Desktop.
Save JosiahParry/4734f8b92934ea98ddbb0223a9e1546c to your computer and use it in GitHub Desktop.
#' @export
new_schedule_builder <- function() {
builder <- list(
sec = "0",
min = "0",
hour = "0",
dom = "*",
month = "*",
dow = "*",
year = "*"
)
structure(builder, class = c("schedule_builder", "list"))
}
#' @export
build <- function(x, ...) {
UseMethod("build")
}
#' @export
build.schedule_builder <- function(x, ...) {
paste0(unlist(x), collapse = " ")
}
#' @export
with_each_minute <- function(builder, every = NULL) {
check_number_whole(every, min = 0, allow_null = TRUE)
if (!is.null(every)) {
builder[["min"]] <- paste0("*", "/", every)
} else {
builder[["min"]] <- "*"
}
builder
}
#' @export
with_each_hour <- function(builder, every = NULL) {
check_number_whole(every, min = 0, allow_null = TRUE)
if (!is.null(every)) {
builder[["hour"]] <- paste0("*", "/", every)
} else {
builder[["hour"]] <- "*"
}
builder
}
#' @export
with_each_day_of_month <- function(builder, every = NULL) {
check_number_whole(every, min = 0, allow_null = TRUE)
if (!is.null(every)) {
builder[["dom"]] <- paste0("*", "/", every)
} else {
builder[["dom"]] <- "*"
}
builder
}
#' @export
with_each_month <- function(builder, every = NULL) {
check_number_whole(every, min = 0, allow_null = TRUE)
if (!is.null(every)) {
builder[["month"]] <- paste0("*", "/", every)
} else {
builder[["month"]] <- "*"
}
builder
}
#' @export
with_each_day_of_week <- function(builder, every = NULL) {
if (!is.null(every)) {
builder[["dow"]] <- paste0("*", "/", every)
} else {
builder[["dow"]] <- "*"
}
builder
}
#' @export
with_seconds <- function(builder, seconds) {
# days must be between 1-7
for (second in seconds) {
check_number_whole(seconds, min = 0, max = 59)
}
if (length(seconds) > 1) {
seconds <- paste0(seconds, collapse = ",")
}
builder[["sec"]] <- seconds
builder
}
#' @export
with_minutes <- function(builder, minutes) {
for (minute in minutes) {
check_number_whole(minute, min = 0, max = 59)
}
if (length(minutes) > 1) {
minutes <- paste0(minutes, collapse = ",")
}
builder[["min"]] <- minutes
builder
}
#' @export
with_hours <- function(builder, hours) {
for (hour in hours) {
check_number_whole(hour, min = 0, max = 23)
}
if (length(hours) > 1) {
hours <- paste0(hours, collapse = ",")
}
builder[["hour"]] <- hours
builder
}
#' Schedule for each specified day of the week
#' @export
with_day_of_week <- function(builder, days) {
# days must be between 1-7
for (day in days) {
check_number_whole(day, min = 1, max = 7)
}
if (length(days) > 1) {
days <- paste0(days, collapse = ",")
}
builder[["dow"]] <- days
builder
}
#' @export
with_day_of_month <- function(builder, days) {
# days must be between 1-31
for (day in days) {
check_number_whole(day, min = 1, max = 31)
}
if (length(days) > 1) {
days <- paste0(days, collapse = ",")
}
builder[["dom"]] <- days
builder
}
#' @export
format.schedule_builder <- function(x, ...) {
NextMethod()
}
#' @export
print.schedule_builder <- function(x, ...) {
print(format(x))
invisible(x)
}
# Validate cron schedule
split_cron_schedule <- function(schedule) {
check_string(schedule, allow_empty = FALSE, allow_na = FALSE, allow_null = NA)
parts <- strsplit(schedule, " ")[[1]]
if (!(length(parts) %in% 6:7)) {
stop("Invalid cron schedule")
}
parts
}
validate_cron_part <- function(part, min = 1, max = 59) {
if (part == "*") {
return(TRUE)
}
# if ends in a comma, error
if (grepl(",$", part)) {
stop("cron part cannot end in a comma")
}
# check if there is a / divider
# if there is, then the first part must be either *, whole number
# a number range, or comma separated list of numbers
if (grepl("/", part)) {
parts <- strsplit(part, "/")[[1]]
if (length(parts) != 2) {
return(FALSE)
}
# break into upper and lower part
first_part <- parts[[1]]
# if ends in a comma, error
if (grepl(",$", first_part)) {
warning("cron part cannot end in a comma")
return(FALSE)
}
second_part <- as.integer(parts[[2]])
# if its isnt the wid card and doesn't contain
if (first_part != "*" && !grepl(",", first_part) && !grepl("-", first_part)) {
first_part <- as.integer(first_part)
check_number_whole(first_part, min = min, max = max)
}
if (grepl("-", first_part)) {
range_parts <- strsplit(first_part, "-")[[1]]
if (length(range_parts) > 2) {
warning("Ranges can only have two parts")
return(FALSE)
}
check_number_whole(as.integer(range_parts[[1]]), min = min, max = max)
check_number_whole(as.integer(range_parts[[2]]), min = min, max = max)
}
# no upper limitation on the "every" part
check_number_whole(second_part, min = 1)
} else {
if (grepl("-", part)) {
range_parts <- strsplit(part, "-")[[1]]
if (length(range_parts) > 2) {
stop("Ranges can only have two parts")
}
check_number_whole(as.integer(range_parts[[1]]), min = min, max = max)
check_number_whole(as.integer(range_parts[[2]]), min = min, max = max)
}
if (part != "*" && !grepl(",", part) && !grepl("-", part)) {
check_number_whole(as.integer(part), min = min, max = max)
}
}
TRUE
}
validate_cron_schedule <- function(schedule) {
check_string(schedule)
parts <- split_cron_schedule(schedule)
if (length(parts) == 6) {
c(sec, min, hour, dom, mon, dow) %<-% parts
year <- NULL
} else {
c(sec, min, hour, dom, mon, dow, year) %<-% parts
}
validate_cron_part(sec)
validate_cron_part(min)
validate_cron_part(hour, 0, 23)
validate_cron_part(dom, 1, 31)
validate_cron_part(mon, 1, 12)
validate_cron_part(dow, 1, 7)
validate_cron_part(dow, 1, 7)
if (!is.null(year)) {
validate_cron_part(year, 1970, 2100)
}
TRUE
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment