git.fiddlerwoaroof.com
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

Ed Langley authored on 10/10/2018 09:27:22
Showing 6 changed files
... ...
@@ -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)))))