aboutsummaryrefslogtreecommitdiff
path: root/module/vcalendar/recurrence/parse.scm
blob: abead3a90d346fb2c8a1c5f81232dc42ebce9fe1 (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
(define-module (vcalendar recurrence parse)
  #:use-module (srfi srfi-1)
  #:use-module (srfi srfi-19)           ; Datetime
  #:use-module (srfi srfi-19 util)
  #:use-module (srfi srfi-26)
  #:use-module ((vcalendar datetime) #:select (parse-datetime))
  #:duplicates (last)                   ; Replace @var{count}
  #:use-module (vcalendar recurrence internal)
  #:use-module (util)
  #:use-module (exceptions)
  #:use-module (ice-9 curried-definitions)
  #:export (parse-recurrence-rule))

(define (parse-recurrence-rule str)
  "Takes a RECUR value (string), and returuns a <recur-rule> object"
 (catch #t
  (lambda () (%build-recur-rules str))
  (lambda (err cont obj key val . rest)
    (let ((fmt (case err
                 ((unfulfilled-constraint)
                  "ERR ~a [~a] doesn't fulfill constraint of type [~a], ignoring~%")
                 ((invalid-value)
                  "ERR ~a [~a] for key [~a], ignoring.~%")
                 (else "~a ~a ~a"))))
      (format #t fmt err val key))
    (cont obj))))

(eval-when (expand)
 (define ((handle-case stx obj) key val proc)
   (with-syntax ((skey (datum->syntax
                        stx (symbol-downcase (syntax->datum key)))))
     #`((#,key)
        (let ((v #,val))
          (cond ((not v) (throw-returnable 'invalid-value #,obj (quote #,key) v))
                ((#,proc #,val) (set! (skey #,obj) v))
                (else (set! (skey #,obj)
                            (throw-returnable 'unfulfilled-constraint
                                              #,obj (quote #,key) v)))))))))


;; A special form of case only useful in parse-recurrence-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 (stx)
   (syntax-case stx ()
     ((_ var-key obj (key val proc) ...)
      #`(case var-key
          #,@(map (handle-case stx #'obj)
                  #'(key ...)
                  #'(val ...)
                  #'(proc ...))
        (else obj))))))

(define-syntax all-in
  (syntax-rules ()
    ((_ var rules ...)
     (cut every (lambda (var) (and rules ...)) <>))))

(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 (%build-recur-rules str)
  (fold
   (lambda (kv obj)
     (let* (((key val) kv)
            ;; Lazy fields for the poor man.
            (symb (lambda () (string->symbol val)))
            (date (lambda () (parse-datetime val)))
            (num  (lambda () (string->number val)))
            (nums (lambda () (string->number-list val #\,))))
       (quick-case (string->symbol key) obj
                   (FREQ     (symb) (cut memv <> intervals)) ; Requirek
                   (UNTIL    (date) identity)
                   (COUNT    (num)  (cut <= 0 <>))
                   (INTERVAL (num)  (cut <= 0 <>))
                   (BYSECOND (nums) (all-in n (<= 0 n 60)))
                   (BYMINUTE (nums) (all-in n (<= 0 n 59)))
                   (BYHOUR   (nums) (all-in n (<= 0 n 23)))

                   ;; TODO
                   ;; <weekday> ∈ weekdays
                   ;; <weekdaynum> ::= [[±] <num>] <weekday> ;; +3MO
                   ;; (<weekadynum>, ...)
                   ;; (BYDAY (string-split val #\,))

                   (BYMONTHDAY (nums) (all-in n (<=  -31 n  31) (!= n 0)))
                   (BYYEARDAY  (nums) (all-in n (<= -366 n 366) (!= n 0)))
                   (BYWEEKNO   (nums) (all-in n (<=  -53 n  53) (!= n 0)))
                   (BYMONTH    (nums) (all-in n (<=    1 n  12)))
                   (BYSETPOS   (nums) (all-in n (<= -366 n 366) (!= n 0)))

                   (WKST (symb) (cut memv <> weekdays))
                   )))

   ;; obj
   (make-recur-rule 1 'MO)

   ;; ((key val) ...)
   (map (cut string-split <> #\=)
        (string-split str #\;))))