git.fiddlerwoaroof.com
Browse code

Add sample code for extracting reading list

Ed Langley authored on 12/09/2018 16:38:36
Showing 6 changed files
... ...
@@ -29,4 +29,5 @@ demo.app
29 29
 NSRect-Expose
30 30
 *.nib
31 31
 .*.sw?
32
-venv
33 32
\ No newline at end of file
33
+venv
34
+/reading-list2org
... ...
@@ -8,7 +8,7 @@ dylib: nsrect-expose.m
8 8
 
9 9
 demo-app: dylib
10 10
 	$(CCL) --load ~/quicklisp/setup.lisp \
11
-           --load save.lisp
11
+		   --load save.lisp
12 12
 
13 13
 demo-app.iconset: demo-app.svg
14 14
 	rm -rf demo-app.iconset
... ...
@@ -153,13 +153,14 @@
153 153
            :pointer p])))
154 154
 
155 155
 ;#+null
156
-(defun main ()
156
+(defun old-code ()
157 157
   #+sbcl
158 158
   (sb-int:set-floating-point-modes :traps '())
159 159
 
160 160
   (trivial-main-thread:with-body-in-main-thread (:blocking t)
161 161
     [#@NSAutoReleasePool @(new)]
162 162
     [#@NSApplication @(sharedApplication)]
163
+    #+nil
163 164
     [objc-runtime::ns-app @(setActivationPolicy:) :int 0]
164 165
 
165 166
     ;; Setup the app delegate class. We register this one because it's useful
... ...
@@ -187,39 +188,47 @@
187 188
 
188 189
 (defparameter *application-shim* (make-instance 'application-shim))
189 190
 
190
-;#+nil
191
-(defun old-code ()
192
-  #+null
191
+                                        ;#+nil
192
+(defun main ()
193
+  (load "~/quicklisp/setup.lisp")
194
+  (funcall (intern "QUICKLOAD" (find-package :QL)) :swank)
195
+  (funcall (intern "CREATE-SERVER" (find-package :swank)) :port 5060 :dont-close t)
196
+
193 197
   (trivial-main-thread:with-body-in-main-thread (:blocking t)
194
-    (sb-int:with-float-traps-masked
195
-     (:underflow :overflow :inexact
196
-                 :invalid :divide-by-zero)))
197
-  [#@NSAutoReleasePool @(new)]
198
-  [#@NSApplication @(sharedApplication)]
199
-  [objc-runtime::ns-app @(setActivationPolicy) :int 0]
200
-
201
-  (let* ((application-name [[#@NSProcessInfo @(processInfo)] @(processName)]))
202
-    (let* ((menubar [[#@NSMenu @(new)] @(autorelease)])
203
-           (app-menu-item [[#@NSMenuItem @(new)] @(autorelease)])
204
-           (app-menu [[#@NSMenu @(new)] @(autorelease)])
205
-           (quit-name [[#@NSString @(alloc)] @(initWithEncoding) :string "Quit" :uint 4])
206
-           (key [[#@NSString @(alloc)] @(initWithCString:length:) :string "q" :uint 1])
207
-           (quit-menu-item
208
-            [[[#@NSMenuItem @(alloc)] @(initWithTitle:action:keyEquivalent:) :pointer quit-name :pointer @(terminate?) :string key] @(autorelease)]))
209
-      [menubar @(addItem:) :pointer app-menu-item]
210
-      [app-menu @(addItem:) :pointer quit-menu-item]
211
-      [app-menu-item @(setSubmenu:) :pointer app-menu]
212
-      [objc-runtime::ns-app @(setMainMenu:) :pointer menubar] )
213
-
214
-    (setf (main-view *application-shim*)
215
-          [#@NSStackView @(stackViewWithViews:) :pointer [[#@NSArray @(alloc)] @(init)]])
216
-    (with-point (p (20 20))
217
-      (let* ((foreign-rect (make-rect 10 10 120 120))
218
-             (the-window (init-window [#@NSWindow @(alloc)] foreign-rect 1 2 nil)))
219
-        
220
-        [(value-for-key the-window "contentView") @(addSubview:) :pointer (main-view *application-shim*)]
221
-        [the-window @(cascadeTopLeftFromPoint:) :pointer p]
222
-        [the-window @(setTitle:) :pointer application-name]
223
-        [the-window @(makeKeyAndOrderFront:) :pointer (cffi:null-pointer)]
224
-        [ objc-runtime::ns-app @(activateIgnoringOtherApps:) :boolean t]
225
-        [ objc-runtime::ns-app @(run)]))))
198
+    #+sbcl
199
+    (sb-int:set-floating-point-modes :traps '())
200
+
201
+    [#@NSAutoReleasePool @(new)]
202
+    [#@NSApplication @(sharedApplication)]
203
+
204
+    (format t "~&app: ~s~%" objc-runtime::ns-app)
205
+    #+nil
206
+    [objc-runtime::ns-app @(setActivationPolicy) :int 0]
207
+
208
+    (let* ((application-name [[#@NSProcessInfo @(processInfo)] @(processName)]))
209
+      (let* ((menubar [[#@NSMenu @(new)] @(autorelease)])
210
+             (app-menu-item [[#@NSMenuItem @(new)] @(autorelease)])
211
+             (app-menu [[#@NSMenu @(new)] @(autorelease)])
212
+             (quit-name @"Quit")
213
+             (key @"q")
214
+             (quit-menu-item
215
+              [[[#@NSMenuItem @(alloc)]
216
+                @(initWithTitle:action:keyEquivalent:) :pointer quit-name :pointer @(terminate?) :string key]
217
+               @(autorelease)]))
218
+        [menubar @(addItem:) :pointer app-menu-item]
219
+        [app-menu @(addItem:) :pointer quit-menu-item]
220
+        [app-menu-item @(setSubmenu:) :pointer app-menu]
221
+        [objc-runtime::ns-app @(setMainMenu:) :pointer menubar] )
222
+
223
+      (setf (main-view *application-shim*)
224
+            [#@NSStackView @(stackViewWithViews:) :pointer [[#@NSArray @(alloc)] @(init)]])
225
+      (with-point (p (20 20))
226
+        (let* ((foreign-rect (make-rect 10 10 120 120))
227
+               (the-window (init-window [#@NSWindow @(alloc)] foreign-rect 1 2 nil)))
228
+          
229
+          [(value-for-key the-window "contentView") @(addSubview:) :pointer (main-view *application-shim*)]
230
+          [the-window @(cascadeTopLeftFromPoint:) :pointer p]
231
+          [the-window @(setTitle:) :pointer application-name]
232
+          [the-window @(makeKeyAndOrderFront:) :pointer (cffi:null-pointer)]
233
+          [ objc-runtime::ns-app @(activateIgnoringOtherApps:) :boolean t]
234
+          [ objc-runtime::ns-app @(run)])))))
... ...
@@ -33,6 +33,3 @@
33 33
        (declare (ignorable ,context-sym))
34 34
        ,@body)
35 35
      (define-symbol-macro ,name (callback ,name))))
36
-
37
-(def-gcd-callback foo-callback (context)
38
-  (demo-app::show-alert "Uh-oh"))
... ...
@@ -10,7 +10,7 @@
10 10
   (define-foreign-library appkit
11 11
     (:darwin (:framework "AppKit")))
12 12
   (define-foreign-library expose-stuff
13
-    (:darwin #p"./libnsrect-expose.dylib")))
13
+    (:darwin #p"libnsrect-expose.dylib")))
14 14
 
15 15
 
16 16
 (use-foreign-library foundation)
... ...
@@ -193,6 +193,8 @@
193 193
 (defun make-nsstring (str)
194 194
   [[#@NSString @(alloc)] @(initWithCString:encoding:) :string str :uint 1])
195 195
 
196
+(defun extract-nsstring (ns-str)
197
+  [ns-str @(UTF8String)]s)
196 198
 
197 199
 (defun get-method-names (thing)
198 200
   (mapcar (alexandria:compose #'sel-get-name
199 201
new file mode 100644
... ...
@@ -0,0 +1,132 @@
1
+#+build
2
+(eval-when (:compile-toplevel :load-toplevel :execute)
3
+  (setf *default-pathname-defaults* (truename "~/git_repos/objc-lisp-bridge/"))
4
+  (load (compile-file "objc-runtime.asd")))
5
+
6
+#+build
7
+(eval-when (:compile-toplevel :load-toplevel :execute)
8
+  (ql:quickload '(:objc-runtime :yason :plump :cl-ppcre)))
9
+
10
+(defpackage :reading-list-reader
11
+  (:use :cl )
12
+  (:export ))
13
+(in-package :reading-list-reader)
14
+
15
+(serapeum:eval-always
16
+ (named-readtables:in-readtable :objc-readtable))
17
+
18
+(defparameter *reading-list-location* "~/Library/Safari/Bookmarks.plist")
19
+
20
+(defun get-plist (file)
21
+  [#@NSDictionary @(dictionaryWithContentsOfFile:)
22
+                  :pointer (objc-runtime::make-nsstring file)])
23
+
24
+
25
+(defun objc-isa (obj class)
26
+  (unless (or (cffi:null-pointer-p obj)
27
+              (cffi:null-pointer-p class))
28
+    (= [obj @(isKindOfClass:) :pointer class]#
29
+       1)))
30
+
31
+(serapeum:eval-always
32
+  (defun make-cases (cases obj)
33
+    (mapcar (serapeum:op
34
+              `(if (objc-isa ,obj ,(car _1))
35
+                   (progn ,@(cdr _1))))
36
+            cases)))
37
+
38
+(defmacro objc-typecase (form &body ((case-type &body case-handler) &rest cases))
39
+  (alexandria:once-only (form)
40
+    (let* ((initial-cases `((,case-type ,@case-handler) ,@(butlast cases)))
41
+           (cases (fw.lu:rollup-list (make-cases initial-cases form)
42
+                                     (if (eql t (caar (last cases)))
43
+                                         `((progn ,@(cdar (last cases))))
44
+                                         (make-cases (last cases) form)))))
45
+      cases)))
46
+
47
+(defun map-nsarray (fn arr)
48
+  (unless (and (cffi:pointerp arr)
49
+               (objc-isa arr #@NSArray))
50
+    (error "must provide a NSArray pointer"))
51
+  (loop for x below [arr @(count)]#
52
+       collect (funcall fn [arr @(objectAtIndex:) :int x])))
53
+
54
+(defun nsarray-contents (arr)
55
+  (unless (and (cffi:pointerp arr)
56
+               (objc-isa arr #@NSArray))
57
+    (error "must provide a NSArray pointer"))
58
+  (dotimes (n [arr @(count)]#)
59
+    (let ((obj [arr @(objectAtIndex:) :int n ]))
60
+      (objc-typecase obj
61
+        (#@NSString (format t "~&string~%"))
62
+        (#@NSArray (format t "~&array~%"))
63
+        (#@NSDictionary (format t "~&dictionary~%"))
64
+        (t (format t "~&other... ~s~%" (objc-runtime::objc-class-get-name
65
+                                        (objc-runtime::object-get-class obj))))))))
66
+
67
+(defun extract-from-objc (obj)
68
+  (objc-typecase obj
69
+    (#@NSDate [[[[#@NSISO8601DateFormatter @(alloc)]
70
+                 @(init)]
71
+                @(stringFromDate:) :pointer obj]
72
+               @(UTF8String)]s)
73
+    (#@NSString [obj @(UTF8String)]s)
74
+    (#@NSNumber (parse-number:parse-number
75
+                 (objc-runtime::extract-nsstring
76
+                  [obj @(stringValue)])))
77
+    (#@NSArray (map-nsarray #'extract-from-objc obj))
78
+    (#@NSDictionary (fw.lu:alist-string-hash-table
79
+                     (pairlis (map-nsarray #'extract-from-objc [obj @(allKeys)])
80
+                              (map-nsarray #'extract-from-objc [obj @(allValues)]))))
81
+    (t (format *error-output* "~&other... ~s~%" (objc-runtime::objc-class-get-name
82
+                                                 (objc-runtime::object-get-class obj))))))
83
+
84
+(defun extract-nsdictionary (nsdict)
85
+  (yason:with-output (*standard-output* :indent t)
86
+    (maphash 'yason:encode-object-element
87
+             (extract-from-objc nsdict))))
88
+
89
+(defun select-child (d title)
90
+  (remove-if-not (serapeum:op
91
+                   (equal (gethash "Title" _)
92
+                          title))
93
+                 (gethash "Children" d)))
94
+
95
+(defun slugify (s)
96
+  (when s
97
+    (cl-ppcre:regex-replace-all "\\s+" (string-downcase s) "_")))
98
+
99
+(defun make-org-entry (s title url preview tag)
100
+  (format s "~&** ~a :~{~a:~}~% ~a~2% ~{~<~% ~1,80:;~a~> ~}~%"
101
+          title (alexandria:ensure-list tag)
102
+          url
103
+          (serapeum:tokens preview)))
104
+
105
+(defun get-readinglist-info (bookmarks)
106
+  (mapcar (serapeum:juxt
107
+           (fw.lu:op (fw.lu:pick '("URIDictionary" "title") _))
108
+           (fw.lu:op (fw.lu:pick '("URLString") _))
109
+           (fw.lu:op (plump:decode-entities (coerce (fw.lu:pick '("ReadingList" "PreviewText") _)
110
+                                                    'simple-string)
111
+                                            t))
112
+           (fw.lu:op (slugify (fw.lu:pick '("ReadingListNonSync" "siteName") _))))
113
+          (gethash "Children"
114
+                   (car
115
+                    (select-child bookmarks "com.apple.ReadingList")))))
116
+
117
+
118
+(defun make-org-file (s bookmarks)
119
+  (format s "~&* Safari Reading List~%")
120
+  (serapeum:mapply (serapeum:partial 'make-org-entry s)
121
+                   (get-readinglist-info bookmarks)))
122
+
123
+(defun main ()
124
+  #+(and build sbcl)
125
+  (progn (sb-ext:disable-debugger)
126
+         (sb-alien:alien-funcall
127
+          (sb-alien:extern-alien "disable_lossage_handler" (function sb-alien:void))))
128
+  (make-org-file *standard-output*
129
+                 (extract-from-objc (get-plist (uiop:unix-namestring (truename *reading-list-location*))))))
130
+
131
+#+build
132
+(sb-ext:save-lisp-and-die "reading-list2org" :toplevel 'main :executable t)