! See http://factorcode.org/license.txt for BSD license
USING: accessors arrays ascii assocs calendar calendar.english
-calendar.private combinators io kernel literals locals math
-math.order math.parser math.ranges sequences splitting ;
+calendar.private combinators combinators.short-circuit io kernel
+literals math math.order math.parser ranges sequences splitting ;
IN: crontab
+ERROR: invalid-cronentry value ;
+
:: parse-value ( value quot: ( value -- value' ) seq -- value )
value {
{ [ CHAR: , over member? ] [
"/" split1 [ quot seq parse-value 0 over length 1 - ] dip
string>number <range> swap nths ] }
{ [ CHAR: - over member? ] [
- "-" split1 quot bi@ [a,b] ] }
+ "-" split1 quot bi@ [a..b] ] }
[ quot call 1array ]
} cond ; inline recursive
{ "@hourly" "0 * * * *" }
}
+: check-cronentry ( cronentry -- cronentry )
+ dup {
+ [ days-of-week>> [ 0 6 between? ] all? ]
+ [ months>> [ 1 12 between? ] all? ]
+ [
+ [ days>> 1 ] [ months>> ] bi [
+ { 0 31 29 31 30 31 30 31 31 30 31 30 31 } nth
+ ] map supremum [ between? ] 2curry all?
+ ]
+ [ minutes>> [ 0 59 between? ] all? ]
+ [ hours>> [ 0 23 between? ] all? ]
+ } 1&& [ invalid-cronentry ] unless ;
+
: parse-cronentry ( entry -- cronentry )
" " split1 [ aliases ?at drop ] dip " " glue
" " split1 " " split1 " " split1 " " split1 " " split1 {
[ [ string>number ] T{ range f 0 60 1 } parse-value ]
[ [ string>number ] T{ range f 0 24 1 } parse-value ]
- [ [ string>number ] T{ range f 0 31 1 } parse-value ]
- [ [ parse-month ] T{ range f 0 12 1 } parse-value ]
+ [ [ string>number ] T{ range f 1 31 1 } parse-value ]
+ [ [ parse-month ] T{ range f 1 12 1 } parse-value ]
[ [ parse-day ] T{ range f 0 7 1 } parse-value ]
[ ]
- } spread cronentry boa ;
+ } spread cronentry boa check-cronentry ;
-:: next-time-after ( cronentry timestamp -- )
+<PRIVATE
- timestamp second>> 0 > [
- timestamp 0 >>second 1 minutes (time+) 2drop
- ] when
-
- timestamp month>> :> month
- cronentry months>> [ month >= ] find nip [
- dup month = [ drop f ] [ timestamp month<< t ] if
- ] [
- timestamp cronentry months>> first >>month 1 +year
- ] if* [ cronentry timestamp next-time-after ] when
+:: (next-time-after) ( cronentry timestamp -- )
- timestamp hour>> :> hour
- cronentry hours>> [ hour >= ] find nip [
- dup hour = [ drop f ] [
- timestamp hour<< 0 timestamp minute<< t
- ] if
- ] [
- timestamp cronentry hours>> first >>hour 1 +day
- ] if* [ cronentry timestamp next-time-after ] when
+ f ! should we keep searching for a matching time
- timestamp minute>> :> minute
- cronentry minutes>> [ minute >= ] find nip [
- dup minute = [ drop f ] [ timestamp minute<< t ] if
- ] [
- timestamp cronentry minutes>> first >>minute 1 +hour
- ] if* [ cronentry timestamp next-time-after ] when
+ timestamp month>> :> month
+ cronentry months>> [ month >= ] find nip
+ dup month = [ drop ] [
+ [ cronentry months>> first timestamp 1 +year drop ] unless*
+ timestamp 1 >>day 0 >>hour 0 >>minute month<< drop t
+ ] if
timestamp day-of-week :> weekday
cronentry days-of-week>> [ weekday >= ] find nip [
cronentry days-of-week>> first 7 +
- ] unless* weekday -
+ ] unless* weekday - :> days-to-weekday
timestamp day>> :> day
cronentry days>> [ day >= ] find nip [
- day -
- ] [
- timestamp 1 months time+
- cronentry days>> first >>day
- day-of-year timestamp day-of-year -
- ] if*
-
- min [
- timestamp swap +day drop
- cronentry timestamp next-time-after
- ] unless-zero ;
+ cronentry days>> first timestamp days-in-month +
+ ] unless* day - :> days-to-day
+
+ cronentry days-of-week>> T{ range f 0 7 1 } =
+ cronentry days>> T{ range f 1 31 1 } = 2array
+ {
+ { { f t } [ days-to-weekday ] }
+ { { t f } [ days-to-day ] }
+ [ drop days-to-weekday days-to-day min ]
+ } case [
+ timestamp 0 >>hour 0 >>minute swap +day 2drop t
+ ] unless-zero
+
+ timestamp hour>> :> hour
+ cronentry hours>> [ hour >= ] find nip
+ dup hour = [ drop ] [
+ [ cronentry hours>> first timestamp 1 +day drop ] unless*
+ timestamp 0 >>minute hour<< drop t
+ ] if
+
+ timestamp minute>> :> minute
+ cronentry minutes>> [ minute >= ] find nip
+ dup minute = [ drop ] [
+ [ cronentry minutes>> first timestamp 1 +hour drop ] unless*
+ timestamp minute<< drop t
+ ] if
+
+ [ cronentry timestamp (next-time-after) ] when ;
+
+PRIVATE>
+
+: next-time-after ( cronentry timestamp -- timestamp )
+ 1 minutes time+ 0 >>second [ (next-time-after) ] keep ;
: next-time ( cronentry -- timestamp )
- now [ next-time-after ] keep ;
+ now next-time-after ;
+
+: next-times-after ( cronentry n timestamp -- timestamps )
+ swap [ dupd next-time-after dup ] replicate 2nip ;
+
+: next-times ( cronentry n -- timestamps )
+ now next-times-after ;
-: parse-crontab ( -- entries )
- lines harvest [ parse-cronentry ] map ;
+: read-crontab ( -- entries )
+ read-lines harvest [ parse-cronentry ] map ;