Browse code
Miscellaneous changes
- Explain README.org
- Add a clim utility for browsing objc classes
- Fix a bug in objc-msg-send-nsstring due to macro- vs runtime
- Optimize + provide new utilties
- Minor changes to the reading-list-reader
Showing 6 changed files
- README.org
- clim-objc-browser.lisp
- objc-data-extractors.lisp
- objc-runtime.asd
- objc-runtime.lisp
- reading-list-reader.lisp
... | ... |
@@ -66,7 +66,7 @@ From demo-app.lisp: |
66 | 66 |
** Type-directed Objective-C extractors |
67 | 67 |
|
68 | 68 |
#+name: extractor-framework |
69 |
- #+begin_src lisp :tangle no :results no |
|
69 |
+ #+begin_src lisp :tangle no :results no :comments both |
|
70 | 70 |
(defvar *objc-extractors* (list) |
71 | 71 |
"Functions called to extract specific data types") |
72 | 72 |
|
... | ... |
@@ -118,33 +118,39 @@ From demo-app.lisp: |
118 | 118 |
(defun main () |
119 | 119 |
<<disable-sbcl-debugger>> |
120 | 120 |
(make-org-file *standard-output* |
121 |
- (translate-plist |
|
122 |
- (get-bookmark-filename)) |
|
123 |
- (get-readinglist-info bookmarks))) |
|
121 |
+ (get-readinglist-info |
|
122 |
+ (translate-plist |
|
123 |
+ (get-bookmark-filename))))) |
|
124 | 124 |
#+end_src |
125 | 125 |
|
126 |
- This pair of functions builds an org file from data extracted from the Safari bookmark file. |
|
126 |
+ This pair of functions builds an org file from data extracted from the Safari bookmark file. |
|
127 | 127 |
|
128 | 128 |
#+name: make-org-file |
129 | 129 |
#+begin_src lisp :tangle no :results no |
130 |
- (defun make-org-file (s bookmarks reading-list-info) |
|
130 |
+ (defun make-org-file (s reading-list-info) |
|
131 | 131 |
(format s "~&* Safari Reading List~%") |
132 | 132 |
(serapeum:mapply (serapeum:partial 'make-org-entry s) |
133 | 133 |
reading-list-info)) |
134 | 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) |
|
135 |
+ (defun make-org-entry (s date title url preview tag) |
|
136 |
+ (format s "~&** ~a (~a) :~{~a:~}~% ~a~2% ~{~<~% ~1,80:;~a~> ~}~2%" |
|
137 |
+ title |
|
138 |
+ (local-time:format-timestring nil date |
|
139 |
+ :format local-time:+rfc3339-format/date-only+) |
|
140 |
+ (alexandria:ensure-list tag) |
|
138 | 141 |
url |
139 | 142 |
(serapeum:tokens preview))) |
140 | 143 |
#+end_src |
144 |
+ |
|
145 |
+ Here we extract the data from Bookmarks.plist using our polymorphic objc data extractor framework |
|
141 | 146 |
|
142 | 147 |
#+name: translate-plist |
143 | 148 |
#+begin_src lisp :tangle no :results no |
144 | 149 |
(defparameter *reading-list-location* "Library/Safari/Bookmarks.plist") |
145 | 150 |
(defun get-bookmark-filename () |
146 |
- (merge-pathnames *reading-list-location* |
|
147 |
- (truename "~/"))) |
|
151 |
+ (uiop:native-namestring |
|
152 |
+ (merge-pathnames *reading-list-location* |
|
153 |
+ (truename "~/")))) |
|
148 | 154 |
|
149 | 155 |
(defun translate-plist (fn) |
150 | 156 |
(objc-runtime.data-extractors:extract-from-objc |
... | ... |
@@ -154,37 +160,30 @@ From demo-app.lisp: |
154 | 160 |
#+name: translate-data |
155 | 161 |
#+begin_src lisp :tangle no :results no |
156 | 162 |
(defun get-readinglist-info (bookmarks) |
157 |
- (mapcar 'extract-link-info |
|
158 |
- (gethash "Children" |
|
159 |
- (car |
|
160 |
- (select-child bookmarks |
|
161 |
- "com.apple.ReadingList"))))) |
|
163 |
+ (sort (mapcar 'extract-link-info |
|
164 |
+ (gethash "Children" |
|
165 |
+ (car |
|
166 |
+ (select-child bookmarks |
|
167 |
+ "com.apple.ReadingList")))) |
|
168 |
+ 'local-time:timestamp> |
|
169 |
+ :key 'car)) |
|
162 | 170 |
|
163 | 171 |
(defun extract-link-info (link) |
164 |
- (list (fw.lu:pick '("URIDictionary" "title") link) |
|
172 |
+ (list (local-time:parse-rfc3339-timestring (or (fw.lu:pick '("ReadingList" "DateAdded") link) |
|
173 |
+ (fw.lu:pick '("ReadingList" "DateLastViewed") link) |
|
174 |
+ (fw.lu:pick '("ReadingListNonSync" "DateLastFetched") link) |
|
175 |
+ (local-time:now))) |
|
176 |
+ (fw.lu:pick '("URIDictionary" "title") link) |
|
165 | 177 |
(fw.lu:pick '("URLString") link) |
166 | 178 |
(plump:decode-entities (coerce (fw.lu:pick '("ReadingList" "PreviewText") link) 'simple-string) t) |
167 | 179 |
(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 | 180 |
#+end_src |
182 | 181 |
|
183 | 182 |
** Appendices |
184 | 183 |
|
185 | 184 |
*** objc-data-extractor.lisp |
186 | 185 |
|
187 |
- #+begin_src lisp :tangle objc-data-extractors.lisp :noweb yes |
|
186 |
+ #+begin_src lisp :tangle objc-data-extractors.lisp :noweb yes :comments both |
|
188 | 187 |
(defpackage :objc-runtime.data-extractors |
189 | 188 |
(:use :cl ) |
190 | 189 |
(:export |
... | ... |
@@ -320,10 +319,26 @@ From demo-app.lisp: |
320 | 319 |
(serapeum:eval-always |
321 | 320 |
(named-readtables:in-readtable :objc-readtable)) |
322 | 321 |
|
323 |
- <<r-l-r-main>> |
|
324 |
- <<translage-plist>> |
|
322 |
+ (defun slugify (s) |
|
323 |
+ (cl-ppcre:regex-replace-all "\\s+" |
|
324 |
+ (string-downcase s) |
|
325 |
+ "_")) |
|
326 |
+ |
|
327 |
+ (defun select-child (d title) |
|
328 |
+ (flet ((get-title (h) |
|
329 |
+ (equal (gethash "Title" h) |
|
330 |
+ title))) |
|
331 |
+ (fw.lu:let-each (:be *) |
|
332 |
+ (gethash "Children" d) |
|
333 |
+ (remove-if-not #'get-title *)))) |
|
334 |
+ |
|
335 |
+ <<translate-plist>> |
|
336 |
+ |
|
325 | 337 |
<<make-org-file>> |
338 |
+ |
|
326 | 339 |
<<translate-data>> |
340 |
+ |
|
341 |
+ <<r-l-r-main>> |
|
327 | 342 |
#+end_src |
328 | 343 |
|
329 | 344 |
#+name: disable-sbcl-debugger |
330 | 345 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,91 @@ |
1 |
+(defpackage :clim-objc-browser |
|
2 |
+ (:use :clim-lisp :clim)) |
|
3 |
+(in-package :clim-objc-browser) |
|
4 |
+ |
|
5 |
+(define-application-frame class-browser () |
|
6 |
+ ((classes :initarg :classes :reader classes) |
|
7 |
+ (visible-classes :initform nil :accessor visible-classes) |
|
8 |
+ (current-class :initform nil :accessor current-class)) |
|
9 |
+ (:panes (classes :application |
|
10 |
+ :incremental-redisplay t |
|
11 |
+ :display-function 'display-classes) |
|
12 |
+ (methods :application |
|
13 |
+ :incremental-redisplay t |
|
14 |
+ :display-function 'display-methods) |
|
15 |
+ (int :interactor)) |
|
16 |
+ (:pointer-documentation t) |
|
17 |
+ (:layouts (default (vertically () |
|
18 |
+ (horizontally () |
|
19 |
+ classes methods) |
|
20 |
+ int))) |
|
21 |
+ (:default-initargs |
|
22 |
+ :classes (sort (remove-if (serapeum:op (alexandria:starts-with #\_ |
|
23 |
+ (objc-runtime::objc-class-get-name _))) |
|
24 |
+ (objc-runtime::get-classes)) |
|
25 |
+ #'string-lessp |
|
26 |
+ :key 'objc-runtime::objc-class-get-name))) |
|
27 |
+ |
|
28 |
+(defun reset-application-frame () |
|
29 |
+ (setf (visible-classes clim:*application-frame*) nil |
|
30 |
+ (current-class clim:*application-frame*) nil |
|
31 |
+ (slot-value clim:*application-frame* 'classes) |
|
32 |
+ (sort (remove-if (serapeum:op (alexandria:starts-with #\_ |
|
33 |
+ (objc-runtime::objc-class-get-name _))) |
|
34 |
+ (objc-runtime::get-classes)) |
|
35 |
+ #'string-lessp |
|
36 |
+ :key 'objc-runtime::objc-class-get-name))) |
|
37 |
+ |
|
38 |
+(define-presentation-type objc-class ()) |
|
39 |
+(define-presentation-method present (object (type objc-class) stream view &key) |
|
40 |
+ (declare (ignore view)) |
|
41 |
+ (format stream "#[OBJC Class: ~a]" |
|
42 |
+ (objc-runtime::objc-class-get-name object))) |
|
43 |
+ |
|
44 |
+(define-presentation-type objc-method ()) |
|
45 |
+(define-presentation-method present (object (type objc-method) stream view &key) |
|
46 |
+ (declare (ignore view)) |
|
47 |
+ (format stream "@(~a)" |
|
48 |
+ (objc-runtime::get-method-name object))) |
|
49 |
+ |
|
50 |
+(defun display-classes (frame pane) |
|
51 |
+ (updating-output (pane :unique-id (or (visible-classes frame) |
|
52 |
+ (classes frame)) |
|
53 |
+ :id-test 'eq) |
|
54 |
+ (loop for class in (or (visible-classes frame) |
|
55 |
+ (classes frame)) |
|
56 |
+ do |
|
57 |
+ (updating-output (pane :unique-id (cffi:pointer-address class) |
|
58 |
+ :id-test 'eql |
|
59 |
+ :cache-value class |
|
60 |
+ :cache-test 'eql) |
|
61 |
+ (with-output-as-presentation (pane class 'objc-class) |
|
62 |
+ (format pane "~& ~a~%" (objc-runtime::objc-class-get-name class))))))) |
|
63 |
+ |
|
64 |
+(defun display-methods (frame pane) |
|
65 |
+ (updating-output (pane :unique-id (current-class frame) |
|
66 |
+ :id-test 'eq) |
|
67 |
+ (when (current-class frame) |
|
68 |
+ (loop for method in (sort (objc-runtime::get-methods (current-class frame)) |
|
69 |
+ 'string< |
|
70 |
+ :key 'objc-runtime::get-method-name) |
|
71 |
+ do |
|
72 |
+ (with-output-as-presentation (pane method 'objc-method) |
|
73 |
+ (format pane " Method: ~a~%" (objc-runtime::get-method-name method))))))) |
|
74 |
+ |
|
75 |
+(define-class-browser-command (com-get-methods :name t :menu t) ((the-class objc-class :gesture :select)) |
|
76 |
+ (setf (current-class *application-frame*) the-class)) |
|
77 |
+ |
|
78 |
+ |
|
79 |
+(define-class-browser-command (com-refresh-classes :name t :menu t) () |
|
80 |
+ (reset-application-frame)) |
|
81 |
+ |
|
82 |
+(define-class-browser-command (com-filter-classes :name t :menu t) ((prefix string)) |
|
83 |
+ (setf (visible-classes *application-frame*) |
|
84 |
+ (remove-if-not (serapeum:op |
|
85 |
+ (alexandria:starts-with-subseq prefix _ :test #'char-equal)) |
|
86 |
+ (classes *application-frame*) |
|
87 |
+ :key 'objc-runtime::objc-class-get-name))) |
|
88 |
+ |
|
89 |
+(defun main () |
|
90 |
+ (clim:run-frame-top-level |
|
91 |
+ (clim:make-application-frame 'class-browser))) |
... | ... |
@@ -1,3 +1,7 @@ |
1 |
+;; objc-data-extractor.lisp |
|
2 |
+ |
|
3 |
+ |
|
4 |
+;; [[file:~/git_repos/objc-lisp-bridge/README.org::*objc-data-extractor.lisp][objc-data-extractor.lisp:1]] |
|
1 | 5 |
(defpackage :objc-runtime.data-extractors |
2 | 6 |
(:use :cl ) |
3 | 7 |
(:export |
... | ... |
@@ -119,3 +123,4 @@ |
119 | 123 |
'objc-subclass-p |
120 | 124 |
:key 'car))) |
121 | 125 |
*objc-extractors*)) |
126 |
+;; objc-data-extractor.lisp:1 ends here |
... | ... |
@@ -10,10 +10,12 @@ |
10 | 10 |
#:serapeum |
11 | 11 |
#:fwoar.lisputils |
12 | 12 |
#:cffi |
13 |
+ #:cffi-libffi |
|
13 | 14 |
#:trivial-main-thread |
14 | 15 |
#:trivial-features |
15 | 16 |
#:cffi-libffi) |
16 |
- :defsystem-depends-on (#:cffi-grovel) |
|
17 |
+ :defsystem-depends-on (#:cffi-grovel |
|
18 |
+ #:cffi-libffi) |
|
17 | 19 |
:components ((:file "package") |
18 | 20 |
(:cffi-grovel-file "objc-runtime-types" :depends-on ("package")) |
19 | 21 |
(:file "readtable" :depends-on ("package")) |
... | ... |
@@ -99,8 +99,10 @@ |
99 | 99 |
(sel o-selector) |
100 | 100 |
&rest) |
101 | 101 |
|
102 |
-(defun objc-msg-send-nsstring (thing selector &rest args) |
|
103 |
- [(apply 'objc-msg-send thing selector args) @(UTF8String)]s) |
|
102 |
+;;; This is a macro, because objc-msg-send is a macro.... which makes "apply" impossible |
|
103 |
+;;; \o/ |
|
104 |
+(defmacro objc-msg-send-nsstring (thing selector &rest args) |
|
105 |
+ `[[,thing ,selector ,@args] @(UTF8String)]s) |
|
104 | 106 |
|
105 | 107 |
(defcfun (class-copy-method-list "class_copyMethodList" :library foundation) |
106 | 108 |
:pointer |
... | ... |
@@ -159,13 +161,12 @@ |
159 | 161 |
(prop :pointer)) |
160 | 162 |
|
161 | 163 |
(defun get-classes () |
162 |
- (let ((num-classes (objc-get-class-list (null-pointer) 0))) |
|
164 |
+ (let ((num-classes (objc-get-class-list (null-pointer) 0)) |
|
165 |
+ (result (list))) |
|
163 | 166 |
(with-foreign-object (classes :pointer num-classes) |
164 |
- (objc-get-class-list classes num-classes) |
|
165 |
- (let ((result (list))) |
|
166 |
- (dotimes (n num-classes (nreverse result)) |
|
167 |
+ (dotimes (n (objc-get-class-list classes num-classes) (nreverse result)) |
|
167 | 168 |
(push (mem-aref classes :pointer n) |
168 |
- result)))))) |
|
169 |
+ result))))) |
|
169 | 170 |
|
170 | 171 |
(defgeneric get-methods (class) |
171 | 172 |
(:method ((class string)) |
... | ... |
@@ -196,6 +197,9 @@ |
196 | 197 |
(defun extract-nsstring (ns-str) |
197 | 198 |
[ns-str @(UTF8String)]s) |
198 | 199 |
|
200 |
+(defun get-method-name (method) |
|
201 |
+ (sel-get-name (method-get-name method))) |
|
202 |
+ |
|
199 | 203 |
(defun get-method-names (thing) |
200 | 204 |
(mapcar (alexandria:compose #'sel-get-name |
201 | 205 |
#'method-get-name) |
... | ... |
@@ -6,46 +6,69 @@ |
6 | 6 |
(serapeum:eval-always |
7 | 7 |
(named-readtables:in-readtable :objc-readtable)) |
8 | 8 |
|
9 |
-(defun main () |
|
10 |
- #+(and build sbcl) |
|
11 |
- (progn (sb-ext:disable-debugger) |
|
12 |
- (sb-alien:alien-funcall |
|
13 |
- (sb-alien:extern-alien "disable_lossage_handler" |
|
14 |
- (function sb-alien:void)))) |
|
15 |
- (make-org-file *standard-output* |
|
16 |
- (translate-plist (get-bookmark-filename)))) |
|
9 |
+(defun slugify (s) |
|
10 |
+ (cl-ppcre:regex-replace-all "\\s+" |
|
11 |
+ (string-downcase s) |
|
12 |
+ "_")) |
|
13 |
+ |
|
14 |
+(defun select-child (d title) |
|
15 |
+ (flet ((get-title (h) |
|
16 |
+ (equal (gethash "Title" h) |
|
17 |
+ title))) |
|
18 |
+ (fw.lu:let-each (:be *) |
|
19 |
+ (gethash "Children" d) |
|
20 |
+ (remove-if-not #'get-title *)))) |
|
21 |
+ |
|
22 |
+(defparameter *reading-list-location* "Library/Safari/Bookmarks.plist") |
|
23 |
+(defun get-bookmark-filename () |
|
24 |
+ (uiop:native-namestring |
|
25 |
+ (merge-pathnames *reading-list-location* |
|
26 |
+ (truename "~/")))) |
|
27 |
+ |
|
28 |
+(defun translate-plist (fn) |
|
29 |
+ (objc-runtime.data-extractors:extract-from-objc |
|
30 |
+ (objc-runtime.data-extractors:get-plist fn))) |
|
17 | 31 |
|
18 |
-(defun make-org-file (s bookmarks) |
|
32 |
+(defun make-org-file (s reading-list-info) |
|
19 | 33 |
(format s "~&* Safari Reading List~%") |
20 | 34 |
(serapeum:mapply (serapeum:partial 'make-org-entry s) |
21 |
- (get-readinglist-info bookmarks))) |
|
22 |
-(defun make-org-entry (s title url preview tag) |
|
23 |
- (format s "~&** ~a :~{~a:~}~% ~a~2% ~{~<~% ~1,80:;~a~> ~}~%" |
|
24 |
- title (alexandria:ensure-list tag) |
|
35 |
+ reading-list-info)) |
|
36 |
+ |
|
37 |
+(defun make-org-entry (s date title url preview tag) |
|
38 |
+ (format s "~&** ~a (~a) :~{~a:~}~% ~a~2% ~{~<~% ~1,80:;~a~> ~}~2%" |
|
39 |
+ title |
|
40 |
+ (local-time:format-timestring nil date |
|
41 |
+ :format local-time:+rfc3339-format/date-only+) |
|
42 |
+ (alexandria:ensure-list tag) |
|
25 | 43 |
url |
26 | 44 |
(serapeum:tokens preview))) |
45 |
+ |
|
27 | 46 |
(defun get-readinglist-info (bookmarks) |
28 |
- (mapcar 'extract-link-info |
|
47 |
+ (sort (mapcar 'extract-link-info |
|
29 | 48 |
(gethash "Children" |
30 | 49 |
(car |
31 | 50 |
(select-child bookmarks |
32 |
- "com.apple.ReadingList"))))) |
|
51 |
+ "com.apple.ReadingList")))) |
|
52 |
+ 'local-time:timestamp> |
|
53 |
+ :key 'car)) |
|
33 | 54 |
|
34 | 55 |
(defun extract-link-info (link) |
35 |
- (list (fw.lu:pick '("URIDictionary" "title") link) |
|
56 |
+ (list (local-time:parse-rfc3339-timestring (or (fw.lu:pick '("ReadingList" "DateAdded") link) |
|
57 |
+ (fw.lu:pick '("ReadingList" "DateLastViewed") link) |
|
58 |
+ (fw.lu:pick '("ReadingListNonSync" "DateLastFetched") link) |
|
59 |
+ (local-time:now))) |
|
60 |
+ (fw.lu:pick '("URIDictionary" "title") link) |
|
36 | 61 |
(fw.lu:pick '("URLString") link) |
37 | 62 |
(plump:decode-entities (coerce (fw.lu:pick '("ReadingList" "PreviewText") link) 'simple-string) t) |
38 | 63 |
(fw.lu:may (slugify (fw.lu:pick '("ReadingListNonSync" "siteName") link))))) |
39 | 64 |
|
40 |
-(defun slugify (s) |
|
41 |
- (cl-ppcre:regex-replace-all "\\s+" |
|
42 |
- (string-downcase s) |
|
43 |
- "_")) |
|
44 |
- |
|
45 |
-(defun select-child (d title) |
|
46 |
- (flet ((get-title (h) |
|
47 |
- (equal (gethash "Title" h) |
|
48 |
- title))) |
|
49 |
- (fw.lu:let-each (:be *) |
|
50 |
- (gethash "Children" d) |
|
51 |
- (remove-if-not #'get-title *)))) |
|
65 |
+(defun main () |
|
66 |
+ #+(and build sbcl) |
|
67 |
+ (progn (sb-ext:disable-debugger) |
|
68 |
+ (sb-alien:alien-funcall |
|
69 |
+ (sb-alien:extern-alien "disable_lossage_handler" |
|
70 |
+ (function sb-alien:void)))) |
|
71 |
+ (make-org-file *standard-output* |
|
72 |
+ (get-readinglist-info |
|
73 |
+ (translate-plist |
|
74 |
+ (get-bookmark-filename))))) |