Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
    Page
Library
Module
Module type
Parameter
Class
Class type
Source
cron.ml1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221(* This file is part of ocaml-crontab. * * Copyright (C) 2019 Yann Régis-Gianas * * ocaml-crontab is distributed under the terms of the MIT license. See the * included LICENSE file for details. *) (** Cron is a task scheduler standardized by POSIX. *) (* Excerpt from POSIX [https://pubs.opengroup.org/onlinepubs/9699919799/utilities/crontab.html] Each of these patterns can be either an <asterisk> (meaning all valid values), an element, or a list of elements separated by <comma> characters. An element shall be either a number or two numbers separated by a <hyphen-minus> (meaning an inclusive range). The specification of days can be made by two fields (day of the month and day of the week). If month, day of month, and day of week are all <asterisk> characters, every day shall be matched. If either the month or day of month is specified as an element or list, but the day of week is an <asterisk>, the month and day of month fields shall specify the days that match. If both month and day of month are specified as an <asterisk>, but day of week is an element or list, then only the specified days of the week match. Finally, if either the month or day of month is specified as an element or list, and the day of week is also specified as an element or list, then any day matching either the month and day of month, or the day of week, shall be matched. *) type field = | All | List of element list and element = | Single of int | Range of int * int type entry = { minute : field; hour : field; day_of_the_month : field; month_of_the_year : field; day_of_the_week : field; command : string; } type t = entry list let entries t = t let make t = t exception InvalidElement of string * int type validator = int -> unit let single validate v = validate v; Single v let range validate start stop = validate start; validate stop; Range (start, stop) let valid_range what start stop k = if not ((k >= start) && (k <= stop)) then raise (InvalidElement (what, k)) let valid_minute = valid_range "minute" 0 59 let valid_hour = valid_range "hour" 0 23 let valid_day_of_the_month = valid_range "day of the month" 1 31 let valid_month_of_the_year = valid_range "month of the year" 1 12 let valid_day_of_the_week = valid_range "day of the week" 0 6 let make_entry ?(minute = All) ?(hour = All) ?(day_of_the_month = All) ?(month_of_the_year = All) ?(day_of_the_week = All) command = { minute; hour; day_of_the_week; day_of_the_month; month_of_the_year; command } let minute e = e.minute let hour e = e.hour let day_of_the_month e = e.day_of_the_month let month_of_the_year e = e.month_of_the_year let day_of_the_week e = e.day_of_the_week exception ParseError of int * string let blanks = Str.regexp " +" let error ?(lineno=0) msg = raise (ParseError (lineno, msg)) let invalid_field f = error (Printf.sprintf "`%s' is an invalid field." f) let parse_element valid e = match Str.(split (regexp "-") e) with | [d] -> (try single valid (int_of_string d) with _ -> invalid_field e) | [start; stop] -> (try let start = int_of_string start and stop = int_of_string stop in range valid start stop with _ -> invalid_field e) | _ -> invalid_field e let parse_field valid f = match Str.(split (regexp ",") f) with | [] -> assert false (* By split. *) | ["*"] -> All | elements -> List (List.map (parse_element valid) elements) let entry_of_string s = match Str.(split blanks s) with | minute :: hour :: day_of_the_month :: month_of_the_year :: day_of_the_week :: command -> let command = String.concat " " command and minute = parse_field valid_minute minute and hour = parse_field valid_hour hour and day_of_the_month = parse_field valid_day_of_the_month day_of_the_month and month_of_the_year = parse_field valid_month_of_the_year month_of_the_year and day_of_the_week = parse_field valid_day_of_the_week day_of_the_week in make_entry ~minute ~hour ~day_of_the_month ~month_of_the_year ~day_of_the_week command | _ -> Printf.eprintf "%s\n" s; error "Invalid number of fields: there must be 6, separated by blanks." let string_of_element = function | Single k -> string_of_int k | Range (start, stop) -> Printf.sprintf "%d-%d" start stop let string_of_field = function | All -> "*" | List es -> String.concat "," (List.map string_of_element es) let string_of_entry e = let field a e = string_of_field (a e) in String.concat " " [ field minute e; field hour e; field day_of_the_month e; field month_of_the_year e; field day_of_the_week e; e.command ] let crontab_of_string input = let maybe_entry_of_string lineno s = let blank_line s = (String.length s = 0) in let comment s = (s.[0] = '#') in if blank_line s || comment s then [] else [ try entry_of_string s with ParseError (_, msg) -> raise (ParseError (lineno, msg)) ] in let lines = Str.(split (regexp "\n") input) in List.(flatten (mapi maybe_entry_of_string lines)) let string_of_crontab t = String.concat "\n" (List.map string_of_entry t) ^ "\n" exception CrontabError of Unix.process_status let crontab_command user = Printf.sprintf "crontab %s" (match user with None -> "" | Some u -> "-u " ^ u) let crontab_result = function | (lines, Unix.WEXITED 0) -> lines | (_, Unix.WEXITED 1) -> "" | (_, status) -> raise (CrontabError status) let crontab ?user mode options = let command = Printf.sprintf "%s %s" (crontab_command user) options in let (cout, cin) as cs = Unix.open_process command in begin match mode with | `Read -> X.read_all (Buffer.create 13) cout | `Write lines -> output_string cin lines; "" end |> fun out -> (out, Unix.close_process cs) |> crontab_result let crontab_install ?user crontable = crontab ?user (`Write (string_of_crontab crontable)) "-" |> ignore let crontab_get ?user () = crontab ?user `Read "-l" |> crontab_of_string let crontab_remove ?user () = crontab ?user `Read "-r" |> ignore let crontab_insert_entry ?user entry = let table = crontab_get ?user () in if not (List.mem entry table) then crontab_install ?user (entry :: table) let crontab_remove_entry ?user entry = let table = crontab_get ?user () in if not (List.mem entry table) then raise Not_found else let table, _ = List.partition (( <> ) entry) table in crontab_install ?user table let version = Version.current