Browse code
add beginnings of expositions of the extractor framework
Ed Langley authored on 23/09/2018 09:47:55
Showing 1 changed files
Showing 1 changed files
... | ... |
@@ -110,220 +110,232 @@ From demo-app.lisp: |
110 | 110 |
|
111 | 111 |
** Reading List to Org-file converter |
112 | 112 |
|
113 |
- #+name: r-l-r-main |
|
114 |
- #+begin_src lisp :tangle no :results no :noweb yes |
|
115 |
- (defun main () |
|
116 |
- <<disable-sbcl-debugger>> |
|
117 |
- (make-org-file *standard-output* |
|
118 |
- (translate-plist (get-bookmark-filename)))) |
|
119 |
- #+end_src |
|
120 |
- |
|
121 |
- #+name: translate-plist |
|
122 |
- #+begin_src lisp :tangle no :results no |
|
123 |
- (defparameter *reading-list-location* "Library/Safari/Bookmarks.plist") |
|
124 |
- (defun get-bookmark-filename () |
|
125 |
- (merge-pathnames *reading-list-location* |
|
126 |
- (truename "~/"))) |
|
127 |
- |
|
128 |
- (defun translate-plist (fn) |
|
129 |
- (objc-runtime.data-extractors:extract-from-objc |
|
130 |
- (objc-runtime.data-extractors:get-plist fn))) |
|
131 |
- #+end_src |
|
132 |
- |
|
133 |
- #+name: make-org-file |
|
134 |
- #+begin_src lisp :tangle no :results no |
|
135 |
- (defun make-org-file (s bookmarks) |
|
136 |
- (format s "~&* Safari Reading List~%") |
|
137 |
- (serapeum:mapply (serapeum:partial 'make-org-entry s) |
|
113 |
+ The entry-point is fairly unremarkable: it delegates most of the work to other functions and disables the debugger so |
|
114 |
+ that this doesn't blow up when an error occurs in non-interactive mode. |
|
115 |
+ |
|
116 |
+ #+name: r-l-r-main |
|
117 |
+ #+begin_src lisp :tangle no :results no :noweb yes |
|
118 |
+ (defun main () |
|
119 |
+ <<disable-sbcl-debugger>> |
|
120 |
+ (make-org-file *standard-output* |
|
121 |
+ (translate-plist |
|
122 |
+ (get-bookmark-filename)) |
|
138 | 123 |
(get-readinglist-info bookmarks))) |
139 |
- (defun make-org-entry (s title url preview tag) |
|
140 |
- (format s "~&** ~a :~{~a:~}~% ~a~2% ~{~<~% ~1,80:;~a~> ~}~%" |
|
141 |
- title (alexandria:ensure-list tag) |
|
142 |
- url |
|
143 |
- (serapeum:tokens preview))) |
|
144 |
- #+end_src |
|
145 |
- |
|
146 |
- #+name: translate-data |
|
147 |
- #+begin_src lisp :tangle no :results no |
|
148 |
- (defun get-readinglist-info (bookmarks) |
|
149 |
- (mapcar 'extract-link-info |
|
150 |
- (gethash "Children" |
|
151 |
- (car |
|
152 |
- (select-child bookmarks |
|
153 |
- "com.apple.ReadingList"))))) |
|
154 |
- |
|
155 |
- (defun extract-link-info (link) |
|
156 |
- (list (fw.lu:pick '("URIDictionary" "title") link) |
|
157 |
- (fw.lu:pick '("URLString") link) |
|
158 |
- (plump:decode-entities (coerce (fw.lu:pick '("ReadingList" "PreviewText") link) 'simple-string) t) |
|
159 |
- (fw.lu:may (slugify (fw.lu:pick '("ReadingListNonSync" "siteName") link))))) |
|
160 |
- |
|
161 |
- (defun slugify (s) |
|
162 |
- (cl-ppcre:regex-replace-all "\\s+" |
|
163 |
- (string-downcase s) |
|
164 |
- "_")) |
|
165 |
- |
|
166 |
- (defun select-child (d title) |
|
167 |
- (flet ((get-title (h) |
|
168 |
- (equal (gethash "Title" h) |
|
169 |
- title))) |
|
170 |
- (fw.lu:let-each (:be *) |
|
171 |
- (gethash "Children" d) |
|
172 |
- (remove-if-not #'get-title *)))) |
|
173 |
- #+end_src |
|
124 |
+ #+end_src |
|
125 |
+ |
|
126 |
+ This pair of functions builds an org file from data extracted from the Safari bookmark file. |
|
127 |
+ |
|
128 |
+ #+name: make-org-file |
|
129 |
+ #+begin_src lisp :tangle no :results no |
|
130 |
+ (defun make-org-file (s bookmarks reading-list-info) |
|
131 |
+ (format s "~&* Safari Reading List~%") |
|
132 |
+ (serapeum:mapply (serapeum:partial 'make-org-entry s) |
|
133 |
+ reading-list-info)) |
|
134 |
+ |
|
135 |
+ (defun make-org-entry (s title url preview tag) |
|
136 |
+ (format s "~&** ~a :~{~a:~}~% ~a~2% ~{~<~% ~1,80:;~a~> ~}~%" |
|
137 |
+ title (alexandria:ensure-list tag) |
|
138 |
+ url |
|
139 |
+ (serapeum:tokens preview))) |
|
140 |
+ #+end_src |
|
141 |
+ |
|
142 |
+ #+name: translate-plist |
|
143 |
+ #+begin_src lisp :tangle no :results no |
|
144 |
+ (defparameter *reading-list-location* "Library/Safari/Bookmarks.plist") |
|
145 |
+ (defun get-bookmark-filename () |
|
146 |
+ (merge-pathnames *reading-list-location* |
|
147 |
+ (truename "~/"))) |
|
148 |
+ |
|
149 |
+ (defun translate-plist (fn) |
|
150 |
+ (objc-runtime.data-extractors:extract-from-objc |
|
151 |
+ (objc-runtime.data-extractors:get-plist fn))) |
|
152 |
+ #+end_src |
|
153 |
+ |
|
154 |
+ #+name: translate-data |
|
155 |
+ #+begin_src lisp :tangle no :results no |
|
156 |
+ (defun get-readinglist-info (bookmarks) |
|
157 |
+ (mapcar 'extract-link-info |
|
158 |
+ (gethash "Children" |
|
159 |
+ (car |
|
160 |
+ (select-child bookmarks |
|
161 |
+ "com.apple.ReadingList"))))) |
|
162 |
+ |
|
163 |
+ (defun extract-link-info (link) |
|
164 |
+ (list (fw.lu:pick '("URIDictionary" "title") link) |
|
165 |
+ (fw.lu:pick '("URLString") link) |
|
166 |
+ (plump:decode-entities (coerce (fw.lu:pick '("ReadingList" "PreviewText") link) 'simple-string) t) |
|
167 |
+ (fw.lu:may (slugify (fw.lu:pick '("ReadingListNonSync" "siteName") link))))) |
|
168 |
+ |
|
169 |
+ (defun slugify (s) |
|
170 |
+ (cl-ppcre:regex-replace-all "\\s+" |
|
171 |
+ (string-downcase s) |
|
172 |
+ "_")) |
|
173 |
+ |
|
174 |
+ (defun select-child (d title) |
|
175 |
+ (flet ((get-title (h) |
|
176 |
+ (equal (gethash "Title" h) |
|
177 |
+ title))) |
|
178 |
+ (fw.lu:let-each (:be *) |
|
179 |
+ (gethash "Children" d) |
|
180 |
+ (remove-if-not #'get-title *)))) |
|
181 |
+ #+end_src |
|
174 | 182 |
|
175 | 183 |
** Appendices |
176 | 184 |
|
177 | 185 |
*** objc-data-extractor.lisp |
178 | 186 |
|
179 |
- #+begin_src lisp :tangle objc-data-extractors.lisp :noweb yes |
|
180 |
- (defpackage :objc-runtime.data-extractors |
|
181 |
- (:use :cl ) |
|
182 |
- (:export |
|
183 |
- #:extract-from-objc |
|
184 |
- #:define-extractor |
|
185 |
- #:clear-extractors |
|
186 |
- #:add-extractor |
|
187 |
- #:get-plist)) |
|
188 |
- |
|
189 |
- (in-package :objc-runtime.data-extractors) |
|
190 |
- (named-readtables:in-readtable :objc-readtable) |
|
191 |
- |
|
192 |
- (defun get-plist (file) |
|
193 |
- [#@NSDictionary @(dictionaryWithContentsOfFile:) |
|
194 |
- :pointer (objc-runtime::make-nsstring file)]) |
|
195 |
- |
|
196 |
- (defun objc-subclass-p (sub super) |
|
197 |
- (unless (or (cffi:null-pointer-p sub) |
|
198 |
- (cffi:null-pointer-p super)) |
|
199 |
- (or (eql sub super) |
|
200 |
- (= [sub @(isSubclassOfClass:) :pointer [super @(class)]]# |
|
201 |
- 1)))) |
|
202 |
- |
|
203 |
- (defun order-objc-classes (classes &rest r &key key) |
|
204 |
- (declare (ignore key)) |
|
205 |
- (apply 'stable-sort |
|
206 |
- (copy-seq classes) |
|
207 |
- 'objc-subclass-p |
|
208 |
- r)) |
|
209 |
- |
|
210 |
- (defun objc-isa (obj class) |
|
211 |
- (unless (or (cffi:null-pointer-p obj) |
|
212 |
- (cffi:null-pointer-p class)) |
|
213 |
- (= [obj @(isKindOfClass:) :pointer class]# |
|
214 |
- 1))) |
|
215 |
- |
|
216 |
- (defun objc-pick-by-type (obj pairs) |
|
217 |
- (assoc obj |
|
218 |
- (order-objc-classes pairs :key 'car) |
|
219 |
- :test 'objc-isa)) |
|
220 |
- |
|
221 |
- (serapeum:eval-always |
|
222 |
- (defun make-cases (cases obj) |
|
223 |
- (mapcar (serapeum:op |
|
224 |
- `(if (objc-isa ,obj ,(car _1)) |
|
225 |
- (progn ,@(cdr _1)))) |
|
226 |
- cases))) |
|
227 |
- |
|
228 |
- (defmacro objc-typecase (form &body ((case-type &body case-handler) &rest cases)) |
|
229 |
- (alexandria:once-only (form) |
|
230 |
- (let* ((initial-cases `((,case-type ,@case-handler) ,@(butlast cases))) |
|
231 |
- (cases (fw.lu:rollup-list (make-cases initial-cases form) |
|
232 |
- (if (eql t (caar (last cases))) |
|
233 |
- `((progn ,@(cdar (last cases)))) |
|
234 |
- (make-cases (last cases) form))))) |
|
235 |
- cases))) |
|
236 |
- |
|
237 |
- (defun map-nsarray (fn arr) |
|
238 |
- (unless (and (cffi:pointerp arr) |
|
239 |
- (objc-isa arr #@NSArray)) |
|
240 |
- (error "must provide a NSArray pointer")) |
|
241 |
- (loop for x below [arr @(count)]# |
|
242 |
- collect (funcall fn [arr @(objectAtIndex:) :int x]))) |
|
243 |
- |
|
244 |
- (defun nsarray-contents (arr) |
|
245 |
- (unless (and (cffi:pointerp arr) |
|
246 |
- (objc-isa arr #@NSArray)) |
|
247 |
- (error "must provide a NSArray pointer")) |
|
248 |
- (dotimes (n [arr @(count)]#) |
|
249 |
- (let ((obj [arr @(objectAtIndex:) :int n ])) |
|
250 |
- (objc-typecase obj |
|
251 |
- (#@NSString (format t "~&string~%")) |
|
252 |
- (#@NSArray (format t "~&array~%")) |
|
253 |
- (#@NSDictionary (format t "~&dictionary~%")) |
|
254 |
- (t (format t "~&other... ~s~%" (objc-runtime::objc-class-get-name |
|
255 |
- (objc-runtime::object-get-class obj)))))))) |
|
256 |
- |
|
257 |
- (defmacro funcall-some (fun &rest args) |
|
258 |
- (alexandria:once-only (fun) |
|
259 |
- `(if ,fun |
|
260 |
- (funcall ,fun ,@args)))) |
|
261 |
- |
|
262 |
- <<extractor-framework>> |
|
263 |
- #+end_src |
|
187 |
+ #+begin_src lisp :tangle objc-data-extractors.lisp :noweb yes |
|
188 |
+ (defpackage :objc-runtime.data-extractors |
|
189 |
+ (:use :cl ) |
|
190 |
+ (:export |
|
191 |
+ #:extract-from-objc |
|
192 |
+ #:define-extractor |
|
193 |
+ #:clear-extractors |
|
194 |
+ #:add-extractor |
|
195 |
+ #:get-plist)) |
|
196 |
+ |
|
197 |
+ (in-package :objc-runtime.data-extractors) |
|
198 |
+ (named-readtables:in-readtable :objc-readtable) |
|
199 |
+ |
|
200 |
+ (defun get-plist (file) |
|
201 |
+ [#@NSDictionary @(dictionaryWithContentsOfFile:) |
|
202 |
+ :pointer (objc-runtime::make-nsstring file)]) |
|
203 |
+ |
|
204 |
+ (defun objc-subclass-p (sub super) |
|
205 |
+ (unless (or (cffi:null-pointer-p sub) |
|
206 |
+ (cffi:null-pointer-p super)) |
|
207 |
+ (or (eql sub super) |
|
208 |
+ (= [sub @(isSubclassOfClass:) :pointer [super @(class)]]# |
|
209 |
+ 1)))) |
|
210 |
+ |
|
211 |
+ (defun order-objc-classes (classes &rest r &key key) |
|
212 |
+ (declare (ignore key)) |
|
213 |
+ (apply 'stable-sort |
|
214 |
+ (copy-seq classes) |
|
215 |
+ 'objc-subclass-p |
|
216 |
+ r)) |
|
217 |
+ |
|
218 |
+ (defun objc-isa (obj class) |
|
219 |
+ (unless (or (cffi:null-pointer-p obj) |
|
220 |
+ (cffi:null-pointer-p class)) |
|
221 |
+ (= [obj @(isKindOfClass:) :pointer class]# |
|
222 |
+ 1))) |
|
223 |
+ |
|
224 |
+ (defun objc-pick-by-type (obj pairs) |
|
225 |
+ (assoc obj |
|
226 |
+ (order-objc-classes pairs :key 'car) |
|
227 |
+ :test 'objc-isa)) |
|
228 |
+ |
|
229 |
+ (serapeum:eval-always |
|
230 |
+ (defun make-cases (cases obj) |
|
231 |
+ (mapcar (serapeum:op |
|
232 |
+ `(if (objc-isa ,obj ,(car _1)) |
|
233 |
+ (progn ,@(cdr _1)))) |
|
234 |
+ cases))) |
|
235 |
+ |
|
236 |
+ (defmacro objc-typecase (form &body ((case-type &body case-handler) &rest cases)) |
|
237 |
+ (alexandria:once-only (form) |
|
238 |
+ (let* ((initial-cases `((,case-type ,@case-handler) ,@(butlast cases))) |
|
239 |
+ (cases (fw.lu:rollup-list (make-cases initial-cases form) |
|
240 |
+ (if (eql t (caar (last cases))) |
|
241 |
+ `((progn ,@(cdar (last cases)))) |
|
242 |
+ (make-cases (last cases) form))))) |
|
243 |
+ cases))) |
|
244 |
+ |
|
245 |
+ (defun map-nsarray (fn arr) |
|
246 |
+ (unless (and (cffi:pointerp arr) |
|
247 |
+ (objc-isa arr #@NSArray)) |
|
248 |
+ (error "must provide a NSArray pointer")) |
|
249 |
+ (loop for x below [arr @(count)]# |
|
250 |
+ collect (funcall fn [arr @(objectAtIndex:) :int x]))) |
|
251 |
+ |
|
252 |
+ (defun nsarray-contents (arr) |
|
253 |
+ (unless (and (cffi:pointerp arr) |
|
254 |
+ (objc-isa arr #@NSArray)) |
|
255 |
+ (error "must provide a NSArray pointer")) |
|
256 |
+ (dotimes (n [arr @(count)]#) |
|
257 |
+ (let ((obj [arr @(objectAtIndex:) :int n ])) |
|
258 |
+ (objc-typecase obj |
|
259 |
+ (#@NSString (format t "~&string~%")) |
|
260 |
+ (#@NSArray (format t "~&array~%")) |
|
261 |
+ (#@NSDictionary (format t "~&dictionary~%")) |
|
262 |
+ (t (format t "~&other... ~s~%" (objc-runtime::objc-class-get-name |
|
263 |
+ (objc-runtime::object-get-class obj)))))))) |
|
264 |
+ |
|
265 |
+ (defmacro funcall-some (fun &rest args) |
|
266 |
+ (alexandria:once-only (fun) |
|
267 |
+ `(if ,fun |
|
268 |
+ (funcall ,fun ,@args)))) |
|
269 |
+ |
|
270 |
+ <<extractor-framework>> |
|
271 |
+ #+end_src |
|
264 | 272 |
|
265 | 273 |
*** build-reading-list-reader.sh |
266 | 274 |
|
267 |
- #+begin_src sh :tangle build-reading-list-reader.sh |
|
268 |
- #!/usr/bin/env bash |
|
269 |
- set -eu -x -o pipefail |
|
270 |
- |
|
271 |
- cd "$(dirname $0)" |
|
272 |
- mkdir -p dist |
|
273 |
- |
|
274 |
- pushd dist |
|
275 |
- rm -rf fwoar.lisputils |
|
276 |
- git clone https://github.com/fiddlerwoaroof/fwoar.lisputils.git |
|
277 |
- popd |
|
278 |
- |
|
279 |
- export CL_SOURCE_REGISTRY="$PWD/dist//" |
|
280 |
- sbcl --no-userinit \ |
|
281 |
- --load ~/quicklisp/setup.lisp \ |
|
282 |
- --load build.lisp |
|
283 |
- #+end_src |
|
275 |
+ #+begin_src sh :tangle build-reading-list-reader.sh |
|
276 |
+ #!/usr/bin/env bash |
|
277 |
+ set -eu -x -o pipefail |
|
284 | 278 |
|
285 |
-*** build.lisp |
|
279 |
+ cd "$(dirname $0)" |
|
280 |
+ mkdir -p dist |
|
286 | 281 |
|
287 |
- #+begin_src lisp :mkdirp yes :results no :noweb yes :tangle build.lisp |
|
288 |
- (eval-when (:compile-toplevel :load-toplevel :execute) |
|
289 |
- (setf *default-pathname-defaults* (truename "~/git_repos/objc-lisp-bridge/")) |
|
290 |
- (load (compile-file "objc-runtime.asd"))) |
|
282 |
+ pushd dist |
|
283 |
+ rm -rf fwoar.lisputils |
|
284 |
+ git clone https://github.com/fiddlerwoaroof/fwoar.lisputils.git |
|
285 |
+ popd |
|
291 | 286 |
|
292 |
- (eval-when (:compile-toplevel :load-toplevel :execute) |
|
293 |
- (ql:quickload '(:objc-runtime :yason :plump :cl-ppcre))) |
|
287 |
+ export CL_SOURCE_REGISTRY="$PWD/dist//" |
|
288 |
+ sbcl --no-userinit \ |
|
289 |
+ --load ~/quicklisp/setup.lisp \ |
|
290 |
+ --load build.lisp |
|
291 |
+ #+end_src |
|
294 | 292 |
|
295 |
- (load "reading-list-reader.lisp") |
|
293 |
+*** build.lisp |
|
296 | 294 |
|
297 |
- (eval-when (:compile-toplevel :load-toplevel :execute) |
|
298 |
- (sb-ext:save-lisp-and-die "reading-list2org" |
|
299 |
- :toplevel (intern "MAIN" |
|
300 |
- "READING-LIST-READER") |
|
301 |
- :executable t)) |
|
302 |
- #+end_src |
|
295 |
+ #+begin_src lisp :mkdirp yes :results no :noweb yes :tangle build.lisp |
|
296 |
+ (eval-when (:compile-toplevel :load-toplevel :execute) |
|
297 |
+ (setf *default-pathname-defaults* (truename "~/git_repos/objc-lisp-bridge/")) |
|
298 |
+ (load (compile-file "objc-runtime.asd"))) |
|
303 | 299 |
|
304 |
-*** reading-list-reader.lisp |
|
300 |
+ (eval-when (:compile-toplevel :load-toplevel :execute) |
|
301 |
+ (ql:quickload '(:objc-runtime :yason :plump :cl-ppcre))) |
|
305 | 302 |
|
306 |
- #+begin_src lisp :mkdirp yes :results no :noweb yes :tangle reading-list-reader.lisp |
|
307 |
- (defpackage :reading-list-reader |
|
308 |
- (:use :cl ) |
|
309 |
- (:export )) |
|
310 |
- (in-package :reading-list-reader) |
|
303 |
+ (load "reading-list-reader.lisp") |
|
311 | 304 |
|
312 |
- (serapeum:eval-always |
|
313 |
- (named-readtables:in-readtable :objc-readtable)) |
|
305 |
+ (eval-when (:compile-toplevel :load-toplevel :execute) |
|
306 |
+ (sb-ext:save-lisp-and-die "reading-list2org" |
|
307 |
+ :toplevel (intern "MAIN" |
|
308 |
+ "READING-LIST-READER") |
|
309 |
+ :executable t)) |
|
310 |
+ #+end_src |
|
314 | 311 |
|
315 |
- <<r-l-r-main>> |
|
316 |
- <<translage-plist>> |
|
317 |
- <<make-org-file>> |
|
318 |
- <<translate-data>> |
|
319 |
- #+end_src |
|
312 |
+*** reading-list-reader.lisp |
|
320 | 313 |
|
321 |
- #+name: disable-sbcl-debugger |
|
322 |
- #+begin_src lisp :tangle no |
|
323 |
- ,#+(and build sbcl) |
|
324 |
- (progn (sb-ext:disable-debugger) |
|
325 |
- (sb-alien:alien-funcall |
|
326 |
- (sb-alien:extern-alien "disable_lossage_handler" |
|
327 |
- (function sb-alien:void)))) |
|
328 |
- #+end_src |
|
314 |
+ #+begin_src lisp :mkdirp yes :results no :noweb yes :tangle reading-list-reader.lisp |
|
315 |
+ (defpackage :reading-list-reader |
|
316 |
+ (:use :cl ) |
|
317 |
+ (:export )) |
|
318 |
+ (in-package :reading-list-reader) |
|
319 |
+ |
|
320 |
+ (serapeum:eval-always |
|
321 |
+ (named-readtables:in-readtable :objc-readtable)) |
|
322 |
+ |
|
323 |
+ <<r-l-r-main>> |
|
324 |
+ <<translage-plist>> |
|
325 |
+ <<make-org-file>> |
|
326 |
+ <<translate-data>> |
|
327 |
+ #+end_src |
|
328 |
+ |
|
329 |
+ #+name: disable-sbcl-debugger |
|
330 |
+ #+begin_src lisp :tangle no |
|
331 |
+ ,#+(and build sbcl) |
|
332 |
+ (progn (sb-ext:disable-debugger) |
|
333 |
+ (sb-alien:alien-funcall |
|
334 |
+ (sb-alien:extern-alien "disable_lossage_handler" |
|
335 |
+ (function sb-alien:void)))) |
|
336 |
+ #+end_src |
|
329 | 337 |
|
338 |
+ |
|
339 |
+# Local Variables: |
|
340 |
+# fill-column: 120 : |
|
341 |
+# End: |