git.fiddlerwoaroof.com
Browse code

feat: ics->sqlite

Edward authored on 14/03/2021 23:44:06
Showing 2 changed files
... ...
@@ -13,3 +13,18 @@
13 13
   :components ((:file "packages")
14 14
                (:file "parser")
15 15
                (:file "build-tree")))
16
+(defsystem :aion/sqlite
17
+  :description ""
18
+  :author "Ed L <edward@elangley.org>"
19
+  :license "MIT"
20
+  :depends-on (:aion
21
+               :alexandria
22
+               :cl-ppcre
23
+               :fwoar-lisputils
24
+               :local-time
25
+               :serapeum
26
+               :sqlite
27
+               :sxql
28
+               :trivia)
29
+  :serial t
30
+  :components ((:file "sqlite")))
16 31
new file mode 100644
... ...
@@ -0,0 +1,186 @@
1
+(defpackage :aion.sqlite
2
+  (:use :cl )
3
+  (:import-from :aion.parser
4
+                #:handle-begin #:handle-end
5
+                #:handle-property #:process-ics)
6
+  (:export
7
+   #:ics->sqlite))
8
+(in-package :aion.sqlite)
9
+
10
+(fw.lu:defclass+ emit-sql ()
11
+  ((%lines :accessor lines :initform ())
12
+   (%tzid :accessor tzid :initform nil)
13
+   (%db :reader db :initarg :db)))
14
+(defmethod handle-begin ((client emit-sql) block)
15
+  (values))
16
+(defmethod handle-end :after ((client emit-sql) block)
17
+  (setf (lines client) nil))
18
+(defmethod handle-end ((client emit-sql) block)
19
+  )
20
+(defmethod handle-end :before ((client emit-sql) block)
21
+  (values))
22
+(defmethod handle-end ((client emit-sql) (block (eql :vevent)))
23
+  (macrolet ((get-setter ()
24
+               `(sxql:set= ,@(mapcan (lambda (it)
25
+                                       (list it `(serapeum:assocadr
26
+                                                  ,(alexandria:make-keyword
27
+                                                    (substitute #\- #\_
28
+                                                                (string it)))
29
+                                                  (lines client))))
30
+                                     '(:X_APPLE_TRAVEL_ADVISORY_BEHAVIOR
31
+                                       :VEVENT :VALARM :RECURRENCE_ID
32
+                                       :ORGANIZER :LAST_MODIFIED
33
+                                       :EXDATE :CREATED :ATTENDEE
34
+                                       :ATTENDEE :ATTACH :CATEGORIES
35
+                                       :DESCRIPTION :DTEND :DTSTAMP
36
+                                       :DTSTART :GEO :LOCATION :RRULE
37
+                                       :SEQUENCE :STATUS :SUMMARY
38
+                                       :TRANSP :UID :URL
39
+                                       :X_ALT_DESC)))))
40
+    (multiple-value-bind (query params)
41
+        (sxql:yield
42
+         (sxql:insert-into :vevent
43
+           (get-setter)))
44
+      (apply 'sqlite:execute-single
45
+             (db client)
46
+             query
47
+             params))))
48
+(defmethod handle-property ((client emit-sql) tag params content)
49
+  (push (list tag content)
50
+        (lines client)))
51
+
52
+(defparameter +datetime-scanner+
53
+  (cl-ppcre:create-scanner
54
+   '(:SEQUENCE
55
+     :START-ANCHOR
56
+     ;; date yyyy-mm-dd
57
+     (:REGISTER (:GREEDY-REPETITION 4 4 :DIGIT-CLASS))
58
+     (:REGISTER (:GREEDY-REPETITION 2 2 :DIGIT-CLASS))
59
+     (:REGISTER (:GREEDY-REPETITION 2 2 :DIGIT-CLASS))
60
+     #\T
61
+     ;; time hh-mm-ss
62
+     (:REGISTER (:GREEDY-REPETITION 2 2 :DIGIT-CLASS))
63
+     (:REGISTER (:GREEDY-REPETITION 2 2 :DIGIT-CLASS))
64
+     (:REGISTER (:GREEDY-REPETITION 2 2 :DIGIT-CLASS))
65
+     ;; tz
66
+     (:GREEDY-REPETITION 0 1 (:REGISTER #\Z))
67
+     :END-ANCHOR)))
68
+(defparameter +date-scanner+
69
+  (cl-ppcre:create-scanner
70
+   '(:SEQUENCE
71
+     :START-ANCHOR
72
+     ;; date yyyy-mm-dd
73
+     (:REGISTER (:GREEDY-REPETITION 4 4 :DIGIT-CLASS))
74
+     (:REGISTER (:GREEDY-REPETITION 2 2 :DIGIT-CLASS))
75
+     (:REGISTER (:GREEDY-REPETITION 2 2 :DIGIT-CLASS))
76
+     :END-ANCHOR)))
77
+(defparameter +time-scanner+
78
+  (cl-ppcre:create-scanner
79
+   '(:SEQUENCE
80
+     :START-ANCHOR
81
+     ;; time hh-mm-ss
82
+     (:REGISTER (:GREEDY-REPETITION 2 2 :DIGIT-CLASS))
83
+     (:REGISTER (:GREEDY-REPETITION 2 2 :DIGIT-CLASS))
84
+     (:REGISTER (:GREEDY-REPETITION 2 2 :DIGIT-CLASS))
85
+     ;; tz
86
+     (:GREEDY-REPETITION 0 1 (:REGISTER #\Z))
87
+     :END-ANCHOR)))
88
+
89
+(defparameter +sqlite-format+
90
+  '((:YEAR 4) #\- (:MONTH 2) #\- (:DAY 2)
91
+    #\space (:HOUR 2) #\: (:MIN 2) #\: (:SEC 2)
92
+    :GMT-OFFSET-OR-Z))
93
+
94
+(defun parse-datetime (time timezone)
95
+  (trivia:ematch (nth-value 1 (cl-ppcre:scan-to-strings +datetime-scanner+ time))
96
+    (#(ye mo da ho mi se tz)
97
+      (local-time:encode-timestamp
98
+       0
99
+       (fw.lu:if-let* ((se (parse-integer se))
100
+                       (_ (= 60 se)))
101
+         59
102
+         se)
103
+       (parse-integer mi) (parse-integer ho)
104
+       (parse-integer da) (parse-integer mo) (parse-integer ye)
105
+       :timezone (if (equal tz "Z")
106
+                     local-time:+utc-zone+
107
+                     timezone)))))
108
+
109
+(defun parse-date (time)
110
+  (trivia:ematch (nth-value 1 (cl-ppcre:scan-to-strings +date-scanner+ time))
111
+    (#(ye mo da)
112
+      (local-time:encode-timestamp
113
+       0 0 0 0
114
+       (parse-integer da) (parse-integer mo) (parse-integer ye)))))
115
+
116
+;;what do I do for the date here???
117
+#+(or)
118
+(defun parse-time (time)
119
+  (trivia::match (nth-value 1 (cl-ppcre:scan-to-strings +time-scanner+ time))
120
+    (#(ho mi se)
121
+      (local-time:make-timestamp
122
+       0 0 0 0
123
+       (parse-integer da) (parse-integer mo) (parse-integer ye)))))
124
+
125
+(defun handle-ical-date (client tag params content)
126
+  (push (list tag
127
+              (local-time:format-timestring
128
+               nil
129
+               (case (alexandria:make-keyword
130
+                      (string-upcase
131
+                       (serapeum:assocadr :value params)))
132
+                 (:date (parse-date content))
133
+                 (t (parse-datetime content
134
+                                    (or (local-time:find-timezone-by-location-name
135
+                                         (serapeum:assocadr :tzid params))
136
+                                        (tzid client)))))
137
+               :format +sqlite-format+))
138
+        (lines client)))
139
+
140
+(defmethod handle-property ((client emit-sql) (tag (eql :dtstart)) params content)
141
+  (handle-ical-date client tag params content))
142
+(defmethod handle-property ((client emit-sql) (tag (eql :dtend)) params content)
143
+  (handle-ical-date client tag params content))
144
+(defmethod handle-property ((client emit-sql) (tag (eql :created)) params content)
145
+  (handle-ical-date client tag params content))
146
+(defmethod handle-property ((client emit-sql) (tag (eql :dtstamp)) params content)
147
+  (handle-ical-date client tag params content))
148
+
149
+(defun setup-sql ()
150
+  (sxql:yield
151
+   (sxql:create-table (:vevent :if-not-exists t)
152
+       ((:ATTACH :type 'text)
153
+        (:ATTENDEE :type 'text)
154
+        (:CATEGORIES :type 'text)
155
+        (:CREATED :type 'text)
156
+        (:DESCRIPTION :type 'text)
157
+        (:DTEND :type 'text)
158
+        (:DTSTAMP :type 'text)
159
+        (:DTSTART :type 'text)
160
+        (:EXDATE :type 'text)
161
+        (:GEO :type 'text)
162
+        (:LAST_MODIFIED :type 'text)
163
+        (:LOCATION :type 'text)
164
+        (:ORGANIZER :type 'text)
165
+        (:RECURRENCE_ID :type 'text)
166
+        (:RRULE :type 'text)
167
+        (:SEQUENCE :type 'text)
168
+        (:STATUS :type 'text)
169
+        (:SUMMARY :type 'text)
170
+        (:TRANSP :type 'text)
171
+        (:UID :type 'text)
172
+        (:URL :type 'text)
173
+        (:VALARM :type 'text)
174
+        (:VEVENT :type 'text)
175
+        (:X_ALT_DESC :type 'text)
176
+        (:X_APPLE_TRAVEL_ADVISORY_BEHAVIOR :type 'text))
177
+     (sxql:primary-key '(:sequence :uid :recurrence_id)))))
178
+
179
+(defun ics->sqlite (fn data)
180
+  (sqlite:with-open-database (db fn)
181
+    (sqlite:execute-non-query
182
+     db (sxql:yield (sxql:drop-table :vevent :if-exists t)))
183
+    (sqlite:execute-non-query
184
+     db (setup-sql))
185
+    (sqlite:with-transaction db
186
+      (process-ics (emit-sql db) data))))