]> gitweb.factorcode.org Git - factor.git/blob - extra/crontab/crontab.factor
66521a5d7002c666fd3d481a11179bbbf679a4b1
[factor.git] / extra / crontab / crontab.factor
1 ! Copyright (C) 2018 John Benediktsson
2 ! See https://factorcode.org/license.txt for BSD license
3
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 ;
9
10 IN: crontab
11
12 ERROR: invalid-cronentry value ;
13
14 TUPLE: cronentry minutes hours days months days-of-week command ;
15
16 <PRIVATE
17
18 :: parse-value ( value quot: ( value -- value' ) seq -- value )
19     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? ] [
25             "/" split1 [
26                 quot seq parse-value
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 ] }
34         [ quot call 1array ]
35     } cond members sort ; inline recursive
36
37 : parse-day ( str -- n )
38     [ string>number dup 7 = [ drop 0 ] when ] [
39         >lower $[ day-abbreviations3 [ >lower ] map ] index
40     ] ?unless ;
41
42 : parse-month ( str -- n )
43     [ string>number ] [
44         >lower $[ month-abbreviations [ >lower ] map ] index
45     ] ?unless ;
46
47 CONSTANT: aliases H{
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 * * * *" }
55 }
56
57 : check-cronentry ( cronentry -- cronentry )
58     dup {
59         [ days-of-week>> [ 0 6 between? ] all? ]
60         [ months>> [ 1 12 between? ] all? ]
61         [
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?
65         ]
66         [ minutes>> [ 0 59 between? ] all? ]
67         [ hours>> [ 0 23 between? ] all? ]
68     } 1&& [ invalid-cronentry ] unless ;
69
70 PRIVATE>
71
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 ]
80         [ ]
81     } spread cronentry boa check-cronentry ;
82
83 <PRIVATE
84
85 : ?parse-cronentry ( entry -- cronentry )
86     dup cronentry? [ parse-cronentry ] unless ;
87
88 :: (next-time-after) ( cronentry timestamp -- )
89
90     f ! should we keep searching for a matching time
91
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
97     ] if
98
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
103
104     timestamp day>> :> day
105     cronentry days>> [ day >= ] find nip [
106         cronentry days>> first timestamp days-in-month +
107     ] unless* day - :> days-to-day
108
109     cronentry days-of-week>> length 7 =
110     cronentry days>> length 31 = 2array
111     {
112         { { f t } [ days-to-weekday ] }
113         { { t f } [ days-to-day ] }
114         [ drop days-to-weekday days-to-day min ]
115     } case [
116         timestamp 0 >>hour 0 >>minute swap +day 2drop t
117     ] unless-zero
118
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
124     ] if
125
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
131     ] if
132
133     [ cronentry timestamp (next-time-after) ] when ;
134
135 PRIVATE>
136
137 : next-time-after ( cronentry timestamp -- timestamp )
138     [ ?parse-cronentry ] dip 1 minutes time+ 0 >>second
139     [ (next-time-after) ] keep ;
140
141 : next-time ( cronentry -- timestamp )
142     now next-time-after ;
143
144 : next-times-after ( cronentry n timestamp -- timestamps )
145     swap [ dupd next-time-after dup ] replicate 2nip ;
146
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 ;
150
151 : next-times-until ( cronentry timestamp -- timestamps )
152     [ now start-of-minute ] dip next-times-from-until ;
153
154 : next-times ( cronentry n -- timestamps )
155     now next-times-after ;
156
157 : read-crontab ( -- entries )
158     read-lines harvest [ parse-cronentry ] map ;
159
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 ;
164
165 : group-crons-for-duration-from ( cronstrings duration from-timestamp -- entries )
166     tuck time+ group-crons ;
167
168 : group-crons-for-duration ( cronstrings duration -- entries )
169     now utc group-crons-for-duration-from ;
170
171 : crons-for-minute ( cronstrings timestamp -- entries )
172     utc start-of-minute dup end-of-minute group-crons ;
173
174 : crons-for-hour ( cronstrings timestamp -- entries )
175     utc start-of-hour dup end-of-hour group-crons ;
176
177 : crons-for-day ( cronstrings timestamp -- entries )
178     utc start-of-day dup end-of-day group-crons ;
179
180 : crons-for-week ( cronstrings timestamp -- entries )
181     utc start-of-week dup end-of-week group-crons ;
182
183 : crons-for-month ( cronstrings timestamp -- entries )
184     utc start-of-month dup end-of-month group-crons ;
185
186 : crons-for-year ( cronstrings timestamp -- entries )
187     utc start-of-year dup end-of-year group-crons ;
188
189 : crons-for-decade ( cronstrings timestamp -- entries )
190     utc start-of-decade dup end-of-decade group-crons ;
191
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 ;
202
203 : keys-unix-to-rfc822 ( assoc -- assoc' )
204     [ unix-time>timestamp timestamp>rfc822 ] map-keys ;
205
206 : keys-rfc822-to-unix ( assoc -- assoc' )
207     [ rfc822>timestamp timestamp>unix-time ] map-keys ;
208
209 : grouped-crons. ( assoc -- )
210     keys-unix-to-rfc822 [ first2 [ write bl ] [ ... ] bi* ] each ;