Browse code
Add more in-depth example to README
Ed Langley authored on 18/09/2018 16:46:25
Showing 1 changed files
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 |