Compare commits
No commits in common. "299a529151bc8290b8bf288e53825f9b5c462d79" and "780951faaa1fe184f2c65fb6bdaacee5666a39df" have entirely different histories.
299a529151
...
780951faaa
|
@ -22,10 +22,6 @@
|
||||||
;; One should never USE this package, since some class-names shadow
|
;; One should never USE this package, since some class-names shadow
|
||||||
;; core Common Lisp symbols! Beware! :P
|
;; core Common Lisp symbols! Beware! :P
|
||||||
(:export
|
(:export
|
||||||
;; Functions
|
|
||||||
:parse
|
|
||||||
;; Globals
|
|
||||||
:*ap-packages*
|
|
||||||
;; Classes
|
;; Classes
|
||||||
:accept :activity :add :announce :application :arrive :article :audio
|
:accept :activity :add :announce :application :arrive :article :audio
|
||||||
:collection :collection-page :create :delete :dislike :document :event :flag
|
:collection :collection-page :create :delete :dislike :document :event :flag
|
||||||
|
@ -63,18 +59,11 @@
|
||||||
|
|
||||||
;;; Globals
|
;;; Globals
|
||||||
;;; ————————————————————————————————————————
|
;;; ————————————————————————————————————————
|
||||||
(defparameter *ap-packages* (list *package*)
|
;; Used in YASON:ENCODE to ensure that a single top-level @context can be
|
||||||
"A list of packages in which we should search for AP classes and slot-symbols
|
;; created where AP objects contain other AP objects in their slots.
|
||||||
during JSON parsing. The class-name searched for is simply the value of the JSON
|
;; This variable is overridden locally (LET), and should never be
|
||||||
object’s “type” key. The package first in the list to export such a symbol
|
;; modified globally (as we expect it to be nil in top-level objects).
|
||||||
is the winner.")
|
(defparameter *@context* nil)
|
||||||
|
|
||||||
;; Private, internal variable.
|
|
||||||
(defparameter *@context* nil
|
|
||||||
"Used in YASON:ENCODE to ensure that a single top-level @context can be
|
|
||||||
created where AP objects contain other AP objects in their slots.
|
|
||||||
This variable is overridden locally during encoding (LET), and should never be
|
|
||||||
modified globally (as we expect it to be nil in top-level objects.")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -84,13 +73,18 @@ modified globally (as we expect it to be nil in top-level objects.")
|
||||||
"Identical to DEFCLASS, but with one convenience: A slot definition, if being
|
"Identical to DEFCLASS, but with one convenience: A slot definition, if being
|
||||||
simply a symbol, will default to a slot with an accessor and init-arg named after the
|
simply a symbol, will default to a slot with an accessor and init-arg named after the
|
||||||
symbol. The init-arg will be “:symbol”, and the accessor will be “classname-symbol”.
|
symbol. The init-arg will be “:symbol”, and the accessor will be “classname-symbol”.
|
||||||
For example, the following two forms are equivalent:
|
For instance,
|
||||||
|
|
||||||
(defclass-w-accessors PERSON () (AGE
|
(defclass-w-accessors PERSON () (AGE
|
||||||
HEIGHT
|
HEIGHT
|
||||||
(NAME :INIT-FORM “Unknown”)))
|
(NAME :INIT-FORM “Unknown”)))
|
||||||
|
```
|
||||||
|
is equivalent to
|
||||||
|
```
|
||||||
(defclass PERSON () ((AGE :INIT-ARG :AGE :ACCESSOR PERSON-AGE)
|
(defclass PERSON () ((AGE :INIT-ARG :AGE :ACCESSOR PERSON-AGE)
|
||||||
(HEIGHT :INIT-ARG :HEIGHT :ACCESSOR PERSON-HEIGHT)
|
(HEIGHT :INIT-ARG :HEIGHT :ACCESSOR PERSON-HEIGHT)
|
||||||
(NAME :INIT-FORM “Unknown”)))"
|
(NAME :INIT-FORM “Unknown”)))
|
||||||
|
```"
|
||||||
`(defclass ,name ,direct-superclasses
|
`(defclass ,name ,direct-superclasses
|
||||||
,(mapcar
|
,(mapcar
|
||||||
(lambda (slot)
|
(lambda (slot)
|
||||||
|
@ -228,53 +222,38 @@ again and again, by YASON:ENCODE-SLOTS."
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
;;; JSON parsing
|
;;; Util
|
||||||
;;; ————————————————————————————————————————
|
;;; ————————————————————————————————————————
|
||||||
(defun parse (string)
|
(defun camel-case (string)
|
||||||
"Parse a string containing JSON into an ActivityPub object."
|
"Convert a STRING to camel-casing.
|
||||||
(parse-table (yason:parse string)))
|
Wrapper around STR:CAMEL-CASE, working around a bug that a non-alphanumeric
|
||||||
|
character at the start of the string gets erroneously (or at least undesireably,
|
||||||
|
to us) removed."
|
||||||
|
(if (not (alphanumericp (aref string 0)))
|
||||||
|
(concatenate 'string
|
||||||
|
(string (aref string 0))
|
||||||
|
(str:camel-case string))
|
||||||
|
(str:camel-case string)))
|
||||||
|
|
||||||
(defun parse-table (table)
|
(defun class-pretty-name (class)
|
||||||
"Parse a hash-table corresponding to YASON-parsed JSON into an ActivityPub object."
|
"Return a CLASS’es name in a “pretty” (sentence-capitalized) string."
|
||||||
(let* ((class (car (find-registered-classes (gethash "type" table))))
|
(string-capitalize (symbol-name (class-name class))))
|
||||||
(obj (make-instance class)))
|
|
||||||
(loop for key being each hash-key of table
|
|
||||||
for val being each hash-value of table
|
|
||||||
do (let ((slot-sym (car (find-registered-symbols key)))
|
|
||||||
(val (parse-value val)))
|
|
||||||
(when slot-sym
|
|
||||||
(setf (slot-value obj slot-sym) val))))
|
|
||||||
obj))
|
|
||||||
|
|
||||||
(defun parse-value (val)
|
(defun merge-lists (a b)
|
||||||
"Parse the value of a key found in YASON-parsed JSON.
|
"Given lists A and B, merge them into one list non-redundantly — all unique
|
||||||
All ActivityPub objects (hash-tables containing “type”) will be parsed into
|
items in each will be contained in the resultant list."
|
||||||
ActivityPub objects; all others will parsed into associative lists."
|
(append a (remove-if (lambda (item) (find item a :test #'equal)) b)))
|
||||||
(typecase val
|
|
||||||
(hash-table (maybe-parse-table val))
|
|
||||||
(list (mapcar (lambda (a)
|
|
||||||
(if (hash-table-p a)
|
|
||||||
(maybe-parse-table a)
|
|
||||||
a))
|
|
||||||
val))
|
|
||||||
(t val)))
|
|
||||||
|
|
||||||
(defun maybe-parse-table (table)
|
|
||||||
"If a hash-table seems to be a valid ActivityPub object, attempt parsing it
|
|
||||||
into one. Otherwise, parse it into an associative list."
|
|
||||||
(if (gethash "type" table)
|
|
||||||
(parse-table table)
|
|
||||||
(alexandria:hash-table-alist table)))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
;;; JSON serialization
|
;;; JSON serialization
|
||||||
;;; ————————————————————————————————————————
|
;;; ————————————————————————————————————————
|
||||||
(defmethod yason:encode ((obj object) &optional (stream *standard-output*))
|
(defmethod yason:encode ((obj object) &optional (stream *standard-output*))
|
||||||
|
(yason:with-output (stream)
|
||||||
(yason:with-object ()
|
(yason:with-object ()
|
||||||
(if *@context* ; If this object is nested in another, use old context.
|
(if *@context*
|
||||||
(yason:encode-object obj)
|
(yason:encode-object obj)
|
||||||
(let ((*@context* (slot-value obj '@context))) ; Unnested, so create context.
|
(let ((*@context* (slot-value obj '@context)))
|
||||||
(yason:encode-object obj)
|
(yason:encode-object obj)
|
||||||
(yason:encode-object-element
|
(yason:encode-object-element
|
||||||
"@context"
|
"@context"
|
||||||
|
@ -282,7 +261,7 @@ into one. Otherwise, parse it into an associative list."
|
||||||
(yason:encode-object-element
|
(yason:encode-object-element
|
||||||
"type"
|
"type"
|
||||||
(or (object-type obj)
|
(or (object-type obj)
|
||||||
(class-pretty-name (class-of obj))))))
|
(class-pretty-name (class-of obj)))))))
|
||||||
|
|
||||||
(defun class-slots-to-camel-cased-strings-alist (class)
|
(defun class-slots-to-camel-cased-strings-alist (class)
|
||||||
"Return an associative list of a CLASS’es direct slots (by symbol) matched with
|
"Return an associative list of a CLASS’es direct slots (by symbol) matched with
|
||||||
|
@ -307,47 +286,7 @@ containing both of their elements."
|
||||||
(if (listp a) a (list a))
|
(if (listp a) a (list a))
|
||||||
(if (listp b) b (list b))))))
|
(if (listp b) b (list b))))))
|
||||||
|
|
||||||
|
;; Ensure all classes have their slots’ encodings defined with YASON.
|
||||||
|
|
||||||
;;; Util
|
|
||||||
;;; ————————————————————————————————————————
|
|
||||||
(defun camel-case (string)
|
|
||||||
"Convert a STRING to camel-casing.
|
|
||||||
Wrapper around STR:CAMEL-CASE, working around a bug that a non-alphanumeric
|
|
||||||
character at the start of the string gets erroneously (or at least undesireably,
|
|
||||||
to us) removed."
|
|
||||||
(if (not (alphanumericp (aref string 0)))
|
|
||||||
(concatenate 'string
|
|
||||||
(string (aref string 0))
|
|
||||||
(str:camel-case string))
|
|
||||||
(str:camel-case string)))
|
|
||||||
|
|
||||||
(defun class-pretty-name (class)
|
|
||||||
"Return a CLASS’es name in a “pretty” (sentence-capitalized) string."
|
|
||||||
(string-capitalize (symbol-name (class-name class))))
|
|
||||||
|
|
||||||
(defun merge-lists (a b)
|
|
||||||
"Given lists A and B, merge them into one list non-redundantly — all unique
|
|
||||||
items in each will be contained in the resultant list."
|
|
||||||
(append a (remove-if (lambda (item) (find item a :test #'equal)) b)))
|
|
||||||
|
|
||||||
(defun find-registered-symbols (str)
|
|
||||||
"Find all symbols identified by string STR within packages in the
|
|
||||||
*ap-packages* list."
|
|
||||||
(mapcar (lambda (package) (find-symbol (string-upcase str) package))
|
|
||||||
*ap-packages*))
|
|
||||||
|
|
||||||
(defun find-registered-classes (str)
|
|
||||||
"Find all classes identified by string STR within pacakges in the
|
|
||||||
*ap-packages* list."
|
|
||||||
(mapcar (lambda (sym) (find-class sym))
|
|
||||||
(find-registered-symbols str)))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
;;; Defining YASON:ENCODE-SLOTS
|
|
||||||
;;; ————————————————————————————————————————
|
|
||||||
;; On-the-fly define YASON:ENCODE for each of our distinct AP classes.
|
|
||||||
(mapcar (lambda (class)
|
(mapcar (lambda (class)
|
||||||
(closer-mop:finalize-inheritance class)
|
(closer-mop:finalize-inheritance class)
|
||||||
(eval `(define-yason-encode-slots ,class)))
|
(eval `(define-yason-encode-slots ,class)))
|
||||||
|
|
Ŝarĝante…
Reference in New Issue