aboutsummaryrefslogtreecommitdiff
path: root/vcalendar/recur.scm
blob: 169a28836fcec6561ac1421b50f5d5a20e584fab (plain)
1
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
(define-module (vcalendar recur)
  #:use-module (srfi srfi-1)
  #:use-module (srfi srfi-9 gnu)
  #:use-module (srfi srfi-26)
  #:use-module (vcalendar datetime)
  #:use-module (util)
  #:export (<recur-rule> build-recur-rules))

(define-immutable-record-type <recur-rule>
  (make-recur-rules
   freq until count interval bysecond byminute byhour wkst)
  recur-rule?
  (freq     get-freq     set-freq)
  (until    get-until    set-until)
  (count    get-count    set-count)
  (interval get-interval set-interval)  ; 1
  (bysecond get-bysecond set-bysecond)
  (byminute get-byminute set-byminute)
  (byhour   get-byhour   set-byhour)
  (wkst     get-wkst     set-wkst)      ; MO
  )


;; (build-recur-rules "FREQ=HOURLY") ; => #<<recur-rule> freq: HOURLY until: #f count: #f interval: #f>
;; (build-recur-rules "FREQ=HOURLY;COUNT=3") ; => #<<recur-rule> freq: HOURLY until: #f count: 3 interval: #f>
;; (build-recur-rules "FREQ=ERR;COUNT=3") ; => #<<recur-rule> freq: #f until: #f count: 3 interval: #f>
;; (build-recur-rules "FREQ=HOURLY;COUNT=err") ; => #<<recur-rule> freq: HOURLY until: #f count: #f interval: #f>
;; (build-recur-rules "FREQ=HOURLY;COUNT=-1") ; => #<<recur-rule> freq: HOURLY until: #f count: #f interval: #f>

(define (build-recur-rules str)
 (catch-let
  (lambda () (%build-recur-rules str))

  ((unknown-key
    (lambda (err cont obj key . rest)
      (format #t "ERR Invalid key [~a] while parsing recurence rule, ignoring.~%" key)
      (cont obj)))

   (unfulfilled-constraint
    (lambda (err cont obj key val . rest)
      (let ((default-value (case key
                             ((INTERVAL) 1)
                             (else #f))))
        (format #t "ERR Value [~a] doesn't fulfill constraint of type [~a], defauting to [~a].~%"
                val key default-value)
        (cont default-value))))

   (invalid-value
    (lambda (err cont obj key val . rest)
      (format #t "ERR Invalid value [~a] for key [~a], ignoring.~%" val key)
      (cont obj))))))

(define (string->number-list val delim)
  (map string->number (string-split val delim)))

(define (string->symbols val delim)
  (map string->symbol (string-split val delim)))

(define weekdays
  '(SU MO TU WE TH FR SA))

;;; A special form of case only useful in build-recur-rules above.
;;; Each case is on the form (KEY val check-proc) where:
;;; `key` is what should be matched against, and what is used for the setter
;;; `val` is the value to bind to the loop object and
;;; `check` is something the object must conform to



(define-syntax quick-case 
  (lambda (x)
    (let ((syntax-helper
           (lambda (obj parent-expr expr) 
             "Helper function for quick-case below"
             (with-syntax ((obj (datum->syntax parent-expr obj)))
               (syntax-case expr ()
                 ((key val proc)
                  (let ((make-setter (lambda (symb) (symbol-append 'set- (symbol-downcase symb)))))
                    (with-syntax ((setter (datum->syntax parent-expr (make-setter (syntax->datum (syntax key))))))
                      #'((key)
                         (cond ((not val) (call/cc (lambda (cont) (throw 'invalid-value cont obj (quote key) val))))
                               ((proc val) (setter obj val))
                               (else (setter obj (call/cc (lambda (cont) (throw 'unfulfilled-constraint cont obj (quote key) val)))))))))))))))
      (syntax-case x ()
        ((_ var-key obj (key val proc) ...)
         (let ((cc (lambda (lst) (map (cut syntax-helper (syntax->datum (syntax obj)) x <>)
                                 lst))))
           #`(case var-key
               #,@(cc #'((key val proc) ...))
               (else (call/cc (lambda (cont) (throw 'unknown-key cont obj var-key)))))))))))

(define (%build-recur-rules str)
  (fold-lists
   (lambda ((key val) obj)
     (quick-case (string->symbol key) obj
                 (FREQ  (string->symbol val) (cut memv <> '(SECONDLY MINUTELY HOURLY DAILY WEEKLY MONTHLY YEARLY)))
                 (UNTIL (parse-datetime val) identity)
                 (COUNT (string->number val) (cut <= 0 <>))
                 (INTERVAL (string->number val) (cut <= 0 <>))
                 (BYSECOND (string->number-list val #\,) (cut every (cut <= 0 <> 60) <>))
                 (BYMINUTE (string->number-list val #\,) (cut every (cut <= 0 <> 59) <>))
                 (BYHOUR (string->number-list val #\,) (cut every (cut <= 0 <> 23) <>))
                 ;; TODO implement these
                 ;; (BYDAY)
                 ;; (BYMONTHDAY)
                 ;; (BYYEARDAY)
                 ;; (BYWEEKNO)
                 ;; (BYMONTH)
                 ;; (BYSETPOS)
                 (WKST (string->symbol val) (cut memv <> weekdays))
                 ))
   ((record-constructor <recur-rule> '(interval wkst)) 1 'MO)
   (map (cut string-split <> #\=)
        (string-split str #\;))))