Browse code
feat: ics->sqlite
Edward authored on 14/03/2021 23:44:06
Showing 2 changed files
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)))) |