├── .Rbuildignore ├── .gitignore ├── DESCRIPTION ├── LICENSE ├── NAMESPACE ├── R ├── cron_add.R ├── cron_clear.R ├── cron_load.R ├── cron_ls.R ├── cron_njobs.R ├── cron_rm.R ├── cron_save.R ├── deparse_crontab.R ├── no_crontab_error.R ├── ordinal_swap.R ├── parse_cron_jobs.R ├── parse_crontab.R ├── parse_time_slots.R └── wrap.R ├── README.md ├── cronR.Rproj ├── example.cron ├── man ├── cron_add.Rd ├── cron_clear.Rd ├── cron_load.Rd ├── cron_ls.Rd ├── cron_njobs.Rd ├── cron_rm.Rd └── cron_save.Rd ├── test.cron └── tests └── general_tests.R /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: cronR 2 | Type: Package 3 | Title: A package for interacting with cron jobs from R. 4 | Version: 0.1.0 5 | Date: 2013-11-23 6 | Author: Kevin Ushey 7 | Maintainer: Kevin Ushey 8 | Description: This package allows you to create, edit, and remove cron 9 | jobs on your unix-alike system. It provides a set of easy-to-use wrappers 10 | to crontab. 11 | Imports: 12 | digest 13 | License: MIT + file LICENSE 14 | SystemRequirements: cron 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2013 2 | COPYRIGHT HOLDER: Kevin Ushey 3 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | export(cron_add) 2 | export(cron_clear) 3 | export(cron_load) 4 | export(cron_ls) 5 | export(cron_njobs) 6 | export(cron_rm) 7 | export(cron_save) 8 | importFrom(digest,digest) 9 | -------------------------------------------------------------------------------- /R/cron_add.R: -------------------------------------------------------------------------------- 1 | ##' Make a simple cron job 2 | ##' 3 | ##' Generate a cron job, and pass it to crontab. 4 | ##' 5 | ##' The goal is to be able to translate simple English statements of intent 6 | ##' to the actual \code{cron} statement that could execute that intent. For example, 7 | ##' 8 | ##' \emph{"I want to run a job daily at 7AM."} 9 | ##' 10 | ##' is simply 11 | ##' 12 | ##' \code{cron_add(, "daily", at="7AM")} 13 | ##' 14 | ##' Another example, \emph{"I want to run a job on the 15th of every month."} 15 | ##' 16 | ##' is 17 | ##' 18 | ##' \code{cron_add(, "monthly", days_of_month="15th")} 19 | ##' 20 | ##' @param command A command to execute. 21 | ##' @param frequency A character string equal to one of 22 | ##' \code{"minutely"}, \code{"hourly"}, \code{"daily"}, 23 | ##' \code{"monthly"}, or \code{"yearly"}. 24 | ##' @param at The actual time of day at which to execute the command. 25 | ##' When unspecified, we default to \code{"3AM"}, when the command is to 26 | ##' be run less frequently than \code{"hourly"}. 27 | ##' @param days_of_month Optional; the day(s) of the month on which we execute the 28 | ##' command. 29 | ##' @param days_of_week Optional; the day(s) of the week on which we execute the 30 | ##' command. 31 | ##' @param months Optional; the month(s) of the year on which we execute 32 | ##' the command. 33 | ##' @param id An id, or name, to give to the cronjob task, for easier 34 | ##' revision in the future. 35 | ##' @param tags A set of tags, used for easy listing and retrieval 36 | ##' of cron jobs. 37 | ##' @param description A short description of the job, and its purpose. 38 | ##' @param dry_run Boolean; if \code{TRUE} we do not submit the cron job; 39 | ##' instead we return the parsed text that would be submitted as a cron job. 40 | ##' @param user The user whose cron jobs we wish to examine. 41 | ##' @importFrom digest digest 42 | ##' @export 43 | cron_add <- function(command, frequency="daily", at, days_of_month, days_of_week, months, 44 | id, tags="", description="", dry_run=FALSE, user="") { 45 | 46 | crontab <- tryCatch(parse_crontab(user=user), 47 | error=function(e) { 48 | return( character() ) 49 | }) 50 | 51 | ## make sure the id generated / used is unique 52 | call <- match.call() 53 | digested <- FALSE 54 | if (missing(id)) { 55 | digested <- TRUE 56 | id <- digest(call) 57 | } 58 | 59 | if (length(crontab) && length(crontab$cronR)) { 60 | if (id %in% sapply(crontab$cronR, "[[", "id")) { 61 | if (digested) { 62 | warning("This id was auto-generated by 'digest'; it is likely that ", 63 | "you attempted to submit an identical job.") 64 | } 65 | stop("Can't add this job: a job with id '", id, 66 | "' already exists.") 67 | } 68 | } 69 | 70 | call_str <- paste( collapse="", 71 | gsub(" +$", "", capture.output(call) ) 72 | ) 73 | 74 | job <- list( 75 | min=NULL, 76 | hour=NULL, 77 | day_of_month=NULL, 78 | month=NULL, 79 | day_of_week=NULL, 80 | command=NULL 81 | ) 82 | 83 | frequency <- tolower(frequency) 84 | switch( frequency, 85 | minutely={ 86 | job[["min"]] <- "0-59" 87 | job[["hour"]] <- "*" 88 | job[["day_of_month"]] <- "*" 89 | job[["month"]] <- "*" 90 | job[["day_of_week"]] <- "*" 91 | }, 92 | hourly={ 93 | job[["min"]] <- 0 94 | job[["hour"]] <- "*" 95 | job[["day_of_month"]] <- "*" 96 | job[["month"]] <- "*" 97 | job[["day_of_week"]] <- "*" 98 | }, 99 | daily={ 100 | job[["min"]] <- 0 101 | job[["hour"]] <- 0 102 | job[["day_of_month"]] <- "*" 103 | job[["month"]] <- "*" 104 | job[["day_of_week"]] <- "*" 105 | }, 106 | monthly={ 107 | job[["min"]] <- 0 108 | job[["hour"]] <- 0 109 | job[["day_of_month"]] <- 1 110 | job[["month"]] <- "*" 111 | job[["day_of_week"]] <- 1 112 | }, 113 | yearly={ 114 | job[["min"]] <- 0 115 | job[["hour"]] <- 0 116 | job[["day_of_month"]] <- 1 117 | job[["month"]] <- 1 118 | job[["day_of_week"]] <- 1 119 | }, 120 | stop("Unrecognized string passed to frequency: '", frequency, "'") 121 | ) 122 | 123 | if (!missing(days_of_week)) { 124 | days_of_week <- ordinal_swap(days_of_week, adj=-1) 125 | job[["day_of_week"]] <- paste( unique( sort(sapply(days_of_week, parse_day_of_week)), collapse=",") ) 126 | } 127 | 128 | if (!missing(days_of_month)) { 129 | days_of_month <- ordinal_swap(days_of_month) 130 | job[["day_of_month"]] <- paste( unique( sort(sapply(days_of_month, parse_day_of_month)), collapse=",") ) 131 | } 132 | 133 | if (!missing(months)) { 134 | months <- ordinal_swap(months) 135 | job[["month"]] <- paste( unique( sort(sapply(months, parse_month)), collapse=",") ) 136 | } 137 | 138 | if (!missing(at)) { 139 | at_list <- lapply(at, parse_time) 140 | job[["min"]] <- paste( sapply(at_list, "[[", "minutes"), collapse="," ) 141 | job[["hour"]] <- paste( sapply(at_list, "[[", "hours"), collapse="," ) 142 | } 143 | 144 | job[["command"]] <- command 145 | 146 | if (any(is.null(job))) 147 | stop("NULL commands in 'job!' Job is: ", paste(job, collapse=" ", sep=" ")) 148 | 149 | description <- unlist(strsplit( wrap(description), "\n" )) 150 | if (length(description) > 1) { 151 | description[2:length(description)] <- 152 | paste0("## ", description[2:length(description)]) 153 | } 154 | description <- paste(description, collapse="\n") 155 | 156 | header <- paste( sep="\n", collapse="\n", 157 | "## cronR job", 158 | paste0("## id: ", id), 159 | paste0("## tags: ", paste(tags, collapse=", ")), 160 | paste0("## desc: ", description) 161 | ) 162 | 163 | job_str <- paste( sep="\n", collapse="\n", 164 | header, 165 | paste(job, collapse=" ", sep=" ") 166 | ) 167 | 168 | message("Adding cronjob:\n", 169 | "---------------\n\n", 170 | job_str 171 | ) 172 | 173 | if (!dry_run) { 174 | 175 | old_crontab <- suppressWarnings( 176 | system("crontab -l", intern=TRUE, ignore.stderr=TRUE) 177 | ) 178 | 179 | old_crontab[ old_crontab == " " ] <- "" 180 | 181 | if (length(old_crontab)) { 182 | new_crontab <- paste( sep="\n", 183 | paste(old_crontab, collapse="\n"), 184 | paste0(job_str, "\n") 185 | ) 186 | } else { 187 | new_crontab <- paste0(job_str, "\n") 188 | } 189 | 190 | tempfile <- tempfile() 191 | on.exit( unlink(tempfile) ) 192 | cat(new_crontab, "\n", file=tempfile) 193 | system( paste("crontab", tempfile) ) 194 | } 195 | 196 | return (invisible(job)) 197 | 198 | } 199 | 200 | ##' @rdname cron_add 201 | cronjob <- cron_add -------------------------------------------------------------------------------- /R/cron_clear.R: -------------------------------------------------------------------------------- 1 | ##' Clear all cron jobs 2 | ##' 3 | ##' @param ask Boolean; ask before removal? 4 | ##' @param user The user whose crontab we are clearing. 5 | ##' @export 6 | cron_clear <- function(ask=TRUE, user="") { 7 | 8 | if (user == "") 9 | current_user <- Sys.getenv("USER") 10 | 11 | if (ask && user == "") { 12 | cat( sep="", "Are you sure you want to clear all cron jobs for '", 13 | current_user, "'? [y/n]: ") 14 | input <- tolower(scan(what=character(), n=1, quiet=TRUE)) 15 | if (input == "y") { 16 | system("crontab -r") 17 | message("Crontab cleared.") 18 | } else { 19 | message("No action taken.") 20 | } 21 | } else { 22 | system("crontab -r") 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /R/cron_load.R: -------------------------------------------------------------------------------- 1 | ##' Load a crontab from file 2 | ##' 3 | ##' @param file The file location of a crontab. 4 | ##' @param user The user for whom we will be loading a crontab. 5 | ##' @export 6 | cron_load <- function(file, user="") { 7 | if (user == "") { 8 | system(paste("crontab", file)) 9 | } else { 10 | system(paste("crontab -u", user, file)) 11 | } 12 | crontab <- parse_crontab(user=user) 13 | if (is.null(crontab$other)) { 14 | message("Crontab with ", length(crontab$cronR), " cronR job", 15 | if (length(crontab$cronR) != 1) "s" else "", 16 | "loaded.") 17 | } else { 18 | n_other_jobs <- length(grep("^[# ]", inver=TRUE, 19 | unlist(strsplit(crontab$other, "\n", fixed=TRUE)) 20 | )) 21 | message("Crontab with ", length(crontab$cronR), " cronR job", 22 | if (length(crontab$cronR) != 1) "s" else "", " and ", 23 | n_other_jobs, " other job", 24 | if (n_other_jobs != 1) "s" else "", " loaded.") 25 | } 26 | 27 | return (invisible(crontab)) 28 | } 29 | -------------------------------------------------------------------------------- /R/cron_ls.R: -------------------------------------------------------------------------------- 1 | ##' List the contents of a crontab 2 | ##' 3 | ##' We only list the contents that are handeld by \code{cronR}. 4 | ##' 5 | ##' @param id Return cron jobs with a certain \code{id}. 6 | ##' @param tags Return cron jobs with a certain (set of) tags. 7 | ##' @param user The user's crontab to display 8 | ##' @export 9 | cron_ls <- function(id, tags, user="") { 10 | 11 | crontab <- try(parse_crontab(user=user), silent=TRUE) 12 | if (inherits(crontab, "try-error")) { 13 | no_crontab_error(user=user) 14 | return (invisible(NULL)) 15 | } 16 | 17 | if (missing(id) && missing(tags)) { 18 | output <- deparse_crontab(crontab) 19 | message(output) 20 | return (invisible(output)) 21 | } else { 22 | if (missing(id)) id <- "__MISSING__" 23 | if (missing(tags)) tags <- "__MISSING__" 24 | keep <- crontab$cronR[ sapply(crontab$cronR, function(x) { 25 | (x$id %in% id) | (any(x$tags %in% tags)) 26 | })] 27 | if (!length(keep)) { 28 | message("No cron jobs found.") 29 | return (invisible(NULL)) 30 | } 31 | output <- deparse_crontab(list(cronR=keep, other=NULL)) 32 | message(output) 33 | return (invisible(keep)) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /R/cron_njobs.R: -------------------------------------------------------------------------------- 1 | ##' List the number of rCron cron jobs 2 | ##' 3 | ##' @param user The user whose cron jobs we wish to examine. 4 | ##' @export 5 | cron_njobs <- function(user="") { 6 | crontab <- try(parse_crontab(user=user), silent=TRUE) 7 | if (inherits(crontab, "try-error")) { 8 | no_crontab_error(user=user) 9 | return (invisible(0)) 10 | } 11 | if (!is.null(crontab$other)) { 12 | n_other_jobs <- length(grep("^[# ]", invert=TRUE, 13 | unlist(strsplit(crontab$other, "\n", fixed=TRUE)) 14 | )) 15 | } else { 16 | n_other_jobs <- 0 17 | } 18 | 19 | message("There are a total of ", length(crontab$cronR), " cronR cron jobs ", 20 | "and ", n_other_jobs, " other cron jobs currently running.") 21 | return (invisible(length(crontab$other) + n_other_jobs)) 22 | } 23 | -------------------------------------------------------------------------------- /R/cron_rm.R: -------------------------------------------------------------------------------- 1 | ##' Remove a cronjob 2 | ##' 3 | ##' Use this command to remove a cron job added by \code{cron_add}. 4 | ##' 5 | ##' @param id A set of ids, partially matched from the beginning, 6 | ##' we wish to remove. We only remove the id if it is uniquely 7 | ##' matched in the file. 8 | ##' @param dry_run Boolean; if \code{TRUE} we do not submit the cron job; 9 | ##' instead we return the parsed text that would be submitted as a cron job. 10 | ##' @param user The user whose crontab we will be modifying. 11 | ##' @export 12 | cron_rm <- function(id, dry_run=FALSE, user="") { 13 | 14 | crontab <- parse_crontab(user=user) 15 | if (!is.null(crontab$cronR)) { 16 | to_keep <- sapply(crontab$cronR, function(x) { 17 | !(x$id %in% id) 18 | }) 19 | } 20 | 21 | n_removed <- sum(!to_keep) 22 | if (n_removed == 0) { 23 | message("No cron job with id matched to '", id, "' found.") 24 | return (invisible(NULL)) 25 | } else { 26 | message("Removed ", n_removed, " cron job", if (n_removed != 1) "s" else "", ".") 27 | } 28 | 29 | new_crontab <- list( 30 | cronR=crontab$cronR[to_keep], 31 | other=crontab$other 32 | ) 33 | deparsed <- deparse_crontab(new_crontab) 34 | 35 | if (!dry_run) { 36 | if (!(length(new_crontab))) { 37 | system( paste("crontab -r") ) 38 | } else { 39 | tempfile <- tempfile() 40 | on.exit( unlink(tempfile) ) 41 | cat(deparsed, file=tempfile) 42 | system( paste("crontab", tempfile) ) 43 | } 44 | } 45 | 46 | return (invisible(new_crontab)) 47 | 48 | } -------------------------------------------------------------------------------- /R/cron_save.R: -------------------------------------------------------------------------------- 1 | ##' Save the current crontab 2 | ##' 3 | ##' @param file The file location at which you wish to save your 4 | ##' \code{crontab}. 5 | ##' @param overwrite logical; should we overwrite the file at path \code{file} 6 | ##' if it already exists? 7 | ##' @param user The user whose cron jobs we will be saving. 8 | ##' @export 9 | ##' @seealso \code{\link{file.copy}} 10 | cron_save <- function(file, overwrite=FALSE, user="") { 11 | crontab <- parse_crontab(user=user) 12 | tempfile <- tempfile() 13 | on.exit( unlink(tempfile) ) 14 | cat( deparse_crontab(crontab), "\n", file=tempfile) 15 | if (file.copy(tempfile, file, overwrite=overwrite)) { 16 | message("Saved crontab to file: ", normalizePath(file)) 17 | return(TRUE) 18 | } else { 19 | message("Unable to save crontab to file!") 20 | return(FALSE) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /R/deparse_crontab.R: -------------------------------------------------------------------------------- 1 | ## deparse a parsed crontab 2 | deparse_crontab <- function(parsed_crontab) { 3 | cronR <- paste( collapse="\n", sapply(parsed_crontab$cronR, function(x) { 4 | description <- unlist( strsplit( wrap(x$desc, 76 - 6), "\n", fixed=TRUE ) ) 5 | if (length(description) > 1) { 6 | description[2:length(description)] <- 7 | paste0("## ", description[2:length(description)]) 8 | } 9 | description <- paste(description, collapse="\n") 10 | paste( sep="", collapse="\n", c( 11 | "## cronR job", 12 | paste0("## id: ", x$id), 13 | paste0("## tags: ", paste(x$tags, collapse=", ")), 14 | paste0("## desc: ", description), 15 | x$job, 16 | "" 17 | )) 18 | })) 19 | other <- parsed_crontab$other 20 | return (paste(cronR, other, sep="\n", collapse="\n")) 21 | } 22 | -------------------------------------------------------------------------------- /R/no_crontab_error.R: -------------------------------------------------------------------------------- 1 | no_crontab_error <- function(user) { 2 | if (user == "") 3 | current_user <- Sys.getenv("USER") 4 | else 5 | current_user <- user 6 | message("No cron jobs available for user '", current_user, "'.") 7 | } 8 | -------------------------------------------------------------------------------- /R/ordinal_swap.R: -------------------------------------------------------------------------------- 1 | ON <- read.table(header=TRUE, text="Ordinal Number 2 | first 1 3 | second 2 4 | third 3 5 | fourth 4 6 | fifth 5 7 | sixth 6 8 | seventh 7 9 | eighth 8 10 | nineth 9 11 | tenth 10 12 | eleventh 11 13 | twelveth 12 14 | thirteenth 13 15 | fourteenth 14 16 | fifteenth 15 17 | sixteenth 16 18 | seventeenth 17 19 | eighteenth 18 20 | nineteenth 19 21 | twentyth 20 22 | twentyfirst 21 23 | twentysecond 22 24 | twentythird 23 25 | twentyfourth 24 26 | twentyfifth 25 27 | twentysixth 26 28 | twentyseventh 27 29 | twentyeighth 28 30 | twentyninth 29 31 | thirtyth 30 32 | thirtyfirst 31 33 | ") 34 | 35 | ordinal_swap <- function(x, adj=0) { 36 | x <- gsub("(?<=[0-9])st$|(?<=[0-9])nd$|(?<=[0-9])rd$|(?<=[0-9])th$", "", x, perl=TRUE) 37 | for (i in 1:nrow(ON)) { 38 | x <- gsub(ON$Ordinal[i], ON$Number[i] + adj, x) 39 | } 40 | return (x) 41 | } 42 | -------------------------------------------------------------------------------- /R/parse_cron_jobs.R: -------------------------------------------------------------------------------- 1 | parse_cron_jobs <- function(jobs) { 2 | lapply(jobs, function(job) { 3 | 4 | ## From wikipedia; 5 | ## http://en.wikipedia.org/wiki/Cron#Predefined_scheduling_definitions 6 | 7 | # * * * * * command to execute 8 | # ┬ ┬ ┬ ┬ ┬ 9 | # │ │ │ │ │ 10 | # │ │ │ │ │ 11 | # │ │ │ │ └───── day of week (0 - 6) (0 to 6 are Sunday to Saturday, or use names) 12 | # │ │ │ └────────── month (1 - 12) 13 | # │ │ └─────────────── day of month (1 - 31) 14 | # │ └──────────────────── hour (0 - 23) 15 | # └───────────────────────── min (0 - 59) 16 | 17 | split_string <- "__CRON_JOB_SPLIT__" 18 | pattern <- "(.*?)[[:space:]]+(.*?)[[:space:]]+(.*?)[[:space:]]+(.*?)[[:space:]]+(.*?)[[:space:]]+(.*)" 19 | replacement <- paste( c("\\1", "\\2", "\\3", "\\4", "\\5", "\\6"), collapse=split_string) 20 | 21 | job_regex <- gsub(pattern, replacement, job, perl=TRUE) 22 | job_split <- unlist(strsplit(job_regex, split_string, fixed=TRUE)) 23 | times <- list( 24 | min=parse_minutes(job_split[[1]]), 25 | hour=parse_hours(job_split[[2]]), 26 | day_of_month=parse_day_of_month(job_split[[3]]), 27 | month=parse_month(job_split[[4]]), 28 | day_of_week=parse_day_of_week(job_split[[5]]), 29 | command=job_split[[6]] 30 | ) 31 | 32 | }) 33 | } 34 | -------------------------------------------------------------------------------- /R/parse_crontab.R: -------------------------------------------------------------------------------- 1 | ## parse the cron jobs as returned by crontab 2 | parse_crontab <- function(crontab, user="") { 3 | 4 | if (missing(crontab)) { 5 | if (user == "") { 6 | crontab <- suppressWarnings( 7 | system("crontab -l", intern=TRUE, ignore.stderr=TRUE) 8 | ) 9 | } else { 10 | crontab <- suppressWarnings( 11 | system(paste("crontab -u", user, "-l", intern=TRUE, ignore.stderr=TRUE)) 12 | ) 13 | } 14 | } 15 | 16 | if (!length(crontab)) { 17 | stop("No crontab available") 18 | } else { 19 | ## eliminate empty spaces 20 | crontab[ crontab == " " ] <- "" 21 | 22 | ## make sure last element is "" 23 | if (crontab[length(crontab)] != "") 24 | crontab <- c(crontab, "") 25 | 26 | ## find all the cronR jobs 27 | starts <- grep("## cronR job", crontab) 28 | ends <- sapply(starts, function(x) { 29 | ind <- which( crontab == "" ) 30 | return (ind[ ind > x ][1]) 31 | }) 32 | 33 | stopifnot( length(starts) == length(ends) ) 34 | 35 | parsed_jobs <- lapply( seq_along(starts), function(i) { 36 | start <- starts[i] 37 | end <- ends[i] 38 | cronR_job <- crontab[start:(end-1)] 39 | id <- gsub("#+ *id: *", "", grep("^#+ *id:", cronR_job, value=TRUE)) 40 | tags <- unlist( strsplit( split=", ", 41 | gsub("#+ *tags: *", "", grep("^#+ *tags:", cronR_job, value=TRUE)) 42 | ) ) 43 | desc_start <- grep("^#+ *desc:", cronR_job) 44 | desc <- paste( collapse=" ", 45 | gsub("## +|## desc: ", "", cronR_job[desc_start:(length(cronR_job)-1)]) 46 | ) 47 | job <- cronR_job[length(cronR_job)] 48 | return (list( 49 | id=id, 50 | tags=tags, 51 | desc=desc, 52 | job=job 53 | )) 54 | }) 55 | 56 | ## add in those jobs not managed by cronR 57 | covered_lines <- unlist( mapply( function(x, y) seq(x, y), starts, ends ) ) 58 | uncovered_lines <- 1:length(crontab) 59 | uncovered_lines <- uncovered_lines[ !(uncovered_lines %in% covered_lines) ] 60 | if (length(uncovered_lines)) { 61 | other_lines <- paste(crontab[uncovered_lines], collapse="\n") 62 | } else { 63 | other_lines <- NULL 64 | } 65 | 66 | jobs <- list( 67 | cronR=parsed_jobs, 68 | other=other_lines 69 | ) 70 | 71 | return (jobs) 72 | 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /R/parse_time_slots.R: -------------------------------------------------------------------------------- 1 | parse_element <- function(x, min_range, max_range) { 2 | 3 | x_orig <- x 4 | 5 | ## first, substitute * with the min/max range 6 | x <- gsub("*", paste(min_range, max_range, sep="-"), x, fixed=TRUE) 7 | 8 | ## trick: turn all types of input into (a-b/c) format 9 | ## eg. 5 becomes 5-5/1 10 | 11 | ## if we don't have a -, add it + initial val 12 | if (!grepl("-", x)) { 13 | x <- paste0( gsub("/.*", "", x), "-", x) 14 | } 15 | 16 | if (!grepl("/", x)) { 17 | ## implicitly add a /1 so later code can handle 18 | x <- paste0(x, "/1") 19 | } 20 | 21 | x_split <- as.integer(unlist( strsplit(x, "[-/]") )) 22 | if (any(is.na(x_split))) { 23 | stop("Parsing failed for element '", x_orig, "'") 24 | } 25 | start <- x_split[[1]] 26 | end <- x_split[[2]] 27 | by <- x_split[[3]] 28 | return (seq(start, end, by)) 29 | 30 | } 31 | 32 | parse_timeslot <- function(x, min_range=0, max_range=59) { 33 | sort( unlist( lapply( unlist( strsplit(x, ",", fixed=TRUE) ), function(xx) { 34 | parse_element(xx, min_range=min_range, max_range=max_range) 35 | } ) ) ) 36 | } 37 | 38 | parse_minutes <- function(x) { 39 | parse_timeslot(x, min_range=0, max_range=59) 40 | } 41 | 42 | parse_hours <- function(x) { 43 | parse_timeslot(x, min_range=0, max_range=23) 44 | } 45 | 46 | parse_day_of_month <- function(x) { 47 | parse_timeslot(x, min_range=1, max_range=31) 48 | } 49 | 50 | parse_month <- function(x) { 51 | 52 | x <- paste( collapse=",", sapply( unlist( strsplit(x, ",", fixed=TRUE) ), function(x) { 53 | x <- gsub("jan.*", 1, x, ignore.case=TRUE, perl=TRUE) 54 | x <- gsub("feb.*", 2, x, ignore.case=TRUE, perl=TRUE) 55 | x <- gsub("mar.*", 3, x, ignore.case=TRUE, perl=TRUE) 56 | x <- gsub("apr.*", 4, x, ignore.case=TRUE, perl=TRUE) 57 | x <- gsub("may.*", 5, x, ignore.case=TRUE, perl=TRUE) 58 | x <- gsub("jun.*", 6, x, ignore.case=TRUE, perl=TRUE) 59 | x <- gsub("jul.*", 7, x, ignore.case=TRUE, perl=TRUE) 60 | x <- gsub("aug.*", 8, x, ignore.case=TRUE, perl=TRUE) 61 | x <- gsub("sep.*", 9, x, ignore.case=TRUE, perl=TRUE) 62 | x <- gsub("oct.*", 10, x, ignore.case=TRUE, perl=TRUE) 63 | x <- gsub("nov.*", 11, x, ignore.case=TRUE, perl=TRUE) 64 | x <- gsub("dec.*", 12, x, ignore.case=TRUE, perl=TRUE) 65 | return(x) 66 | })) 67 | 68 | parse_timeslot(x, min_range=1, max_range=12) 69 | } 70 | 71 | parse_day_of_week <- function(x) { 72 | 73 | ## pre-process -- substitute days with numbers 74 | x <- paste( collapse=",", sapply( unlist( strsplit(x, ",", fixed=TRUE) ), function(x) { 75 | x <- gsub("sun.*", 0, x, ignore.case=TRUE, perl=TRUE) 76 | x <- gsub("mon.*", 1, x, ignore.case=TRUE, perl=TRUE) 77 | x <- gsub("tues.*", 2, x, ignore.case=TRUE, perl=TRUE) 78 | x <- gsub("weds.*", 3, x, ignore.case=TRUE, perl=TRUE) 79 | x <- gsub("thurs.*", 4, x, ignore.case=TRUE, perl=TRUE) 80 | x <- gsub("fri.*", 5, x, ignore.case=TRUE, perl=TRUE) 81 | x <- gsub("sat.*", 6, x, ignore.case=TRUE, perl=TRUE) 82 | return(x) 83 | })) 84 | 85 | parse_timeslot(x, min_range=0, max_range=6) 86 | } 87 | 88 | parse_time <- function(x) { 89 | if (grepl(":", x)) { 90 | hours <- as.integer(gsub(":.*", "", x)) 91 | minutes <- as.integer(gsub("[[:alpha:]]", "", gsub(".*:", "", x))) 92 | } else { 93 | hours <- as.integer(gsub("[[:alpha:]]", "", x)) 94 | minutes <- 0 95 | } 96 | if (grepl("pm", x, ignore.case=TRUE) && hours < 12) { 97 | hours <- hours + 12 98 | } 99 | if (grepl("am", x, ignore.case=TRUE) && hours == 12) { 100 | hours <- 0 101 | } 102 | return (list( 103 | hours=hours %% 24, 104 | minutes=minutes 105 | )) 106 | } 107 | -------------------------------------------------------------------------------- /R/wrap.R: -------------------------------------------------------------------------------- 1 | wrap <- function(x, width=76, ...) { 2 | return( paste( strwrap(x, width, ...), collapse="\n" ) ) 3 | } 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | *This project is now superceded by the fork at https://github.com/bnosac/cronR.* 2 | 3 | # cronR 4 | 5 | `cronR` is a simple R package for managing your cron jobs. 6 | 7 | Install me with: 8 | 9 | devtools::install_github("cronR", "kevinushey") 10 | 11 | # Warning 12 | 13 | Currently, `cronR` does not preserve or handle cron jobs not 14 | generated through the package. This will be handled some time in 15 | the future. To be safe, you should run `cron_save("cron.backup")` 16 | before fiddling around. 17 | -------------------------------------------------------------------------------- /cronR.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: knitr 13 | LaTeX: pdfLaTeX 14 | 15 | BuildType: Package 16 | PackageInstallArgs: --no-multiarch --with-keep.source 17 | PackageRoxygenize: rd,collate,namespace 18 | -------------------------------------------------------------------------------- /example.cron: -------------------------------------------------------------------------------- 1 | ## cronR job 2 | ## id: abc 3 | ## tags: test1, test2 4 | ## desc: This is a really long description that will hopefully be wrapped 5 | ## appropriately. 6 | 0 0 * * * testing! 7 | 8 | # Minute Hour Day of Month Month Day of Week Command 9 | # (0-59) (0-23) (1-31) (1-12 or Jan-Dec) (0-6 or Sun-Sat) 10 | 0 2 12 * 0,6 /usr/bin/find 11 | -------------------------------------------------------------------------------- /man/cron_add.Rd: -------------------------------------------------------------------------------- 1 | \name{cron_add} 2 | \alias{cron_add} 3 | \alias{cronjob} 4 | \title{Make a simple cron job} 5 | \usage{ 6 | cron_add(command, frequency = "daily", at, days_of_month, days_of_week, 7 | months, id, tags = "", description = "", dry_run = FALSE, user = "") 8 | 9 | cronjob(command, frequency = "daily", at, days_of_month, days_of_week, months, 10 | id, tags = "", description = "", dry_run = FALSE, user = "") 11 | } 12 | \arguments{ 13 | \item{command}{A command to execute.} 14 | 15 | \item{frequency}{A character string equal to one of 16 | \code{"minutely"}, \code{"hourly"}, \code{"daily"}, 17 | \code{"monthly"}, or \code{"yearly"}.} 18 | 19 | \item{at}{The actual time of day at which to execute the 20 | command. When unspecified, we default to \code{"3AM"}, 21 | when the command is to be run less frequently than 22 | \code{"hourly"}.} 23 | 24 | \item{days_of_month}{Optional; the day(s) of the month on 25 | which we execute the command.} 26 | 27 | \item{days_of_week}{Optional; the day(s) of the week on 28 | which we execute the command.} 29 | 30 | \item{months}{Optional; the month(s) of the year on which 31 | we execute the command.} 32 | 33 | \item{id}{An id, or name, to give to the cronjob task, 34 | for easier revision in the future.} 35 | 36 | \item{tags}{A set of tags, used for easy listing and 37 | retrieval of cron jobs.} 38 | 39 | \item{description}{A short description of the job, and 40 | its purpose.} 41 | 42 | \item{dry_run}{Boolean; if \code{TRUE} we do not submit 43 | the cron job; instead we return the parsed text that 44 | would be submitted as a cron job.} 45 | 46 | \item{user}{The user whose cron jobs we wish to examine.} 47 | } 48 | \description{ 49 | Generate a cron job, and pass it to crontab. 50 | } 51 | \details{ 52 | The goal is to be able to translate simple English 53 | statements of intent to the actual \code{cron} statement 54 | that could execute that intent. For example, 55 | 56 | \emph{"I want to run a job daily at 7AM."} 57 | 58 | is simply 59 | 60 | \code{cron_add(, "daily", at="7AM")} 61 | 62 | Another example, \emph{"I want to run a job on the 15th 63 | of every month."} 64 | 65 | is 66 | 67 | \code{cron_add(, "monthly", 68 | days_of_month="15th")} 69 | } 70 | 71 | -------------------------------------------------------------------------------- /man/cron_clear.Rd: -------------------------------------------------------------------------------- 1 | \name{cron_clear} 2 | \alias{cron_clear} 3 | \title{Clear all cron jobs} 4 | \usage{ 5 | cron_clear(ask = TRUE, user = "") 6 | } 7 | \arguments{ 8 | \item{ask}{Boolean; ask before removal?} 9 | 10 | \item{user}{The user whose crontab we are clearing.} 11 | } 12 | \description{ 13 | Clear all cron jobs 14 | } 15 | 16 | -------------------------------------------------------------------------------- /man/cron_load.Rd: -------------------------------------------------------------------------------- 1 | \name{cron_load} 2 | \alias{cron_load} 3 | \title{Load a crontab from file} 4 | \usage{ 5 | cron_load(file, user = "") 6 | } 7 | \arguments{ 8 | \item{file}{The file location of a crontab.} 9 | 10 | \item{user}{The user for whom we will be loading a 11 | crontab.} 12 | } 13 | \description{ 14 | Load a crontab from file 15 | } 16 | 17 | -------------------------------------------------------------------------------- /man/cron_ls.Rd: -------------------------------------------------------------------------------- 1 | \name{cron_ls} 2 | \alias{cron_ls} 3 | \title{List the contents of a crontab} 4 | \usage{ 5 | cron_ls(id, tags, user = "") 6 | } 7 | \arguments{ 8 | \item{id}{Return cron jobs with a certain \code{id}.} 9 | 10 | \item{tags}{Return cron jobs with a certain (set of) 11 | tags.} 12 | 13 | \item{user}{The user's crontab to display} 14 | } 15 | \description{ 16 | We only list the contents that are handeld by 17 | \code{cronR}. 18 | } 19 | 20 | -------------------------------------------------------------------------------- /man/cron_njobs.Rd: -------------------------------------------------------------------------------- 1 | \name{cron_njobs} 2 | \alias{cron_njobs} 3 | \title{List the number of rCron cron jobs} 4 | \usage{ 5 | cron_njobs(user = "") 6 | } 7 | \arguments{ 8 | \item{user}{The user whose cron jobs we wish to examine.} 9 | } 10 | \description{ 11 | List the number of rCron cron jobs 12 | } 13 | 14 | -------------------------------------------------------------------------------- /man/cron_rm.Rd: -------------------------------------------------------------------------------- 1 | \name{cron_rm} 2 | \alias{cron_rm} 3 | \title{Remove a cronjob} 4 | \usage{ 5 | cron_rm(id, dry_run = FALSE, user = "") 6 | } 7 | \arguments{ 8 | \item{id}{A set of ids, partially matched from the 9 | beginning, we wish to remove. We only remove the id if it 10 | is uniquely matched in the file.} 11 | 12 | \item{dry_run}{Boolean; if \code{TRUE} we do not submit 13 | the cron job; instead we return the parsed text that 14 | would be submitted as a cron job.} 15 | 16 | \item{user}{The user whose crontab we will be modifying.} 17 | } 18 | \description{ 19 | Use this command to remove a cron job added by 20 | \code{cron_add}. 21 | } 22 | 23 | -------------------------------------------------------------------------------- /man/cron_save.Rd: -------------------------------------------------------------------------------- 1 | \name{cron_save} 2 | \alias{cron_save} 3 | \title{Save the current crontab} 4 | \usage{ 5 | cron_save(file, overwrite = FALSE, user = "") 6 | } 7 | \arguments{ 8 | \item{file}{The file location at which you wish to save 9 | your \code{crontab}.} 10 | 11 | \item{overwrite}{logical; should we overwrite the file at 12 | path \code{file} if it already exists?} 13 | 14 | \item{user}{The user whose cron jobs we will be saving.} 15 | } 16 | \description{ 17 | Save the current crontab 18 | } 19 | \seealso{ 20 | \code{\link{file.copy}} 21 | } 22 | 23 | -------------------------------------------------------------------------------- /test.cron: -------------------------------------------------------------------------------- 1 | ## cronR job 2 | ## id: abc 3 | ## tags: test1, test2 4 | ## desc: This is a really long description that will hopefully be wrapped 5 | ## appropriately. 6 | 0 0 * * * testing! 7 | 8 | ## cronR job 9 | ## id: 123 10 | ## tags: test2, test3 11 | ## desc: 12 | 0 0 * * * testing2! 13 | 14 | 15 | -------------------------------------------------------------------------------- /tests/general_tests.R: -------------------------------------------------------------------------------- 1 | library(cronR) 2 | cron_ls() 3 | cron_njobs() 4 | cron_add("testing!", id="abc", tags=c("test1", "test2"), 5 | description="This is a really long description that will hopefully be wrapped appropriately.") 6 | cron_ls() 7 | cron_njobs() 8 | cron_add("testing2!", id="123", tags=c("test2", "test3")) 9 | 10 | cron_njobs() 11 | cron_ls() 12 | cron_ls(tag="test1") 13 | cron_ls(tag="test0") 14 | 15 | cron_save(file="test.cron", overwrite=TRUE) 16 | cron_load(file="test.cron") 17 | 18 | cron_rm("abc") 19 | cron_ls() 20 | cron_rm("123") 21 | cron_ls() 22 | cron_clear(FALSE) 23 | 24 | cron_load(file="example.cron") 25 | cron_ls() 26 | cron_njobs() 27 | cron_ls("abc") 28 | cron_clear(FALSE) 29 | --------------------------------------------------------------------------------