git.fiddlerwoaroof.com
Browse code

Add more in-depth example to README

Ed Langley authored on 18/09/2018 16:46:25
Showing 1 changed files
... ...
@@ -61,3 +61,268 @@ From demo-app.lisp:
61 61
       [objc-runtime::ns-app @(run)]))
62 62
 
63 63
 #+END_SRC
64
+
65
+* In-depth example
66
+** Type-directed Objective-C extractors
67
+
68
+ #+name: extractor-framework
69
+ #+begin_src lisp :tangle no :results no
70
+   (defvar *objc-extractors* (list)
71
+     "Functions called to extract specific data types")
72
+
73
+   (defun extract-from-objc (obj)
74
+     (objc-typecase obj
75
+       (#@NSDate [[[[#@NSISO8601DateFormatter @(alloc)]
76
+                    @(init)]
77
+                   @(stringFromDate:) :pointer obj]
78
+                  @(UTF8String)]s)
79
+       (#@NSString [obj @(UTF8String)]s)
80
+       (#@NSNumber (parse-number:parse-number
81
+                    (objc-runtime::extract-nsstring
82
+                     [obj @(stringValue)])))
83
+       (#@NSArray (map-nsarray #'extract-from-objc obj))
84
+       (#@NSDictionary (fw.lu:alist-string-hash-table
85
+                        (pairlis (map-nsarray #'extract-from-objc [obj @(allKeys)])
86
+                                 (map-nsarray #'extract-from-objc [obj @(allValues)]))))
87
+       (t (or (funcall-some (cdr (objc-pick-by-type obj *objc-extractors*))
88
+                            obj)
89
+              obj))))
90
+
91
+   (defmacro define-extractor (class (o) &body body)
92
+     `(serapeum:eval-always
93
+        (add-extractor ,class
94
+                       (lambda (,o)
95
+                         ,@body))
96
+        ,*objc-extractors*))
97
+
98
+   (defun clear-extractors ()
99
+     (setf *objc-extractors* ()))
100
+
101
+   (serapeum:eval-always
102
+     (defun add-extractor (class cb)
103
+       (unless (member class *objc-extractors* :test 'cffi:pointer-eq :key #'car)
104
+         (setf *objc-extractors*
105
+               (merge 'list *objc-extractors* (list (cons class cb))
106
+                      'objc-subclass-p
107
+                      :key 'car)))
108
+       ,*objc-extractors*))
109
+ #+end_src
110
+
111
+** Reading List to Org-file converter
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)
138
+                      (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
174
+
175
+** Appendices
176
+  
177
+*** objc-data-extractor.lisp
178
+
179
+ #+begin_src lisp :tangle untangled/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
264
+
265
+*** build-reading-list-reader.sh
266
+   
267
+ #+begin_src sh :tangle untangled/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
284
+
285
+*** build.lisp
286
+
287
+ #+begin_src lisp :mkdirp yes :results no :noweb yes :tangle untangled/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")))
291
+
292
+   (eval-when (:compile-toplevel :load-toplevel :execute)
293
+     (ql:quickload '(:objc-runtime :yason :plump :cl-ppcre)))
294
+
295
+   (load "reading-list-reader.lisp")
296
+
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
303
+
304
+*** reading-list-reader.lisp
305
+
306
+ #+begin_src lisp :mkdirp yes :results no :noweb yes :tangle untangled/reading-list-reader.lisp 
307
+   (defpackage :reading-list-reader
308
+     (:use :cl )
309
+     (:export ))
310
+   (in-package :reading-list-reader)
311
+
312
+   (serapeum:eval-always
313
+     (named-readtables:in-readtable :objc-readtable))
314
+
315
+   <<r-l-r-main>>
316
+   <<translage-plist>>
317
+   <<make-org-file>>
318
+   <<translate-data>>
319
+ #+end_src
320
+
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