Browse code
Add sample code for extracting reading list
Ed Langley authored on 12/09/2018 16:38:36
Showing 6 changed files
Showing 6 changed files
... | ... |
@@ -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)]))))) |
... | ... |
@@ -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) |