1 ! Copyright (C) 2018 John Benediktsson
2 ! See https://factorcode.org/license.txt for BSD license
4 USING: accessors arrays ascii assocs assocs.extras calendar
5 calendar.english calendar.format calendar.parser
6 calendar.private circular combinators combinators.short-circuit
7 io kernel literals math math.order math.parser prettyprint
8 random ranges sequences sets sorting splitting ;
12 ERROR: invalid-cronentry value ;
14 TUPLE: cronentry minutes hours days months days-of-week command ;
18 :: parse-value ( value quot: ( value -- value' ) seq -- value )
20 { [ CHAR: , over member? ] [
21 "," split [ quot seq parse-value ] map concat ] }
22 { [ dup "*" = ] [ drop seq ] }
23 { [ dup "~" = ] [ drop seq random 1array ] }
24 { [ CHAR: / over member? ] [
27 dup length 1 = [ seq swap first seq first - ] [ 0 ] if
28 over length dup 7 = [ [ <circular> ] 2dip ] [ 1 - ] if
29 ] dip string>number <range> swap nths ] }
30 { [ CHAR: - over member? ] [
31 "-" split1 quot bi@ [a..b] ] }
32 { [ CHAR: ~ over member? ] [
33 "~" split1 quot bi@ [a..b] random 1array ] }
35 } cond members sort ; inline recursive
37 : parse-day ( str -- n )
38 [ string>number dup 7 = [ drop 0 ] when ] [
39 >lower $[ day-abbreviations3 [ >lower ] map ] index
42 : parse-month ( str -- n )
44 >lower $[ month-abbreviations [ >lower ] map ] index
48 { "@yearly" "0 0 1 1 *" }
49 { "@annually" "0 0 1 1 *" }
50 { "@monthly" "0 0 1 * *" }
51 { "@weekly" "0 0 * * 0" }
52 { "@daily" "0 0 * * *" }
53 { "@midnight" "0 0 * * *" }
54 { "@hourly" "0 * * * *" }
57 : check-cronentry ( cronentry -- cronentry )
59 [ days-of-week>> [ 0 6 between? ] all? ]
60 [ months>> [ 1 12 between? ] all? ]
62 [ days>> 1 ] [ months>> ] bi [
63 { 0 31 29 31 30 31 30 31 31 30 31 30 31 } nth
64 ] map maximum [ between? ] 2curry all?
66 [ minutes>> [ 0 59 between? ] all? ]
67 [ hours>> [ 0 23 between? ] all? ]
68 } 1&& [ invalid-cronentry ] unless ;
72 : parse-cronentry ( entry -- cronentry )
73 " " split1 [ aliases ?at drop ] dip " " glue
74 " " split1 " " split1 " " split1 " " split1 " " split1 {
75 [ [ string>number ] T{ range f 0 60 1 } parse-value ]
76 [ [ string>number ] T{ range f 0 24 1 } parse-value ]
77 [ [ string>number ] T{ range f 1 31 1 } parse-value ]
78 [ [ parse-month ] T{ range f 1 12 1 } parse-value ]
79 [ [ parse-day ] T{ range f 0 7 1 } parse-value ]
81 } spread cronentry boa check-cronentry ;
85 : ?parse-cronentry ( entry -- cronentry )
86 dup cronentry? [ parse-cronentry ] unless ;
88 :: (next-time-after) ( cronentry timestamp -- )
90 f ! should we keep searching for a matching time
92 timestamp month>> :> month
93 cronentry months>> [ month >= ] find nip
94 dup month = [ drop ] [
95 [ cronentry months>> first timestamp 1 +year drop ] unless*
96 timestamp 1 >>day 0 >>hour 0 >>minute month<< drop t
99 timestamp day-of-week :> weekday
100 cronentry days-of-week>> [ weekday >= ] find nip [
101 cronentry days-of-week>> first 7 +
102 ] unless* weekday - :> days-to-weekday
104 timestamp day>> :> day
105 cronentry days>> [ day >= ] find nip [
106 cronentry days>> first timestamp days-in-month +
107 ] unless* day - :> days-to-day
109 cronentry days-of-week>> length 7 =
110 cronentry days>> length 31 = 2array
112 { { f t } [ days-to-weekday ] }
113 { { t f } [ days-to-day ] }
114 [ drop days-to-weekday days-to-day min ]
116 timestamp 0 >>hour 0 >>minute swap +day 2drop t
119 timestamp hour>> :> hour
120 cronentry hours>> [ hour >= ] find nip
121 dup hour = [ drop ] [
122 [ cronentry hours>> first timestamp 1 +day drop ] unless*
123 timestamp 0 >>minute hour<< drop t
126 timestamp minute>> :> minute
127 cronentry minutes>> [ minute >= ] find nip
128 dup minute = [ drop ] [
129 [ cronentry minutes>> first timestamp 1 +hour drop ] unless*
130 timestamp minute<< drop t
133 [ cronentry timestamp (next-time-after) ] when ;
137 : next-time-after ( cronentry timestamp -- timestamp )
138 [ ?parse-cronentry ] dip 1 minutes time+ 0 >>second
139 [ (next-time-after) ] keep ;
141 : next-time ( cronentry -- timestamp )
142 now next-time-after ;
144 : next-times-after ( cronentry n timestamp -- timestamps )
145 swap [ dupd next-time-after dup ] replicate 2nip ;
147 : next-times-from-until ( cronentry from-timestamp until-timestamp -- timestamps )
148 [ dup second>> 0 = [ 1 minutes time- ] when ] dip
149 '[ dupd next-time-after dup dup _ before? ] [ ] produce 3nip ;
151 : next-times-until ( cronentry timestamp -- timestamps )
152 [ now start-of-minute ] dip next-times-from-until ;
154 : next-times ( cronentry n -- timestamps )
155 now next-times-after ;
157 : read-crontab ( -- entries )
158 read-lines harvest [ parse-cronentry ] map ;
160 : group-crons ( cronstrings from-timestamp until-timestamp -- entries )
161 '[ _ _ next-times-from-until [ timestamp>unix-time ] map ] zip-with
162 [ first2 [ 2array ] with map ] map concat
163 [ nip ] collect-key-by sort-keys ;
165 : group-crons-for-duration-from ( cronstrings duration from-timestamp -- entries )
166 tuck time+ group-crons ;
168 : group-crons-for-duration ( cronstrings duration -- entries )
169 now utc group-crons-for-duration-from ;
171 : crons-for-minute ( cronstrings timestamp -- entries )
172 utc start-of-minute dup end-of-minute group-crons ;
174 : crons-for-hour ( cronstrings timestamp -- entries )
175 utc start-of-hour dup end-of-hour group-crons ;
177 : crons-for-day ( cronstrings timestamp -- entries )
178 utc start-of-day dup end-of-day group-crons ;
180 : crons-for-week ( cronstrings timestamp -- entries )
181 utc start-of-week dup end-of-week group-crons ;
183 : crons-for-month ( cronstrings timestamp -- entries )
184 utc start-of-month dup end-of-month group-crons ;
186 : crons-for-year ( cronstrings timestamp -- entries )
187 utc start-of-year dup end-of-year group-crons ;
189 : crons-for-decade ( cronstrings timestamp -- entries )
190 utc start-of-decade dup end-of-decade group-crons ;
192 : crons-this-minute ( cronstrings -- entries ) now crons-for-minute ;
193 : crons-this-hour ( cronstrings -- entries ) now crons-for-hour ;
194 : crons-this-day ( cronstrings -- entries ) now crons-for-day ;
195 ALIAS: crons-today crons-this-day
196 : crons-yesterday ( cronstrings -- entries ) 1 days ago crons-for-day ;
197 : crons-tomorrow ( cronstrings -- entries ) 1 days hence crons-for-day ;
198 : crons-this-week ( cronstrings -- entries ) now crons-for-week ;
199 : crons-this-month ( cronstrings -- entries ) now crons-for-month ;
200 : crons-this-year ( cronstrings -- entries ) now crons-for-year ;
201 : crons-this-decade ( cronstrings -- entries ) now crons-for-decade ;
203 : keys-unix-to-rfc822 ( assoc -- assoc' )
204 [ unix-time>timestamp timestamp>rfc822 ] map-keys ;
206 : keys-rfc822-to-unix ( assoc -- assoc' )
207 [ rfc822>timestamp timestamp>unix-time ] map-keys ;
209 : grouped-crons. ( assoc -- )
210 keys-unix-to-rfc822 [ first2 [ write bl ] [ ... ] bi* ] each ;