Let's maybe start committing some stuff

main
Arne Brasseur 1 year ago
commit c8b0d4e686

14
.gitignore vendored

@ -0,0 +1,14 @@
.cpcache
.nrepl-port
target
repl
scratch.clj
.shadow-cljs
target
yarn.lock
node_modules/
.DS_Store
resources/public/ui
.store
deps.local.edn
.#*

@ -0,0 +1,2 @@
{:deps {com.lambdaisland/launchpad #_{:mvn/version "0.12.64-alpha"}
{:local/root "/home/arne/github/lambdaisland/launchpad"}}}

@ -0,0 +1,6 @@
#!/usr/bin/env bb
(require '[lambdaisland.launchpad :as launchpad])
(launchpad/main {:steps (into [(partial launchpad/ensure-java-version 17)]
launchpad/default-steps)})

@ -0,0 +1,6 @@
{:deps {hato/hato {:mvn/version "0.9.0"}
cheshire/cheshire {:mvn/version "5.11.0"}
seancorfield/next.jdbc {:mvn/version "1.2.659"}
com.impossibl.pgjdbc-ng/pgjdbc-ng {:mvn/version "0.8.9"}
}}

@ -0,0 +1,156 @@
(ns first-experiments
(:require
[clojure.string :as str]
[hato.client :as hato])
(:import
(com.apicatalog.jsonld JsonLd)
(com.apicatalog.jsonld.document JsonDocument)))
(set! *warn-on-reflection* true)
#_
(:body
(hato/get #_"https://toot.cat/.well-known/webfinger?resource=acct:plexus@toot.cat"
#_"https://toot.cat/users/plexus"
#_"https://www.w3.org/ns/activitystreams"
{:headers {"Accept" "application/json"}
:as :stream}))
(defn json-fetch [url]
(hato/get url
{:headers {"Accept" "application/json"}
:http-client {:redirect-policy :normal}
:as :json-string-keys}))
(defn expand-context
([new-context]
(expand-context {} new-context))
([current-context new-context]
(cond
(string? new-context)
(do (println '->> new-context)
(expand-context current-context
(get (:body (json-fetch new-context)) "@context")))
(sequential? new-context)
(reduce expand-context current-context new-context)
(map? new-context)
(into current-context
(map
(fn [[k v]]
(let [id (if (map? v) (get v "@id" v) v)
[prefix suffix] (str/split id #":")]
(if-let [base (get (merge current-context new-context) prefix)]
[k (assoc (if (map? v) v {})
"@id" (str (if (map? base) (get base "@id") base)
suffix))]
[k (if (map? v) v {"@id" v})]))))
new-context))))
(defn apply-context [v ctx]
(into {}
(map (fn [[k v]]
(let [attr (get ctx k)
k (get-in ctx [k "@id"] k)
v (cond
(map? v)
(apply-context v ctx)
(= "@type" k)
(get-in ctx [v "@id"] v)
:else
v)]
[k (if attr
(assoc (dissoc attr "@id") "@value" v)
v)])))
v))
(let [user (:body (json-fetch "https://toot.cat/users/plexus"))
ctx (expand-context {} (get user "@context"))]
(apply-context (dissoc user "@context") ctx)
)
(:body (json-fetch "https://www.w3.org/ns/activitystreams"))
(def clojure-prefixes
{"http://purl.org/dc/terms/" "org.purl.dc"
"http://www.w3.org/ns/ldp#" "org.w3.ldp"
"http://schema.org#" "org.schema"
"http://www.w3.org/2006/vcard/ns#" "org.w3.vcard"
"http://joinmastodon.org/ns#" "org.joinmastodon"
"https://w3id.org/security#" "org.w3id.security"
"https://www.w3.org/ns/activitystreams#" "org.w3.activitystreams"
"http://www.w3.org/2001/XMLSchema#" "org.w3.xmlns"})
(json-fetch "https://toot.cat/users/plexus")
(def context (expand-context "https://toot.cat/users/plexus"))
(keep (comp #(get % "@id") val) context)
"https://toot.cat/@plexus/109403085288497274"
(expand-context "https://www.w3.org/ns/activitystreams#Image")
#_(.get (com.apicatalog.jsonld.JsonLd/expand "https://toot.cat/users/plexus"))
;; JsonLd.expand("https://w3c.github.io/json-ld-api/tests/expand/0001-in.jsonld")
;; .ordered()
;; .get();
(def ^JsonDocument json-doc
(JsonDocument/of
^java.io.InputStream
(:body
(hato/get "https://toot.cat/users/plexus"
{:headers {"Accept" "application/json"}
:as :stream}))))
(def expanded (.get (JsonLd/expand json-doc)))
(def flattened (.get (JsonLd/flatten json-doc)))
(def rdf (.get (JsonLd/toRdf json-doc)))
(defn to-clj [v]
(cond
(instance? java.util.List v)
(into [] (map to-clj) v)
(instance? java.util.Map v)
(update-vals v to-clj)
(instance? jakarta.json.JsonString v)
(.getString ^jakarta.json.JsonString v)
(instance? jakarta.json.JsonValue v)
(let [t (.getValueType ^jakarta.json.JsonValue v)]
(cond
(= t jakarta.json.JsonValue$ValueType/TRUE) true
(= t jakarta.json.JsonValue$ValueType/FALSE) false)
)
:else v
))
(to-clj expanded)
;; (clojure.reflect/reflect jakarta.json.JsonValue)
;; (clojure.reflect/reflect rdf)
(clojure.walk/postwalk
(fn [v]
(if (map? v)
(update-keys v (fn [k]
(case k
"@id" :json-ld/id
"@type" :json-ld/type
"@value" :json-ld/value
(if-let [kw (and (string? k)
(some (fn [[url ns]]
(when (.startsWith k url)
(keyword ns
(subs k (.length url)))))
clojure-prefixes))]
kw
k))))
v))
(to-clj expanded))
(set! *print-namespace-maps* false)

@ -0,0 +1,239 @@
(ns repl-sessions.first-stream
(:require [lambdaisland.souk.json-ld :refer :all]))
(spit "resources/lambdaisland/souk/json_ld_contexts.edn"
(with-out-str (clojure.pprint/pprint @context-cache)))
(reset!
context-cache
{"https://www.w3.org/ns/activitystreams"
{"Dislike" "as:Dislike",
"Leave" "as:Leave",
"Application" "as:Application",
"Listen" "as:Listen",
"followers" {"@id" "as:followers", "@type" "@id"},
"startIndex" {"@id" "as:startIndex", "@type" "xsd:nonNegativeInteger"},
"View" "as:View",
"inbox" {"@id" "ldp:inbox", "@type" "@id"},
"object" {"@id" "as:object", "@type" "@id"},
"Like" "as:Like",
"shares" {"@id" "as:shares", "@type" "@id"},
"nameMap" {"@id" "as:name", "@container" "@language"},
"width" {"@id" "as:width", "@type" "xsd:nonNegativeInteger"},
"Relationship" "as:Relationship",
"origin" {"@id" "as:origin", "@type" "@id"},
"Link" "as:Link",
"url" {"@id" "as:url", "@type" "@id"},
"bto" {"@id" "as:bto", "@type" "@id"},
"inReplyTo" {"@id" "as:inReplyTo", "@type" "@id"},
"next" {"@id" "as:next", "@type" "@id"},
"ldp" "http://www.w3.org/ns/ldp#",
"signClientKey" {"@id" "as:signClientKey", "@type" "@id"},
"CollectionPage" "as:CollectionPage",
"describes" {"@id" "as:describes", "@type" "@id"},
"anyOf" {"@id" "as:anyOf", "@type" "@id"},
"Organization" "as:Organization",
"OrderedCollection" "as:OrderedCollection",
"orderedItems" {"@id" "as:items", "@type" "@id", "@container" "@list"},
"Announce" "as:Announce",
"OrderedCollectionPage" "as:OrderedCollectionPage",
"height" {"@id" "as:height", "@type" "xsd:nonNegativeInteger"},
"Note" "as:Note",
"formerType" {"@id" "as:formerType", "@type" "@id"},
"Offer" "as:Offer",
"Video" "as:Video",
"Object" "as:Object",
"Travel" "as:Travel",
"Mention" "as:Mention",
"image" {"@id" "as:image", "@type" "@id"},
"Audio" "as:Audio",
"IntransitiveActivity" "as:IntransitiveActivity",
"endpoints" {"@id" "as:endpoints", "@type" "@id"},
"bcc" {"@id" "as:bcc", "@type" "@id"},
"Flag" "as:Flag",
"longitude" {"@id" "as:longitude", "@type" "xsd:float"},
"Question" "as:Question",
"radius" {"@id" "as:radius", "@type" "xsd:float"},
"Public" {"@id" "as:Public", "@type" "@id"},
"Activity" "as:Activity",
"IsMember" "as:IsMember",
"id" "@id",
"proxyUrl" {"@id" "as:proxyUrl", "@type" "@id"},
"IsContact" "as:IsContact",
"Event" "as:Event",
"hreflang" "as:hreflang",
"Block" "as:Block",
"Person" "as:Person",
"altitude" {"@id" "as:altitude", "@type" "xsd:float"},
"sharedInbox" {"@id" "as:sharedInbox", "@type" "@id"},
"latitude" {"@id" "as:latitude", "@type" "xsd:float"},
"liked" {"@id" "as:liked", "@type" "@id"},
"Arrive" "as:Arrive",
"summary" "as:summary",
"Delete" "as:Delete",
"attachment" {"@id" "as:attachment", "@type" "@id"},
"relationship" {"@id" "as:relationship", "@type" "@id"},
"href" {"@id" "as:href", "@type" "@id"},
"name" "as:name",
"closed" {"@id" "as:closed", "@type" "xsd:dateTime"},
"vcard" "http://www.w3.org/2006/vcard/ns#",
"Article" "as:Article",
"tag" {"@id" "as:tag", "@type" "@id"},
"published" {"@id" "as:published", "@type" "xsd:dateTime"},
"items" {"@id" "as:items", "@type" "@id"},
"startTime" {"@id" "as:startTime", "@type" "xsd:dateTime"},
"location" {"@id" "as:location", "@type" "@id"},
"Update" "as:Update",
"Add" "as:Add",
"Read" "as:Read",
"context" {"@id" "as:context", "@type" "@id"},
"partOf" {"@id" "as:partOf", "@type" "@id"},
"Remove" "as:Remove",
"preferredUsername" "as:preferredUsername",
"Profile" "as:Profile",
"totalItems" {"@id" "as:totalItems", "@type" "xsd:nonNegativeInteger"},
"prev" {"@id" "as:prev", "@type" "@id"},
"Follow" "as:Follow",
"IsFollowing" "as:IsFollowing",
"Tombstone" "as:Tombstone",
"subject" {"@id" "as:subject", "@type" "@id"},
"Page" "as:Page",
"@vocab" "_:",
"current" {"@id" "as:current", "@type" "@id"},
"content" "as:content",
"units" "as:units",
"Place" "as:Place",
"instrument" {"@id" "as:instrument", "@type" "@id"},
"Undo" "as:Undo",
"alsoKnownAs" {"@id" "as:alsoKnownAs", "@type" "@id"},
"duration" {"@id" "as:duration", "@type" "xsd:duration"},
"last" {"@id" "as:last", "@type" "@id"},
"rel" "as:rel",
"source" "as:source",
"TentativeReject" "as:TentativeReject",
"type" "@type",
"outbox" {"@id" "as:outbox", "@type" "@id"},
"mediaType" "as:mediaType",
"oneOf" {"@id" "as:oneOf", "@type" "@id"},
"deleted" {"@id" "as:deleted", "@type" "xsd:dateTime"},
"target" {"@id" "as:target", "@type" "@id"},
"replies" {"@id" "as:replies", "@type" "@id"},
"provideClientKey" {"@id" "as:provideClientKey", "@type" "@id"},
"Create" "as:Create",
"updated" {"@id" "as:updated", "@type" "xsd:dateTime"},
"generator" {"@id" "as:generator", "@type" "@id"},
"endTime" {"@id" "as:endTime", "@type" "xsd:dateTime"},
"TentativeAccept" "as:TentativeAccept",
"oauthAuthorizationEndpoint"
{"@id" "as:oauthAuthorizationEndpoint", "@type" "@id"},
"audience" {"@id" "as:audience", "@type" "@id"},
"Service" "as:Service",
"Image" "as:Image",
"Accept" "as:Accept",
"Document" "as:Document",
"preview" {"@id" "as:preview", "@type" "@id"},
"Invite" "as:Invite",
"contentMap" {"@id" "as:content", "@container" "@language"},
"Group" "as:Group",
"oauthTokenEndpoint" {"@id" "as:oauthTokenEndpoint", "@type" "@id"},
"uploadMedia" {"@id" "as:uploadMedia", "@type" "@id"},
"to" {"@id" "as:to", "@type" "@id"},
"accuracy" {"@id" "as:accuracy", "@type" "xsd:float"},
"IsFollowedBy" "as:IsFollowedBy",
"Reject" "as:Reject",
"summaryMap" {"@id" "as:summary", "@container" "@language"},
"Join" "as:Join",
"Move" "as:Move",
"as" "https://www.w3.org/ns/activitystreams#",
"actor" {"@id" "as:actor", "@type" "@id"},
"likes" {"@id" "as:likes", "@type" "@id"},
"following" {"@id" "as:following", "@type" "@id"},
"streams" {"@id" "as:streams", "@type" "@id"},
"cc" {"@id" "as:cc", "@type" "@id"},
"attributedTo" {"@id" "as:attributedTo", "@type" "@id"},
"result" {"@id" "as:result", "@type" "@id"},
"xsd" "http://www.w3.org/2001/XMLSchema#",
"first" {"@id" "as:first", "@type" "@id"},
"Collection" "as:Collection",
"icon" {"@id" "as:icon", "@type" "@id"},
"Ignore" "as:Ignore"},
"https://w3id.org/security/v1"
{"EncryptedMessage" "sec:EncryptedMessage",
"dc" "http://purl.org/dc/terms/",
"canonicalizationAlgorithm" "sec:canonicalizationAlgorithm",
"owner" {"@id" "sec:owner", "@type" "@id"},
"created" {"@id" "dc:created", "@type" "xsd:dateTime"},
"signatureValue" "sec:signatureValue",
"CryptographicKey" "sec:Key",
"publicKeyPem" "sec:publicKeyPem",
"iterationCount" "sec:iterationCount",
"id" "@id",
"publicKey" {"@id" "sec:publicKey", "@type" "@id"},
"Ed25519Signature2018" "sec:Ed25519Signature2018",
"publicKeyWif" "sec:publicKeyWif",
"GraphSignature2012" "sec:GraphSignature2012",
"creator" {"@id" "dc:creator", "@type" "@id"},
"publicKeyBase58" "sec:publicKeyBase58",
"cipherAlgorithm" "sec:cipherAlgorithm",
"digestAlgorithm" "sec:digestAlgorithm",
"LinkedDataSignature2015" "sec:LinkedDataSignature2015",
"cipherData" "sec:cipherData",
"privateKey" {"@id" "sec:privateKey", "@type" "@id"},
"EcdsaKoblitzSignature2016" "sec:EcdsaKoblitzSignature2016",
"expires" {"@id" "sec:expiration", "@type" "xsd:dateTime"},
"signatureAlgorithm" "sec:signingAlgorithm",
"signature" "sec:signature",
"domain" "sec:domain",
"LinkedDataSignature2016" "sec:LinkedDataSignature2016",
"revoked" {"@id" "sec:revoked", "@type" "xsd:dateTime"},
"encryptionKey" "sec:encryptionKey",
"cipherKey" "sec:cipherKey",
"salt" "sec:salt",
"digestValue" "sec:digestValue",
"type" "@type",
"password" "sec:password",
"expiration" {"@id" "sec:expiration", "@type" "xsd:dateTime"},
"publicKeyService" {"@id" "sec:publicKeyService", "@type" "@id"},
"nonce" "sec:nonce",
"authenticationTag" "sec:authenticationTag",
"privateKeyPem" "sec:privateKeyPem",
"sec" "https://w3id.org/security#",
"normalizationAlgorithm" "sec:normalizationAlgorithm",
"initializationVector" "sec:initializationVector",
"xsd" "http://www.w3.org/2001/XMLSchema#"}})
(:body (json-get "https://toot.cat/users/plexus"))
"@type" "https://www.w3.org/ns/activitystreams#Person"
(:body (json-get "https://toot.cat/users/plexus/outbox"))
(:body (json-get "https://toot.cat/users/plexus/outbox?page=true"))
(:body (json-get "https://www.w3.org/ns/activitystreams"))
(:body (json-get "https://toot.cat/users/plexus/outbox"))
(expand-context
(get (:body (json-get "https://toot.cat/users/plexus"))
"@context"))
{"inbox" "https://www.w3.org/ns/activitystreams#inbox"
"as" "https://www.w3.org/ns/activitystreams#"
}
JSON-LD linked data
RDF Resource Description Framework
[entity attribute value]
[subject predicate object]
["http://.../clojure" "http://.../type-of-language" "https://..../functional"]
Uniform Resource Locator: URL
Uniform Resource Identifier: URI
Internationalized Resource Identifiers: IRI
"@context"
- url
- map (object)
- list of ...

@ -0,0 +1,410 @@
(ns repl-sessions.json-ld-stuff
(:require
[clojure.string :as str]
[hato.client :as hato]
[cheshire.core :as json])
(:import (org.jsoup Jsoup)))
(set! *print-namespace-maps* false)
(def json-ld-spec "https://www.w3.org/TR/json-ld11/")
(def doc (Jsoup/parse ^String (slurp json-ld-spec)))
(def examples
(for [example (.select doc ".example")
:let [title (first (.select example ".example-title"))
pre (first (.select example "pre"))]
:when (and title pre)]
(do
(doseq [comment (.select pre ".comment")]
(.remove comment))
[(str/replace (.text title) #"^: " "")
(try
(json/parse-string (.text (first (.select example "pre")))
)
(catch Exception e
e))])))
(defn example [title]
(get (into {} examples) title))
(defn json-fetch [url]
(hato/get url
{:headers {"Accept" "application/json"}
:http-client {:redirect-policy :normal}
:as :json-string-keys}))
(def expand-context
(memoize
(fn
([new-context]
(expand-context {} new-context))
([current-context new-context]
(cond
(string? new-context)
(do (println '->> new-context)
(expand-context current-context
(get (:body (json-fetch new-context)) "@context")))
(sequential? new-context)
(reduce expand-context current-context new-context)
(map? new-context)
(into current-context
(map
(fn [[k v]]
(let [id (if (map? v) (get v "@id" v) v)
[prefix suffix] (str/split id #":")]
(if-let [base (get (merge current-context new-context) prefix)]
[k (assoc (if (map? v) v {})
"@id" (str (if (map? base) (get base "@id") base)
suffix))]
[k (if (map? v) v {"@id" v})]))))
new-context))))))
(defn expand-id [id ctx]
(if (string? id)
(if-let [t (get-in ctx [id "@id"])]
t
(if (str/includes? id ":")
(let [[prefix suffix] (str/split id #":")]
(if-let [prefix-url (get-in ctx [prefix "@id"])]
(str prefix-url suffix)
id))
id))
id))
(defn apply-context [v ctx]
(into {}
(map (fn [[k v]]
(let [attr (get ctx k)
k (get-in ctx [k "@id"] k)
v (cond
(map? v)
(apply-context v ctx)
(= "@type" k)
(expand-id v ctx)
:else
v)]
(prn [k v])
[k (if attr
(if (= "@id" (get attr "@type"))
(assoc attr "@id" (expand-id v ctx))
(assoc (dissoc (cond-> attr
(contains? attr "@type")
(update "@type" expand-id ctx))
"@id") "@value" v))
v)])))
v))
(defn expand [json-ld]
(let [ctx (expand-context {} (get json-ld "@context"))]
(apply-context (dissoc json-ld "@context") ctx)))
(def clojure-prefixes
{"dcterms" "http://purl.org/dc/terms/"
"ldp" "http://www.w3.org/ns/ldp#"
"schema" "http://schema.org#"
"vcard" "http://www.w3.org/2006/vcard/ns#"
"mastodon" "http://joinmastodon.org/ns#"
"security" "https://w3id.org/security#"
"activitystreams" "https://www.w3.org/ns/activitystreams#"
"xsd" "http://www.w3.org/2001/XMLSchema#"})
(defn to-clj [json-ld prefixes]
(let [shorten #(if (string? %)
(or (some (fn [[ns url]]
(when (.startsWith % url)
(keyword ns
(subs % (.length url)))))
prefixes)
%)
%)]
(clojure.walk/postwalk
(fn [v]
(if (map? v)
(-> v
(update-keys (fn [k]
(case k
"@id" :rdf/id
"@type" :rdf/type
"@value" :rdf/value
(if-let [kw (shorten k)]
kw
k))))
(update-vals (fn [v]
(cond
(map? v)
(cond
(and (= "@id" (:rdf/type v))
(contains? v :rdf/id))
(:rdf/id v)
(contains? v :rdf/value)
(case (:rdf/type v)
:xsd/dateTime
(java.time.ZonedDateTime/parse (:rdf/value v))
(:rdf/value v))
:else
v)
(string? v)
(shorten v)
:else
v))))
v))
(expand json-ld))))
(defn compact [entity context prefix-map]
(let [ctx (into {} (map (juxt (comp #(get % "@id") val) key))
(expand-context context))
kw->iri (fn [k]
(let [iri (str (get prefix-map (namespace k))
(name k))]
(if-let [n (get ctx iri)]
n
(if-let [n (some (fn [[k v]]
(when (.startsWith iri k)
(str v ":" (subs iri (count k)))))
ctx)]
n
k))))
expand-kv (fn expand-kv [[k v]]
(cond
(not (keyword? k))
[k v]
(= :rdf/id k)
["@id" v]
(= :rdf/type k)
["@type" (kw->iri v)]
:else
[(kw->iri k)
(cond
(map? v)
(into {} (map expand-kv) v)
(sequential? v)
(map #(if (map? %)
(into {} (map expand-kv) %)
%) v)
:else v)]))]
(into {"@context" context}
(map expand-kv)
(dissoc entity :rdf/id :rdf/type))))
(comment
(println (json/encode (assoc (expand (example "Referencing a JSON-LD context"))
"@context" (expand-context {} (get (example "Referencing a JSON-LD context") "@context")))))
(println (json/encode (assoc (expand (example "Referencing a JSON-LD context"))
"@context" {"foaf" "http://xmlns.com/foaf/0.1/"})))
(println (json/encode(example "Referencing a JSON-LD context")))
(to-clj (example "Referencing a JSON-LD context")
(update-vals (expand-context (get (example "Referencing a JSON-LD context") "@context"))
#(get % "@id")))
(def raw-profile (:body (json-fetch "https://toot.cat/users/plexus")))
(def profile-ctx (expand-context (get raw-profile "@context")))
(def expanded-profile (expand raw-profile))
(def plexus-profile
(to-clj expanded-profile clojure-prefixes))
(get raw-profile "@context")
(expand-context "https://www.w3.org/ns/activitystreams")
(compact plexus-profile
["https://w3id.org/security/v1"
"https://www.w3.org/ns/activitystreams"
{"toot" "http://joinmastodon.org/ns#",}]
clojure-prefixes)
(update-vals (to-clj (expand )
clojure-prefixes)
#(or (:rdf/type %)
%))
(to-clj (expand (:body (json-fetch "https://toot.cat/users/plexus/followers?page=1")))
clojure-prefixes)
(->> "https://www.w3.org/ns/activitystreams#OrderedCollectionPage"
json-fetch
:body
(#(get % "@context"))
vals
(keep #(get % "@type"))
(into #{}))
(to-clj (expand (:body (json-fetch "https://toot.cat/users/plexus/collections/featured",)))
clojure-prefixes)
(example "Loading a relative context")
(to-clj (expand (example "In-line context definition"))
{"org.schema" "http://schema.org/"}))
"Values of @id are interpreted as IRI"
"IRIs can be relative"
"IRI as a key"
"Term expansion from context definition"
"Type coercion"
"Identifying a node"
"Specifying the type for a node"
"Specifying multiple types for a node"
"Using a term to specify the type"
"Referencing Objects on the Web"
"Embedding Objects"
"Using multiple contexts"
"Describing disconnected nodes with @graph"
"Embedded contexts within node objects"
"Combining external and local contexts"
"Setting @version in context"
"Using a default vocabulary"
"Using the null keyword to ignore data"
"Using a default vocabulary relative to a previous default vocabulary"
"Use a relative IRI reference as node identifier"
"Setting the document base in a document"
"Using \"#\" as the vocabulary mapping"
"Using \"#\" as the vocabulary mapping (expanded)"
"Prefix expansion"
"Using vocabularies"
"Expanded document used to illustrate compact IRI creation"
"Compact IRI generation context (1.0)"
"Compact IRI generation term selection (1.0)"
"Compact IRI generation context (1.1)"
"Compact IRI generation term selection (1.1)"
"Aliasing keywords"
"IRI expansion within a context"
"Using a term to define the IRI of another term within a context"
"Using a compact IRI as a term"
"Illegal Aliasing of a compact IRI to a different IRI"
"Associating context definitions with IRIs"
"Illegal circular definition of terms within a context"
"Defining an @context within a term definition"
"Defining an @context within a term definition used on @type"
"Expansion using embedded and scoped contexts"
"Expansion using embedded and scoped contexts (embedding equivalent)"
"Marking a context to not propagate"
"A remote context to be imported in a type-scoped context"
"Sourcing a context in a type-scoped context and setting it to propagate"
"Result of sourcing a context in a type-scoped context and setting it to propagate"
"Sourcing a context to modify @vocab and a term definition"
"Result of sourcing a context to modify @vocab and a term definition"
"A protected term definition can generally not be overridden"
"A protected @context with an exception"
"Overriding permitted if both definitions are identical"
"overriding permitted in property scoped context"
"Expanded term definition with type coercion"
"Expanded value with type"
"Example demonstrating the context-sensitivity for @type"
"Example demonstrating the context-sensitivity for @type (statements)"
"JSON Literal"
"Expanded term definition with types"
"Term expansion for values, not identifiers"
"Terms not expanded when document-relative"
"Term definitions using IRIs and compact IRIs"
"Setting the default language of a JSON-LD document"
"Clearing default language"
"Expanded term definition with language"
"Language map expressing a property in three languages"
"Overriding default language using an expanded value"
"Removing language information using an expanded value"
"Setting the default base direction of a JSON-LD document"
"Clearing default base direction"
"Expanded term definition with language and direction"
"Overriding default language and default base direction using an expanded value"
"Multiple values with no inherent order"
"Using an expanded form to set multiple values"
"Multiple array values of different types"
"An ordered collection of values in JSON-LD"
"Specifying that a collection is ordered in the context"
"Coordinates expressed in GeoJSON"
"Coordinates expressed in JSON-LD"
"An unordered collection of values in JSON-LD"
"Specifying that a collection is unordered in the context"
"Setting @container: @set on @type"
"Nested properties"
"Nested properties folded into containing object"
"Defining property nesting - Expanded Input"
"Defining property nesting - Context"
"Defining property nesting"
"Referencing node objects"
"Embedding a node object as property value of another node object"
"Referencing an unidentified node"
"Specifying a local blank node identifier"
"Indexing data in JSON-LD"
"Indexing data using @none"
"Property-based data indexing"
"Indexing languaged-tagged strings in JSON-LD"
"Indexing languaged-tagged strings in JSON-LD with @set representation"
"Indexing languaged-tagged strings using @none for no language"
"Indexing data in JSON-LD by node identifiers"
"Indexing data in JSON-LD by node identifiers with @set representation"
"Indexing data in JSON-LD by node identifiers using @none"
"Indexing data in JSON-LD by type"
"Indexing data in JSON-LD by type with @set representation"
"Indexing data in JSON-LD by type using @none"
"Included Blocks"
"Flattened form for included blocks"
"Describing disconnected nodes with @included"
"A document with children linking to their parent"
"A person and its children using a reverse property"
"Using @reverse to define reverse properties"
"Identifying and making statements about a graph"
"Using @graph to explicitly express the default graph"
"Context needs to be duplicated if @graph is not used"
"Implicitly named graph"
"Indexing graph data in JSON-LD"
"Indexing graphs using @none for no index"
"Referencing named graphs using an id map"
"Referencing named graphs using an id map with @none"
"Sample JSON-LD document to be expanded"
"Expanded form for the previous example"
"Sample expanded JSON-LD document"
"Sample context"
"Compact form of the sample document once sample context has been applied"
"Compacting using a default vocabulary"
"Compacting using a base IRI"
"Coercing Values to Strings"
"Using Arrays for Lists"
"Reversing Node Relationships"
"Indexing language-tagged strings"
"Forcing Object Values"
"Indexing language-tagged strings and @set"
"Term Selection"
"Sample JSON-LD document to be flattened"
"Flattened and compacted form for the previous example"
"Sample library frame"
"Flattened library objects"
"Framed library objects"
"Referencing a JSON-LD context from a JSON document via an HTTP Link Header"
"Specifying an alternate location via an HTTP Link Header"
"Embedding JSON-LD in HTML"
"Combining multiple JSON-LD script elements into a single dataset"
"Using the document base URL to establish the default base IRI"
"Embedding JSON-LD containing HTML in HTML"
"Targeting a specific script element by id"
"Illegal Unconnected Node"
"Linked Data Dataset"
"Sample JSON-LD document"
"Flattened and expanded form for the previous example"
"Turtle representation of expanded/flattened document"
"A set of statements serialized in Turtle"
"The same set of statements serialized in JSON-LD"
"Embedding in Turtle"
"Same embedding example in JSON-LD"
"JSON-LD using native data types for numbers and boolean values"
"Same example in Turtle using typed literals"
"A list of values in Turtle"
"Same example with a list of values in JSON-LD"
"RDFa fragment that describes three people"
"Same description in JSON-LD (context shared among node objects)"
"HTML that describes a book using microdata"
"Same book description in JSON-LD (avoiding contexts)"
"HTTP Request with profile requesting an expanded document"
"HTTP Request with profile requesting a compacted document"
"HTTP Request with profile requesting a compacted document with a reference to a compaction context"

@ -0,0 +1,226 @@
(ns repl-sessions.pg-stuff
(:require [clojure.string :as str]
[next.jdbc :as jdbc]
[honey.sql :as sql]
[next.jdbc.sql :as nsql]
[next.jdbc.date-time :as jdbc-date-time]
[next.jdbc.result-set :as rs]
[next.jdbc.plan]
[cheshire.core :as json]
))
(defn pg-url [db-name]
(str "jdbc:pgsql://localhost:5432/" db-name "?user=postgres"))
(defn recreate-db! [name]
(let [ds (jdbc/get-datasource (pg-url "postgres"))]
(jdbc/execute! ds [(str "DROP DATABASE IF EXISTS " name)])
(jdbc/execute! ds [(str "CREATE DATABASE " name)])))
(recreate-db! "souk")
(defn sql-ident [v]
(if (sequential? v)
(str/join "." (map identifier v))
(str "\""
(if (keyword? v)
(subs (str v) 1)
v)
"\"")))
(defn sql-kw [k]
(str/upper-case
(str/replace
(if (keyword? k)
(name k)
k)
#"-" " ")))
(defn sql-str [& ss]
(str "'" (str/replace (apply str ss) #"'" "''") "'"))
(defn sql-list
([items]
(sql-list "(" ")" ", " items))
([before after separator items]
(str before (apply str (str/join separator items)) after)))
(defn strs [& items]
(str/join " " items))
(defn sql [& items]
(apply strs
(map (fn [x]
(cond
(vector? x)
(case (first x)
:ident (sql-ident (second x))
:kw (sql-kw (second x))
:str (sql-str (second x))
:raw (second x)
:list (sql-list (map sql (next x)))
:commas (sql-list "" "" ", " (map sql (next x)))
:fn (str (second x)
(sql-list (map sql (nnext x))))
(apply sql x))
(keyword? x)
(sql-ident x)
(symbol? x)
(sql-kw x)
(string? x)
(sql-str x)
(sequential? x)
(sql-list "(" ")" ", " (map sql x))))
items)))
(sql 'create-table :activitystreams/Person
(list
[:souk/id 'text 'primary-key]
[:souk/properties 'jsonb 'default "{}"] ))
(def set-ts-trigger-def "CREATE OR REPLACE FUNCTION trigger_set_timestamp()\nRETURNS TRIGGER AS $$\nBEGIN\n NEW.\"meta/updated-at\" = NOW();\n RETURN NEW;\nEND;\n$$ LANGUAGE plpgsql;")
(def default-properties
[[:rdf/id 'text 'primary-key]
[:rdf/props 'jsonb 'default "{}"]
[:meta/created-at 'timestamp-with-time-zone 'default [:fn 'now] 'not-null]
[:meta/updated-at 'timestamp-with-time-zone]])
(let [ds (jdbc/get-datasource (pg-url "souk"))]
(jdbc/execute! ds [set-ts-trigger-def])
(jdbc/execute! ds [(sql 'drop-table
'if-exists
:activitystreams/Person)])
(jdbc/execute! ds [(sql 'create-table :activitystreams/Person
(concat
default-properties
[[:activitystreams/name 'text 'not-null]
[:activitystreams/preferredUsername 'text 'not-null]
[:activitystreams/url 'text 'not-null]
[:activitystreams/summary 'text]
[:ldp/inbox 'text 'not-null]
[:activitystreams/outbox 'text 'not-null]
[:activitystreams/published 'timestamp-with-time-zone]]))])
(jdbc/execute! ds [(sql 'create-trigger :set-timestamp
'before-update
'on :activitystreams/Person
'for-each-row
'execute-procedure [:fn 'trigger_set_timestamp])]))
(def table-columns
(into {}
(let [ds (jdbc/get-datasource (pg-url "souk"))
opts {}]
(with-open [con (jdbc/get-connection ds opts)]
(let [md (.getMetaData con)] ; produces java.sql.DatabaseMetaData
(doall
(for [{:keys [pg_class/TABLE_NAME]} (-> md
;; return a java.sql.ResultSet describing all tables and views:
(.getTables nil nil nil (into-array ["TABLE" "VIEW"]))
(rs/datafiable-result-set ds opts))]
[(keyword TABLE_NAME)
(map (comp keyword :COLUMN_NAME) (rs/datafiable-result-set (.getColumns md nil nil TABLE_NAME nil) ds opts)
)
])))))))
(to-clj (expand (:body (json-fetch "https://toot.cat/users/plexus")))
clojure-prefixes)
(defn pg-coerce [val]
(cond
(instance? java.time.ZonedDateTime val)
(.toOffsetDateTime val)
#_[:raw (strs (sql-kw 'timestamp-with-time-zone)
(sql-str
(.toLocalDate ^java.time.ZonedDateTime val) " "
(let [t (.toLocalTime ^java.time.ZonedDateTime val)]
(format "%d:%02d:%02d" (.getHour t)
(.getMinute t) (.getSecond t)))
(.getZone ^java.time.ZonedDateTime val)))]
:else
val))
(defn insert-sql [entity]
(let [{:rdf/keys [type]} entity
cols (get table-columns type)
props (seq (select-keys entity cols))]
(into [(sql 'insert-into type
(cons :rdf/props
(map key props))
'values
(repeat (inc (count props)) '?)
'on-conflict [:raw "(\"rdf/id\")"]
'do
'update-set
(into [:commas]
(map (fn [[k]]
[k '= '?]))
props))]
(cons
(json/encode (apply dissoc entity :rdf/type (map key props)))
(concat
(map (comp pg-coerce val) props)
(map (comp pg-coerce val) props))))))
(let [ds (jdbc/get-datasource (pg-url "souk"))]
(jdbc/execute! ds (insert-sql (assoc repl-sessions.json-ld-stuff/plexus-profile
:activitystreams/name "John Doe")))
)
(defrecord MyMapResultSetBuilder [^java.sql.ResultSet rs rsmeta cols]
rs/RowBuilder
(->row [this] (transient {}))
(column-count [this] (count cols))
(with-column [this row i]
(rs/with-column-value this row (nth cols (dec i))
(if (= java.sql.Types/TIMESTAMP_WITH_TIMEZONE (.getColumnType rsmeta i))
(.getObject rs ^Integer i ^Class java.time.OffsetDateTime)
(rs/read-column-by-index (.getObject rs ^Integer i) rsmeta i))))
(with-column-value [this row col v]
(assoc! row col v))
(row! [this row] (persistent! row))
rs/ResultSetBuilder
(->rs [this] (transient []))
(with-row [this mrs row]
(conj! mrs row))
(rs! [this mrs] (persistent! mrs)))
(defn my-builder
[rs opts]
(let [rsmeta (.getMetaData rs)
cols (rs/get-unqualified-column-names rsmeta opts)]
(def rs rs)
(def meta rsmeta)
(def cols cols)
(->MyMapResultSetBuilder rs rsmeta cols)))
(.getColumnType meta 3)
(let [ds (jdbc/get-datasource (pg-url "souk"))]
(jdbc/execute-one! ds [(sql 'select '* 'from :activitystreams/Person)]
{:builder-fn my-builder})
)
(pg-coerce (java.time.ZonedDateTime/parse "2017-04-11T00:00Z"))
{:activitystreams/followers "@id",
:activitystreams/published "xsd:dateTime",
:mastodon/devices "@id",
:json-ld/type nil,
:activitystreams/outbox "@id",
:activitystreams/following "@id",
:activitystreams/endpoints "@id",
:activitystreams/name nil,
:activitystreams/icon "@id",
:security/publicKey "@id",
:mastodon/featured "@id",
:activitystreams/manuallyApprovesFollowers nil,
:activitystreams/summary nil,
:activitystreams/image "@id",
:activitystreams/tag "@id",
:mastodon/discoverable nil,
:json-ld/id nil,
:activitystreams/preferredUsername nil,
:activitystreams/url "@id",
:ldp/inbox "@id",
:activitystreams/attachment "@id",
:mastodon/featuredTags "@id"}

@ -0,0 +1,47 @@
(ns repl-sessions.rsa-keys
(:require
[clojure.string :as str])
(:import
(java.security KeyPairGenerator Signature)
(java.security.spec X509EncodedKeySpec)
(java.util Base64)))
(def kpg (KeyPairGenerator/getInstance "RSA"))
(.initialize kpg 2048)
(def kp (.generateKeyPair kpg))
(.getEncoded (.getPublic kp))
(.getEncoded (.getPrivate kp))
(let [s (.encodeToString (Base64/getEncoder) (.getEncoded (.getPublic kp)))
parts (map (partial apply str) (partition-all 64 s))]
(str/join
(map #(str % "\r\n")
`["-----BEGIN PUBLIC KEY-----"
~@parts
"-----END PUBLIC KEY-----"])))
(def pem "-----BEGIN PUBLIC KEY-----\r\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy3WsUuEyZLsy/2XxJ+ou\r\nnNr14R1x9laQh4EitjT4e1OPJwHHIBqEPUWk4MQzU13Jga4uua28Ecl3BxC9lSnf\r\nDp96Z0NAdkYjuCgC9xo9EjKaK8ijIbm58d4uifIl/XKZE6tYTGXXzmnx4nCfcWfF\r\n67tut/4k+/wVMjjHMLl9VhzHsBz3Wr+h7v+4SLFftq9NorMknWQuIh3IzQUNZBps\r\nCw8JRDUx8Of/I44mJMc2N12f41TLK65VCvkXF3K5qIS9jTEdhhOA8dsB92DEyaTu\r\ns+jhqXM4ivFfxDyOasQRZ0bEO+OEcJua7nnvNsFzGLkIb3/eJ1HlCQ+AKVSUGcBZ\r\nbwIDAQAB\r\n-----END PUBLIC KEY-----\r\n")
(X509EncodedKeySpec.
(.decode (Base64/getDecoder)
(str/replace pem #"(-+(BEGIN|END) PUBLIC KEY-+|\R)" ""))
)
;; sign
(def sign (Signature/getInstance "SHA256withRSA"))
(.initSign sign (.getPrivate kp))
(.update sign (.getBytes "hello"))
(def signature (.sign sign))
(.encodeToString (Base64/getEncoder) signature)
;; verify
(def sign (Signature/getInstance "SHA256withRSA"))
(.initVerify sign (.getPublic kp))
(.update sign (.getBytes "hello"))
(.verify sign signature)

File diff suppressed because it is too large Load Diff

@ -0,0 +1,198 @@
{"https://www.w3.org/ns/activitystreams"
{"Dislike" "as:Dislike",
"Leave" "as:Leave",
"Application" "as:Application",
"Listen" "as:Listen",
"followers" {"@id" "as:followers", "@type" "@id"},
"startIndex"
{"@id" "as:startIndex", "@type" "xsd:nonNegativeInteger"},
"View" "as:View",
"inbox" {"@id" "ldp:inbox", "@type" "@id"},
"object" {"@id" "as:object", "@type" "@id"},
"Like" "as:Like",
"shares" {"@id" "as:shares", "@type" "@id"},
"nameMap" {"@id" "as:name", "@container" "@language"},
"width" {"@id" "as:width", "@type" "xsd:nonNegativeInteger"},
"Relationship" "as:Relationship",
"origin" {"@id" "as:origin", "@type" "@id"},
"Link" "as:Link",
"url" {"@id" "as:url", "@type" "@id"},
"bto" {"@id" "as:bto", "@type" "@id"},
"inReplyTo" {"@id" "as:inReplyTo", "@type" "@id"},
"next" {"@id" "as:next", "@type" "@id"},
"ldp" "http://www.w3.org/ns/ldp#",
"signClientKey" {"@id" "as:signClientKey", "@type" "@id"},
"CollectionPage" "as:CollectionPage",
"describes" {"@id" "as:describes", "@type" "@id"},
"anyOf" {"@id" "as:anyOf", "@type" "@id"},
"Organization" "as:Organization",
"OrderedCollection" "as:OrderedCollection",
"orderedItems"
{"@id" "as:items", "@type" "@id", "@container" "@list"},
"Announce" "as:Announce",
"OrderedCollectionPage" "as:OrderedCollectionPage",
"height" {"@id" "as:height", "@type" "xsd:nonNegativeInteger"},
"Note" "as:Note",
"formerType" {"@id" "as:formerType", "@type" "@id"},
"Offer" "as:Offer",
"Video" "as:Video",
"Object" "as:Object",
"Travel" "as:Travel",
"Mention" "as:Mention",
"image" {"@id" "as:image", "@type" "@id"},
"Audio" "as:Audio",
"IntransitiveActivity" "as:IntransitiveActivity",
"endpoints" {"@id" "as:endpoints", "@type" "@id"},
"bcc" {"@id" "as:bcc", "@type" "@id"},
"Flag" "as:Flag",
"longitude" {"@id" "as:longitude", "@type" "xsd:float"},
"Question" "as:Question",
"radius" {"@id" "as:radius", "@type" "xsd:float"},
"Public" {"@id" "as:Public", "@type" "@id"},
"Activity" "as:Activity",
"IsMember" "as:IsMember",
"id" "@id",
"proxyUrl" {"@id" "as:proxyUrl", "@type" "@id"},
"IsContact" "as:IsContact",
"Event" "as:Event",
"hreflang" "as:hreflang",
"Block" "as:Block",
"Person" "as:Person",
"altitude" {"@id" "as:altitude", "@type" "xsd:float"},
"sharedInbox" {"@id" "as:sharedInbox", "@type" "@id"},
"latitude" {"@id" "as:latitude", "@type" "xsd:float"},
"liked" {"@id" "as:liked", "@type" "@id"},
"Arrive" "as:Arrive",
"summary" "as:summary",
"Delete" "as:Delete",
"attachment" {"@id" "as:attachment", "@type" "@id"},
"relationship" {"@id" "as:relationship", "@type" "@id"},
"href" {"@id" "as:href", "@type" "@id"},
"name" "as:name",
"closed" {"@id" "as:closed", "@type" "xsd:dateTime"},
"vcard" "http://www.w3.org/2006/vcard/ns#",
"Article" "as:Article",
"tag" {"@id" "as:tag", "@type" "@id"},
"published" {"@id" "as:published", "@type" "xsd:dateTime"},
"items" {"@id" "as:items", "@type" "@id"},
"startTime" {"@id" "as:startTime", "@type" "xsd:dateTime"},
"location" {"@id" "as:location", "@type" "@id"},
"Update" "as:Update",
"Add" "as:Add",
"Read" "as:Read",
"context" {"@id" "as:context", "@type" "@id"},
"partOf" {"@id" "as:partOf", "@type" "@id"},
"Remove" "as:Remove",
"preferredUsername" "as:preferredUsername",
"Profile" "as:Profile",
"totalItems"
{"@id" "as:totalItems", "@type" "xsd:nonNegativeInteger"},
"prev" {"@id" "as:prev", "@type" "@id"},
"Follow" "as:Follow",
"IsFollowing" "as:IsFollowing",
"Tombstone" "as:Tombstone",
"subject" {"@id" "as:subject", "@type" "@id"},
"Page" "as:Page",
"@vocab" "_:",
"current" {"@id" "as:current", "@type" "@id"},
"content" "as:content",
"units" "as:units",
"Place" "as:Place",
"instrument" {"@id" "as:instrument", "@type" "@id"},
"Undo" "as:Undo",
"alsoKnownAs" {"@id" "as:alsoKnownAs", "@type" "@id"},
"duration" {"@id" "as:duration", "@type" "xsd:duration"},
"last" {"@id" "as:last", "@type" "@id"},
"rel" "as:rel",
"source" "as:source",
"TentativeReject" "as:TentativeReject",
"type" "@type",
"outbox" {"@id" "as:outbox", "@type" "@id"},
"mediaType" "as:mediaType",
"oneOf" {"@id" "as:oneOf", "@type" "@id"},
"deleted" {"@id" "as:deleted", "@type" "xsd:dateTime"},
"target" {"@id" "as:target", "@type" "@id"},
"replies" {"@id" "as:replies", "@type" "@id"},
"provideClientKey" {"@id" "as:provideClientKey", "@type" "@id"},
"Create" "as:Create",
"updated" {"@id" "as:updated", "@type" "xsd:dateTime"},
"generator" {"@id" "as:generator", "@type" "@id"},
"endTime" {"@id" "as:endTime", "@type" "xsd:dateTime"},
"TentativeAccept" "as:TentativeAccept",
"oauthAuthorizationEndpoint"
{"@id" "as:oauthAuthorizationEndpoint", "@type" "@id"},
"audience" {"@id" "as:audience", "@type" "@id"},
"Service" "as:Service",
"Image" "as:Image",
"Accept" "as:Accept",
"Document" "as:Document",
"preview" {"@id" "as:preview", "@type" "@id"},
"Invite" "as:Invite",
"contentMap" {"@id" "as:content", "@container" "@language"},
"Group" "as:Group",
"oauthTokenEndpoint" {"@id" "as:oauthTokenEndpoint", "@type" "@id"},
"uploadMedia" {"@id" "as:uploadMedia", "@type" "@id"},
"to" {"@id" "as:to", "@type" "@id"},
"accuracy" {"@id" "as:accuracy", "@type" "xsd:float"},
"IsFollowedBy" "as:IsFollowedBy",
"Reject" "as:Reject",
"summaryMap" {"@id" "as:summary", "@container" "@language"},
"Join" "as:Join",
"Move" "as:Move",
"as" "https://www.w3.org/ns/activitystreams#",
"actor" {"@id" "as:actor", "@type" "@id"},
"likes" {"@id" "as:likes", "@type" "@id"},
"following" {"@id" "as:following", "@type" "@id"},
"streams" {"@id" "as:streams", "@type" "@id"},
"cc" {"@id" "as:cc", "@type" "@id"},
"attributedTo" {"@id" "as:attributedTo", "@type" "@id"},
"result" {"@id" "as:result", "@type" "@id"},
"xsd" "http://www.w3.org/2001/XMLSchema#",
"first" {"@id" "as:first", "@type" "@id"},
"Collection" "as:Collection",
"icon" {"@id" "as:icon", "@type" "@id"},
"Ignore" "as:Ignore"},
"https://w3id.org/security/v1"
{"EncryptedMessage" "sec:EncryptedMessage",
"dc" "http://purl.org/dc/terms/",
"canonicalizationAlgorithm" "sec:canonicalizationAlgorithm",
"owner" {"@id" "sec:owner", "@type" "@id"},
"created" {"@id" "dc:created", "@type" "xsd:dateTime"},
"signatureValue" "sec:signatureValue",
"CryptographicKey" "sec:Key",
"publicKeyPem" "sec:publicKeyPem",
"iterationCount" "sec:iterationCount",
"id" "@id",
"publicKey" {"@id" "sec:publicKey", "@type" "@id"},
"Ed25519Signature2018" "sec:Ed25519Signature2018",
"publicKeyWif" "sec:publicKeyWif",
"GraphSignature2012" "sec:GraphSignature2012",
"creator" {"@id" "dc:creator", "@type" "@id"},
"publicKeyBase58" "sec:publicKeyBase58",
"cipherAlgorithm" "sec:cipherAlgorithm",
"digestAlgorithm" "sec:digestAlgorithm",
"LinkedDataSignature2015" "sec:LinkedDataSignature2015",
"cipherData" "sec:cipherData",
"privateKey" {"@id" "sec:privateKey", "@type" "@id"},
"EcdsaKoblitzSignature2016" "sec:EcdsaKoblitzSignature2016",
"expires" {"@id" "sec:expiration", "@type" "xsd:dateTime"},
"signatureAlgorithm" "sec:signingAlgorithm",
"signature" "sec:signature",
"domain" "sec:domain",
"LinkedDataSignature2016" "sec:LinkedDataSignature2016",
"revoked" {"@id" "sec:revoked", "@type" "xsd:dateTime"},
"encryptionKey" "sec:encryptionKey",
"cipherKey" "sec:cipherKey",
"salt" "sec:salt",
"digestValue" "sec:digestValue",
"type" "@type",
"password" "sec:password",
"expiration" {"@id" "sec:expiration", "@type" "xsd:dateTime"},
"publicKeyService" {"@id" "sec:publicKeyService", "@type" "@id"},
"nonce" "sec:nonce",
"authenticationTag" "sec:authenticationTag",
"privateKeyPem" "sec:privateKeyPem",
"sec" "https://w3id.org/security#",
"normalizationAlgorithm" "sec:normalizationAlgorithm",
"initializationVector" "sec:initializationVector",
"xsd" "http://www.w3.org/2001/XMLSchema#"}}

@ -0,0 +1,19 @@
(ns lambdaisland.souk.activitypub
(:require [lambdaisland.souk.json-ld :as ld]))
(set! *print-namespace-maps* false)
(def common-prefixes
{"dcterms" "http://purl.org/dc/terms/"
"ldp" "http://www.w3.org/ns/ldp#"
"schema" "http://schema.org/"
"vcard" "http://www.w3.org/2006/vcard/ns#"
"mastodon" "http://joinmastodon.org/ns#"
"security" "https://w3id.org/security#"
"activitystreams" "https://www.w3.org/ns/activitystreams#"
"xsd" "http://www.w3.org/2001/XMLSchema#"
"owl" "http://www.w3.org/2002/07/owl#"
"rdfs" "http://www.w3.org/2000/01/rdf-schema#"})
(defn GET [url]
(ld/internalize (ld/expand (:body (ld/json-get url))) common-prefixes))

@ -0,0 +1,150 @@
(ns lambdaisland.souk.json-ld
(:require [hato.client :as hato]
[clojure.string :as str]
[clojure.walk :as walk]))
(defn json-get [url]
(hato/get url
{:headers {"Accept" "application/json"}
:http-client {:redirect-policy :normal}
:as :json-string-keys}))
(def context-cache (atom {})) ;; url -> context
(defn fetch-context [url]
(if-let [ctx (get @context-cache url)]
ctx
(get
(swap! context-cache
(fn [cache]
(assoc cache url (get (:body (json-get url)) "@context"))))
url)))
(defn expand-context
([new-context]
(expand-context {} new-context))
([current-context new-context]
(cond
(string? new-context)
(expand-context current-context (fetch-context new-context))
(sequential? new-context)
(reduce expand-context current-context new-context)
(map? new-context)
(into current-context
(map
(fn [[k v]]
(let [id (if (map? v) (get v "@id" v) v)
[prefix suffix] (str/split id #":")]
(if-let [base (get (merge current-context new-context) prefix)]
[k (assoc (if (map? v) v {})
"@id" (str (if (map? base) (get base "@id") base)
suffix))]
[k (if (map? v) v {"@id" v})]))))
new-context))))
(defn expand-id [id ctx]
(if (string? id)
(if-let [t (get-in ctx [id "@id"])]
t
(if (str/includes? id ":")
(let [[prefix suffix] (str/split id #":")]
(if-let [prefix-url (get-in ctx [prefix "@id"])]
(str prefix-url suffix)
id))
id))
id))
(defn apply-context [v ctx]
(cond
(map? v)
(into {}
(map (fn [[k v]]
(let [attr (get ctx k)
k (get-in ctx [k "@id"] k)
v (apply-context v ctx)]
[k (if attr
(cond
(and (#{"@id" "@type"} k) (string? v))
(expand-id v ctx)
(and (= "@id" (get attr "@type")) (string? v))
(assoc attr "@id" (expand-id v ctx))
:else
(assoc (dissoc (cond-> attr
(contains? attr "@type")
(update "@type" expand-id ctx))
"@id")
"@value" v))
v)])))
v)
(sequential? v)
(into (empty v) (map #(apply-context % ctx)) v)
(and (string? v) (str/includes? v ":"))
(expand-id v ctx)
:else
v))
(defn expand [json-ld]
(let [ctx (expand-context {} (get json-ld "@context"))]
(apply-context (dissoc json-ld "@context") ctx)))
(defn internalize [v prefixes]
(let [shorten #(if (and (string? %) (str/includes? % "://"))
(or (some (fn [[ns url]]
(when (.startsWith % url)
(keyword ns
(subs % (.length url)))))
prefixes)
%)
%)]
(cond
(sequential? v)
(into (empty v) (map #(internalize % prefixes)) v)
(map? v)
(-> v
(cond-> (contains? v "@type")
(doto prn)
(contains? v "@type")
(update "@type" shorten))
(update-keys (fn [k]
(case k
"@id" :rdf/id
"@type" :rdf/type
"@value" :rdf/value
(if-let [kw (shorten k)]
kw
k))))
(update-vals (fn [v]
(cond
(map? v)
(let [{id "@id" type "@type" value "@value"} v]
(cond
(and (= "@id" type) (contains? v "@id"))
id
(contains? v "@value")
(case type
"http://www.w3.org/2001/XMLSchema#dateTime"
(java.time.ZonedDateTime/parse value)
(internalize value prefixes))
:else
(internalize v prefixes)))
(string? v)
(shorten v)
:else
(internalize v prefixes)))))
:else
v)))
;; (compact
;; (expand (:body (json-get "https://toot.cat/users/plexus")))
;; common-prefixes)
;; "name"
;; "profileName"
Loading…
Cancel
Save