aboutsummaryrefslogtreecommitdiff
path: root/vcalendar/recur.scm
blob: 572fb020ea2e0793fe6b03616540b9f1091c7238 (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
(define-module (vcalendar recur)
  #:use-module (srfi srfi-1)
  #:use-module (srfi srfi-9 gnu)
  #:use-module (srfi srfi-26)
  #:use-module (util))


(define-immutable-record-type <recur-rule>
  (make-recur-rules freq until count interval)
  recur-rule?
  (freq     get-freq     set-freq)
  (until    get-until    set-until)
  (count    get-count    set-count)
  (interval get-interval set-interval))


;; (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 (%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 (string->date 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) <>))
                 ))
   (make-recur-rules #f #f #f 1)
   (map (cut string-split <> #\=)
        (string-split str #\;))))


;;; 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-helper obj parent-expr expr )
  "Helper function for quick-case below"
  (with-syntax ((obj (datum->syntax parent-expr obj)))
    (syntax-case expr ()
      ((key val proc)
       (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))))))))))))

(define-syntax quick-case 
  (lambda (x)
    (syntax-case x ()
      ((_ var-key obj (key val proc) ...)
       (let* ((make-setter (lambda (symb) (symbol-append 'set- (symbol-downcase symb))))
              (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))))))))))