Adopt Step 7 language features across SX codebase

112 conversions across 19 .sx files using match, let-match, and pipe operators:

match (17): type/value dispatch replacing cond/if chains
  - lib/vm.sx: HO form dispatch (for-each/map/filter/reduce/some/every?)
  - lib/tree-tools.sx: node-display, node-matches?, rename, count, replace, free-symbols
  - lib/types.sx: narrow-type, substitute-in-type, infer-type, resolve-type
  - web/engine.sx: default-trigger, resolve-target, classify-trigger
  - web/deps.sx: scan-refs-walk, scan-io-refs-walk

let-match (89): dict destructuring replacing (get d "key") patterns
  - shared/page-functions.sx (20), blog/admin.sx (17), pub-api.sx (13)
  - events/ layouts/page/tickets/entries/forms (27 total)
  - specs-explorer.sx (7), federation/social.sx (3), lib/ small files (3)

-> pipes (6): replacing triple-chained gets in lib/vm.sx
  - frame-closure → closure-code → code-bytecode chains

Also: lib/vm.sx accessor upgrades (get vm "sp" → vm-sp vm throughout)

2650/2650 tests pass, zero regressions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-04 20:49:02 +00:00
parent aee4770a6a
commit c0665ba58e
19 changed files with 4974 additions and 3771 deletions

View File

@@ -144,78 +144,140 @@
edit-form delete-form)) edit-form delete-form))
;; Data-driven snippets list (replaces Python _snippets_sx loop) ;; Data-driven snippets list (replaces Python _snippets_sx loop)
(defcomp ~admin/snippets-from-data (&key snippets user-id is-admin csrf badge-colours) (defcomp
~admin/snippets-from-data
(&key snippets user-id is-admin csrf badge-colours)
(~admin/snippets-list (~admin/snippets-list
:rows (<> (map (lambda (s) :rows (<>
(let* ((s-id (get s "id")) (map
(s-name (get s "name")) (lambda
(s-uid (get s "user_id")) (s)
(s-vis (get s "visibility")) (let-match
(owner (if (= s-uid user-id) "You" (str "User #" s-uid))) {:visibility s-vis :delete_url delete-url :patch_url patch-url :id s-id :user_id s-uid :name s-name}
(badge-cls (or (get badge-colours s-vis) "bg-stone-200 text-stone-700")) s
(extra (<> (let*
(when is-admin ((owner (if (= s-uid user-id) "You" (str "User #" s-uid)))
(~admin/snippet-visibility-select (badge-cls
:patch-url (get s "patch_url") (or (get badge-colours s-vis) "bg-stone-200 text-stone-700"))
:hx-headers (str "{\"X-CSRFToken\": \"" csrf "\"}") (extra
:options (<> (<>
(~admin/snippet-option :value "private" :selected (= s-vis "private") :label "private") (when
(~admin/snippet-option :value "shared" :selected (= s-vis "shared") :label "shared") is-admin
(~admin/snippet-option :value "admin" :selected (= s-vis "admin") :label "admin")) (~admin/snippet-visibility-select
:cls "text-sm border border-stone-300 rounded px-2 py-1")) :patch-url patch-url
(when (or (= s-uid user-id) is-admin) :hx-headers (str "{\"X-CSRFToken\": \"" csrf "\"}")
(~shared:misc/delete-btn :url (get s "delete_url") :trigger-target "#snippets-list" :options (<>
:title "Delete snippet?" (~admin/snippet-option
:text (str "Delete \u201c" s-name "\u201d?") :value "private"
:sx-headers (str "{\"X-CSRFToken\": \"" csrf "\"}") :selected (= s-vis "private")
:cls "px-3 py-1 text-sm bg-red-200 hover:bg-red-300 rounded text-red-800 flex-shrink-0"))))) :label "private")
(~admin/snippet-row :name s-name :owner owner :badge-cls badge-cls (~admin/snippet-option
:visibility s-vis :extra extra))) :value "shared"
(or snippets (list)))))) :selected (= s-vis "shared")
:label "shared")
(~admin/snippet-option
:value "admin"
:selected (= s-vis "admin")
:label "admin"))
:cls "text-sm border border-stone-300 rounded px-2 py-0.5"))
(when
(or (= s-uid user-id) is-admin)
(~shared:misc/delete-btn
:url delete-url
:trigger-target "#snippets-list"
:title "Delete snippet?"
:text (str "Delete \"" s-name "\"?")
:sx-headers (str "{\"X-CSRFToken\": \"" csrf "\"}")
:cls "px-3 py-1 text-sm bg-red-200 hover:bg-red-300 text-red-800 rounded-full")))))
(~admin/snippet-row
:name s-name
:owner owner
:badge-cls badge-cls
:visibility s-vis
:extra extra))))
(or snippets (list))))))
;; Data-driven menu items list (replaces Python _menu_items_list_sx loop) ;; Data-driven menu items list (replaces Python _menu_items_list_sx loop)
(defcomp ~admin/menu-items-from-data (&key items csrf) (defcomp
~admin/menu-items-from-data
(&key items csrf)
(~admin/menu-items-list (~admin/menu-items-list
:rows (<> (map (lambda (item) :rows (<>
(let* ((img (~shared:misc/img-or-placeholder :src (get item "feature_image") :alt (get item "label") (map
:size-cls "w-12 h-12 rounded-full object-cover flex-shrink-0"))) (lambda
(~admin/menu-item-row (item)
:img img :label (get item "label") :slug (get item "slug") (let-match
:sort-order (get item "sort_order") :edit-url (get item "edit_url") {:delete_url delete-url :sort_order sort-order :edit_url edit-url :feature_image feature-image :label label :slug slug}
:delete-url (get item "delete_url") item
:confirm-text (str "Remove " (get item "label") " from the menu?") (let
:hx-headers (str "{\"X-CSRFToken\": \"" csrf "\"}")))) ((img (~shared:misc/img-or-placeholder :src feature-image :alt label :size-cls "w-12 h-12 rounded-full object-cover flex-shrink-0")))
(or items (list)))))) (~admin/menu-item-row
:img img
:label label
:slug slug
:sort-order sort-order
:edit-url edit-url
:delete-url delete-url
:confirm-text (str "Remove " label " from the menu?")
:hx-headers (str "{\"X-CSRFToken\": \"" csrf "\"}")))))
(or items (list))))))
;; Data-driven tag groups main (replaces Python _tag_groups_main_panel_sx loops) ;; Data-driven tag groups main (replaces Python _tag_groups_main_panel_sx loops)
(defcomp ~admin/tag-groups-from-data (&key groups unassigned-tags csrf create-url) (defcomp
~admin/tag-groups-from-data
(&key groups unassigned-tags csrf create-url)
(~admin/tag-groups-main (~admin/tag-groups-main
:form (~admin/tag-groups-create-form :create-url create-url :csrf csrf) :form (~admin/tag-groups-create-form :create-url create-url :csrf csrf)
:groups (if (empty? (or groups (list))) :groups (if
(~shared:misc/empty-state :message "No tag groups yet." :cls "text-stone-500 text-sm") (empty? (or groups (list)))
(~shared:misc/empty-state
:message "No tag groups yet."
:cls "text-stone-500 text-sm")
(~admin/tag-groups-list (~admin/tag-groups-list
:items (<> (map (lambda (g) :items (<>
(let* ((icon (if (get g "feature_image") (map
(~admin/tag-group-icon-image :src (get g "feature_image") :name (get g "name")) (lambda
(~admin/tag-group-icon-color :style (get g "style") :initial (get g "initial"))))) (g)
(~admin/tag-group-li :icon icon :edit-href (get g "edit_href") (let-match
:name (get g "name") :slug (get g "slug") :sort-order (get g "sort_order")))) {:sort_order sort-order :feature_image feature-image :slug slug :edit_href edit-href :initial initial :name name :style style}
groups)))) g
:unassigned (when (not (empty? (or unassigned-tags (list)))) (let
((icon (if feature-image (~admin/tag-group-icon-image :src feature-image :name name) (~admin/tag-group-icon-color :style style :initial initial))))
(~admin/tag-group-li
:icon icon
:edit-href edit-href
:name name
:slug slug
:sort-order sort-order))))
groups))))
:unassigned (when
(not (empty? (or unassigned-tags (list))))
(~admin/unassigned-tags (~admin/unassigned-tags
:heading (str "Unassigned Tags (" (len unassigned-tags) ")") :heading (str "Unassigned Tags (" (len unassigned-tags) ")")
:spans (<> (map (lambda (t) :spans (<>
(~admin/unassigned-tag :name (get t "name"))) (map
unassigned-tags)))))) (lambda (t) (~admin/unassigned-tag :name (get t "name")))
unassigned-tags))))))
;; Data-driven tag group edit (replaces Python _tag_groups_edit_main_panel_sx loop) ;; Data-driven tag group edit (replaces Python _tag_groups_edit_main_panel_sx loop)
(defcomp ~admin/tag-checkboxes-from-data (&key tags) (defcomp
(<> (map (lambda (t) ~admin/tag-checkboxes-from-data
(~admin/tag-checkbox (&key tags)
:tag-id (get t "tag_id") :checked (get t "checked") (<>
:img (when (get t "feature_image") (~admin/tag-checkbox-image :src (get t "feature_image"))) (map
:name (get t "name"))) (lambda
(or tags (list))))) (t)
(let-match
{:tag_id tag-id :checked checked :feature_image feature-image :name name}
t
(~admin/tag-checkbox
:tag-id tag-id
:checked checked
:img (when
feature-image
(~admin/tag-checkbox-image :src feature-image))
:name name)))
(or tags (list)))))
;; Preview panel components ;; Preview panel components
@@ -258,113 +320,175 @@
;; --------------------------------------------------------------------------- ;; ---------------------------------------------------------------------------
;; Snippets — receives serialized snippet dicts from service ;; Snippets — receives serialized snippet dicts from service
(defcomp ~admin/snippets-content (&key snippets is-admin csrf) (defcomp
~admin/snippets-content
(&key snippets is-admin csrf)
(~admin/snippets-panel (~admin/snippets-panel
:list (if (empty? (or snippets (list))) :list (if
(~shared:misc/empty-state :icon "fa fa-puzzle-piece" (empty? (or snippets (list)))
(~shared:misc/empty-state
:icon "fa fa-puzzle-piece"
:message "No snippets yet. Create one from the blog editor.") :message "No snippets yet. Create one from the blog editor.")
(~admin/snippets-list (~admin/snippets-list
:rows (map (lambda (s) :rows (map
(let* ((badge-colours (dict (lambda
"private" "bg-stone-200 text-stone-700" (s)
"shared" "bg-blue-100 text-blue-700" (let-match
"admin" "bg-amber-100 text-amber-700")) {:visibility vis* :delete_url delete-url :owner owner :can_delete can-delete :patch_url patch-url :name name}
(vis (or (get s "visibility") "private")) s
(badge-cls (or (get badge-colours vis) "bg-stone-200 text-stone-700")) (let*
(name (get s "name")) ((vis (or vis* "private"))
(owner (get s "owner")) (badge-colours
(can-delete (get s "can_delete"))) (dict
(~admin/snippet-row "private"
:name name :owner owner :badge-cls badge-cls :visibility vis "bg-stone-200 text-stone-700"
:extra (<> "shared"
(when is-admin "bg-blue-100 text-blue-700"
(~admin/snippet-visibility-select "admin"
:patch-url (get s "patch_url") "bg-amber-100 text-amber-700"))
:hx-headers {:X-CSRFToken csrf} (badge-cls
:options (<> (or (get badge-colours vis) "bg-stone-200 text-stone-700")))
(~admin/snippet-option :value "private" :selected (= vis "private") :label "private") (~admin/snippet-row
(~admin/snippet-option :value "shared" :selected (= vis "shared") :label "shared") :name name
(~admin/snippet-option :value "admin" :selected (= vis "admin") :label "admin")))) :owner owner
(when can-delete :badge-cls badge-cls
(~shared:misc/delete-btn :visibility vis
:url (get s "delete_url") :extra (<>
:trigger-target "#snippets-list" (when
:title "Delete snippet?" is-admin
:text (str "Delete \u201c" name "\u201d?") (~admin/snippet-visibility-select
:sx-headers {:X-CSRFToken csrf} :patch-url patch-url
:cls "px-3 py-1 text-sm bg-red-200 hover:bg-red-300 rounded text-red-800 flex-shrink-0")))))) :hx-headers (str "{\"X-CSRFToken\": \"" csrf "\"}")
:options (<>
(~admin/snippet-option
:value "private"
:selected (= vis "private")
:label "private")
(~admin/snippet-option
:value "shared"
:selected (= vis "shared")
:label "shared")
(~admin/snippet-option
:value "admin"
:selected (= vis "admin")
:label "admin"))))
(when
can-delete
(~shared:misc/delete-btn
:url delete-url
:trigger-target "#snippets-list"
:title "Delete snippet?"
:text (str "Delete \"" name "\"?")
:sx-headers (str "{\"X-CSRFToken\": \"" csrf "\"}")
:cls "px-3 py-1 text-sm bg-red-200 hover:bg-red-300 text-red-800 rounded-full")))))))
(or snippets (list))))))) (or snippets (list)))))))
;; Menu Items — receives serialized menu item dicts from service ;; Menu Items — receives serialized menu item dicts from service
(defcomp ~admin/menu-items-content (&key menu-items new-url csrf) (defcomp
~admin/menu-items-content
(&key menu-items new-url csrf)
(~admin/menu-items-panel (~admin/menu-items-panel
:new-url new-url :new-url new-url
:list (if (empty? (or menu-items (list))) :list (if
(~shared:misc/empty-state :icon "fa fa-inbox" (empty? (or menu-items (list)))
(~shared:misc/empty-state
:icon "fa fa-inbox"
:message "No menu items yet. Add one to get started!") :message "No menu items yet. Add one to get started!")
(~admin/menu-items-list (~admin/menu-items-list
:rows (map (lambda (mi) :rows (map
(~admin/menu-item-row (lambda
:img (~shared:misc/img-or-placeholder (mi)
:src (get mi "feature_image") :alt (get mi "label") (let-match
:size-cls "w-12 h-12 rounded-full object-cover flex-shrink-0") {:delete_url delete-url :url url :sort_order sort-order :edit_url edit-url :feature_image feature-image :label label}
:label (get mi "label") mi
:slug (get mi "url") (~admin/menu-item-row
:sort-order (str (or (get mi "sort_order") 0)) :img (~shared:misc/img-or-placeholder
:edit-url (get mi "edit_url") :src feature-image
:delete-url (get mi "delete_url") :alt label
:confirm-text (str "Remove " (get mi "label") " from the menu?") :size-cls "w-12 h-12 rounded-full object-cover flex-shrink-0")
:hx-headers {:X-CSRFToken csrf})) :label label
:slug url
:sort-order (str (or sort-order 0))
:edit-url edit-url
:delete-url delete-url
:confirm-text (str "Remove " label " from the menu?")
:hx-headers (str "{\"X-CSRFToken\": \"" csrf "\"}"))))
(or menu-items (list))))))) (or menu-items (list)))))))
;; Tag Groups — receives serialized tag group data from service ;; Tag Groups — receives serialized tag group data from service
(defcomp ~admin/tag-groups-content (&key groups unassigned-tags create-url csrf) (defcomp
~admin/tag-groups-content
(&key groups unassigned-tags create-url csrf)
(~admin/tag-groups-main (~admin/tag-groups-main
:form (~admin/tag-groups-create-form :create-url create-url :csrf csrf) :form (~admin/tag-groups-create-form :create-url create-url :csrf csrf)
:groups (if (empty? (or groups (list))) :groups (if
(~shared:misc/empty-state :icon "fa fa-tags" :message "No tag groups yet.") (empty? (or groups (list)))
(~shared:misc/empty-state
:icon "fa fa-tags"
:message "No tag groups yet.")
(~admin/tag-groups-list (~admin/tag-groups-list
:items (map (lambda (g) :items (map
(let* ((fi (get g "feature_image")) (lambda
(colour (get g "colour")) (g)
(name (get g "name")) (let-match
(initial (slice (or name "?") 0 1)) {:colour colour :sort_order sort-order* :feature_image fi :edit_href edit-href :slug slug* :name name}
(icon (if fi g
(~admin/tag-group-icon-image :src fi :name name) (let*
(~admin/tag-group-icon-color ((initial (slice (or name "?") 0 1))
:style (if colour (str "background:" colour) "background:#e7e5e4") (icon
:initial initial)))) (if
(~admin/tag-group-li fi
:icon icon (~admin/tag-group-icon-image :src fi :name name)
:edit-href (get g "edit_href") (~admin/tag-group-icon-color
:name name :style (if
:slug (or (get g "slug") "") colour
:sort-order (or (get g "sort_order") 0)))) (str "background:" colour)
"background:#e7e5e4")
:initial initial))))
(~admin/tag-group-li
:icon icon
:edit-href edit-href
:name name
:slug (or slug* "")
:sort-order (or sort-order* 0)))))
(or groups (list))))) (or groups (list)))))
:unassigned (when (not (empty? (or unassigned-tags (list)))) :unassigned (when
(not (empty? (or unassigned-tags (list))))
(~admin/unassigned-tags (~admin/unassigned-tags
:heading (str (len (or unassigned-tags (list))) " Unassigned Tags") :heading (str (len (or unassigned-tags (list))) " Unassigned Tags")
:spans (map (lambda (t) :spans (map
(~admin/unassigned-tag :name (get t "name"))) (lambda (t) (~admin/unassigned-tag :name (get t "name")))
(or unassigned-tags (list))))))) (or unassigned-tags (list)))))))
;; Tag Group Edit — receives serialized tag group + tags from service ;; Tag Group Edit — receives serialized tag group + tags from service
(defcomp ~admin/tag-group-edit-content (&key group all-tags save-url delete-url csrf) (defcomp
~admin/tag-group-edit-content
(&key group all-tags save-url delete-url csrf)
(~admin/tag-group-edit-main (~admin/tag-group-edit-main
:edit-form (~admin/tag-group-edit-form :edit-form (let-match
:save-url save-url :csrf csrf {:colour colour :sort_order sort-order :feature_image feature-image :name name}
:name (get group "name") group
:colour (get group "colour") (~admin/tag-group-edit-form
:sort-order (get group "sort_order") :save-url save-url
:feature-image (get group "feature_image") :csrf csrf
:tags (map (lambda (t) :name name
(~admin/tag-checkbox :colour colour
:tag-id (get t "id") :sort-order sort-order
:checked (get t "checked") :feature-image feature-image
:img (when (get t "feature_image") :tags (map
(~admin/tag-checkbox-image :src (get t "feature_image"))) (lambda
:name (get t "name"))) (t)
(or all-tags (list)))) (let-match
{:checked checked :feature_image t-feature-image :id tag-id :name t-name}
t
(~admin/tag-checkbox
:tag-id tag-id
:checked checked
:img (when
t-feature-image
(~admin/tag-checkbox-image :src t-feature-image))
:name t-name)))
(or all-tags (list)))))
:delete-form (~admin/tag-group-delete-form :delete-url delete-url :csrf csrf))) :delete-form (~admin/tag-group-delete-form :delete-url delete-url :csrf csrf)))
;; --------------------------------------------------------------------------- ;; ---------------------------------------------------------------------------
@@ -400,31 +524,54 @@
(code value) (code value)
value)))) value))))
(defcomp ~admin/data-scalar-table (&key columns) (defcomp
(div :class "w-full overflow-x-auto sm:overflow-visible" ~admin/data-scalar-table
(table :class "w-full table-fixed text-sm border border-neutral-200 rounded-xl overflow-hidden" (&key columns)
(thead :class "bg-neutral-50/70" (div
(tr (th :class "px-3 py-2 text-left font-medium w-40 sm:w-56" "Field") :class "w-full overflow-x-auto sm:overflow-visible"
(th :class "px-3 py-2 text-left font-medium" "Value"))) (table
:class "w-full table-fixed text-sm border border-neutral-200 rounded-xl overflow-hidden"
(thead
:class "bg-neutral-50/70"
(tr
(th :class "px-3 py-2 text-left font-medium w-40 sm:w-56" "Field")
(th :class "px-3 py-2 text-left font-medium" "Value")))
(tbody (tbody
(map (lambda (col) (map
(tr :class "border-t border-neutral-200 align-top" (lambda
(td :class "px-3 py-2 whitespace-nowrap text-neutral-600 align-top" (get col "key")) (col)
(td :class "px-3 py-2 align-top" (let-match
(~admin/data-value-cell :value (get col "value") :value-type (get col "type"))))) {:value value :key key :type type}
col
(tr
:class "border-t border-neutral-200 align-top"
(td :class "px-3 py-2 whitespace-nowrap text-neutral-600" key)
(td
:class "px-3 py-2 align-top"
(~admin/data-value-cell :value value :value-type type)))))
(or columns (list))))))) (or columns (list)))))))
(defcomp ~admin/data-relationship-item (&key index summary children) (defcomp
(tr :class "border-t border-neutral-200 align-top" ~admin/data-relationship-item
(&key index summary children)
(tr
:class "border-t border-neutral-200 align-top"
(td :class "px-2 py-1 whitespace-nowrap align-top" (str index)) (td :class "px-2 py-1 whitespace-nowrap align-top" (str index))
(td :class "px-2 py-1 align-top" (td
(pre :class "whitespace-pre-wrap break-words break-all text-xs" :class "px-2 py-1 align-top"
(pre
:class "whitespace-pre-wrap break-words break-all text-xs"
(code summary)) (code summary))
(when children (when
(div :class "mt-2 pl-3 border-l border-neutral-200" children
(~admin/data-model-content (div
:columns (get children "columns") :class "mt-2 pl-3 border-l border-neutral-200"
:relationships (get children "relationships"))))))) (let-match
{:relationships relationships :columns columns}
children
(~admin/data-model-content
:columns columns
:relationships relationships)))))))
(defcomp ~admin/data-relationship (&key name cardinality class-name loaded value) (defcomp ~admin/data-relationship (&key name cardinality class-name loaded value)
(div :class "rounded-xl border border-neutral-200" (div :class "rounded-xl border border-neutral-200"
@@ -463,29 +610,50 @@
:columns (get (get value "children") "columns") :columns (get (get value "children") "columns")
:relationships (get (get value "children") "relationships")))))))))) :relationships (get (get value "children") "relationships"))))))))))
(defcomp ~admin/data-model-content (&key columns relationships) (defcomp
(div :class "space-y-4" ~admin/data-model-content
(&key columns relationships)
(div
:class "space-y-4"
(~admin/data-scalar-table :columns columns) (~admin/data-scalar-table :columns columns)
(when (not (empty? (or relationships (list)))) (when
(div :class "space-y-3" (not (empty? (or relationships (list))))
(map (lambda (rel) (div
(~admin/data-relationship :class "space-y-3"
:name (get rel "name") (map
:cardinality (get rel "cardinality") (lambda
:class-name (get rel "class_name") (rel)
:loaded (get rel "loaded") (let-match
:value (get rel "value"))) {:cardinality cardinality :class_name class-name :loaded loaded :value value :name name}
rel
(~admin/data-relationship
:name name
:cardinality cardinality
:class-name class-name
:loaded loaded
:value value)))
relationships))))) relationships)))))
(defcomp ~admin/data-table-content (&key tablename model-data) (defcomp
(if (not model-data) ~admin/data-table-content
(&key tablename model-data)
(if
(not model-data)
(div :class "px-4 py-8 text-stone-400" "No post data available.") (div :class "px-4 py-8 text-stone-400" "No post data available.")
(div :class "px-4 py-8" (div
(div :class "mb-6 text-sm text-neutral-500" :class "px-4 py-8"
"Model: " (code "Post") " \u2022 Table: " (code tablename)) (div
(~admin/data-model-content :class "mb-6 text-sm text-neutral-500"
:columns (get model-data "columns") "Model: "
:relationships (get model-data "relationships"))))) (code "Post")
" • Table: "
(code tablename))
(let-match
{:relationships relationships :columns columns}
model-data
(~admin/data-model-content
:columns columns
:relationships relationships)))))
;; --------------------------------------------------------------------------- ;; ---------------------------------------------------------------------------
;; Calendar month view for browsing/toggling entries (B1) ;; Calendar month view for browsing/toggling entries (B1)
@@ -518,59 +686,117 @@
:sx-on:afterSwap "document.body.dispatchEvent(new CustomEvent('entryToggled'))" :sx-on:afterSwap "document.body.dispatchEvent(new CustomEvent('entryToggled'))"
(span :class "truncate block" name))) (span :class "truncate block" name)))
(defcomp ~admin/calendar-view (&key cal-id year month-name (defcomp
current-url prev-month-url prev-year-url ~admin/calendar-view
next-month-url next-year-url (&key
weekday-names days csrf) cal-id
(let* ((target (str "#calendar-view-" cal-id))) year
(div :id (str "calendar-view-" cal-id) month-name
:sx-get current-url :sx-trigger "entryToggled from:body" :sx-swap "outerHTML" current-url
(header :class "flex items-center justify-center mb-4" prev-month-url
(nav :class "flex items-center gap-2 text-xl" prev-year-url
(a :class "px-2 py-1 hover:bg-stone-100 rounded" next-month-url
:sx-get prev-year-url :sx-target target :sx-swap "outerHTML" next-year-url
weekday-names
days
csrf)
(let*
((target (str "#calendar-view-" cal-id)))
(div
:id (str "calendar-view-" cal-id)
:sx-get current-url
:sx-trigger "entryToggled from:body"
:sx-swap "outerHTML"
(header
:class "flex items-center justify-center mb-4"
(nav
:class "flex items-center gap-2 text-xl"
(a
:class "px-2 py-1 hover:bg-stone-100 rounded"
:sx-get prev-year-url
:sx-target target
:sx-swap "outerHTML"
(raw! "&laquo;")) (raw! "&laquo;"))
(a :class "px-2 py-1 hover:bg-stone-100 rounded" (a
:sx-get prev-month-url :sx-target target :sx-swap "outerHTML" :class "px-2 py-1 hover:bg-stone-100 rounded"
:sx-get prev-month-url
:sx-target target
:sx-swap "outerHTML"
(raw! "&lsaquo;")) (raw! "&lsaquo;"))
(div :class "px-3 font-medium" (str month-name " " year)) (div :class "px-3 font-medium" (str month-name " " year))
(a :class "px-2 py-1 hover:bg-stone-100 rounded" (a
:sx-get next-month-url :sx-target target :sx-swap "outerHTML" :class "px-2 py-1 hover:bg-stone-100 rounded"
:sx-get next-month-url
:sx-target target
:sx-swap "outerHTML"
(raw! "&rsaquo;")) (raw! "&rsaquo;"))
(a :class "px-2 py-1 hover:bg-stone-100 rounded" (a
:sx-get next-year-url :sx-target target :sx-swap "outerHTML" :class "px-2 py-1 hover:bg-stone-100 rounded"
:sx-get next-year-url
:sx-target target
:sx-swap "outerHTML"
(raw! "&raquo;")))) (raw! "&raquo;"))))
(div :class "rounded border bg-white" (div
(div :class "hidden sm:grid grid-cols-7 text-center text-xs font-semibold text-stone-700 bg-stone-50 border-b" :class "rounded border bg-white"
(map (lambda (wd) (div :class "py-2" wd)) (or weekday-names (list)))) (div
(div :class "grid grid-cols-1 sm:grid-cols-7 gap-px bg-stone-200" :class "hidden sm:grid grid-cols-7 text-center text-xs font-semibold text-stone-700 bg-stone-50 border-b"
(map (lambda (day) (map
(let* ((extra-cls (if (get day "in_month") "" " bg-stone-50 text-stone-400")) (lambda (wd) (div :class "py-2" wd))
(entries (or (get day "entries") (list)))) (or weekday-names (list))))
(div :class (str "min-h-20 bg-white px-2 py-2 text-xs" extra-cls) (div
(div :class "font-medium mb-1" (str (get day "day"))) :class "grid grid-cols-1 sm:grid-cols-7 gap-px bg-stone-200"
(when (not (empty? entries)) (map
(div :class "space-y-0.5" (lambda
(map (lambda (e) (day)
(if (get e "is_associated") (let-match
(~admin/cal-entry-associated {:entries entries* :in_month in-month :day day-num}
:name (get e "name") :toggle-url (get e "toggle_url") :csrf csrf) day
(~admin/cal-entry-unassociated (let*
:name (get e "name") :toggle-url (get e "toggle_url") :csrf csrf))) ((extra-cls (if in-month "" " bg-stone-50 text-stone-400"))
entries)))))) (entries (or entries* (list))))
(div
:class (str "min-h-20 bg-white px-2 py-2 text-xs" extra-cls)
(div :class "font-medium mb-1" (str day-num))
(when
(not (empty? entries))
(div
:class "space-y-0.5"
(map
(lambda
(e)
(let-match
{:is_associated is-associated :toggle_url toggle-url :name name}
e
(if
is-associated
(~admin/cal-entry-associated
:name name
:toggle-url toggle-url
:csrf csrf)
(~admin/cal-entry-unassociated
:name name
:toggle-url toggle-url
:csrf csrf))))
entries)))))))
(or days (list)))))))) (or days (list))))))))
;; --------------------------------------------------------------------------- ;; ---------------------------------------------------------------------------
;; Nav entries OOB — renders associated entry/calendar items in scroll wrapper (B2) ;; Nav entries OOB — renders associated entry/calendar items in scroll wrapper (B2)
;; --------------------------------------------------------------------------- ;; ---------------------------------------------------------------------------
(defcomp ~admin/nav-entries-oob (&key entries calendars) (defcomp
(let* ((entry-list (or entries (list))) ~admin/nav-entries-oob
(cal-list (or calendars (list))) (&key entries calendars)
(has-items (or (not (empty? entry-list)) (not (empty? cal-list)))) (let*
(nav-cls "justify-center cursor-pointer flex flex-row items-center gap-2 rounded bg-stone-200 text-black [.hover-capable_&]:hover:bg-yellow-300 aria-selected:bg-stone-500 aria-selected:text-white [.hover-capable_&[aria-selected=true]:hover]:bg-orange-500 p-2") ((entry-list (or entries (list)))
(scroll-hs "on load or scroll if window.innerWidth >= 640 and my.scrollWidth > my.clientWidth remove .hidden from .entries-nav-arrow add .flex to .entries-nav-arrow else add .hidden to .entries-nav-arrow remove .flex from .entries-nav-arrow end")) (cal-list (or calendars (list)))
(if (not has-items) (has-items (or (not (empty? entry-list)) (not (empty? cal-list))))
(nav-cls
"justify-center cursor-pointer flex flex-row items-center gap-2 rounded bg-stone-200 text-black [.hover-capable_&]:hover:bg-yellow-300 aria-selected:bg-stone-500 aria-selected:text-white [.hover-capable_&[aria-selected=true]:hover]:bg-orange-500 p-2")
(scroll-hs
"on load or scroll if window.innerWidth >= 640 and my.scrollWidth > my.clientWidth remove .hidden from .entries-nav-arrow add .flex to .entries-nav-arrow else add .hidden to .entries-nav-arrow remove .flex from .entries-nav-arrow end"))
(if
(not has-items)
(~shared:nav/blog-nav-entries-empty) (~shared:nav/blog-nav-entries-empty)
(~shared:misc/scroll-nav-wrapper (~shared:misc/scroll-nav-wrapper
:wrapper-id "entries-calendars-nav-wrapper" :wrapper-id "entries-calendars-nav-wrapper"
@@ -580,14 +806,27 @@
:scroll-hs scroll-hs :scroll-hs scroll-hs
:right-hs "on click set #associated-items-container.scrollLeft to #associated-items-container.scrollLeft + 200" :right-hs "on click set #associated-items-container.scrollLeft to #associated-items-container.scrollLeft + 200"
:items (<> :items (<>
(map (lambda (e) (map
(~shared:navigation/calendar-entry-nav (lambda
:href (get e "href") :nav-class nav-cls (e)
:name (get e "name") :date-str (get e "date_str"))) (let-match
{:href href :date_str date-str :name name}
e
(~shared:navigation/calendar-entry-nav
:href href
:nav-class nav-cls
:name name
:date-str date-str)))
entry-list) entry-list)
(map (lambda (c) (map
(~shared:nav/blog-nav-calendar-item (lambda
:href (get c "href") :nav-cls nav-cls (c)
:name (get c "name"))) (let-match
{:href href :name name}
c
(~shared:nav/blog-nav-calendar-item
:href href
:nav-cls nav-cls
:name name)))
cal-list)) cal-list))
:oob true)))) :oob true))))

View File

@@ -159,91 +159,147 @@
:btn (~page/tw-plus)))))) :btn (~page/tw-plus))))))
;; Entry card (list view) from data ;; Entry card (list view) from data
(defcomp ~entries/entry-card-from-data (&key entry-href name day-href (defcomp
page-badge-href page-badge-title cal-name ~entries/entry-card-from-data
date-str start-time end-time is-page-scoped (&key
cost has-ticket ticket-data) entry-href
name
day-href
page-badge-href
page-badge-title
cal-name
date-str
start-time
end-time
is-page-scoped
cost
has-ticket
ticket-data)
(~entries/entry-card (~entries/entry-card
:title (if entry-href :title (if
entry-href
(~entries/entry-title-linked :href entry-href :name name) (~entries/entry-title-linked :href entry-href :name name)
(~entries/entry-title-plain :name name)) (~entries/entry-title-plain :name name))
:badges (<> :badges (<>
(when page-badge-title (when
(~entries/entry-page-badge :href page-badge-href :title page-badge-title)) page-badge-title
(when cal-name (~entries/entry-page-badge
(~entries/entry-cal-badge :name cal-name))) :href page-badge-href
:title page-badge-title))
(when cal-name (~entries/entry-cal-badge :name cal-name)))
:time-parts (<> :time-parts (<>
(when (and day-href (not is-page-scoped)) (when
(and day-href (not is-page-scoped))
(~entries/entry-time-linked :href day-href :date-str date-str)) (~entries/entry-time-linked :href day-href :date-str date-str))
(when (and (not day-href) (not is-page-scoped) date-str) (when
(and (not day-href) (not is-page-scoped) date-str)
(~entries/entry-time-plain :date-str date-str)) (~entries/entry-time-plain :date-str date-str))
start-time start-time
(when end-time (str " \u2013 " end-time))) (when end-time (str " " end-time)))
:cost (when cost (~entries/entry-cost :cost cost)) :cost (when cost (~entries/entry-cost :cost cost))
:widget (when has-ticket :widget (when
(~entries/entry-widget-wrapper has-ticket
:widget (~entries/tw-widget-from-data (let-match
:entry-id (get ticket-data "entry-id") {:csrf csrf :entry-id entry-id :qty qty :price price :ticket-url ticket-url}
:price (get ticket-data "price") ticket-data
:qty (get ticket-data "qty") (~entries/entry-widget-wrapper
:ticket-url (get ticket-data "ticket-url") :widget (~entries/tw-widget-from-data
:csrf (get ticket-data "csrf")))))) :entry-id entry-id
:price price
:qty qty
:ticket-url ticket-url
:csrf csrf))))))
;; Entry card (tile view) from data ;; Entry card (tile view) from data
(defcomp ~entries/entry-card-tile-from-data (&key entry-href name day-href (defcomp
page-badge-href page-badge-title cal-name ~entries/entry-card-tile-from-data
date-str time-str (&key
cost has-ticket ticket-data) entry-href
name
day-href
page-badge-href
page-badge-title
cal-name
date-str
time-str
cost
has-ticket
ticket-data)
(~entries/entry-card-tile (~entries/entry-card-tile
:title (if entry-href :title (if
entry-href
(~entries/entry-title-tile-linked :href entry-href :name name) (~entries/entry-title-tile-linked :href entry-href :name name)
(~entries/entry-title-tile-plain :name name)) (~entries/entry-title-tile-plain :name name))
:badges (<> :badges (<>
(when page-badge-title (when
(~entries/entry-page-badge :href page-badge-href :title page-badge-title)) page-badge-title
(when cal-name (~entries/entry-page-badge
(~entries/entry-cal-badge :name cal-name))) :href page-badge-href
:title page-badge-title))
(when cal-name (~entries/entry-cal-badge :name cal-name)))
:time time-str :time time-str
:cost (when cost (~entries/entry-cost :cost cost)) :cost (when cost (~entries/entry-cost :cost cost))
:widget (when has-ticket :widget (when
(~entries/entry-tile-widget-wrapper has-ticket
:widget (~entries/tw-widget-from-data (let-match
:entry-id (get ticket-data "entry-id") {:csrf csrf :entry-id entry-id :qty qty :price price :ticket-url ticket-url}
:price (get ticket-data "price") ticket-data
:qty (get ticket-data "qty") (~entries/entry-tile-widget-wrapper
:ticket-url (get ticket-data "ticket-url") :widget (~entries/tw-widget-from-data
:csrf (get ticket-data "csrf")))))) :entry-id entry-id
:price price
:qty qty
:ticket-url ticket-url
:csrf csrf))))))
;; Entry cards list (with date separators + sentinel) from data ;; Entry cards list (with date separators + sentinel) from data
(defcomp ~entries/entry-cards-from-data (&key items view page has-more next-url) (defcomp
~entries/entry-cards-from-data
(&key items view page has-more next-url)
(<> (<>
(map (lambda (item) (map
(if (get item "is-separator") (lambda
(~entries/date-separator :date-str (get item "date-str")) (item)
(if (= view "tile") (let-match
(~entries/entry-card-tile-from-data {:date-str date-str :time-str time-str :has-ticket has-ticket :is-separator is-separator :ticket-data ticket-data :day-href day-href :page-badge-title page-badge-title :entry-href entry-href :start-time start-time :end-time end-time :is-page-scoped is-page-scoped :page-badge-href page-badge-href :cal-name cal-name :cost cost :name name}
:entry-href (get item "entry-href") :name (get item "name") item
:day-href (get item "day-href") (if
:page-badge-href (get item "page-badge-href") is-separator
:page-badge-title (get item "page-badge-title") (~entries/date-separator :date-str date-str)
:cal-name (get item "cal-name") (if
:date-str (get item "date-str") :time-str (get item "time-str") (= view "tile")
:cost (get item "cost") :has-ticket (get item "has-ticket") (~entries/entry-card-tile-from-data
:ticket-data (get item "ticket-data")) :entry-href entry-href
(~entries/entry-card-from-data :name name
:entry-href (get item "entry-href") :name (get item "name") :day-href day-href
:day-href (get item "day-href") :page-badge-href page-badge-href
:page-badge-href (get item "page-badge-href") :page-badge-title page-badge-title
:page-badge-title (get item "page-badge-title") :cal-name cal-name
:cal-name (get item "cal-name") :date-str date-str
:date-str (get item "date-str") :time-str time-str
:start-time (get item "start-time") :end-time (get item "end-time") :cost cost
:is-page-scoped (get item "is-page-scoped") :has-ticket has-ticket
:cost (get item "cost") :has-ticket (get item "has-ticket") :ticket-data ticket-data)
:ticket-data (get item "ticket-data"))))) (~entries/entry-card-from-data
:entry-href entry-href
:name name
:day-href day-href
:page-badge-href page-badge-href
:page-badge-title page-badge-title
:cal-name cal-name
:date-str date-str
:start-time start-time
:end-time end-time
:is-page-scoped is-page-scoped
:cost cost
:has-ticket has-ticket
:ticket-data ticket-data)))))
(or items (list))) (or items (list)))
(when has-more (when
(~shared:misc/sentinel-simple :id (str "sentinel-" page) :next-url next-url)))) has-more
(~shared:misc/sentinel-simple
:id (str "sentinel-" page)
:next-url next-url))))
;; Events main panel (toggle + cards grid) from data ;; Events main panel (toggle + cards grid) from data
(defcomp ~entries/main-panel-from-data (&key toggle items view page has-more next-url) (defcomp ~entries/main-panel-from-data (&key toggle items view page has-more next-url)

View File

@@ -323,28 +323,43 @@
;; --------------------------------------------------------------------------- ;; ---------------------------------------------------------------------------
;; Day checkboxes from data — replaces Python loop ;; Day checkboxes from data — replaces Python loop
(defcomp ~forms/day-checkboxes-from-data (&key days-data all-checked) (defcomp
~forms/day-checkboxes-from-data
(&key days-data all-checked)
(<> (<>
(~forms/day-all-checkbox :checked (when all-checked "checked")) (~forms/day-all-checkbox :checked (when all-checked "checked"))
(map (lambda (d) (map
(~forms/day-checkbox (lambda
:name (get d "name") (d)
:label (get d "label") (let-match
:checked (when (get d "checked") "checked"))) {:checked checked :label label :name name}
d
(~forms/day-checkbox
:name name
:label label
:checked (when checked "checked"))))
(or days-data (list))))) (or days-data (list)))))
;; Slot options from data — replaces _slot_options_html Python loop ;; Slot options from data — replaces _slot_options_html Python loop
(defcomp ~forms/slot-options-from-data (&key slots) (defcomp
(<> (map (lambda (s) ~forms/slot-options-from-data
(~forms/slot-option (&key slots)
:value (get s "value") (<>
:data-start (get s "data-start") (map
:data-end (get s "data-end") (lambda
:data-flexible (get s "data-flexible") (s)
:data-cost (get s "data-cost") (let-match
:selected (get s "selected") {:data-end data-end :data-flexible data-flexible :selected selected :value value :data-cost data-cost :label label :data-start data-start}
:label (get s "label"))) s
(or slots (list))))) (~forms/slot-option
:value value
:data-start data-start
:data-end data-end
:data-flexible data-flexible
:data-cost data-cost
:selected selected
:label label)))
(or slots (list)))))
;; Slot picker from data — wraps picker + options ;; Slot picker from data — wraps picker + options
(defcomp ~forms/slot-picker-from-data (&key id slots) (defcomp ~forms/slot-picker-from-data (&key id slots)

View File

@@ -5,155 +5,247 @@
;; Auto-fetching header macros — calendar, day, entry, slot, tickets ;; Auto-fetching header macros — calendar, day, entry, slot, tickets
;; --------------------------------------------------------------------------- ;; ---------------------------------------------------------------------------
(defmacro ~events-calendar-header-auto (oob) (defmacro
~events-calendar-header-auto
(oob)
"Calendar header row using (events-calendar-ctx)." "Calendar header row using (events-calendar-ctx)."
(quasiquote (quasiquote
(let ((__cal (events-calendar-ctx)) (let
(__sc (select-colours))) ((__cal (events-calendar-ctx)) (__sc (select-colours)))
(when (get __cal "slug") (let-match
(~shared:layout/menu-row-sx :id "calendar-row" :level 3 {:description description :slug slug :name name}
:link-href (url-for "calendar.get" __cal
:calendar-slug (get __cal "slug")) (when
:link-label-content (~header/calendar-label slug
:name (get __cal "name") (~shared:layout/menu-row-sx
:description (get __cal "description")) :id "calendar-row"
:nav (<> :level 3
(~shared:layout/nav-link :href (url-for "defpage_slots_listing" :link-href (url-for "calendar.get" :calendar-slug slug)
:calendar-slug (get __cal "slug")) :link-label-content (~header/calendar-label :name name :description description)
:icon "fa fa-clock" :label "Slots" :nav (<>
:select-colours __sc) (~shared:layout/nav-link
(let ((__rights (app-rights))) :href (url-for "defpage_slots_listing" :calendar-slug slug)
(when (get __rights "admin") :icon "fa fa-clock"
(~shared:layout/nav-link :href (url-for "defpage_calendar_admin" :label "Slots"
:calendar-slug (get __cal "slug")) :select-colours __sc)
:icon "fa fa-cog" (let
:select-colours __sc)))) ((__rights (app-rights)))
:child-id "calendar-header-child" (when
:oob (unquote oob)))))) (get __rights "admin")
(~shared:layout/nav-link
:href (url-for "defpage_calendar_admin" :calendar-slug slug)
:icon "fa fa-cog"
:select-colours __sc))))
:child-id "calendar-header-child"
:oob (unquote oob)))))))
(defmacro ~events-calendar-admin-header-auto (oob) (defmacro
~events-calendar-admin-header-auto
(oob)
"Calendar admin header row." "Calendar admin header row."
(quasiquote (quasiquote
(let ((__cal (events-calendar-ctx)) (let
(__sc (select-colours))) ((__cal (events-calendar-ctx)) (__sc (select-colours)))
(when (get __cal "slug") (let-match
(~shared:layout/menu-row-sx :id "calendar-admin-row" :level 4 {:slug slug}
:link-label "admin" :icon "fa fa-cog" __cal
:nav (<> (when
(~shared:layout/nav-link :href (url-for "defpage_slots_listing" slug
:calendar-slug (get __cal "slug")) (~shared:layout/menu-row-sx
:label "slots" :select-colours __sc) :id "calendar-admin-row"
(~shared:layout/nav-link :href (url-for "calendar.admin.calendar_description_edit" :level 4
:calendar-slug (get __cal "slug")) :link-label "admin"
:label "description" :select-colours __sc)) :icon "fa fa-cog"
:child-id "calendar-admin-header-child" :nav (<>
:oob (unquote oob)))))) (~shared:layout/nav-link
:href (url-for "defpage_slots_listing" :calendar-slug slug)
:label "slots"
:select-colours __sc)
(~shared:layout/nav-link
:href (url-for
"calendar.admin.calendar_description_edit"
:calendar-slug slug)
:label "description"
:select-colours __sc))
:child-id "calendar-admin-header-child"
:oob (unquote oob)))))))
(defmacro ~events-day-header-auto (oob) (defmacro
~events-day-header-auto
(oob)
"Day header row using (events-day-ctx)." "Day header row using (events-day-ctx)."
(quasiquote (quasiquote
(let ((__day (events-day-ctx)) (let
(__cal (events-calendar-ctx))) ((__day (events-day-ctx)) (__cal (events-calendar-ctx)))
(when (get __day "date-str") (let-match
(~shared:layout/menu-row-sx :id "day-row" :level 4 {:date-str date-str :nav nav :year year :day day :month month}
:link-href (url-for "calendar.day.show_day" __day
:calendar-slug (get __cal "slug") (when
:year (get __day "year") date-str
:month (get __day "month") (let-match
:day (get __day "day")) {:slug cal-slug}
:link-label-content (~header/day-label __cal
:date-str (get __day "date-str")) (~shared:layout/menu-row-sx
:nav (get __day "nav") :id "day-row"
:child-id "day-header-child" :level 4
:oob (unquote oob)))))) :link-href (url-for
"calendar.day.show_day"
:calendar-slug cal-slug
:year year
:month month
:day day)
:link-label-content (~header/day-label :date-str date-str)
:nav nav
:child-id "day-header-child"
:oob (unquote oob))))))))
(defmacro ~events-day-admin-header-auto (oob) (defmacro
~events-day-admin-header-auto
(oob)
"Day admin header row." "Day admin header row."
(quasiquote (quasiquote
(let ((__day (events-day-ctx)) (let
(__cal (events-calendar-ctx))) ((__day (events-day-ctx)) (__cal (events-calendar-ctx)))
(when (get __day "date-str") (let-match
(~shared:layout/menu-row-sx :id "day-admin-row" :level 5 {:date-str date-str :year year :day day :month month}
:link-href (url-for "defpage_day_admin" __day
:calendar-slug (get __cal "slug") (when
:year (get __day "year") date-str
:month (get __day "month") (let-match
:day (get __day "day")) {:slug cal-slug}
:link-label "admin" :icon "fa fa-cog" __cal
:child-id "day-admin-header-child" (~shared:layout/menu-row-sx
:oob (unquote oob)))))) :id "day-admin-row"
:level 5
:link-href (url-for
"defpage_day_admin"
:calendar-slug cal-slug
:year year
:month month
:day day)
:link-label "admin"
:icon "fa fa-cog"
:child-id "day-admin-header-child"
:oob (unquote oob))))))))
(defmacro ~events-entry-header-auto (oob) (defmacro
~events-entry-header-auto
(oob)
"Entry header row using (events-entry-ctx)." "Entry header row using (events-entry-ctx)."
(quasiquote (quasiquote
(let ((__ectx (events-entry-ctx))) (let
(when (get __ectx "id") ((__ectx (events-entry-ctx)))
(~shared:layout/menu-row-sx :id "entry-row" :level 5 (let-match
:link-href (get __ectx "link-href") {:time-str time-str :nav nav :link-href link-href :id id :name name}
:link-label-content (~header/entry-label __ectx
:entry-id (get __ectx "id") (when
:title (~admin/entry-title :name (get __ectx "name")) id
:times (~admin/entry-times :time-str (get __ectx "time-str"))) (~shared:layout/menu-row-sx
:nav (get __ectx "nav") :id "entry-row"
:child-id "entry-header-child" :level 5
:oob (unquote oob)))))) :link-href link-href
:link-label-content (~header/entry-label
:entry-id id
:title (~admin/entry-title :name name)
:times (~admin/entry-times :time-str time-str))
:nav nav
:child-id "entry-header-child"
:oob (unquote oob)))))))
(defmacro ~events-entry-admin-header-auto (oob) (defmacro
~events-entry-admin-header-auto
(oob)
"Entry admin header row." "Entry admin header row."
(quasiquote (quasiquote
(let ((__ectx (events-entry-ctx))) (let
(when (get __ectx "id") ((__ectx (events-entry-ctx)))
(~shared:layout/menu-row-sx :id "entry-admin-row" :level 6 (let-match
:link-href (get __ectx "admin-href") {:admin-href admin-href :is-admin is-admin :ticket-types-href ticket-types-href :select-colours select-colours :id id}
:link-label "admin" :icon "fa fa-cog" __ectx
:nav (when (get __ectx "is-admin") (when
(~shared:layout/nav-link :href (get __ectx "ticket-types-href") id
:label "ticket_types" (~shared:layout/menu-row-sx
:select-colours (get __ectx "select-colours"))) :id "entry-admin-row"
:child-id "entry-admin-header-child" :level 6
:oob (unquote oob)))))) :link-href admin-href
:link-label "admin"
:icon "fa fa-cog"
:nav (when
is-admin
(~shared:layout/nav-link
:href ticket-types-href
:label "ticket_types"
:select-colours select-colours))
:child-id "entry-admin-header-child"
:oob (unquote oob)))))))
(defmacro ~events-slot-header-auto (oob) (defmacro
~events-slot-header-auto
(oob)
"Slot detail header row using (events-slot-ctx)." "Slot detail header row using (events-slot-ctx)."
(quasiquote (quasiquote
(let ((__slot (events-slot-ctx))) (let
(when (get __slot "name") ((__slot (events-slot-ctx)))
(~shared:layout/menu-row-sx :id "slot-row" :level 5 (let-match
:link-label-content (~header/slot-label {:description description :name name}
:name (get __slot "name") __slot
:description (get __slot "description")) (when
:child-id "slot-header-child" name
:oob (unquote oob)))))) (~shared:layout/menu-row-sx
:id "slot-row"
:level 5
:link-label-content (~header/slot-label :name name :description description)
:child-id "slot-header-child"
:oob (unquote oob)))))))
(defmacro ~events-ticket-types-header-auto (oob) (defmacro
~events-ticket-types-header-auto
(oob)
"Ticket types header row." "Ticket types header row."
(quasiquote (quasiquote
(let ((__ectx (events-entry-ctx)) (let
(__cal (events-calendar-ctx))) ((__ectx (events-entry-ctx)) (__cal (events-calendar-ctx)))
(when (get __ectx "id") (let-match
(~shared:layout/menu-row-sx :id "ticket_types-row" :level 7 {:ticket-types-href ticket-types-href :id id}
:link-href (get __ectx "ticket-types-href") __ectx
:link-label-content (<> (when
(i :class "fa fa-ticket") id
(div :class "shrink-0" "ticket types")) (~shared:layout/menu-row-sx
:nav (~forms/admin-placeholder-nav) :id "ticket_types-row"
:child-id "ticket_type-header-child" :level 7
:oob (unquote oob)))))) :link-href ticket-types-href
:link-label-content (<>
(i :class "fa fa-ticket")
(div :class "shrink-0" "ticket types"))
:nav (~forms/admin-placeholder-nav)
:child-id "ticket_type-header-child"
:oob (unquote oob)))))))
(defmacro ~events-ticket-type-header-auto (oob) (defmacro
~events-ticket-type-header-auto
(oob)
"Single ticket type header row using (events-ticket-type-ctx)." "Single ticket type header row using (events-ticket-type-ctx)."
(quasiquote (quasiquote
(let ((__tt (events-ticket-type-ctx))) (let
(when (get __tt "id") ((__tt (events-ticket-type-ctx)))
(~shared:layout/menu-row-sx :id "ticket_type-row" :level 8 (let-match
:link-href (get __tt "link-href") {:link-href link-href :id id :name name}
:link-label-content (div :class "flex flex-col md:flex-row md:gap-2 items-center" __tt
(div :class "flex flex-row items-center gap-2" (when
(i :class "fa fa-ticket") id
(div :class "shrink-0" (get __tt "name")))) (~shared:layout/menu-row-sx
:nav (~forms/admin-placeholder-nav) :id "ticket_type-row"
:child-id "ticket_type-header-child-inner" :level 8
:oob (unquote oob)))))) :link-href link-href
:link-label-content (div
:class "flex flex-col md:flex-row md:gap-2 items-baseline"
(div
:class "flex flex-row items-center gap-2"
(i :class "fa fa-ticket")
(div :class "shrink-0" name)))
:nav (~forms/admin-placeholder-nav)
:child-id "ticket_type-header-child-inner"
:oob (unquote oob)))))))
(defmacro ~events-markets-header-auto (oob) (defmacro ~events-markets-header-auto (oob)
"Markets section header row." "Markets section header row."

View File

@@ -98,24 +98,47 @@
(~page/slot-description-oob :description (or description ""))))) (~page/slot-description-oob :description (or description "")))))
;; Slots table from data ;; Slots table from data
(defcomp ~page/slots-table-from-data (&key list-container slots pre-action add-url (defcomp
tr-cls pill-cls action-btn hx-select csrf-hdr) ~page/slots-table-from-data
(&key
list-container
slots
pre-action
add-url
tr-cls
pill-cls
action-btn
hx-select
csrf-hdr)
(~page/slots-table (~page/slots-table
:list-container list-container :list-container list-container
:rows (if (empty? (or slots (list))) :rows (if
(empty? (or slots (list)))
(~page/slots-empty-row) (~page/slots-empty-row)
(<> (map (lambda (s) (<>
(~page/slots-row (map
:tr-cls tr-cls :slot-href (get s "slot-href") (lambda
:pill-cls pill-cls :hx-select hx-select (s)
:slot-name (get s "slot-name") :description (get s "description") (let-match
:flexible (get s "flexible") {:slot-name slot-name :time-str time-str :flexible flexible :description description :days days :cost-str cost-str :del-url del-url :slot-href slot-href}
:days (~page/days-pills-from-data :days (get s "days")) s
:time-str (get s "time-str") (~page/slots-row
:cost-str (get s "cost-str") :action-btn action-btn :tr-cls tr-cls
:del-url (get s "del-url") :csrf-hdr csrf-hdr)) :slot-href slot-href
(or slots (list))))) :pill-cls pill-cls
:pre-action pre-action :add-url add-url)) :hx-select hx-select
:slot-name slot-name
:description description
:flexible flexible
:days (~page/days-pills-from-data :days days)
:time-str time-str
:cost-str cost-str
:action-btn action-btn
:del-url del-url
:csrf-hdr csrf-hdr)))
(or slots (list)))))
:pre-action pre-action
:add-url add-url))
(defcomp ~page/ticket-type-col (&key label value) (defcomp ~page/ticket-type-col (&key label value)
(div :class "flex flex-col" (div :class "flex flex-col"
@@ -203,47 +226,87 @@
:onclick hide-js "Cancel")))) :onclick hide-js "Cancel"))))
;; Data-driven buy form — Python passes pre-resolved data, .sx does layout + iteration ;; Data-driven buy form — Python passes pre-resolved data, .sx does layout + iteration
(defcomp ~page/buy-form (&key entry-id info-sold info-remaining info-basket (defcomp
ticket-types user-ticket-counts-by-type ~page/buy-form
user-ticket-count price-str adjust-url csrf state (&key
my-tickets-href) entry-id
(if (!= state "confirmed") info-sold
info-remaining
info-basket
ticket-types
user-ticket-counts-by-type
user-ticket-count
price-str
adjust-url
csrf
state
my-tickets-href)
(if
(!= state "confirmed")
(~page/buy-not-confirmed :entry-id (str entry-id)) (~page/buy-not-confirmed :entry-id (str entry-id))
(let ((eid-s (str entry-id)) (let
(target (str "#ticket-buy-" entry-id))) ((eid-s (str entry-id)) (target (str "#ticket-buy-" entry-id)))
(div :id (str "ticket-buy-" entry-id) :class "rounded-xl border border-stone-200 bg-white p-4" (div
(h3 :class "text-sm font-semibold text-stone-700 mb-3" :id (str "ticket-buy-" entry-id)
(i :class "fa fa-ticket mr-1" :aria-hidden "true") "Tickets") :class "rounded-xl border border-stone-200 bg-white p-4"
;; Info bar (h3
(when (or info-sold info-remaining info-basket) :class "text-sm font-semibold text-stone-700 mb-3"
(div :class "flex items-center gap-3 mb-3 text-xs text-stone-500" (i :class "fa fa-ticket mr-1" :aria-hidden "true")
"Tickets")
(when
(or info-sold info-remaining info-basket)
(div
:class "flex items-center gap-3 mb-3 text-xs text-stone-500"
(when info-sold (span (str info-sold " sold"))) (when info-sold (span (str info-sold " sold")))
(when info-remaining (span (str info-remaining " remaining"))) (when info-remaining (span (str info-remaining " remaining")))
(when info-basket (when
(span :class "text-emerald-600 font-medium" info-basket
(i :class "fa fa-shopping-cart text-[0.6rem]" :aria-hidden "true") (span
:class "text-emerald-600 font-medium"
(i
:class "fa fa-shopping-cart text-[0.6rem]"
:aria-hidden "true")
(str " " info-basket " in basket"))))) (str " " info-basket " in basket")))))
;; Body — multi-type or default (if
(if (and ticket-types (not (empty? ticket-types))) (and ticket-types (not (empty? ticket-types)))
(div :class "space-y-2" (div
(map (fn (tt) :class "space-y-2"
(let ((tt-count (if user-ticket-counts-by-type (map
(get user-ticket-counts-by-type (str (get tt "id")) 0) (fn
0)) (tt)
(tt-id (get tt "id"))) (let-match
(div :class "flex items-center justify-between p-3 rounded-lg bg-stone-50 border border-stone-100" {:cost_str cost-str :id tt-id :name tt-name}
(div (div :class "font-medium text-sm" (get tt "name")) tt
(div :class "text-xs text-stone-500" (get tt "cost_str"))) (let
(~page/adjust-inline :csrf csrf :adjust-url adjust-url :target target ((tt-count (if user-ticket-counts-by-type (get user-ticket-counts-by-type (str tt-id) 0) 0)))
:entry-id eid-s :count tt-count :ticket-type-id tt-id (div
:my-tickets-href my-tickets-href)))) :class "flex items-center justify-between p-3 rounded-lg bg-stone-50 border border-stone-100"
(div
(div :class "font-medium text-sm" tt-name)
(div :class "text-xs text-stone-500" cost-str))
(~page/adjust-inline
:csrf csrf
:adjust-url adjust-url
:target target
:entry-id eid-s
:count tt-count
:ticket-type-id tt-id
:my-tickets-href my-tickets-href)))))
ticket-types)) ticket-types))
(<> (div :class "flex items-center justify-between mb-4" (<>
(div (span :class "font-medium text-green-600" price-str) (div
(span :class "text-sm text-stone-500 ml-2" "per ticket"))) :class "flex items-center justify-between mb-4"
(~page/adjust-inline :csrf csrf :adjust-url adjust-url :target target (div
:entry-id eid-s :count (if user-ticket-count user-ticket-count 0) (span :class "font-medium text-green-600" price-str)
:ticket-type-id nil :my-tickets-href my-tickets-href))))))) (span :class "text-sm text-stone-500 ml-2" "per ticket")))
(~page/adjust-inline
:csrf csrf
:adjust-url adjust-url
:target target
:entry-id eid-s
:count (if user-ticket-count user-ticket-count 0)
:ticket-type-id nil
:my-tickets-href my-tickets-href)))))))
;; Inline +/- controls (used by both default and per-type) ;; Inline +/- controls (used by both default and per-type)
(defcomp ~page/adjust-inline (&key csrf adjust-url target entry-id count ticket-type-id my-tickets-href) (defcomp ~page/adjust-inline (&key csrf adjust-url target entry-id count ticket-type-id my-tickets-href)
@@ -285,26 +348,53 @@
"Tickets available once this event is confirmed.")) "Tickets available once this event is confirmed."))
(defcomp ~page/buy-result (&key entry-id tickets remaining my-tickets-href) (defcomp
(let ((count (len tickets)) ~page/buy-result
(suffix (if (= count 1) "" "s"))) (&key entry-id tickets remaining my-tickets-href)
(div :id (str "ticket-buy-" entry-id) :class "rounded-xl border border-emerald-200 bg-emerald-50 p-4" (let
(div :class "flex items-center gap-2 mb-3" ((count (len tickets)) (suffix (if (= count 1) "" "s")))
(div
:id (str "ticket-buy-" entry-id)
:class "rounded-xl border border-emerald-200 bg-emerald-50 p-4"
(div
:class "flex items-center gap-2 mb-3"
(i :class "fa fa-check-circle text-emerald-600" :aria-hidden "true") (i :class "fa fa-check-circle text-emerald-600" :aria-hidden "true")
(span :class "font-semibold text-emerald-800" (str count " ticket" suffix " reserved"))) (span
(div :class "space-y-2 mb-4" :class "font-semibold text-emerald-800"
(map (fn (t) (str count " ticket" suffix " reserved")))
(a :href (get t "href") :class "flex items-center justify-between p-2 rounded-lg bg-white border border-emerald-100 hover:border-emerald-300 transition text-sm" (div
(div :class "flex items-center gap-2" :class "space-y-2 mb-4"
(i :class "fa fa-ticket text-emerald-500" :aria-hidden "true") (map
(span :class "font-mono text-xs text-stone-500" (get t "code_short"))) (fn
(span :class "text-xs text-emerald-600 font-medium" "View ticket"))) (t)
(let-match
{:href href :code_short code-short}
t
(a
:href href
:class "flex items-center justify-between p-2 rounded-lg bg-white border border-emerald-100 hover:border-emerald-300 transition text-sm"
(div
:class "flex items-center gap-2"
(i
:class "fa fa-ticket text-emerald-500"
:aria-hidden "true")
(span :class "font-mono text-xs text-stone-500" code-short))
(span
:class "text-xs text-emerald-600 font-medium"
"View ticket"))))
tickets)) tickets))
(when (not (nil? remaining)) (when
(let ((r-suffix (if (= remaining 1) "" "s"))) (not (nil? remaining))
(p :class "text-xs text-stone-500" (str remaining " ticket" r-suffix " remaining")))) (let
(div :class "mt-3 flex gap-2" ((r-suffix (if (= remaining 1) "" "s")))
(a :href my-tickets-href :class "text-sm text-emerald-700 hover:text-emerald-900 underline" (p
:class "text-xs text-stone-500"
(str remaining " ticket" r-suffix " remaining"))))
(div
:class "mt-3 flex gap-2"
(a
:href my-tickets-href
:class "text-sm text-emerald-700 hover:text-emerald-900 underline"
"View all my tickets"))))) "View all my tickets")))))
;; Single response wrappers for POST routes (include OOB cart icon) ;; Single response wrappers for POST routes (include OOB cart icon)
@@ -477,27 +567,46 @@
(~page/post-img-placeholder))) (~page/post-img-placeholder)))
;; Entry posts nav OOB from data ;; Entry posts nav OOB from data
(defcomp ~page/entry-posts-nav-oob-from-data (&key nav-btn posts) (defcomp
(if (empty? (or posts (list))) ~page/entry-posts-nav-oob-from-data
(&key nav-btn posts)
(if
(empty? (or posts (list)))
(~page/entry-posts-nav-oob-empty) (~page/entry-posts-nav-oob-empty)
(~page/entry-posts-nav-oob (~page/entry-posts-nav-oob
:items (<> (map (lambda (p) :items (<>
(~page/entry-nav-post (map
:href (get p "href") :nav-btn nav-btn (lambda
:img (~page/post-img-from-data :src (get p "img") :alt (get p "title")) (p)
:title (get p "title"))) (let-match
posts))))) {:href href :title title :img img}
p
(~page/entry-nav-post
:href href
:nav-btn nav-btn
:img (~page/post-img-from-data :src img :alt title)
:title title)))
posts)))))
;; Entry posts nav (non-OOB) from data — for desktop nav embedding ;; Entry posts nav (non-OOB) from data — for desktop nav embedding
(defcomp ~page/entry-posts-nav-inner-from-data (&key posts) (defcomp
(when (not (empty? (or posts (list)))) ~page/entry-posts-nav-inner-from-data
(&key posts)
(when
(not (empty? (or posts (list))))
(~page/entry-posts-nav-oob (~page/entry-posts-nav-oob
:items (<> (map (lambda (p) :items (<>
(~page/entry-nav-post-link (map
:href (get p "href") (lambda
:img (~page/post-img-from-data :src (get p "img") :alt (get p "title")) (p)
:title (get p "title"))) (let-match
posts))))) {:href href :title title :img img}
p
(~page/entry-nav-post-link
:href href
:img (~page/post-img-from-data :src img :alt title)
:title title)))
posts)))))
;; Post nav entries+calendars OOB from data ;; Post nav entries+calendars OOB from data
(defcomp ~page/post-nav-wrapper-from-data (&key nav-btn entries calendars hyperscript) (defcomp ~page/post-nav-wrapper-from-data (&key nav-btn entries calendars hyperscript)
@@ -602,14 +711,23 @@
(~shared:layout/nav-link :href admin-href :icon "fa fa-cog")))) (~shared:layout/nav-link :href admin-href :icon "fa fa-cog"))))
;; Post search results from data ;; Post search results from data
(defcomp ~page/post-search-results-from-data (&key items page next-url has-more) (defcomp
~page/post-search-results-from-data
(&key items page next-url has-more)
(<> (<>
(map (lambda (item) (map
(~forms/post-search-item (lambda
:post-url (get item "post-url") :entry-id (get item "entry-id") (item)
:csrf (get item "csrf") :post-id (get item "post-id") (let-match
:img (~page/post-img-from-data :src (get item "img") :alt (get item "title")) {:csrf csrf :entry-id entry-id :post-url post-url :title title :img img :post-id post-id}
:title (get item "title"))) item
(~forms/post-search-item
:post-url post-url
:entry-id entry-id
:csrf csrf
:post-id post-id
:img (~page/post-img-from-data :src img :alt title)
:title title)))
(or items (list))) (or items (list)))
(cond (cond
(has-more (~forms/post-search-sentinel :page page :next-url next-url)) (has-more (~forms/post-search-sentinel :page page :next-url next-url))
@@ -617,16 +735,26 @@
(true "")))) (true ""))))
;; Entry options from data — state-driven button composition ;; Entry options from data — state-driven button composition
(defcomp ~page/entry-options-from-data (&key entry-id state buttons) (defcomp
~page/entry-options-from-data
(&key entry-id state buttons)
(~admin/entry-options (~admin/entry-options
:entry-id entry-id :entry-id entry-id
:buttons (<> (map (lambda (b) :buttons (<>
(~admin/entry-option-button (map
:url (get b "url") :target (str "#calendar_entry_options_" entry-id) (lambda
:csrf (get b "csrf") :btn-type (get b "btn-type") (b)
:action-btn (get b "action-btn") (let-match
:confirm-title (get b "confirm-title") {:csrf csrf :confirm-title confirm-title :url url :btn-type btn-type :action-btn action-btn :confirm-text confirm-text :label label :is-btn is-btn}
:confirm-text (get b "confirm-text") b
:label (get b "label") (~admin/entry-option-button
:is-btn (get b "is-btn"))) :url url
(or buttons (list)))))) :target (str "#calendar_entry_options_" entry-id)
:csrf csrf
:btn-type btn-type
:action-btn action-btn
:confirm-title confirm-title
:confirm-text confirm-text
:label label
:is-btn is-btn)))
(or buttons (list))))))

View File

@@ -211,18 +211,28 @@
;; --------------------------------------------------------------------------- ;; ---------------------------------------------------------------------------
;; My tickets panel from data ;; My tickets panel from data
(defcomp ~tickets/panel-from-data (&key (list-container :as string) (tickets :as list?)) (defcomp
~tickets/panel-from-data
(&key (list-container :as string) (tickets :as list?))
(~tickets/panel (~tickets/panel
:list-container list-container :list-container list-container
:has-tickets (not (empty? (or tickets (list)))) :has-tickets (not (empty? (or tickets (list))))
:cards (<> (map (lambda (t) :cards (<>
(~tickets/card (map
:href (get t "href") :entry-name (get t "entry-name") (lambda
:type-name (get t "type-name") :time-str (get t "time-str") (t)
:cal-name (get t "cal-name") (let-match
:badge (~entries/ticket-state-badge :state (get t "state")) {:time-str time-str :href href :type-name type-name :code-prefix code-prefix :entry-name entry-name :cal-name cal-name :state state}
:code-prefix (get t "code-prefix"))) t
(or tickets (list)))))) (~tickets/card
:href href
:entry-name entry-name
:type-name type-name
:time-str time-str
:cal-name cal-name
:badge (~entries/ticket-state-badge :state state)
:code-prefix code-prefix)))
(or tickets (list))))))
;; Ticket detail from data — uses lg badge variant ;; Ticket detail from data — uses lg badge variant
(defcomp ~tickets/detail-from-data (&key (list-container :as string) (back-href :as string) (header-bg :as string) (entry-name :as string) (defcomp ~tickets/detail-from-data (&key (list-container :as string) (back-href :as string) (header-bg :as string) (entry-name :as string)
@@ -256,54 +266,106 @@
(true nil)))) (true nil))))
;; Ticket admin panel from data ;; Ticket admin panel from data
(defcomp ~tickets/admin-panel-from-data (&key (list-container :as string) (lookup-url :as string) (tickets :as list?) (defcomp
(total :as number?) (confirmed :as number?) (checked-in :as number?) (reserved :as number?)) ~tickets/admin-panel-from-data
(&key
(list-container :as string)
(lookup-url :as string)
(tickets :as list?)
(total :as number?)
(confirmed :as number?)
(checked-in :as number?)
(reserved :as number?))
(~tickets/admin-panel (~tickets/admin-panel
:list-container list-container :list-container list-container
:stats (<> :stats (<>
(~tickets/admin-stat :border "border-stone-200" :bg "" (~tickets/admin-stat
:text-cls "text-stone-900" :label-cls "text-stone-500" :border "border-stone-200"
:value (str (or total 0)) :label "Total") :bg ""
(~tickets/admin-stat :border "border-emerald-200" :bg "bg-emerald-50" :text-cls "text-stone-900"
:text-cls "text-emerald-700" :label-cls "text-emerald-600" :label-cls "text-stone-500"
:value (str (or confirmed 0)) :label "Confirmed") :value (str (or total 0))
(~tickets/admin-stat :border "border-blue-200" :bg "bg-blue-50" :label "Total")
:text-cls "text-blue-700" :label-cls "text-blue-600" (~tickets/admin-stat
:value (str (or checked-in 0)) :label "Checked In") :border "border-emerald-200"
(~tickets/admin-stat :border "border-amber-200" :bg "bg-amber-50" :bg "bg-emerald-50"
:text-cls "text-amber-700" :label-cls "text-amber-600" :text-cls "text-emerald-700"
:value (str (or reserved 0)) :label "Reserved")) :label-cls "text-emerald-600"
:value (str (or confirmed 0))
:label "Confirmed")
(~tickets/admin-stat
:border "border-blue-200"
:bg "bg-blue-50"
:text-cls "text-blue-700"
:label-cls "text-blue-600"
:value (str (or checked-in 0))
:label "Checked In")
(~tickets/admin-stat
:border "border-amber-200"
:bg "bg-amber-50"
:text-cls "text-amber-700"
:label-cls "text-amber-600"
:value (str (or reserved 0))
:label "Reserved"))
:lookup-url lookup-url :lookup-url lookup-url
:has-tickets (not (empty? (or tickets (list)))) :has-tickets (not (empty? (or tickets (list))))
:rows (<> (map (lambda (t) :rows (<>
(~tickets/admin-row-from-data (map
:code (get t "code") :code-short (get t "code-short") (lambda
:entry-name (get t "entry-name") :date-str (get t "date-str") (t)
:type-name (get t "type-name") :state (get t "state") (let-match
:checkin-url (get t "checkin-url") :csrf (get t "csrf") {:date-str date-str :csrf csrf :type-name type-name :code-short code-short :entry-name entry-name :code code :checkin-url checkin-url :checked-in-time checked-in-time :state state}
:checked-in-time (get t "checked-in-time"))) t
(or tickets (list)))))) (~tickets/admin-row-from-data
:code code
:code-short code-short
:entry-name entry-name
:date-str date-str
:type-name type-name
:state state
:checkin-url checkin-url
:csrf csrf
:checked-in-time checked-in-time)))
(or tickets (list))))))
;; Entry tickets admin from data ;; Entry tickets admin from data
(defcomp ~tickets/entry-tickets-admin-from-data (&key (entry-name :as string) (count-label :as string) (tickets :as list?) (csrf :as string)) (defcomp
~tickets/entry-tickets-admin-from-data
(&key
(entry-name :as string)
(count-label :as string)
(tickets :as list?)
(csrf :as string))
(~tickets/entry-tickets-admin-panel (~tickets/entry-tickets-admin-panel
:entry-name entry-name :count-label count-label :entry-name entry-name
:body (if (empty? (or tickets (list))) :count-label count-label
:body (if
(empty? (or tickets (list)))
(~tickets/entry-tickets-admin-empty) (~tickets/entry-tickets-admin-empty)
(~tickets/entry-tickets-admin-table (~tickets/entry-tickets-admin-table
:rows (<> (map (lambda (t) :rows (<>
(~tickets/entry-tickets-admin-row (map
:code (get t "code") :code-short (get t "code-short") (lambda
:type-name (get t "type-name") (t)
:badge (~entries/ticket-state-badge :state (get t "state")) (let-match
:action (cond {:type-name type-name :code-short code-short :code code :checkin-url checkin-url :checked-in-time checked-in-time :state state}
((or (= (get t "state") "confirmed") (= (get t "state") "reserved")) t
(~tickets/entry-tickets-admin-checkin (~tickets/entry-tickets-admin-row
:checkin-url (get t "checkin-url") :code (get t "code") :csrf csrf)) :code code
((= (get t "state") "checked_in") :code-short code-short
(~tickets/admin-checked-in :time-str (or (get t "checked-in-time") ""))) :type-name type-name
(true nil)))) :badge (~entries/ticket-state-badge :state state)
(or tickets (list)))))))) :action (cond
((or (= state "confirmed") (= state "paid"))
(~tickets/entry-tickets-admin-checkin
:checkin-url checkin-url
:code code
:csrf csrf))
((= state "checked-in")
(~tickets/admin-checked-in
:time-str (or checked-in-time "")))
(true nil)))))
(or tickets (list))))))))
;; Checkin success row from data ;; Checkin success row from data
(defcomp ~tickets/checkin-success-row-from-data (&key (code :as string) (code-short :as string) (entry-name :as string) (date-str :as string?) (type-name :as string) (time-str :as string)) (defcomp ~tickets/checkin-success-row-from-data (&key (code :as string) (code-short :as string) (entry-name :as string) (date-str :as string?) (type-name :as string) (time-str :as string))
@@ -316,21 +378,43 @@
:time-str time-str)) :time-str time-str))
;; Ticket types table from data ;; Ticket types table from data
(defcomp ~tickets/types-table-from-data (&key (list-container :as string) (ticket-types :as list?) (action-btn :as string) (add-url :as string) (defcomp
(tr-cls :as string) (pill-cls :as string) (hx-select :as string) (csrf-hdr :as string)) ~tickets/types-table-from-data
(&key
(list-container :as string)
(ticket-types :as list?)
(action-btn :as string)
(add-url :as string)
(tr-cls :as string)
(pill-cls :as string)
(hx-select :as string)
(csrf-hdr :as string))
(~page/ticket-types-table (~page/ticket-types-table
:list-container list-container :list-container list-container
:rows (if (empty? (or ticket-types (list))) :rows (if
(empty? (or ticket-types (list)))
(~page/ticket-types-empty-row) (~page/ticket-types-empty-row)
(<> (map (lambda (tt) (<>
(~page/ticket-types-row (map
:tr-cls tr-cls :tt-href (get tt "tt-href") (lambda
:pill-cls pill-cls :hx-select hx-select (tt)
:tt-name (get tt "tt-name") :cost-str (get tt "cost-str") (let-match
:count (get tt "count") :action-btn action-btn {:tt-href tt-href :count count :cost-str cost-str :tt-name tt-name :del-url del-url}
:del-url (get tt "del-url") :csrf-hdr csrf-hdr)) tt
(or ticket-types (list))))) (~page/ticket-types-row
:action-btn action-btn :add-url add-url)) :tr-cls tr-cls
:tt-href tt-href
:pill-cls pill-cls
:hx-select hx-select
:tt-name tt-name
:cost-str cost-str
:count count
:action-btn action-btn
:del-url del-url
:csrf-hdr csrf-hdr)))
(or ticket-types (list)))))
:action-btn action-btn
:add-url add-url))
;; Lookup result from data ;; Lookup result from data
(defcomp ~tickets/lookup-result-from-data (&key (entry-name :as string) (type-name :as string?) (date-str :as string?) (cal-name :as string?) (defcomp ~tickets/lookup-result-from-data (&key (entry-name :as string) (type-name :as string?) (date-str :as string?) (cal-name :as string?)

View File

@@ -92,52 +92,95 @@
;; --- Data-driven post card (replaces Python _post_card_sx loop) --- ;; --- Data-driven post card (replaces Python _post_card_sx loop) ---
(defcomp ~social/post-card-from-data (&key (d :as dict) (has-actor :as boolean) (csrf :as string) (defcomp
(like-url :as string) (unlike-url :as string) ~social/post-card-from-data
(boost-url :as string) (unboost-url :as string)) (&key
(let* ((boosted-by (get d "boosted_by")) (d :as dict)
(actor-icon (get d "actor_icon")) (has-actor :as boolean)
(actor-name (get d "actor_name")) (csrf :as string)
(initial (or (get d "initial") "?")) (like-url :as string)
(avatar (~shared:misc/avatar (unlike-url :as string)
:src actor-icon (boost-url :as string)
:cls (if actor-icon "w-10 h-10 rounded-full" (unboost-url :as string))
"w-10 h-10 rounded-full bg-stone-300 flex items-center justify-center text-stone-600 font-bold text-sm") (let-match
:initial (when (not actor-icon) initial))) {:actor_name actor-name :liked_by_me liked :boosted_by_me boosted-me :time time :actor_username actor-username :domain domain :content content :object_id oid :boosted_by boosted-by :summary summary :original_url original-url :safe_id safe-id :author_inbox ainbox :reply_url reply-url :like_count like-count :boost_count boost-count :actor_icon actor-icon :initial initial*}
(boost (when boosted-by (~social/boost-label :name boosted-by))) d
(content-sx (if (get d "summary") (let*
(~social/content :content (get d "content") :summary (get d "summary")) ((initial (or initial* "?"))
(~social/content :content (get d "content")))) (avatar
(original (when (get d "original_url") (~shared:misc/avatar
(~social/original-link :url (get d "original_url")))) :src actor-icon
(safe-id (get d "safe_id")) :cls (if
(interactions (when has-actor actor-icon
(let* ((oid (get d "object_id")) "w-10 h-10 rounded-full"
(ainbox (get d "author_inbox")) "w-10 h-10 rounded-full bg-stone-300 flex items-center justify-center text-stone-600 font-bold text-sm")
(target (str "#interactions-" safe-id)) :initial (when (not actor-icon) initial)))
(liked (get d "liked_by_me")) (boost (when boosted-by (~social/boost-label :name boosted-by)))
(boosted-me (get d "boosted_by_me")) (content-sx
(l-action (if liked unlike-url like-url)) (if
(l-cls (str "flex items-center gap-1 " (if liked "text-red-500 hover:text-red-600" "hover:text-red-500"))) summary
(l-icon (if liked "\u2665" "\u2661")) (~social/content :content content :summary summary)
(b-action (if boosted-me unboost-url boost-url)) (~social/content :content content)))
(b-cls (str "flex items-center gap-1 " (if boosted-me "text-green-600 hover:text-green-700" "hover:text-green-600"))) (original
(reply-url (get d "reply_url")) (when original-url (~social/original-link :url original-url)))
(reply (when reply-url (~social/reply-link :url reply-url))) (interactions
(like-form (~social/like-form (when
:action l-action :target target :oid oid :ainbox ainbox has-actor
:csrf csrf :cls l-cls :icon l-icon :count (get d "like_count"))) (let*
(boost-form (~social/boost-form ((target (str "#interactions-" safe-id))
:action b-action :target target :oid oid :ainbox ainbox (l-action (if liked unlike-url like-url))
:csrf csrf :cls b-cls :count (get d "boost_count")))) (l-cls
(div :id (str "interactions-" safe-id) (str
(~social/interaction-buttons :like like-form :boost boost-form :reply reply)))))) "flex items-center gap-1 "
(~social/post-card (if
:boost boost :avatar avatar liked
:actor-name actor-name :actor-username (get d "actor_username") "text-red-500 hover:text-red-600"
:domain (get d "domain") :time (get d "time") "hover:text-red-500")))
:content content-sx :original original (l-icon (if liked "♥" "♡"))
:interactions interactions))) (b-action (if boosted-me unboost-url boost-url))
(b-cls
(str
"flex items-center gap-1 "
(if
boosted-me
"text-green-600 hover:text-green-700"
"hover:text-green-600")))
(reply (when reply-url (~social/reply-link :url reply-url)))
(like-form
(~social/like-form
:action l-action
:target target
:oid oid
:ainbox ainbox
:csrf csrf
:cls l-cls
:icon l-icon
:count like-count))
(boost-form
(~social/boost-form
:action b-action
:target target
:oid oid
:ainbox ainbox
:csrf csrf
:cls b-cls
:count boost-count)))
(div
:id (str "interactions-" safe-id)
(~social/interaction-buttons
:like like-form
:boost boost-form
:reply reply))))))
(~social/post-card
:boost boost
:avatar avatar
:actor-name actor-name
:actor-username actor-username
:domain domain
:time time
:content content-sx
:original original
:interactions interactions))))
;; Data-driven timeline items (replaces Python _timeline_items_sx loop) ;; Data-driven timeline items (replaces Python _timeline_items_sx loop)
(defcomp ~social/timeline-items-from-data (&key (items :as list) (next-url :as string?) (has-actor :as boolean) (csrf :as string) (defcomp ~social/timeline-items-from-data (&key (items :as list) (next-url :as string?) (has-actor :as boolean) (csrf :as string)
@@ -174,35 +217,53 @@
;; Assembled social nav — replaces Python _social_nav_sx ;; Assembled social nav — replaces Python _social_nav_sx
;; --------------------------------------------------------------------------- ;; ---------------------------------------------------------------------------
(defcomp ~social/nav (&key actor) (defcomp
(if (not actor) ~social/nav
(~social/nav-choose-username :url (url-for "identity.choose_username_form")) (&key actor)
(let* ((rp (request-path)) (if
(links (list (not actor)
(dict :endpoint "social.defpage_home_timeline" :label "Timeline") (~social/nav-choose-username
(dict :endpoint "social.defpage_public_timeline" :label "Public") :url (url-for "identity.choose_username_form"))
(dict :endpoint "social.defpage_compose_form" :label "Compose") (let*
(dict :endpoint "social.defpage_following_list" :label "Following") ((rp (request-path))
(dict :endpoint "social.defpage_followers_list" :label "Followers") (links
(dict :endpoint "social.defpage_search" :label "Search")))) (list
(dict :endpoint "social.defpage_home_timeline" :label "Timeline")
(dict :endpoint "social.defpage_public_timeline" :label "Public")
(dict :endpoint "social.defpage_compose_form" :label "Compose")
(dict :endpoint "social.defpage_following_list" :label "Following")
(dict :endpoint "social.defpage_followers_list" :label "Followers")
(dict :endpoint "social.defpage_search" :label "Search"))))
(~social/nav-bar (~social/nav-bar
:items (<> :items (<>
(map (lambda (lnk) (map
(let* ((href (url-for (get lnk "endpoint"))) (lambda
(bold (if (= rp href) " font-bold" ""))) (lnk)
(a :href href (let-match
:class (str "px-2 py-1 rounded hover:bg-stone-200" bold) {:label label :endpoint endpoint}
(get lnk "label")))) lnk
(let*
((href (url-for endpoint))
(bold (if (= rp href) " font-bold" "")))
(a
:href href
:class (str "px-2 py-1 rounded hover:bg-stone-200" bold)
label))))
links) links)
(let* ((notif-url (url-for "social.defpage_notifications")) (let*
(notif-bold (if (= rp notif-url) " font-bold" ""))) ((notif-url (url-for "social.defpage_notifications"))
(notif-bold (if (= rp notif-url) " font-bold" "")))
(~social/nav-notification-link (~social/nav-notification-link
:href notif-url :href notif-url
:cls (str "px-2 py-1 rounded hover:bg-stone-200 relative" notif-bold) :cls (str "px-2 py-1 rounded hover:bg-stone-200 relative" notif-bold)
:count-url (url-for "social.notification_count"))) :count-url (url-for "social.notification_count")))
(a :href (url-for "activitypub.actor_profile" :username (get actor "preferred_username")) (let-match
:class "px-2 py-1 rounded hover:bg-stone-200" {:preferred_username username}
(str "@" (get actor "preferred_username")))))))) actor
(a
:href (url-for "activitypub.actor_profile" :username username)
:class "px-2 py-1 rounded hover:bg-stone-200"
(str "@" username))))))))
;; --------------------------------------------------------------------------- ;; ---------------------------------------------------------------------------
;; Assembled post card — replaces Python _post_card_sx ;; Assembled post card — replaces Python _post_card_sx

View File

@@ -21,7 +21,8 @@
;; Registry of freeze scopes: name → list of {name signal} entries ;; Registry of freeze scopes: name → list of {name signal} entries
(define-library (sx freeze) (define-library
(sx freeze)
(export (export
freeze-registry freeze-registry
freeze-signal freeze-signal
@@ -33,82 +34,96 @@
freeze-to-sx freeze-to-sx
thaw-from-sx) thaw-from-sx)
(begin (begin
(define freeze-registry (dict))
(define freeze-registry (dict)) (define
freeze-signal
;; Register a signal in the current freeze scope :effects (mutation)
(define freeze-signal :effects [mutation] (fn
(fn (name sig) (name sig)
(let ((scope-name (context "sx-freeze-scope" nil))) (let
(when scope-name ((scope-name (context "sx-freeze-scope" nil)))
(let ((entries (or (get freeze-registry scope-name) (list)))) (when
(append! entries (dict "name" name "signal" sig)) scope-name
(dict-set! freeze-registry scope-name entries)))))) (let
((entries (or (get freeze-registry scope-name) (list))))
;; Freeze scope delimiter — collects signals registered within body (append! entries (dict "name" name "signal" sig))
(define freeze-scope :effects [mutation] (dict-set! freeze-registry scope-name entries))))))
(fn (name body-fn) (define
(scope-push! "sx-freeze-scope" name) freeze-scope
;; Initialize empty entry list for this scope :effects (mutation)
(dict-set! freeze-registry name (list)) (fn
(cek-call body-fn nil) (name body-fn)
(scope-pop! "sx-freeze-scope") (scope-push! "sx-freeze-scope" name)
nil)) (dict-set! freeze-registry name (list))
(cek-call body-fn nil)
;; Freeze a named scope → SX dict of signal values (scope-pop! "sx-freeze-scope")
(define cek-freeze-scope :effects [] nil))
(fn (name) (define
(let ((entries (or (get freeze-registry name) (list))) cek-freeze-scope
(signals-dict (dict))) :effects ()
(for-each (fn (entry) (fn
(dict-set! signals-dict (name)
(get entry "name") (let
(signal-value (get entry "signal")))) ((entries (or (get freeze-registry name) (list)))
entries) (signals-dict (dict)))
(dict "name" name "signals" signals-dict)))) (for-each
(fn
;; Freeze all scopes (entry)
(define cek-freeze-all :effects [] (dict-set!
(fn () signals-dict
(map (fn (name) (cek-freeze-scope name)) (get entry "name")
(keys freeze-registry)))) (signal-value (get entry "signal"))))
entries)
;; Thaw a named scope — restore signal values from frozen data (dict "name" name "signals" signals-dict))))
(define cek-thaw-scope :effects [mutation] (define
(fn (name frozen) cek-freeze-all
(let ((entries (or (get freeze-registry name) (list))) :effects ()
(values (get frozen "signals"))) (fn
(when values ()
(for-each (fn (entry) (map (fn (name) (cek-freeze-scope name)) (keys freeze-registry))))
(let ((sig-name (get entry "name")) (define
(sig (get entry "signal")) cek-thaw-scope
(val (get values sig-name))) :effects (mutation)
(when (not (nil? val)) (fn
(reset! sig val)))) (name frozen)
entries))))) (let
((entries (or (get freeze-registry name) (list)))
;; Thaw all scopes from a list of frozen scope dicts (values (get frozen "signals")))
(define cek-thaw-all :effects [mutation] (when
(fn (frozen-list) values
(for-each (fn (frozen) (for-each
(cek-thaw-scope (get frozen "name") frozen)) (fn
frozen-list))) (entry)
(let
;; Serialize a frozen scope to SX text ((sig-name (get entry "name"))
(define freeze-to-sx :effects [] (sig (get entry "signal"))
(fn (name) (val (get values sig-name)))
(sx-serialize (cek-freeze-scope name)))) (when (not (nil? val)) (reset! sig val))))
entries)))))
;; Restore from SX text (define
(define thaw-from-sx :effects [mutation] cek-thaw-all
(fn (sx-text) :effects (mutation)
(let ((parsed (sx-parse sx-text))) (fn
(when (not (empty? parsed)) (frozen-list)
(let ((frozen (first parsed))) (for-each
(cek-thaw-scope (get frozen "name") frozen)))))) (fn (frozen) (cek-thaw-scope (get frozen "name") frozen))
frozen-list)))
(define
)) ;; end define-library freeze-to-sx
:effects ()
(fn (name) (sx-serialize (cek-freeze-scope name))))
(define
thaw-from-sx
:effects (mutation)
(fn
(sx-text)
(let
((parsed (sx-parse sx-text)))
(when
(not (empty? parsed))
(let
((frozen (first parsed)))
(cek-thaw-scope (get frozen "name") frozen)))))))) ;; end define-library
;; Re-export to global namespace for backward compatibility ;; Re-export to global namespace for backward compatibility
(import (sx freeze)) (import (sx freeze))

View File

@@ -1,6 +1,7 @@
(define-library (sx highlight) (define-library
(sx highlight)
(export (export
sx-specials sx-specials
sx-special? sx-special?
@@ -16,204 +17,184 @@
highlight-sx highlight-sx
highlight) highlight)
(begin (begin
(define
(define sx-specials
sx-specials (list
(list "defcomp"
"defcomp" "defrelation"
"defrelation" "defisland"
"defisland" "defpage"
"defpage" "defhelper"
"defhelper" "define"
"define" "defmacro"
"defmacro" "defconfig"
"defconfig" "deftest"
"deftest" "if"
"if" "when"
"when" "cond"
"cond" "case"
"case" "and"
"and" "or"
"or" "not"
"not" "let"
"let" "let*"
"let*" "lambda"
"lambda" "fn"
"fn" "do"
"do" "begin"
"begin" "quote"
"quote" "quasiquote"
"quasiquote" "->"
"->" "map"
"map" "filter"
"filter" "reduce"
"reduce" "some"
"some" "every?"
"every?" "map-indexed"
"map-indexed" "for-each"
"for-each" "&key"
"&key" "&rest"
"&rest" "set!"
"set!")) "satisfies?"
"match"
(define sx-special? (fn (s) (some (fn (x) (= x s)) sx-specials))) "let-match"
"define-protocol"
(define hl-digit? (fn (c) (and (>= c "0") (<= c "9")))) "implement"
"->>"
(define "|>"
hl-alpha? "as->"
(fn (c) (or (and (>= c "a") (<= c "z")) (and (>= c "A") (<= c "Z"))))) "define-library"
"import"
(define "perform"
hl-sym-char? "guard"
(fn "call/cc"
(c) "raise"
(or "define-syntax"
(hl-alpha? c) "syntax-rules"
(hl-digit? c) "make-parameter"
(= c "_") "parameterize"))
(= c "-") (define sx-special? (fn (s) (some (fn (x) (= x s)) sx-specials)))
(= c "?") (define hl-digit? (fn (c) (and (>= c "0") (<= c "9"))))
(= c "!") (define
(= c "+") hl-alpha?
(= c "*") (fn
(= c "/") (c)
(= c "<") (or (and (>= c "a") (<= c "z")) (and (>= c "A") (<= c "Z")))))
(= c ">") (define
(= c "=") hl-sym-char?
(= c "&") (fn
(= c ".")))) (c)
(or
(define hl-ws? (fn (c) (or (= c " ") (= c "\n") (= c "\t") (= c "\r")))) (hl-alpha? c)
(hl-digit? c)
(define hl-escape (fn (s) s)) (= c "_")
(= c "-")
(define (= c "?")
hl-span (= c "!")
(fn (= c "+")
(class text) (= c "*")
(if (= c "/")
(= class "") (= c "<")
(list (quote span) text) (= c ">")
(list (quote span) (make-keyword "class") class text)))) (= c "=")
(= c "&")
(define (= c "."))))
tokenize-sx (define
(fn hl-ws?
(code) (fn (c) (or (= c " ") (= c "\n") (= c "\t") (= c "\r"))))
(let (define hl-escape (fn (s) s))
((tokens (list)) (i 0) (len (string-length code))) (define
(let hl-span
loop (fn
() (class text)
(when (if
(< i len) (= class "")
(list (quote span) text)
(list (quote span) (make-keyword "class") class text))))
(define
tokenize-sx
(fn
(code)
(let
((tokens (list)) (i 0) (len (string-length code)))
(let (let
((c (substring code i (+ i 1)))) loop
(if ()
(= c ";") (when
(< i len)
(let (let
((start i)) ((c (substring code i (+ i 1))))
(set! i (+ i 1))
(let
scan
()
(when
(and
(< i len)
(not (= (substring code i (+ i 1)) "\n")))
(set! i (+ i 1))
(scan)))
(set!
tokens
(append
tokens
(list (list "comment" (substring code start i))))))
(if
(= c "\"")
(let
((start i))
(set! i (+ i 1))
(let
sloop
()
(when
(< i len)
(let
((sc (substring code i (+ i 1))))
(if
(= sc "\\")
(do (set! i (+ i 2)) (sloop))
(if
(= sc "\"")
(set! i (+ i 1))
(do (set! i (+ i 1)) (sloop)))))))
(set!
tokens
(append
tokens
(list (list "string" (substring code start i))))))
(if (if
(= c ":") (= c ";")
(let (let
((start i)) ((start i))
(set! i (+ i 1)) (set! i (+ i 1))
(when (let
(and scan
(< i len) ()
(hl-alpha? (substring code i (+ i 1)))) (when
(let (and
scan (< i len)
() (not (= (substring code i (+ i 1)) "\n")))
(when (set! i (+ i 1))
(and (scan)))
(< i len)
(hl-sym-char? (substring code i (+ i 1))))
(set! i (+ i 1))
(scan))))
(set! (set!
tokens tokens
(append (append
tokens tokens
(list (list "keyword" (substring code start i)))))) (list (list "comment" (substring code start i))))))
(if (if
(= c "~") (= c "\"")
(let (let
((start i)) ((start i))
(set! i (+ i 1)) (set! i (+ i 1))
(let (let
scan sloop
() ()
(when (when
(and (< i len)
(< i len) (let
(let ((sc (substring code i (+ i 1))))
((x (substring code i (+ i 1)))) (if
(or (hl-sym-char? x) (= x "/")))) (= sc "\\")
(set! i (+ i 1)) (do (set! i (+ i 2)) (sloop))
(scan))) (if
(= sc "\"")
(set! i (+ i 1))
(do (set! i (+ i 1)) (sloop)))))))
(set! (set!
tokens tokens
(append (append
tokens tokens
(list (list "component" (substring code start i)))))) (list (list "string" (substring code start i))))))
(if (if
(or (= c ":")
(= c "(") (let
(= c ")") ((start i))
(= c "[") (set! i (+ i 1))
(= c "]") (when
(= c "{") (and
(= c "}")) (< i len)
(do (hl-alpha? (substring code i (+ i 1))))
(let
scan
()
(when
(and
(< i len)
(hl-sym-char? (substring code i (+ i 1))))
(set! i (+ i 1))
(scan))))
(set! (set!
tokens tokens
(append tokens (list (list "paren" c)))) (append
(set! i (+ i 1))) tokens
(list (list "keyword" (substring code start i))))))
(if (if
(hl-digit? c) (= c "~")
(let (let
((start i)) ((start i))
(set! i (+ i 1))
(let (let
scan scan
() ()
@@ -222,53 +203,30 @@
(< i len) (< i len)
(let (let
((x (substring code i (+ i 1)))) ((x (substring code i (+ i 1))))
(or (hl-digit? x) (= x ".")))) (or (hl-sym-char? x) (= x "/"))))
(set! i (+ i 1)) (set! i (+ i 1))
(scan))) (scan)))
(set! (set!
tokens tokens
(append (append
tokens tokens
(list (list "number" (substring code start i)))))) (list
(list "component" (substring code start i))))))
(if (if
(hl-sym-char? c) (or
(let (= c "(")
((start i)) (= c ")")
(let (= c "[")
scan (= c "]")
() (= c "{")
(when (= c "}"))
(and (do
(< i len) (set!
(hl-sym-char? (substring code i (+ i 1)))) tokens
(set! i (+ i 1)) (append tokens (list (list "paren" c))))
(scan))) (set! i (+ i 1)))
(let
((text (substring code start i)))
(if
(or
(= text "true")
(= text "false")
(= text "nil"))
(set!
tokens
(append
tokens
(list (list "boolean" text))))
(if
(sx-special? text)
(set!
tokens
(append
tokens
(list (list "special" text))))
(set!
tokens
(append
tokens
(list (list "symbol" text))))))))
(if (if
(hl-ws? c) (hl-digit? c)
(let (let
((start i)) ((start i))
(let (let
@@ -277,49 +235,106 @@
(when (when
(and (and
(< i len) (< i len)
(hl-ws? (substring code i (+ i 1)))) (let
((x (substring code i (+ i 1))))
(or (hl-digit? x) (= x "."))))
(set! i (+ i 1)) (set! i (+ i 1))
(scan))) (scan)))
(set! (set!
tokens tokens
(append (append
tokens tokens
(list (list "ws" (substring code start i)))))) (list
(do (list "number" (substring code start i))))))
(set! (if
tokens (hl-sym-char? c)
(append tokens (list (list "other" c)))) (let
(set! i (+ i 1)))))))))))) ((start i))
(loop))) (let
tokens))) scan
()
(define sx-token-classes {:boolean "text-orange-600" :component "text-rose-600 font-semibold" :number "text-amber-700" :string "text-emerald-700" :special "text-sky-700 font-semibold" :paren "text-stone-400" :keyword "text-violet-600" :comment "text-stone-400 italic"}) (when
(and
(define (< i len)
render-sx-tokens (hl-sym-char?
(fn (substring code i (+ i 1))))
(tokens) (set! i (+ i 1))
(map (scan)))
(let
((text (substring code start i)))
(if
(or
(= text "true")
(= text "false")
(= text "nil"))
(set!
tokens
(append
tokens
(list (list "boolean" text))))
(if
(sx-special? text)
(set!
tokens
(append
tokens
(list (list "special" text))))
(set!
tokens
(append
tokens
(list (list "symbol" text))))))))
(if
(hl-ws? c)
(let
((start i))
(let
scan
()
(when
(and
(< i len)
(hl-ws? (substring code i (+ i 1))))
(set! i (+ i 1))
(scan)))
(set!
tokens
(append
tokens
(list
(list "ws" (substring code start i))))))
(do
(set!
tokens
(append tokens (list (list "other" c))))
(set! i (+ i 1))))))))))))
(loop)))
tokens)))
(define sx-token-classes {:boolean "text-orange-600" :component "text-rose-600 font-semibold" :number "text-amber-700" :string "text-emerald-700" :special "text-sky-700 font-semibold" :paren "text-stone-400" :keyword "text-violet-600" :comment "text-stone-400 italic"})
(define
render-sx-tokens
(fn (fn
(tok) (tokens)
(let (map
((cls (or (dict-get sx-token-classes (first tok)) ""))) (fn
(hl-span cls (nth tok 1)))) (tok)
tokens))) (let
((cls (or (dict-get sx-token-classes (first tok)) "")))
(define highlight-sx (fn (code) (render-sx-tokens (tokenize-sx code)))) (hl-span cls (nth tok 1))))
tokens)))
(define (define highlight-sx (fn (code) (-> code tokenize-sx render-sx-tokens)))
highlight (define
(fn highlight
(code lang) (fn
(if (code lang)
(or (= lang "lisp") (= lang "sx") (= lang "sexp") (= lang "scheme")) (if
(highlight-sx code) (or
(list (quote code) code)))) (= lang "lisp")
(= lang "sx")
(= lang "sexp")
)) ;; end define-library (= lang "scheme"))
(highlight-sx code)
(list (quote code) code)))))) ;; end define-library
;; Re-export to global namespace for backward compatibility ;; Re-export to global namespace for backward compatibility
(import (sx highlight)) (import (sx highlight))

View File

@@ -1,6 +1,7 @@
(define-library (sx swap) (define-library
(sx swap)
(export (export
_skip-string _skip-string
_find-close _find-close
@@ -16,310 +17,311 @@
strip-oob strip-oob
apply-response) apply-response)
(begin (begin
(define
(define _skip-string
_skip-string (fn
(fn (src i)
(src i)
(if
(>= i (len src))
i
(let
((ch (nth src i)))
(cond
(= ch "\\")
(_skip-string src (+ i 2))
(= ch "\"")
(+ i 1)
:else (_skip-string src (+ i 1)))))))
(define
_find-close
(fn
(src i depth in-str)
(if
(>= i (len src))
-1
(let
((ch (nth src i)))
(cond
in-str
(cond
(= ch "\\")
(_find-close src (+ i 2) depth true)
(= ch "\"")
(_find-close src (+ i 1) depth false)
:else (_find-close src (+ i 1) depth true))
(= ch "\"")
(_find-close src (+ i 1) depth true)
(= ch "(")
(_find-close src (+ i 1) (+ depth 1) false)
(= ch ")")
(if (= depth 1) i (_find-close src (+ i 1) (- depth 1) false))
:else (_find-close src (+ i 1) depth false))))))
(define
_skip-ws
(fn
(src i)
(if
(>= i (len src))
i
(let
((ch (nth src i)))
(if (if
(or (= ch " ") (= ch "\n") (= ch "\t") (= ch "\r")) (>= i (len src))
(_skip-ws src (+ i 1))
i)))))
(define
_skip-token
(fn
(src i)
(if
(>= i (len src))
i
(let
((ch (nth src i)))
(if
(or
(= ch " ")
(= ch "\n")
(= ch "\t")
(= ch "\r")
(= ch "(")
(= ch ")")
(= ch "\""))
i i
(_skip-token src (+ i 1)))))))
(define
_skip-value
(fn
(src i)
(if
(>= i (len src))
i
(let
((ch (nth src i)))
(cond
(= ch "\"")
(_skip-string src (+ i 1))
(= ch "(")
(let (let
((close (_find-close src (+ i 1) 1 false))) ((ch (nth src i)))
(if (= close -1) (len src) (+ close 1))) (cond
:else (_skip-token src i)))))) (= ch "\\")
(_skip-string src (+ i 2))
(define (= ch "\"")
_find-children-start (+ i 1)
(fn :else (_skip-string src (+ i 1)))))))
(src elem-start elem-end) (define
(let _find-close
((after-open (+ elem-start 1))) (fn
(let (src i depth in-str)
((after-tag (_skip-token src (_skip-ws src after-open))))
(define
_skip-attrs
(fn
(j)
(let
((pos (_skip-ws src j)))
(if
(>= pos elem-end)
pos
(if
(= (nth src pos) ":")
(let
((after-kw (_skip-token src pos)))
(_skip-attrs (_skip-value src (_skip-ws src after-kw))))
pos)))))
(_skip-attrs after-tag)))))
(define
_scan-back
(fn
(src i)
(if (< i 0) -1 (if (= (nth src i) "(") i (_scan-back src (- i 1))))))
(define
find-element-by-id
(fn
(src target-id)
(let
((pattern (str ":id \"" target-id "\"")))
(let
((pos (index-of src pattern)))
(if (if
(= pos -1) (>= i (len src))
nil -1
(let (let
((elem-start (_scan-back src (- pos 1)))) ((ch (nth src i)))
(cond
in-str
(cond
(= ch "\\")
(_find-close src (+ i 2) depth true)
(= ch "\"")
(_find-close src (+ i 1) depth false)
:else (_find-close src (+ i 1) depth true))
(= ch "\"")
(_find-close src (+ i 1) depth true)
(= ch "(")
(_find-close src (+ i 1) (+ depth 1) false)
(= ch ")")
(if
(= depth 1)
i
(_find-close src (+ i 1) (- depth 1) false))
:else (_find-close src (+ i 1) depth false))))))
(define
_skip-ws
(fn
(src i)
(if
(>= i (len src))
i
(let
((ch (nth src i)))
(if (if
(= elem-start -1) (or (= ch " ") (= ch "\n") (= ch "\t") (= ch "\r"))
(_skip-ws src (+ i 1))
i)))))
(define
_skip-token
(fn
(src i)
(if
(>= i (len src))
i
(let
((ch (nth src i)))
(if
(or
(= ch " ")
(= ch "\n")
(= ch "\t")
(= ch "\r")
(= ch "(")
(= ch ")")
(= ch "\""))
i
(_skip-token src (+ i 1)))))))
(define
_skip-value
(fn
(src i)
(if
(>= i (len src))
i
(let
((ch (nth src i)))
(cond
(= ch "\"")
(_skip-string src (+ i 1))
(= ch "(")
(let
((close (_find-close src (+ i 1) 1 false)))
(if (= close -1) (len src) (+ close 1)))
:else (_skip-token src i))))))
(define
_find-children-start
(fn
(src elem-start elem-end)
(let
((after-open (+ elem-start 1)))
(let
((after-tag (_skip-token src (_skip-ws src after-open))))
(define
_skip-attrs
(fn
(j)
(let
((pos (_skip-ws src j)))
(if
(>= pos elem-end)
pos
(if
(= (nth src pos) ":")
(let
((after-kw (_skip-token src pos)))
(_skip-attrs
(_skip-value src (_skip-ws src after-kw))))
pos)))))
(_skip-attrs after-tag)))))
(define
_scan-back
(fn
(src i)
(if
(< i 0)
-1
(if (= (nth src i) "(") i (_scan-back src (- i 1))))))
(define
find-element-by-id
(fn
(src target-id)
(let
((pattern (str ":id \"" target-id "\"")))
(let
((pos (index-of src pattern)))
(if
(= pos -1)
nil nil
(let (let
((elem-end (_find-close src (+ elem-start 1) 1 false))) ((elem-start (_scan-back src (- pos 1))))
(if (if
(= elem-end -1) (= elem-start -1)
nil nil
(let (let
((cs (_find-children-start src elem-start elem-end))) ((elem-end (_find-close src (+ elem-start 1) 1 false)))
{:end elem-end :start elem-start :children-start cs})))))))))) (if
(= elem-end -1)
(define nil
sx-swap (let
(fn ((cs (_find-children-start src elem-start elem-end)))
(src mode target-id new-content) {:end elem-end :start elem-start :children-start cs}))))))))))
(let
((info (find-element-by-id src target-id)))
(if
(nil? info)
src
(let
((s (get info "start"))
(e (get info "end"))
(cs (get info "children-start")))
(case
mode
"innerHTML"
(str (slice src 0 cs) new-content (slice src e (len src)))
"outerHTML"
(str (slice src 0 s) new-content (slice src (+ e 1) (len src)))
"beforeend"
(str (slice src 0 e) " " new-content (slice src e (len src)))
"afterbegin"
(str (slice src 0 cs) new-content " " (slice src cs (len src)))
"beforebegin"
(str (slice src 0 s) new-content (slice src s (len src)))
"afterend"
(str
(slice src 0 (+ e 1))
new-content
(slice src (+ e 1) (len src)))
"delete"
(str (slice src 0 s) (slice src (+ e 1) (len src)))
"none"
src
:else src))))))
(define
_extract-attr-value
(fn
(src keyword-end)
(let
((val-start (_skip-ws src keyword-end)))
(if
(= (nth src val-start) "\"")
(let
((str-end (_skip-string src (+ val-start 1))))
(slice src (+ val-start 1) (- str-end 1)))
(let
((tok-end (_skip-token src val-start)))
(slice src val-start tok-end))))))
(define
find-oob-elements
(fn
(src)
(define (define
_scan sx-swap
(fn (fn
(from results) (src mode target-id new-content)
(let (let
((rel-pos (index-of (slice src from (len src)) ":sx-swap-oob"))) ((info (find-element-by-id src target-id)))
(if (if
(= rel-pos -1) (nil? info)
results src
(let-match
{:end e :start s :children-start cs}
info
(case
mode
"innerHTML"
(str (slice src 0 cs) new-content (slice src e (len src)))
"outerHTML"
(str
(slice src 0 s)
new-content
(slice src (+ e 1) (len src)))
"beforeend"
(str
(slice src 0 e)
" "
new-content
(slice src e (len src)))
"afterbegin"
(str
(slice src 0 cs)
new-content
" "
(slice src cs (len src)))
"beforebegin"
(str (slice src 0 s) new-content (slice src s (len src)))
"afterend"
(str
(slice src 0 (+ e 1))
new-content
(slice src (+ e 1) (len src)))
"delete"
(str (slice src 0 s) (slice src (+ e 1) (len src)))
"none"
src
:else src))))))
(define
_extract-attr-value
(fn
(src keyword-end)
(let
((val-start (_skip-ws src keyword-end)))
(if
(= (nth src val-start) "\"")
(let (let
((abs-pos (+ from rel-pos))) ((str-end (_skip-string src (+ val-start 1))))
(let (slice src (+ val-start 1) (- str-end 1)))
((mode (_extract-attr-value src (+ abs-pos 12)))) (let
((tok-end (_skip-token src val-start)))
(slice src val-start tok-end))))))
(define
find-oob-elements
(fn
(src)
(define
_scan
(fn
(from results)
(let
((rel-pos (index-of (slice src from (len src)) ":sx-swap-oob")))
(if
(= rel-pos -1)
results
(let (let
((elem-start (_scan-back src (- abs-pos 1)))) ((abs-pos (+ from rel-pos)))
(if (let
(= elem-start -1) ((mode (_extract-attr-value src (+ abs-pos 12))))
results
(let (let
((elem-end (_find-close src (+ elem-start 1) 1 false))) ((elem-start (_scan-back src (- abs-pos 1))))
(if (if
(= elem-end -1) (= elem-start -1)
results results
(let (let
((id-pattern ":id \"")) ((elem-end (_find-close src (+ elem-start 1) 1 false)))
(let (if
((id-pos (index-of (slice src elem-start (+ elem-end 1)) id-pattern))) (= elem-end -1)
(if results
(= id-pos -1) (let
(_scan (+ elem-end 1) results) ((id-pattern ":id \""))
(let (let
((id-abs (+ elem-start id-pos))) ((id-pos (index-of (slice src elem-start (+ elem-end 1)) id-pattern)))
(let (if
((id-val (_extract-attr-value src (+ id-abs 3)))) (= id-pos -1)
(_scan (+ elem-end 1) results)
(let (let
((cs (_find-children-start src elem-start elem-end))) ((id-abs (+ elem-start id-pos)))
(let (let
((children-str (slice src cs elem-end))) ((id-val (_extract-attr-value src (+ id-abs 3))))
(_scan (let
(+ elem-end 1) ((cs (_find-children-start src elem-start elem-end)))
(append results (list {:end elem-end :mode mode :content children-str :start elem-start :id id-val})))))))))))))))))))) (let
(_scan 0 (list)))) ((children-str (slice src cs elem-end)))
(_scan
(define (+ elem-end 1)
strip-oob (append results (list {:end elem-end :mode mode :content children-str :start elem-start :id id-val}))))))))))))))))))))
(fn (_scan 0 (list))))
(src oob-list) (define
(if strip-oob
(empty? oob-list) (fn
src (src oob-list)
(let (if
((sorted (reverse oob-list))) (empty? oob-list)
(define src
_strip (let
(fn ((sorted (reverse oob-list)))
(s items)
(if
(empty? items)
s
(let
((item (first items)))
(let
((before (slice s 0 (get item "start")))
(after (slice s (+ (get item "end") 1) (len s))))
(_strip (str before after) (rest items)))))))
(_strip src sorted)))))
(define
apply-response
(fn
(page response primary-mode primary-target)
(let
((oobs (find-oob-elements response)))
(let
((main-content (strip-oob response oobs)))
(let
((result (sx-swap page primary-mode primary-target main-content)))
(do
(define (define
_apply-oobs _strip
(fn (fn
(page-acc items) (s items)
(if (if
(empty? items) (empty? items)
page-acc s
(let (let
((oob (first items))) ((item (first items)))
(_apply-oobs (let
(sx-swap ((before (slice s 0 (get item "start")))
page-acc (after (slice s (+ (get item "end") 1) (len s))))
(get oob "mode") (_strip (str before after) (rest items)))))))
(get oob "id") (_strip src sorted)))))
(get oob "content")) (define
(rest items)))))) apply-response
(_apply-oobs result oobs))))))) (fn
(page response primary-mode primary-target)
(let
)) ;; end define-library ((oobs (find-oob-elements response)))
(let
((main-content (strip-oob response oobs)))
(let
((result (sx-swap page primary-mode primary-target main-content)))
(do
(define
_apply-oobs
(fn
(page-acc items)
(if
(empty? items)
page-acc
(let
((oob (first items)))
(_apply-oobs
(sx-swap
page-acc
(get oob "mode")
(get oob "id")
(get oob "content"))
(rest items))))))
(_apply-oobs result oobs))))))))) ;; end define-library
;; Re-export to global namespace for backward compatibility ;; Re-export to global namespace for backward compatibility
(import (sx swap)) (import (sx swap))

View File

@@ -63,33 +63,28 @@
:effects () :effects ()
(fn (fn
(node) (node)
(cond (match
(nil? node) (type-of node)
"nil" ("nil" "nil")
(= (type-of node) "symbol") ("symbol" (symbol-name node))
(symbol-name node) ("keyword" (str ":" (keyword-name node)))
(= (type-of node) "keyword") ("string"
(str ":" (keyword-name node)) (let
(= (type-of node) "string") ((s (if (> (len node) 40) (str (slice node 0 37) "...") node)))
(let (str "\"" s "\"")))
((s (if (> (len node) 40) (str (slice node 0 37) "...") node))) ("number" (str node))
(str "\"" s "\"")) ("boolean" (if node "true" "false"))
(= (type-of node) "number") ("list"
(str node) (if
(= (type-of node) "boolean") (empty? node)
(if node "true" "false") "()"
(list? node) (str
(if "("
(empty? node) (node-display (first node))
"()" (if (> (len node) 1) " ..." "")
(str ")")))
"(" ("dict" "{...}")
(node-display (first node)) (_ (str node)))))
(if (> (len node) 1) " ..." "")
")"))
(= (type-of node) "dict")
"{...}"
:else (str node))))
(define (define
summarise summarise
@@ -244,17 +239,16 @@
:effects () :effects ()
(fn (fn
(node pattern) (node pattern)
(cond (match
(= (type-of node) "symbol") (type-of node)
(contains? (symbol-name node) pattern) ("symbol" (contains? (symbol-name node) pattern))
(string? node) ("string" (contains? node pattern))
(contains? node pattern) ("list"
(and (if
(list? node) (empty? node)
(not (empty? node)) false
(= (type-of (first node)) "symbol")) (some (fn (child) (node-matches? child pattern)) node)))
(contains? (symbol-name (first node)) pattern) (_ false))))
:else false)))
(define (define
node-summary-short node-summary-short
@@ -546,33 +540,33 @@
:effects () :effects ()
(fn (fn
(node replacement) (node replacement)
(cond (match
(and (= (type-of node) "symbol") (= (symbol-name node) "_")) (type-of node)
replacement ("symbol" (if (= (symbol-name node) "_") replacement nil))
(list? node) ("list"
(let (let
((found false) ((found false)
(result (result
(map (map
(fn (fn
(child) (child)
(if
found
child
(if (if
(and found
(= (type-of child) "symbol") child
(= (symbol-name child) "_"))
(do (set! found true) replacement)
(if (if
(list? child) (and
(let (= (type-of child) "symbol")
((sub (replace-placeholder child replacement))) (= (symbol-name child) "_"))
(if (nil? sub) child (do (set! found true) sub))) (do (set! found true) replacement)
child)))) (if
node))) (list? child)
(if found result nil)) (let
:else nil))) ((sub (replace-placeholder child replacement)))
(if (nil? sub) child (do (set! found true) sub)))
child))))
node)))
(if found result nil)))
(_ nil))))
(define (define
tree-set tree-set
@@ -851,12 +845,13 @@
:effects () :effects ()
(fn (fn
(node old-name new-name) (node old-name new-name)
(cond (match
(and (= (type-of node) "symbol") (= (symbol-name node) old-name)) (type-of node)
(make-symbol new-name) ("symbol"
(list? node) (if (= (symbol-name node) old-name) (make-symbol new-name) node))
(map (fn (child) (rename-in-node child old-name new-name)) node) ("list"
:else node))) (map (fn (child) (rename-in-node child old-name new-name)) node))
(_ node))))
(define (define
count-renames count-renames
@@ -873,12 +868,12 @@
:effects () :effects ()
(fn (fn
(node old-name hits) (node old-name hits)
(cond (match
(and (= (type-of node) "symbol") (= (symbol-name node) old-name)) (type-of node)
(append! hits true) ("symbol" (when (= (symbol-name node) old-name) (append! hits true)))
(list? node) ("list"
(for-each (fn (child) (count-in-node child old-name hits)) node) (for-each (fn (child) (count-in-node child old-name hits)) node))
:else nil))) (_ nil))))
(define (define
replace-by-pattern replace-by-pattern
@@ -1341,17 +1336,30 @@
(walk node (dict)) (walk node (dict))
result))) result)))
(define find-use-declarations :effects () (define
(fn (nodes) find-use-declarations
(let ((uses (list))) :effects ()
(for-each (fn (node) (fn
(when (and (list? node) (>= (len node) 2) (nodes)
(= (type-of (first node)) "symbol") (let
(= (symbol-name (first node)) "use")) ((uses (list)))
(for-each (fn (arg) (for-each
(cond (fn
(= (type-of arg) "symbol") (append! uses (symbol-name arg)) (node)
(= (type-of arg) "string") (append! uses arg))) (when
(rest node)))) (and
(list? node)
(>= (len node) 2)
(= (type-of (first node)) "symbol")
(= (symbol-name (first node)) "use"))
(for-each
(fn
(arg)
(cond
(= (type-of arg) "symbol")
(append! uses (symbol-name arg))
(= (type-of arg) "string")
(append! uses arg)))
(rest node))))
(if (list? nodes) nodes (list nodes))) (if (list? nodes) nodes (list nodes)))
uses))) uses)))

File diff suppressed because it is too large Load Diff

133
lib/vm.sx
View File

@@ -79,35 +79,35 @@
(fn (fn
(vm value) (vm value)
(let (let
((sp (get vm "sp")) (stack (get vm "stack"))) ((sp (vm-sp vm)) (stack (vm-stack vm)))
(when (when
(>= sp (vm-stack-length stack)) (>= sp (vm-stack-length stack))
(let (let
((new-stack (make-vm-stack (* sp 2)))) ((new-stack (vm-stack-grow stack sp)))
(vm-stack-copy! stack new-stack sp) (vm-stack-copy! stack new-stack sp)
(dict-set! vm "stack" new-stack) (vm-set-stack! vm new-stack)
(set! stack new-stack))) (set! stack new-stack)))
(vm-stack-set! stack sp value) (vm-stack-set! stack sp value)
(dict-set! vm "sp" (+ sp 1))))) (vm-set-sp! vm (+ sp 1)))))
(define (define
vm-pop vm-pop
(fn (fn
(vm) (vm)
(let (let
((sp (- (get vm "sp") 1))) ((sp (- (vm-sp vm) 1)))
(dict-set! vm "sp" sp) (vm-set-sp! vm sp)
(vm-stack-get (get vm "stack") sp)))) (vm-stack-get (vm-stack vm) sp))))
(define (define
vm-peek vm-peek
(fn (vm) (vm-stack-get (get vm "stack") (- (get vm "sp") 1)))) (fn (vm) (vm-stack-get (vm-stack vm) (- (vm-sp vm) 1))))
(define (define
frame-read-u8 frame-read-u8
(fn (fn
(frame) (frame)
(let (let
((ip (get frame "ip")) ((ip (frame-ip frame))
(bc (get (get (get frame "closure") "vm-code") "vc-bytecode"))) (bc (-> frame frame-closure closure-code code-bytecode)))
(let ((v (nth bc ip))) (dict-set! frame "ip" (+ ip 1)) v)))) (let ((v (nth bc ip))) (frame-set-ip! frame (+ ip 1)) v))))
(define (define
frame-read-u16 frame-read-u16
(fn (fn
@@ -206,31 +206,28 @@
(if (if
(has-key? cells key) (has-key? cells key)
(uv-get (get cells key)) (uv-get (get cells key))
(vm-stack-get (get vm "stack") (+ (get frame "base") slot)))))) (vm-stack-get (vm-stack vm) (+ (frame-base frame) slot))))))
(define (define
frame-local-set frame-local-set
(fn (fn
(vm frame slot value) (vm frame slot value)
"Write a local variable — to shared cell if captured, else to stack." "Write a local variable — to shared cell or stack."
(let (let
((cells (get frame "local-cells")) (key (str slot))) ((cells (get frame "local-cells")) (key (str slot)))
(if (if
(has-key? cells key) (has-key? cells key)
(uv-set! (get cells key) value) (uv-set! (get cells key) value)
(vm-stack-set! (vm-stack-set! (vm-stack vm) (+ (frame-base frame) slot) value)))))
(get vm "stack")
(+ (get frame "base") slot)
value)))))
(define (define
frame-upvalue-get frame-upvalue-get
(fn (fn
(frame idx) (frame idx)
(uv-get (nth (get (get frame "closure") "vm-upvalues") idx)))) (uv-get (nth (-> frame frame-closure closure-upvalues) idx))))
(define (define
frame-upvalue-set frame-upvalue-set
(fn (fn
(frame idx value) (frame idx value)
(uv-set! (nth (get (get frame "closure") "vm-upvalues") idx) value))) (uv-set! (nth (-> frame frame-closure closure-upvalues) idx) value)))
(define frame-ip (fn (frame) (get frame "ip"))) (define frame-ip (fn (frame) (get frame "ip")))
(define frame-set-ip! (fn (frame val) (dict-set! frame "ip" val))) (define frame-set-ip! (fn (frame val) (dict-set! frame "ip" val)))
(define frame-base (fn (frame) (get frame "base"))) (define frame-base (fn (frame) (get frame "base")))
@@ -302,12 +299,12 @@
(vm frame name) (vm frame name)
"Look up a global: globals table → closure env → primitives → HO wrappers" "Look up a global: globals table → closure env → primitives → HO wrappers"
(let (let
((globals (get vm "globals"))) ((globals (vm-globals-ref vm)))
(if (if
(has-key? globals name) (has-key? globals name)
(get globals name) (get globals name)
(let (let
((closure-env (get (get frame "closure") "closure-env"))) ((closure-env (-> frame frame-closure closure-env)))
(if (if
(nil? closure-env) (nil? closure-env)
(cek-try (cek-try
@@ -325,41 +322,42 @@
vm-resolve-ho-form vm-resolve-ho-form
(fn (fn
(vm name) (vm name)
(cond (match
(= name "for-each") name
(fn ("for-each"
(f coll) (fn
(for-each (fn (x) (vm-call-external vm f (list x))) coll)) (f coll)
(= name "map") (for-each (fn (x) (vm-call-external vm f (list x))) coll)))
(fn ("map"
(f coll) (fn
(map (fn (x) (vm-call-external vm f (list x))) coll)) (f coll)
(= name "map-indexed") (map (fn (x) (vm-call-external vm f (list x))) coll)))
(fn ("map-indexed"
(f coll) (fn
(map-indexed (f coll)
(fn (i x) (vm-call-external vm f (list i x))) (map-indexed
coll)) (fn (i x) (vm-call-external vm f (list i x)))
(= name "filter") coll)))
(fn ("filter"
(f coll) (fn
(filter (fn (x) (vm-call-external vm f (list x))) coll)) (f coll)
(= name "reduce") (filter (fn (x) (vm-call-external vm f (list x))) coll)))
(fn ("reduce"
(f init coll) (fn
(reduce (f init coll)
(fn (acc x) (vm-call-external vm f (list acc x))) (reduce
init (fn (acc x) (vm-call-external vm f (list acc x)))
coll)) init
(= name "some") coll)))
(fn ("some"
(f coll) (fn
(some (fn (x) (vm-call-external vm f (list x))) coll)) (f coll)
(= name "every?") (some (fn (x) (vm-call-external vm f (list x))) coll)))
(fn ("every?"
(f coll) (fn
(every? (fn (x) (vm-call-external vm f (list x))) coll)) (f coll)
:else (error (str "VM undefined: " name))))) (every? (fn (x) (vm-call-external vm f (list x))) coll)))
(_ (error (str "VM undefined: " name))))))
(define (define
vm-call-external vm-call-external
(fn (fn
@@ -372,14 +370,14 @@
vm-global-set vm-global-set
(fn (fn
(vm frame name value) (vm frame name value)
"Set a global: write to closure env if name exists there, else globals." "Set a global: write to closure env if found, else globals table."
(let (let
((closure-env (get (get frame "closure") "vm-closure-env")) ((closure-env (get (frame-closure frame) "vm-closure-env"))
(written false)) (written false))
(when (when
(not (nil? closure-env)) (not (nil? closure-env))
(set! written (env-walk-set! closure-env name value))) (set! written (env-walk-set! closure-env name value)))
(when (not written) (dict-set! (get vm "globals") name value))))) (when (not written) (dict-set! (vm-globals-ref vm) name value)))))
(define (define
env-walk env-walk
(fn (fn
@@ -414,20 +412,15 @@
(let (let
((code (code-from-value code-val)) ((code (code-from-value code-val))
(uv-count (uv-count
(if (if (dict? code-val) (or (get code-val "upvalue-count") 0) 0)))
(dict? code-val)
(let
((n (get code-val "upvalue-count")))
(if (nil? n) 0 n))
0)))
(let (let
((upvalues (let ((result (list)) (i 0)) (define capture-loop (fn () (when (< i uv-count) (let ((is-local (frame-read-u8 frame)) (index (frame-read-u8 frame))) (let ((cell (if (= is-local 1) (let ((cells (get frame "local-cells")) (key (str index))) (if (has-key? cells key) (get cells key) (let ((c (make-upvalue-cell (vm-stack-get (get vm "stack") (+ (get frame "base") index))))) (dict-set! cells key c) c))) (nth (get (get frame "closure") "vm-upvalues") index)))) (append! result cell) (set! i (+ i 1)) (capture-loop)))))) (capture-loop) result))) ((upvalues (let ((result (list)) (i 0)) (define capture-loop (fn () (when (< i uv-count) (let ((is-local (frame-read-u8 frame)) (index (frame-read-u8 frame))) (let ((cell (if (= is-local 1) (let ((cells (get frame "local-cells")) (key (str index))) (if (has-key? cells key) (get cells key) (let ((c (make-upvalue-cell (vm-stack-get (vm-stack vm) (+ (frame-base frame) index))))) (dict-set! cells key c) c))) (nth (-> frame frame-closure closure-upvalues) index)))) (append! result cell) (set! i (+ i 1)) (capture-loop)))))) (capture-loop) result)))
(make-vm-closure code upvalues nil (get vm "globals") nil))))) (make-vm-closure code upvalues nil (vm-globals-ref vm) nil)))))
(define (define
vm-run vm-run
(fn (fn
(vm) (vm)
"Execute bytecode until all frames are consumed." "Execute bytecode until all frames are done or IO suspension."
(define (define
loop loop
(fn (fn
@@ -438,9 +431,9 @@
((frame (first (vm-frames vm))) ((frame (first (vm-frames vm)))
(rest-frames (rest (vm-frames vm)))) (rest-frames (rest (vm-frames vm))))
(let (let
((bc (code-bytecode (closure-code (frame-closure frame)))) ((bc (-> frame frame-closure closure-code code-bytecode))
(consts (consts
(code-constants (closure-code (frame-closure frame))))) (-> frame frame-closure closure-code code-constants)))
(if (if
(>= (frame-ip frame) (len bc)) (>= (frame-ip frame) (len bc))
(vm-set-frames! vm (list)) (vm-set-frames! vm (list))

View File

@@ -121,57 +121,63 @@
(case (case
slug slug
"bundle-analyzer" "bundle-analyzer"
(let (let-match
((data (helper "bundle-analyzer-data"))) {:total-macros total-macros :pages pages :io-count io-count :pure-count pure-count :total-components total-components}
(helper "bundle-analyzer-data")
(quasiquote (quasiquote
(~analyzer/bundle-analyzer-content (~analyzer/bundle-analyzer-content
:pages (unquote (get data "pages")) :pages (unquote pages)
:total-components (unquote (get data "total-components")) :total-components (unquote total-components)
:total-macros (unquote (get data "total-macros")) :total-macros (unquote total-macros)
:pure-count (unquote (get data "pure-count")) :pure-count (unquote pure-count)
:io-count (unquote (get data "io-count"))))) :io-count (unquote io-count))))
"routing-analyzer" "routing-analyzer"
(let (let-match
((data (helper "routing-analyzer-data"))) {:pages pages :total-pages total-pages :server-count server-count :registry-sample registry-sample :client-count client-count}
(helper "routing-analyzer-data")
(quasiquote (quasiquote
(~routing-analyzer/content (~routing-analyzer/content
:pages (unquote (get data "pages")) :pages (unquote pages)
:total-pages (unquote (get data "total-pages")) :total-pages (unquote total-pages)
:client-count (unquote (get data "client-count")) :client-count (unquote client-count)
:server-count (unquote (get data "server-count")) :server-count (unquote server-count)
:registry-sample (unquote (get data "registry-sample"))))) :registry-sample (unquote registry-sample))))
"data-test" "data-test"
(let (let-match
((data (helper "data-test-data"))) {:server-time server-time :transport transport :phase phase :items items}
(helper "data-test-data")
(quasiquote (quasiquote
(~data-test/content (~data-test/content
:server-time (unquote (get data "server-time")) :server-time (unquote server-time)
:items (unquote (get data "items")) :items (unquote items)
:phase (unquote (get data "phase")) :phase (unquote phase)
:transport (unquote (get data "transport"))))) :transport (unquote transport))))
"async-io" "async-io"
(quote (~async-io-demo/content)) (quote (~async-io-demo/content))
"affinity" "affinity"
(let (let-match
((data (helper "affinity-demo-data"))) {:components components :page-plans page-plans}
(helper "affinity-demo-data")
(quasiquote (quasiquote
(~affinity-demo/content (~affinity-demo/content
:components (unquote (get data "components")) :components (unquote components)
:page-plans (unquote (get data "page-plans"))))) :page-plans (unquote page-plans))))
"optimistic" "optimistic"
(let (let-match
((data (helper "optimistic-demo-data"))) {:server-time server-time :items items}
(helper "optimistic-demo-data")
(quasiquote (quasiquote
(~optimistic-demo/content (~optimistic-demo/content
:items (unquote (get data "items")) :items (unquote items)
:server-time (unquote (get data "server-time"))))) :server-time (unquote server-time))))
"offline" "offline"
(let (let-match
((data (helper "offline-demo-data"))) {:server-time server-time :notes notes}
(helper "offline-demo-data")
(quasiquote (quasiquote
(~offline-demo/content (~offline-demo/content
:notes (unquote (get data "notes")) :notes (unquote notes)
:server-time (unquote (get data "server-time"))))) :server-time (unquote server-time))))
:else (quote (~plans/isomorphic/plan-isomorphic-content)))))) :else (quote (~plans/isomorphic/plan-isomorphic-content))))))
(define (define
@@ -262,15 +268,18 @@
((found-spec (find-spec slug))) ((found-spec (find-spec slug)))
(if (if
found-spec found-spec
(let (let-match
((src (helper "read-spec-file" (get found-spec "filename")))) {:desc desc :prose prose :title title :filename filename}
(quasiquote found-spec
(~specs/detail-content (let
:spec-title (unquote (get found-spec "title")) ((src (helper "read-spec-file" filename)))
:spec-desc (unquote (get found-spec "desc")) (quasiquote
:spec-filename (unquote (get found-spec "filename")) (~specs/detail-content
:spec-source (unquote src) :spec-title (unquote title)
:spec-prose (unquote (get found-spec "prose"))))) :spec-desc (unquote desc)
:spec-filename (unquote filename)
:spec-source (unquote src)
:spec-prose (unquote prose)))))
(quasiquote (~specs/not-found :slug (unquote slug))))))))) (quasiquote (~specs/not-found :slug (unquote slug)))))))))
(define (define
@@ -324,54 +333,67 @@
(case (case
slug slug
"self-hosting" "self-hosting"
(quasiquote (let-match
(~specs/bootstrapper-self-hosting-content {:g1-output g1-output :py-sx-source py-sx-source :g0-bytes g0-bytes :verification-status verification-status :g0-output g0-output :defines-total defines-total :defines-matched defines-matched :g0-lines g0-lines}
:py-sx-source (unquote (get data "py-sx-source")) data
:g0-output (unquote (get data "g0-output")) (quasiquote
:g1-output (unquote (get data "g1-output")) (~specs/bootstrapper-self-hosting-content
:defines-matched (unquote (get data "defines-matched")) :py-sx-source (unquote py-sx-source)
:defines-total (unquote (get data "defines-total")) :g0-output (unquote g0-output)
:g0-lines (unquote (get data "g0-lines")) :g1-output (unquote g1-output)
:g0-bytes (unquote (get data "g0-bytes")) :defines-matched (unquote defines-matched)
:verification-status (unquote (get data "verification-status")))) :defines-total (unquote defines-total)
:g0-lines (unquote g0-lines)
:g0-bytes (unquote g0-bytes)
:verification-status (unquote verification-status))))
"self-hosting-js" "self-hosting-js"
(quasiquote (let-match
(~specs/bootstrapper-self-hosting-js-content {:js-sx-source js-sx-source :verification-status verification-status :js-sx-lines js-sx-lines :defines-total defines-total :defines-matched defines-matched}
:js-sx-source (unquote (get data "js-sx-source")) data
:defines-matched (unquote (get data "defines-matched")) (quasiquote
:defines-total (unquote (get data "defines-total")) (~specs/bootstrapper-self-hosting-js-content
:js-sx-lines (unquote (get data "js-sx-lines")) :js-sx-source (unquote js-sx-source)
:verification-status (unquote (get data "verification-status")))) :defines-matched (unquote defines-matched)
:defines-total (unquote defines-total)
:js-sx-lines (unquote js-sx-lines)
:verification-status (unquote verification-status))))
"python" "python"
(quasiquote (let-match
(~specs/bootstrapper-py-content {:bootstrapper-source bootstrapper-source :bootstrapped-output bootstrapped-output}
:bootstrapper-source (unquote (get data "bootstrapper-source")) data
:bootstrapped-output (unquote (get data "bootstrapped-output")))) (quasiquote
(~specs/bootstrapper-py-content
:bootstrapper-source (unquote bootstrapper-source)
:bootstrapped-output (unquote bootstrapped-output))))
"page-helpers" "page-helpers"
(let (let-match
((ph-data (helper "page-helpers-demo-data"))) {:attr-result attr-result :sf-source sf-source :ref-ms ref-ms :req-attrs req-attrs :attr-detail attr-detail :attr-keys attr-keys :server-total-ms server-total-ms :attr-ms attr-ms :comp-ms comp-ms :routing-ms routing-ms :comp-source comp-source :routing-result routing-result :sf-categories sf-categories :sf-total sf-total :sf-ms sf-ms :ref-sample ref-sample}
(helper "page-helpers-demo-data")
(quasiquote (quasiquote
(~page-helpers-demo/content (~page-helpers-demo/content
:sf-categories (unquote (get ph-data "sf-categories")) :sf-categories (unquote sf-categories)
:sf-total (unquote (get ph-data "sf-total")) :sf-total (unquote sf-total)
:sf-ms (unquote (get ph-data "sf-ms")) :sf-ms (unquote sf-ms)
:ref-sample (unquote (get ph-data "ref-sample")) :ref-sample (unquote ref-sample)
:ref-ms (unquote (get ph-data "ref-ms")) :ref-ms (unquote ref-ms)
:attr-result (unquote (get ph-data "attr-result")) :attr-result (unquote attr-result)
:attr-ms (unquote (get ph-data "attr-ms")) :attr-ms (unquote attr-ms)
:comp-source (unquote (get ph-data "comp-source")) :comp-source (unquote comp-source)
:comp-ms (unquote (get ph-data "comp-ms")) :comp-ms (unquote comp-ms)
:routing-result (unquote (get ph-data "routing-result")) :routing-result (unquote routing-result)
:routing-ms (unquote (get ph-data "routing-ms")) :routing-ms (unquote routing-ms)
:server-total-ms (unquote (get ph-data "server-total-ms")) :server-total-ms (unquote server-total-ms)
:sf-source (unquote (get ph-data "sf-source")) :sf-source (unquote sf-source)
:attr-detail (unquote (get ph-data "attr-detail")) :attr-detail (unquote attr-detail)
:req-attrs (unquote (get ph-data "req-attrs")) :req-attrs (unquote req-attrs)
:attr-keys (unquote (get ph-data "attr-keys"))))) :attr-keys (unquote attr-keys))))
:else (quasiquote :else (let-match
(~specs/bootstrapper-js-content {:bootstrapper-source bootstrapper-source :bootstrapped-output bootstrapped-output}
:bootstrapper-source (unquote (get data "bootstrapper-source")) data
:bootstrapped-output (unquote (get data "bootstrapped-output")))))))))) (quasiquote
(~specs/bootstrapper-js-content
:bootstrapper-source (unquote bootstrapper-source)
:bootstrapped-output (unquote bootstrapped-output))))))))))
(define (define
test test
@@ -379,24 +401,26 @@
(slug) (slug)
(if (if
(nil? slug) (nil? slug)
(let (let-match
((data (helper "run-modular-tests" "all"))) {:server-results server-results :parser-source parser-source :framework-source framework-source :eval-source eval-source :router-source router-source :engine-source engine-source :render-source render-source :deps-source deps-source}
(helper "run-modular-tests" "all")
(quasiquote (quasiquote
(~testing/overview-content (~testing/overview-content
:server-results (unquote (get data "server-results")) :server-results (unquote server-results)
:framework-source (unquote (get data "framework-source")) :framework-source (unquote framework-source)
:eval-source (unquote (get data "eval-source")) :eval-source (unquote eval-source)
:parser-source (unquote (get data "parser-source")) :parser-source (unquote parser-source)
:router-source (unquote (get data "router-source")) :router-source (unquote router-source)
:render-source (unquote (get data "render-source")) :render-source (unquote render-source)
:deps-source (unquote (get data "deps-source")) :deps-source (unquote deps-source)
:engine-source (unquote (get data "engine-source"))))) :engine-source (unquote engine-source))))
(case (case
slug slug
"runners" "runners"
(quote (~testing/runners-content)) (quote (~testing/runners-content))
:else (let :else (let-match
((data (helper "run-modular-tests" slug))) {:server-results server-results :spec-source spec-source :framework-source framework-source}
(helper "run-modular-tests" slug)
(case (case
slug slug
"eval" "eval"
@@ -404,67 +428,67 @@
(~testing/spec-content (~testing/spec-content
:spec-name "eval" :spec-name "eval"
:spec-title "Evaluator Tests" :spec-title "Evaluator Tests"
:spec-desc "81 tests covering the core evaluator and all primitives." :spec-desc "81 tests covering the core evaluator — literals, symbols, special forms, closures."
:spec-source (unquote (get data "spec-source")) :spec-source (unquote spec-source)
:framework-source (unquote (get data "framework-source")) :framework-source (unquote framework-source)
:server-results (unquote (get data "server-results")))) :server-results (unquote server-results)))
"parser" "parser"
(quasiquote (quasiquote
(~testing/spec-content (~testing/spec-content
:spec-name "parser" :spec-name "parser"
:spec-title "Parser Tests" :spec-title "Parser Tests"
:spec-desc "39 tests covering tokenization and parsing." :spec-desc "39 tests covering tokenization and parsing."
:spec-source (unquote (get data "spec-source")) :spec-source (unquote spec-source)
:framework-source (unquote (get data "framework-source")) :framework-source (unquote framework-source)
:server-results (unquote (get data "server-results")))) :server-results (unquote server-results)))
"router" "router"
(quasiquote (quasiquote
(~testing/spec-content (~testing/spec-content
:spec-name "router" :spec-name "router"
:spec-title "Router Tests" :spec-title "Router Tests"
:spec-desc "18 tests covering client-side route matching." :spec-desc "18 tests covering client-side route matching."
:spec-source (unquote (get data "spec-source")) :spec-source (unquote spec-source)
:framework-source (unquote (get data "framework-source")) :framework-source (unquote framework-source)
:server-results (unquote (get data "server-results")))) :server-results (unquote server-results)))
"render" "render"
(quasiquote (quasiquote
(~testing/spec-content (~testing/spec-content
:spec-name "render" :spec-name "render"
:spec-title "Renderer Tests" :spec-title "Renderer Tests"
:spec-desc "23 tests covering HTML rendering." :spec-desc "23 tests covering HTML rendering."
:spec-source (unquote (get data "spec-source")) :spec-source (unquote spec-source)
:framework-source (unquote (get data "framework-source")) :framework-source (unquote framework-source)
:server-results (unquote (get data "server-results")))) :server-results (unquote server-results)))
"deps" "deps"
(quasiquote (quasiquote
(~testing/spec-content (~testing/spec-content
:spec-name "deps" :spec-name "deps"
:spec-title "Dependency Analysis Tests" :spec-title "Dependency Analysis Tests"
:spec-desc "33 tests covering component dependency analysis." :spec-desc "33 tests covering component dependency analysis."
:spec-source (unquote (get data "spec-source")) :spec-source (unquote spec-source)
:framework-source (unquote (get data "framework-source")) :framework-source (unquote framework-source)
:server-results (unquote (get data "server-results")))) :server-results (unquote server-results)))
"engine" "engine"
(quasiquote (quasiquote
(~testing/spec-content (~testing/spec-content
:spec-name "engine" :spec-name "engine"
:spec-title "Engine Tests" :spec-title "Engine Tests"
:spec-desc "37 tests covering engine pure functions." :spec-desc "37 tests covering engine pure functions."
:spec-source (unquote (get data "spec-source")) :spec-source (unquote spec-source)
:framework-source (unquote (get data "framework-source")) :framework-source (unquote framework-source)
:server-results (unquote (get data "server-results")))) :server-results (unquote server-results)))
"orchestration" "orchestration"
(quasiquote (quasiquote
(~testing/spec-content (~testing/spec-content
:spec-name "orchestration" :spec-name "orchestration"
:spec-title "Orchestration Tests" :spec-title "Orchestration Tests"
:spec-desc "17 tests covering orchestration." :spec-desc "17 tests covering orchestration."
:spec-source (unquote (get data "spec-source")) :spec-source (unquote spec-source)
:framework-source (unquote (get data "framework-source")) :framework-source (unquote framework-source)
:server-results (unquote (get data "server-results")))) :server-results (unquote server-results)))
:else (quasiquote :else (quasiquote
(~testing/overview-content (~testing/overview-content
:server-results (unquote (get data "server-results")))))))))) :server-results (unquote server-results)))))))))
(define (define
reference reference
@@ -478,26 +502,32 @@
(case (case
slug slug
"attributes" "attributes"
(quasiquote (let-match
(~reference/attrs-content {:req-attrs req-attrs :beh-attrs beh-attrs :uniq-attrs uniq-attrs}
:req-table (~docs/attr-table-from-data data
:title "Request Attributes" (quasiquote
:attrs (unquote (get data "req-attrs"))) (~reference/attrs-content
:beh-table (~docs/attr-table-from-data :req-table (~docs/attr-table-from-data
:title "Behavior Attributes" :title "Request Attributes"
:attrs (unquote (get data "beh-attrs"))) :attrs (unquote req-attrs))
:uniq-table (~docs/attr-table-from-data :beh-table (~docs/attr-table-from-data
:title "Unique to sx" :title "Behavior Attributes"
:attrs (unquote (get data "uniq-attrs"))))) :attrs (unquote beh-attrs))
:uniq-table (~docs/attr-table-from-data
:title "Unique to sx"
:attrs (unquote uniq-attrs)))))
"headers" "headers"
(quasiquote (let-match
(~reference/headers-content {:req-headers req-headers :resp-headers resp-headers}
:req-table (~docs/headers-table-from-data data
:title "Request Headers" (quasiquote
:headers (unquote (get data "req-headers"))) (~reference/headers-content
:resp-table (~docs/headers-table-from-data :req-table (~docs/headers-table-from-data
:title "Response Headers" :title "Request Headers"
:headers (unquote (get data "resp-headers"))))) :headers (unquote req-headers))
:resp-table (~docs/headers-table-from-data
:title "Response Headers"
:headers (unquote resp-headers)))))
"events" "events"
(quasiquote (quasiquote
(~reference/events-content (~reference/events-content
@@ -514,17 +544,20 @@
:col1 "Method" :col1 "Method"
:col2 "Description" :col2 "Description"
:items (unquote (get data "js-api-list"))))) :items (unquote (get data "js-api-list")))))
:else (quasiquote :else (let-match
(~reference/attrs-content {:req-attrs req-attrs :beh-attrs beh-attrs :uniq-attrs uniq-attrs}
:req-table (~docs/attr-table-from-data data
:title "Request Attributes" (quasiquote
:attrs (unquote (get data "req-attrs"))) (~reference/attrs-content
:beh-table (~docs/attr-table-from-data :req-table (~docs/attr-table-from-data
:title "Behavior Attributes" :title "Request Attributes"
:attrs (unquote (get data "beh-attrs"))) :attrs (unquote req-attrs))
:uniq-table (~docs/attr-table-from-data :beh-table (~docs/attr-table-from-data
:title "Unique to sx" :title "Behavior Attributes"
:attrs (unquote (get data "uniq-attrs")))))))))) :attrs (unquote beh-attrs))
:uniq-table (~docs/attr-table-from-data
:title "Unique to sx"
:attrs (unquote uniq-attrs))))))))))
(define (define
reference-detail reference-detail
@@ -541,39 +574,48 @@
(if (if
(get data "attr-not-found") (get data "attr-not-found")
(quasiquote (~reference/attr-not-found :slug (unquote slug))) (quasiquote (~reference/attr-not-found :slug (unquote slug)))
(quasiquote (let-match
(~reference/attr-detail-content {:attr-handler attr-handler :attr-title attr-title :attr-example attr-example :attr-description attr-description :attr-demo attr-demo :attr-wire-id attr-wire-id}
:title (unquote (get data "attr-title")) data
:description (unquote (get data "attr-description")) (quasiquote
:demo (unquote (get data "attr-demo")) (~reference/attr-detail-content
:example-code (unquote (get data "attr-example")) :title (unquote attr-title)
:handler-code (unquote (get data "attr-handler")) :description (unquote attr-description)
:wire-placeholder-id (unquote (get data "attr-wire-id")))))) :demo (unquote attr-demo)
:example-code (unquote attr-example)
:handler-code (unquote attr-handler)
:wire-placeholder-id (unquote attr-wire-id))))))
"headers" "headers"
(let (let
((data (helper "header-detail-data" slug))) ((data (helper "header-detail-data" slug)))
(if (if
(get data "header-not-found") (get data "header-not-found")
(quasiquote (~reference/attr-not-found :slug (unquote slug))) (quasiquote (~reference/attr-not-found :slug (unquote slug)))
(quasiquote (let-match
(~reference/header-detail-content {:header-description header-description :header-demo header-demo :header-title header-title :header-example header-example :header-direction header-direction}
:title (unquote (get data "header-title")) data
:direction (unquote (get data "header-direction")) (quasiquote
:description (unquote (get data "header-description")) (~reference/header-detail-content
:example-code (unquote (get data "header-example")) :title (unquote header-title)
:demo (unquote (get data "header-demo")))))) :direction (unquote header-direction)
:description (unquote header-description)
:example-code (unquote header-example)
:demo (unquote header-demo))))))
"events" "events"
(let (let
((data (helper "event-detail-data" slug))) ((data (helper "event-detail-data" slug)))
(if (if
(get data "event-not-found") (get data "event-not-found")
(quasiquote (~reference/attr-not-found :slug (unquote slug))) (quasiquote (~reference/attr-not-found :slug (unquote slug)))
(quasiquote (let-match
(~reference/event-detail-content {:event-example event-example :event-demo event-demo :event-description event-description :event-title event-title}
:title (unquote (get data "event-title")) data
:description (unquote (get data "event-description")) (quasiquote
:example-code (unquote (get data "event-example")) (~reference/event-detail-content
:demo (unquote (get data "event-demo")))))) :title (unquote event-title)
:description (unquote event-description)
:example-code (unquote event-example)
:demo (unquote event-demo))))))
:else nil)))) :else nil))))
(define (define

View File

@@ -1,6 +1,7 @@
(define-library (sx signals) (define-library
(sx signals)
(export (export
make-signal make-signal
signal? signal?
@@ -26,205 +27,193 @@
with-island-scope with-island-scope
register-in-scope) register-in-scope)
(begin (begin
(define
(define make-signal
make-signal (fn
(fn (value)
(value) (dict
(dict "__signal" true "value" value "subscribers" (list) "deps" (list)))) "__signal"
true
(define signal? (fn (x) (and (dict? x) (has-key? x "__signal")))) "value"
value
(define signal-value (fn (s) (get s "value"))) "subscribers"
(list)
(define signal-set-value! (fn (s v) (dict-set! s "value" v))) "deps"
(list))))
(define signal-subscribers (fn (s) (get s "subscribers"))) (define signal? (fn (x) (and (dict? x) (has-key? x "__signal"))))
(define signal-value (fn (s) (get s "value")))
(define (define signal-set-value! (fn (s v) (dict-set! s "value" v)))
signal-add-sub! (define signal-subscribers (fn (s) (get s "subscribers")))
(fn (define
(s f) signal-add-sub!
(when (fn
(not (contains? (get s "subscribers") f)) (s f)
(dict-set! s "subscribers" (append (get s "subscribers") (list f))))))
(define
signal-remove-sub!
(fn
(s f)
(dict-set!
s
"subscribers"
(filter (fn (sub) (not (identical? sub f))) (get s "subscribers")))))
(define signal-deps (fn (s) (get s "deps")))
(define signal-set-deps! (fn (s deps) (dict-set! s "deps" deps)))
(define
signal
:effects ()
(fn ((initial-value :as any)) (make-signal initial-value)))
(define
deref
:effects ()
(fn
((s :as any))
(if
(not (signal? s))
s
(let
((ctx (context "sx-reactive" nil)))
(when (when
ctx (not (contains? (get s "subscribers") f))
(dict-set!
s
"subscribers"
(append (get s "subscribers") (list f))))))
(define
signal-remove-sub!
(fn
(s f)
(dict-set!
s
"subscribers"
(filter
(fn (sub) (not (identical? sub f)))
(get s "subscribers")))))
(define signal-deps (fn (s) (get s "deps")))
(define signal-set-deps! (fn (s deps) (dict-set! s "deps" deps)))
(define
signal
:effects ()
(fn ((initial-value :as any)) (make-signal initial-value)))
(define
deref
:effects ()
(fn
((s :as any))
(if
(not (signal? s))
s
(let (let
((dep-list (get ctx "deps")) (notify-fn (get ctx "notify"))) ((ctx (context "sx-reactive" nil)))
(when (when
(not (contains? dep-list s)) ctx
(append! dep-list s) (let
(signal-add-sub! s notify-fn)))) {:notify notify-fn :deps dep-list}
(signal-value s))))) ctx
(when
(define (not (contains? dep-list s))
reset! (append! dep-list s)
:effects (mutation) (signal-add-sub! s notify-fn))))
(fn (signal-value s)))))
((s :as signal) value) (define
(when reset!
(signal? s) :effects (mutation)
(let (fn
((old (signal-value s))) ((s :as signal) value)
(when (when
(not (identical? old value)) (signal? s)
(signal-set-value! s value) (let
(notify-subscribers s)))))) ((old (signal-value s)))
(when
(define (not (identical? old value))
swap! (signal-set-value! s value)
:effects (mutation) (notify-subscribers s))))))
(fn (define
((s :as signal) (f :as callable) &rest args) swap!
(when :effects (mutation)
(signal? s) (fn
(let ((s :as signal) (f :as callable) &rest args)
((old (signal-value s))
(new-val (trampoline (apply f (cons old args)))))
(when (when
(not (identical? old new-val)) (signal? s)
(signal-set-value! s new-val) (let
(notify-subscribers s)))))) ((old (signal-value s))
(new-val (trampoline (apply f (cons old args)))))
(define (when
computed (not (identical? old new-val))
:effects (mutation) (signal-set-value! s new-val)
(fn (notify-subscribers s))))))
((compute-fn :as lambda)) (define
(let computed
((s (make-signal nil)) (deps (list)) (compute-ctx nil)) :effects (mutation)
(let (fn
((recompute (fn () (for-each (fn ((dep :as signal)) (signal-remove-sub! dep recompute)) (signal-deps s)) (signal-set-deps! s (list)) (let ((ctx (dict "deps" (list) "notify" recompute))) (scope-push! "sx-reactive" ctx) (let ((new-val (cek-call compute-fn nil))) (scope-pop! "sx-reactive") (signal-set-deps! s (get ctx "deps")) (let ((old (signal-value s))) (signal-set-value! s new-val) (when (not (identical? old new-val)) (notify-subscribers s)))))))) ((compute-fn :as lambda))
(recompute)
(register-in-scope (fn () (dispose-computed s)))
s))))
(define
effect
:effects (mutation)
(fn
((effect-fn :as lambda))
(let
((deps (list)) (disposed false) (cleanup-fn nil))
(let
((run-effect (fn () (when (not disposed) (when cleanup-fn (cek-call cleanup-fn nil)) (for-each (fn ((dep :as signal)) (signal-remove-sub! dep run-effect)) deps) (set! deps (list)) (let ((ctx (dict "deps" (list) "notify" run-effect))) (scope-push! "sx-reactive" ctx) (let ((result (cek-call effect-fn nil))) (scope-pop! "sx-reactive") (set! deps (get ctx "deps")) (when (callable? result) (set! cleanup-fn result))))))))
(run-effect)
(let (let
((dispose-fn (fn () (set! disposed true) (when cleanup-fn (cek-call cleanup-fn nil)) (for-each (fn ((dep :as signal)) (signal-remove-sub! dep run-effect)) deps) (set! deps (list))))) ((s (make-signal nil)) (deps (list)) (compute-ctx nil))
(register-in-scope dispose-fn) (let
dispose-fn))))) ((recompute (fn () (for-each (fn ((dep :as signal)) (signal-remove-sub! dep recompute)) (signal-deps s)) (signal-set-deps! s (list)) (let ((ctx (dict "deps" (list) "notify" recompute))) (scope-push! "sx-reactive" ctx) (let ((new-val (cek-call compute-fn nil))) (scope-pop! "sx-reactive") (signal-set-deps! s (get ctx "deps")) (let ((old (signal-value s))) (signal-set-value! s new-val) (when (not (identical? old new-val)) (notify-subscribers s))))))))
(recompute)
(define *batch-depth* 0) (register-in-scope (fn () (dispose-computed s)))
s))))
(define *batch-queue* (list)) (define
effect
(define :effects (mutation)
batch (fn
:effects (mutation) ((effect-fn :as lambda))
(fn
((thunk :as lambda))
(set! *batch-depth* (+ *batch-depth* 1))
(cek-call thunk nil)
(set! *batch-depth* (- *batch-depth* 1))
(when
(= *batch-depth* 0)
(let
((queue *batch-queue*))
(set! *batch-queue* (list))
(let (let
((seen (list)) (pending (list))) ((deps (list)) (disposed false) (cleanup-fn nil))
(for-each (let
(fn ((run-effect (fn () (when (not disposed) (when cleanup-fn (cek-call cleanup-fn nil)) (for-each (fn ((dep :as signal)) (signal-remove-sub! dep run-effect)) deps) (set! deps (list)) (let ((ctx (dict "deps" (list) "notify" run-effect))) (scope-push! "sx-reactive" ctx) (let ((result (cek-call effect-fn nil))) (scope-pop! "sx-reactive") (set! deps (get ctx "deps")) (when (callable? result) (set! cleanup-fn result))))))))
((s :as signal)) (run-effect)
(let
((dispose-fn (fn () (set! disposed true) (when cleanup-fn (cek-call cleanup-fn nil)) (for-each (fn ((dep :as signal)) (signal-remove-sub! dep run-effect)) deps) (set! deps (list)))))
(register-in-scope dispose-fn)
dispose-fn)))))
(define *batch-depth* 0)
(define *batch-queue* (list))
(define
batch
:effects (mutation)
(fn
((thunk :as lambda))
(set! *batch-depth* (+ *batch-depth* 1))
(cek-call thunk nil)
(set! *batch-depth* (- *batch-depth* 1))
(when
(= *batch-depth* 0)
(let
((queue *batch-queue*))
(set! *batch-queue* (list))
(let
((seen (list)) (pending (list)))
(for-each (for-each
(fn (fn
((sub :as lambda)) ((s :as signal))
(when (for-each
(not (contains? seen sub)) (fn
(append! seen sub) ((sub :as lambda))
(append! pending sub))) (when
(signal-subscribers s))) (not (contains? seen sub))
queue) (append! seen sub)
(for-each (fn ((sub :as lambda)) (sub)) pending)))))) (append! pending sub)))
(signal-subscribers s)))
(define queue)
notify-subscribers (for-each (fn ((sub :as lambda)) (sub)) pending))))))
:effects (mutation) (define
(fn notify-subscribers
((s :as signal)) :effects (mutation)
(if (fn
(> *batch-depth* 0) ((s :as signal))
(when (not (contains? *batch-queue* s)) (append! *batch-queue* s)) (if
(flush-subscribers s)))) (> *batch-depth* 0)
(when (not (contains? *batch-queue* s)) (append! *batch-queue* s))
(define (flush-subscribers s))))
flush-subscribers (define
:effects (mutation) flush-subscribers
(fn :effects (mutation)
((s :as dict)) (fn
(for-each (fn (sub) (cek-call sub nil)) (signal-subscribers s)))) ((s :as dict))
(for-each (fn (sub) (cek-call sub nil)) (signal-subscribers s))))
(define (define
dispose-computed dispose-computed
:effects (mutation) :effects (mutation)
(fn (fn
((s :as signal)) ((s :as signal))
(when (when
(signal? s) (signal? s)
(for-each (for-each
(fn ((dep :as signal)) (signal-remove-sub! dep nil)) (fn ((dep :as signal)) (signal-remove-sub! dep nil))
(signal-deps s)) (signal-deps s))
(signal-set-deps! s (list))))) (signal-set-deps! s (list)))))
(define
(define with-island-scope
with-island-scope :effects (mutation)
:effects (mutation) (fn
(fn ((scope-fn :as lambda) (body-fn :as lambda))
((scope-fn :as lambda) (body-fn :as lambda)) (scope-push! "sx-island-scope" scope-fn)
(scope-push! "sx-island-scope" scope-fn) (let ((result (body-fn))) (scope-pop! "sx-island-scope") result)))
(let ((result (body-fn))) (scope-pop! "sx-island-scope") result))) (define
register-in-scope
(define :effects (mutation)
register-in-scope (fn
:effects (mutation) ((disposable :as lambda))
(fn (let
((disposable :as lambda)) ((collector (scope-peek "sx-island-scope")))
(let (when collector (cek-call collector (list disposable)))))))) ;; end define-library
((collector (scope-peek "sx-island-scope")))
(when collector (cek-call collector (list disposable))))))
)) ;; end define-library
;; Re-export to global namespace for backward compatibility ;; Re-export to global namespace for backward compatibility
(import (sx signals)) (import (sx signals))

View File

@@ -9,71 +9,89 @@
;; Actor ;; Actor
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler pub-actor (defhandler
pub-actor
:path "/pub/actor" :path "/pub/actor"
:method :get :method :get
:returns "element" :returns "element"
(&key) (&key)
(let ((actor (helper "pub-actor-data"))) (let-match
{:domain domain :summary summary :display-name display-name :public-key-pem public-key-pem}
(helper "pub-actor-data")
(do (do
(set-response-header "Content-Type" "text/sx; charset=utf-8") (set-response-header "Content-Type" "text/sx; charset=utf-8")
(str (str
"(SxActor" "(SxActor"
"\n :id \"https://" (get actor "domain") "/pub/actor\"" "\n :id \"https://"
domain
"/pub/actor\""
"\n :type \"SxPublisher\"" "\n :type \"SxPublisher\""
"\n :name \"" (get actor "display-name") "\"" "\n :name \""
"\n :summary \"" (get actor "summary") "\"" display-name
"\""
"\n :summary \""
summary
"\""
"\n :inbox \"/pub/inbox\"" "\n :inbox \"/pub/inbox\""
"\n :outbox \"/pub/outbox\"" "\n :outbox \"/pub/outbox\""
"\n :followers \"/pub/followers\"" "\n :followers \"/pub/followers\""
"\n :following \"/pub/following\"" "\n :following \"/pub/following\""
"\n :public-key-pem \"" (get actor "public-key-pem") "\")")))) "\n :public-key-pem \""
public-key-pem
"\")"))))
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
;; Webfinger ;; Webfinger
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler pub-webfinger (defhandler
pub-webfinger
:path "/pub/webfinger" :path "/pub/webfinger"
:method :get :method :get
:returns "element" :returns "element"
(&key) (&key)
(let ((resource (helper "request-arg" "resource" "")) (let
(actor (helper "pub-actor-data"))) ((resource (helper "request-arg" "resource" "")))
(let ((expected (str "acct:" (get actor "preferred-username") "@" (get actor "domain")))) (let-match
(if (!= resource expected) {:domain domain :preferred-username preferred-username}
(do (helper "pub-actor-data")
(set-response-status 404) (let
(str "(Error :message \"Resource not found\")")) ((expected (str "acct:" preferred-username "@" domain)))
(do (if
(set-response-header "Content-Type" "text/sx; charset=utf-8") (!= resource expected)
(str (do
"(SxWebfinger" (set-response-status 404)
"\n :subject \"" expected "\"" (str "(Error :message \"Resource not found\")"))
"\n :actor \"https://" (get actor "domain") "/pub/actor\"" (do
"\n :type \"SxPublisher\")")))))) (set-response-header "Content-Type" "text/sx; charset=utf-8")
(str
"(SxWebfinger"
"\n :subject \""
expected
"\""
"\n :actor \"https://"
domain
"/pub/actor\""
"\n :type \"SxPublisher\")")))))))
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
;; Collections ;; Collections
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler pub-collections (defhandler
pub-collections
:path "/pub/collections" :path "/pub/collections"
:method :get :method :get
:returns "element" :returns "element"
(&key) (&key)
(let ((collections (helper "pub-collections-data"))) (let
((collections (helper "pub-collections-data")))
(do (do
(set-response-header "Content-Type" "text/sx; charset=utf-8") (set-response-header "Content-Type" "text/sx; charset=utf-8")
(let ((items (map (fn (c) (let
(str "\n (SxCollection" ((items (map (fn (c) (let-match {:description description :slug slug :name name} c (str "\n (SxCollection" " :slug \"" slug "\"" " :name \"" name "\"" " :description \"" description "\"" " :href \"/pub/" slug "\")"))) collections)))
" :slug \"" (get c "slug") "\""
" :name \"" (get c "name") "\""
" :description \"" (get c "description") "\""
" :href \"/pub/" (get c "slug") "\")"))
collections)))
(str "(SxCollections" (join "" items) ")"))))) (str "(SxCollections" (join "" items) ")")))))
@@ -81,21 +99,33 @@
;; Status ;; Status
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler pub-status (defhandler
pub-status
:path "/pub/status" :path "/pub/status"
:method :get :method :get
:returns "element" :returns "element"
(&key) (&key)
(let ((status (helper "pub-status-data"))) (let-match
{:db db :domain domain :healthy healthy :ipfs ipfs :actor actor}
(helper "pub-status-data")
(do (do
(set-response-header "Content-Type" "text/sx; charset=utf-8") (set-response-header "Content-Type" "text/sx; charset=utf-8")
(str (str
"(SxPubStatus" "(SxPubStatus"
"\n :healthy " (get status "healthy") "\n :healthy "
"\n :db \"" (get status "db") "\"" healthy
"\n :ipfs \"" (get status "ipfs") "\"" "\n :db \""
"\n :actor \"" (get status "actor") "\"" db
"\n :domain \"" (or (get status "domain") "unknown") "\")")))) "\""
"\n :ipfs \""
ipfs
"\""
"\n :actor \""
actor
"\""
"\n :domain \""
(or domain "unknown")
"\")"))))
;; ========================================================================== ;; ==========================================================================
@@ -107,72 +137,100 @@
;; Publish ;; Publish
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler pub-publish (defhandler
pub-publish
:path "/pub/publish" :path "/pub/publish"
:method :post :method :post
:csrf false :csrf false
:returns "element" :returns "element"
(&key) (&key)
(let ((collection (helper "request-form" "collection" "")) (let
(slug (helper "request-form" "slug" "")) ((collection (helper "request-form" "collection" ""))
(content (helper "request-form" "content" "")) (slug (helper "request-form" "slug" ""))
(title (helper "request-form" "title" "")) (content (helper "request-form" "content" ""))
(summary (helper "request-form" "summary" ""))) (title (helper "request-form" "title" ""))
(if (or (= collection "") (= slug "") (= content "")) (summary (helper "request-form" "summary" "")))
(if
(or (= collection "") (= slug "") (= content ""))
(do (do
(set-response-status 400) (set-response-status 400)
(set-response-header "Content-Type" "text/sx; charset=utf-8") (set-response-header "Content-Type" "text/sx; charset=utf-8")
"(Error :message \"Missing collection, slug, or content\")") "(Error :message \"Missing collection, slug, or content\")")
(let ((result (helper "pub-publish" collection slug content title summary))) (let
(if (get result "error") ((result (helper "pub-publish" collection slug content title summary)))
(if
(get result "error")
(do (do
(set-response-status 500) (set-response-status 500)
(set-response-header "Content-Type" "text/sx; charset=utf-8") (set-response-header "Content-Type" "text/sx; charset=utf-8")
(str "(Error :message \"" (get result "error") "\")")) (str "(Error :message \"" (get result "error") "\")"))
(do (let-match
(set-response-header "Content-Type" "text/sx; charset=utf-8") {:cid cid :hash hash :size size :title title :path path :slug slug :collection collection}
(str result
"(Published" (do
"\n :path \"" (get result "path") "\"" (set-response-header "Content-Type" "text/sx; charset=utf-8")
"\n :cid \"" (get result "cid") "\"" (str
"\n :hash \"" (get result "hash") "\"" "(Published"
"\n :size " (get result "size") "\n :path \""
"\n :collection \"" (get result "collection") "\"" path
"\n :slug \"" (get result "slug") "\"" "\""
"\n :title \"" (get result "title") "\")"))))))) "\n :cid \""
cid
"\""
"\n :hash \""
hash
"\""
"\n :size "
size
"\n :collection \""
collection
"\""
"\n :slug \""
slug
"\""
"\n :title \""
title
"\")"))))))))
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
;; Browse collection ;; Browse collection
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler pub-browse-collection (defhandler
pub-browse-collection
:path "/pub/browse/<collection_slug>" :path "/pub/browse/<collection_slug>"
:method :get :method :get
:returns "element" :returns "element"
(&key collection_slug) (&key collection_slug)
(let ((data (helper "pub-collection-items" collection_slug))) (let
(if (get data "error") ((data (helper "pub-collection-items" collection_slug)))
(if
(get data "error")
(do (do
(set-response-status 404) (set-response-status 404)
(set-response-header "Content-Type" "text/sx; charset=utf-8") (set-response-header "Content-Type" "text/sx; charset=utf-8")
(str "(Error :message \"" (get data "error") "\")")) (str "(Error :message \"" (get data "error") "\")"))
(do (let-match
(set-response-header "Content-Type" "text/sx; charset=utf-8") {:description description :items items-data :collection collection :name name}
(let ((items (map (fn (d) data
(str "\n (SxDocument" (do
" :slug \"" (get d "slug") "\"" (set-response-header "Content-Type" "text/sx; charset=utf-8")
" :title \"" (get d "title") "\"" (let
" :summary \"" (get d "summary") "\"" ((items (map (fn (d) (let-match {:cid cid :size size :summary summary :title title :slug slug} d (str "\n (SxDocument" " :slug \"" slug "\"" " :title \"" title "\"" " :summary \"" summary "\"" " :cid \"" cid "\"" " :size " size ")"))) items-data)))
" :cid \"" (get d "cid") "\"" (str
" :size " (get d "size") ")")) "(SxCollection"
(get data "items")))) "\n :slug \""
(str collection
"(SxCollection" "\""
"\n :slug \"" (get data "collection") "\"" "\n :name \""
"\n :name \"" (get data "name") "\"" name
"\n :description \"" (get data "description") "\"" "\""
(join "" items) ")")))))) "\n :description \""
description
"\""
(join "" items)
")")))))))
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
@@ -226,26 +284,30 @@
;; Outbox ;; Outbox
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler pub-outbox (defhandler
pub-outbox
:path "/pub/outbox" :path "/pub/outbox"
:method :get :method :get
:returns "element" :returns "element"
(&key) (&key)
(let ((page (helper "request-arg" "page" "")) (let
(data (helper "pub-outbox-data" page))) ((page (helper "request-arg" "page" ""))
(data (helper "pub-outbox-data" page)))
(do (do
(set-response-header "Content-Type" "text/sx; charset=utf-8") (set-response-header "Content-Type" "text/sx; charset=utf-8")
(let ((items (map (fn (a) (let-match
(str "\n (" (get a "type") {:total total :page page :items items-data}
" :object-type \"" (get a "object-type") "\"" data
" :published \"" (get a "published") "\"" (let
" :cid \"" (get a "cid") "\")")) ((items (map (fn (a) (let-match {:cid cid :type type :object-type object-type :published published} a (str "\n (" type " :object-type \"" object-type "\"" " :published \"" published "\"" " :cid \"" cid "\")"))) items-data)))
(get data "items")))) (str
(str "(SxOutbox"
"(SxOutbox" "\n :total "
"\n :total " (get data "total") total
"\n :page " (get data "page") "\n :page "
(join "" items) ")"))))) page
(join "" items)
")"))))))
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
@@ -275,48 +337,59 @@
;; Follow a remote server ;; Follow a remote server
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler pub-follow (defhandler
pub-follow
:path "/pub/follow" :path "/pub/follow"
:method :post :method :post
:csrf false :csrf false
:returns "element" :returns "element"
(&key) (&key)
(let ((actor-url (helper "request-form" "actor_url" ""))) (let
(if (= actor-url "") ((actor-url (helper "request-form" "actor_url" "")))
(if
(= actor-url "")
(do (do
(set-response-status 400) (set-response-status 400)
(set-response-header "Content-Type" "text/sx; charset=utf-8") (set-response-header "Content-Type" "text/sx; charset=utf-8")
"(Error :message \"Missing actor_url\")") "(Error :message \"Missing actor_url\")")
(let ((result (helper "pub-follow-remote" actor-url))) (let
((result (helper "pub-follow-remote" actor-url)))
(do (do
(set-response-header "Content-Type" "text/sx; charset=utf-8") (set-response-header "Content-Type" "text/sx; charset=utf-8")
(if (get result "error") (if
(get result "error")
(do (do
(set-response-status 502) (set-response-status 502)
(str "(Error :message \"" (get result "error") "\")")) (str "(Error :message \"" (get result "error") "\")"))
(str (let-match
"(FollowSent" {:status status :actor-url actor-url}
"\n :actor-url \"" (get result "actor-url") "\"" result
"\n :status \"" (get result "status") "\")"))))))) (str
"(FollowSent"
"\n :actor-url \""
actor-url
"\""
"\n :status \""
status
"\")"))))))))
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
;; Followers ;; Followers
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler pub-followers (defhandler
pub-followers
:path "/pub/followers" :path "/pub/followers"
:method :get :method :get
:returns "element" :returns "element"
(&key) (&key)
(let ((data (helper "pub-followers-data"))) (let
((data (helper "pub-followers-data")))
(do (do
(set-response-header "Content-Type" "text/sx; charset=utf-8") (set-response-header "Content-Type" "text/sx; charset=utf-8")
(let ((items (map (fn (f) (let
(str "\n (SxFollower" ((items (map (fn (f) (let-match {:actor-url actor-url :acct acct} f (str "\n (SxFollower" " :acct \"" acct "\"" " :actor-url \"" actor-url "\")"))) data)))
" :acct \"" (get f "acct") "\""
" :actor-url \"" (get f "actor-url") "\")"))
data)))
(str "(SxFollowers" (join "" items) ")"))))) (str "(SxFollowers" (join "" items) ")")))))
@@ -348,48 +421,80 @@
;; Anchor pending activities ;; Anchor pending activities
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler pub-anchor (defhandler
pub-anchor
:path "/pub/anchor" :path "/pub/anchor"
:method :post :method :post
:csrf false :csrf false
:returns "element" :returns "element"
(&key) (&key)
(let ((result (helper "pub-anchor-pending"))) (let-match
{:tree-cid tree-cid :status status :count count :ots-proof-cid ots-proof-cid :merkle-root merkle-root}
(helper "pub-anchor-pending")
(do (do
(set-response-header "Content-Type" "text/sx; charset=utf-8") (set-response-header "Content-Type" "text/sx; charset=utf-8")
(if (= (get result "status") "nothing-to-anchor") (if
(= status "nothing-to-anchor")
"(Anchor :status \"nothing-to-anchor\" :count 0)" "(Anchor :status \"nothing-to-anchor\" :count 0)"
(str (str
"(Anchor" "(Anchor"
"\n :status \"" (get result "status") "\"" "\n :status \""
"\n :count " (get result "count") status
"\n :merkle-root \"" (get result "merkle-root") "\"" "\""
"\n :tree-cid \"" (get result "tree-cid") "\"" "\n :count "
"\n :ots-proof-cid \"" (get result "ots-proof-cid") "\")"))))) count
"\n :merkle-root \""
merkle-root
"\""
"\n :tree-cid \""
tree-cid
"\""
"\n :ots-proof-cid \""
ots-proof-cid
"\")")))))
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
;; Verify a CID's anchor ;; Verify a CID's anchor
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler pub-verify (defhandler
pub-verify
:path "/pub/verify/<cid>" :path "/pub/verify/<cid>"
:method :get :method :get
:returns "element" :returns "element"
(&key cid) (&key cid)
(let ((data (helper "pub-verify-anchor" cid))) (let
((data (helper "pub-verify-anchor" cid)))
(do (do
(set-response-header "Content-Type" "text/sx; charset=utf-8") (set-response-header "Content-Type" "text/sx; charset=utf-8")
(if (get data "error") (if
(get data "error")
(do (do
(set-response-status 404) (set-response-status 404)
(str "(Error :message \"" (get data "error") "\")")) (str "(Error :message \"" (get data "error") "\")"))
(str (let-match
"(AnchorVerification" {:cid cid* :tree-cid tree-cid :status status :verified verified :ots-proof-cid ots-proof-cid :merkle-root merkle-root :published published}
"\n :cid \"" (get data "cid") "\"" data
"\n :status \"" (get data "status") "\"" (str
"\n :verified " (get data "verified") "(AnchorVerification"
"\n :merkle-root \"" (get data "merkle-root") "\"" "\n :cid \""
"\n :tree-cid \"" (get data "tree-cid") "\"" cid*
"\n :ots-proof-cid \"" (get data "ots-proof-cid") "\"" "\""
"\n :published \"" (get data "published") "\")"))))) "\n :status \""
status
"\""
"\n :verified "
verified
"\n :merkle-root \""
merkle-root
"\""
"\n :tree-cid \""
tree-cid
"\""
"\n :ots-proof-cid \""
ots-proof-cid
"\""
"\n :published \""
published
"\")"))))))

View File

@@ -2,25 +2,27 @@
~specs-explorer/spec-explorer-content ~specs-explorer/spec-explorer-content
(&key data) (&key data)
:affinity :server :affinity :server
(~docs/page (let-match
:title (str (get data "title") " — Explorer") {:stats stats :desc desc :title title :filename filename :platform-interface platform-interface :sections sections}
(~specs-explorer/spec-explorer-header data
:filename (get data "filename") (~docs/page
:title (get data "title") :title (str title " — Explorer")
:desc (get data "desc") (~specs-explorer/spec-explorer-header
:slug (replace (get data "filename") ".sx" "")) :filename filename
(~specs-explorer/spec-explorer-stats :stats (get data "stats")) :title title
(map :desc desc
(fn :slug (replace filename ".sx" ""))
(section) (~specs-explorer/spec-explorer-stats :stats stats)
(~specs-explorer/spec-explorer-section (map
:section section (fn
:filename (get data "filename"))) (section)
(get data "sections")) (~specs-explorer/spec-explorer-section
(when :section section
(not (empty? (get data "platform-interface"))) :filename filename))
(~specs-explorer/spec-platform-interface sections)
:items (get data "platform-interface"))))) (when
(not (empty? platform-interface))
(~specs-explorer/spec-platform-interface :items platform-interface)))))
(defcomp (defcomp
~specs-explorer/spec-explorer-header ~specs-explorer/spec-explorer-header
@@ -46,80 +48,91 @@
(defcomp (defcomp
~specs-explorer/spec-explorer-stats ~specs-explorer/spec-explorer-stats
(&key stats) (&key stats)
(div (let-match
(~tw :tokens "flex flex-wrap gap-2 mb-6 text-xs") {:lines lines :io-count io-count :render-count render-count :pure-count pure-count :mutation-count mutation-count :test-total test-total :total-defines total-defines}
(span stats
(~tw :tokens "bg-stone-100 text-stone-600 px-2 py-0.5 rounded font-medium") (div
(str (get stats "total-defines") " defines")) (~tw :tokens "flex flex-wrap gap-2 mb-6 text-xs")
(when
(> (get stats "pure-count") 0)
(span (span
(~tw :tokens "bg-green-100 text-green-700 px-2 py-0.5 rounded") (~tw
(str (get stats "pure-count") " pure"))) :tokens "bg-stone-100 text-stone-600 px-2 py-0.5 rounded font-medium")
(when (str total-defines " defines"))
(> (get stats "mutation-count") 0) (when
(> pure-count 0)
(span
(~tw :tokens "bg-green-100 text-green-700 px-2 py-0.5 rounded")
(str pure-count " pure")))
(when
(> mutation-count 0)
(span
(~tw :tokens "bg-amber-100 text-amber-700 px-2 py-0.5 rounded")
(str mutation-count " mutation")))
(when
(> io-count 0)
(span
(~tw :tokens "bg-orange-100 text-orange-700 px-2 py-0.5 rounded")
(str io-count " io")))
(when
(> render-count 0)
(span
(~tw :tokens "bg-sky-100 text-sky-700 px-2 py-0.5 rounded")
(str render-count " render")))
(when
(> test-total 0)
(span
(~tw :tokens "bg-violet-100 text-violet-700 px-2 py-0.5 rounded")
(str test-total " tests")))
(span (span
(~tw :tokens "bg-amber-100 text-amber-700 px-2 py-0.5 rounded") (~tw :tokens "bg-stone-100 text-stone-500 px-2 py-0.5 rounded")
(str (get stats "mutation-count") " mutation"))) (str lines " lines")))))
(when
(> (get stats "io-count") 0)
(span
(~tw :tokens "bg-orange-100 text-orange-700 px-2 py-0.5 rounded")
(str (get stats "io-count") " io")))
(when
(> (get stats "render-count") 0)
(span
(~tw :tokens "bg-sky-100 text-sky-700 px-2 py-0.5 rounded")
(str (get stats "render-count") " render")))
(when
(> (get stats "test-total") 0)
(span
(~tw :tokens "bg-violet-100 text-violet-700 px-2 py-0.5 rounded")
(str (get stats "test-total") " tests")))
(span
(~tw :tokens "bg-stone-100 text-stone-500 px-2 py-0.5 rounded")
(str (get stats "lines") " lines"))))
(defcomp (defcomp
~specs-explorer/spec-explorer-section ~specs-explorer/spec-explorer-section
(&key section filename) (&key section filename)
(div (let-match
(~tw :tokens "mb-6") {:defines defines :title title :comment comment}
(h2 section
(~tw :tokens "text-base font-semibold text-stone-600 mb-2 border-b border-stone-200 pb-1")
:id (replace (lower (get section "title")) " " "-")
(get section "title"))
(when
(get section "comment")
(p (~tw :tokens "text-sm text-stone-500 mb-2") (get section "comment")))
(div (div
(~tw :tokens "space-y-0.5") (~tw :tokens "mb-6")
(map (h2
(fn (~tw
(d) :tokens "text-base font-semibold text-stone-600 mb-2 border-b border-stone-200 pb-1")
(~specs-explorer/spec-explorer-define :d d :filename filename)) :id (replace (lower title) " " "-")
(get section "defines"))))) title)
(when comment (p (~tw :tokens "text-sm text-stone-500 mb-2") comment))
(div
(~tw :tokens "space-y-0.5")
(map
(fn
(d)
(~specs-explorer/spec-explorer-define :d d :filename filename))
defines)))))
(defcomp (defcomp
~specs-explorer/spec-explorer-define ~specs-explorer/spec-explorer-define
(&key d filename) (&key d filename)
(div (let-match
(~tw :tokens "flex items-center gap-2 py-1.5 px-2 rounded hover:bg-stone-50 cursor-pointer group") {:kind kind :name name}
:id (str "fn-" (get d "name")) d
:sx-get (str (div
"/sx/(language.(spec.(explore." (~tw
(replace filename ".sx" "") :tokens "flex items-center gap-2 py-1.5 px-2 rounded hover:bg-stone-50 cursor-pointer group")
"." :id (str "fn-" name)
(get d "name") :sx-get (str
")))") "/sx/(language.(spec.(explore."
:sx-target "#sx-content" (replace filename ".sx" "")
:sx-select "#sx-content" "."
:sx-swap "innerHTML" name
:sx-push-url "true" ")))")
(span :sx-target "#sx-content"
(~tw :tokens "font-mono text-sm font-medium text-stone-800 group-hover:text-violet-700") :sx-select "#sx-content"
(get d "name")) :sx-swap "innerHTML"
(span (~tw :tokens "text-xs text-stone-400") (get d "kind")))) :sx-push-url "true"
(span
(~tw
:tokens "font-mono text-sm font-medium text-stone-800 group-hover:text-violet-700")
name)
(span (~tw :tokens "text-xs text-stone-400") kind))))
(defcomp (defcomp
~specs-explorer/spec-explorer-define-detail ~specs-explorer/spec-explorer-define-detail
@@ -144,33 +157,39 @@
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-push-url "true" :sx-push-url "true"
(str "← Back to " (replace filename ".sx" "")))) (str "← Back to " (replace filename ".sx" ""))))
(div (let-match
(~tw :tokens "rounded border border-violet-200 bg-violet-50/30 p-4") {:kind kind :effects effects :params params :source source :name name}
d
(div (div
(~tw :tokens "flex items-center gap-2 flex-wrap mb-3") (~tw :tokens "rounded border border-violet-200 bg-violet-50/30 p-4")
(span (div
(~tw :tokens "font-mono text-lg font-semibold text-stone-800") (~tw :tokens "flex items-center gap-2 flex-wrap mb-3")
(get d "name"))
(span (~tw :tokens "text-xs text-stone-400") (get d "kind"))
(if
(empty? (get d "effects"))
(span (span
(~tw :tokens "text-xs px-1.5 py-0.5 rounded bg-green-100 text-green-700") (~tw :tokens "font-mono text-lg font-semibold text-stone-800")
"pure") name)
(map (span (~tw :tokens "text-xs text-stone-400") kind)
(fn (eff) (~specs-explorer/spec-effect-badge :effect eff)) (if
(get d "effects")))) (empty? effects)
(when (span
(not (empty? (get d "params"))) (~tw
(~specs-explorer/spec-param-list :params (get d "params"))) :tokens "text-xs px-1.5 py-0.5 rounded bg-green-100 text-green-700")
(details "pure")
:open "true" (map
(summary (fn (eff) (~specs-explorer/spec-effect-badge :effect eff))
(~tw :tokens "px-3 py-1.5 bg-stone-50 text-xs font-medium text-stone-600 cursor-pointer select-none mt-3 rounded") effects)))
"SX Source") (when
(pre (not (empty? params))
(~tw :tokens "text-xs p-3 overflow-x-auto bg-white rounded mt-1 border border-stone-200") (~specs-explorer/spec-param-list :params params))
(code (~tw :tokens "language-sx") (get d "source"))))))) (details
:open "true"
(summary
(~tw
:tokens "px-3 py-1.5 bg-stone-50 text-xs font-medium text-stone-600 cursor-pointer select-none mt-3 rounded")
"SX Source")
(pre
(~tw
:tokens "text-xs p-3 overflow-x-auto bg-white rounded mt-1 border border-stone-200")
(code (~tw :tokens "language-sx") source)))))))
(defcomp (defcomp
~specs-explorer/spec-effect-badge ~specs-explorer/spec-effect-badge
@@ -197,13 +216,15 @@
(map (map
(fn (fn
(p) (p)
(let (let-match
((name (get p "name")) (typ (get p "type"))) {:type typ :name name}
p
(if (if
(or (= name "&rest") (= name "&key")) (or (= name "&rest") (= name "&key"))
(span (~tw :tokens "text-xs font-mono text-violet-500") name) (span (~tw :tokens "text-xs font-mono text-violet-500") name)
(span (span
(~tw :tokens "text-xs font-mono px-1 py-0.5 rounded bg-stone-50 border border-stone-200") (~tw
:tokens "text-xs font-mono px-1 py-0.5 rounded bg-stone-50 border border-stone-200")
(if (if
typ typ
(<> (<>
@@ -300,7 +321,8 @@
(div (div
(~tw :tokens "mt-8") (~tw :tokens "mt-8")
(h2 (h2
(~tw :tokens "text-lg font-semibold text-stone-700 border-b border-stone-200 pb-1 mb-3") (~tw
:tokens "text-lg font-semibold text-stone-700 border-b border-stone-200 pb-1 mb-3")
"Platform Interface") "Platform Interface")
(p (p
(~tw :tokens "text-sm text-stone-500 mb-3") (~tw :tokens "text-sm text-stone-500 mb-3")
@@ -314,22 +336,29 @@
(~tw :tokens "border-b border-stone-200 bg-stone-50") (~tw :tokens "border-b border-stone-200 bg-stone-50")
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Name") (th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Name")
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Params") (th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Params")
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Returns") (th
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Description"))) (~tw :tokens "px-3 py-2 font-medium text-stone-600")
"Returns")
(th
(~tw :tokens "px-3 py-2 font-medium text-stone-600")
"Description")))
(tbody (tbody
(map (map
(fn (fn
(item) (item)
(tr (let-match
(~tw :tokens "border-b border-stone-100") {:doc doc :params params :returns returns :name name}
(td item
(~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") (tr
(get item "name")) (~tw :tokens "border-b border-stone-100")
(td (td
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500") (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700")
(get item "params")) name)
(td (td
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500") (~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
(get item "returns")) params)
(td (~tw :tokens "px-3 py-2 text-stone-600") (get item "doc")))) (td
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
returns)
(td (~tw :tokens "px-3 py-2 text-stone-600") doc))))
items)))))) items))))))

View File

@@ -1,6 +1,7 @@
(define-library (web deps) (define-library
(web deps)
(export (export
scan-refs scan-refs
scan-refs-walk scan-refs-walk
@@ -22,347 +23,340 @@
page-render-plan page-render-plan
env-components) env-components)
(begin (begin
(define
(define scan-refs
scan-refs :effects ()
:effects () (fn (node) (let ((refs (list))) (scan-refs-walk node refs) refs)))
(fn (node) (let ((refs (list))) (scan-refs-walk node refs) refs))) (define
scan-refs-walk
(define :effects ()
scan-refs-walk
:effects ()
(fn
(node (refs :as list))
(cond
(= (type-of node) "symbol")
(let
((name (symbol-name node)))
(when
(starts-with? name "~")
(when (not (contains? refs name)) (append! refs name))))
(= (type-of node) "list")
(for-each (fn (item) (scan-refs-walk item refs)) node)
(= (type-of node) "dict")
(for-each
(fn (key) (scan-refs-walk (dict-get node key) refs))
(keys node))
:else nil)))
(define
transitive-deps-walk
:effects ()
(fn
((n :as string) (seen :as list) (env :as dict))
(when
(not (contains? seen n))
(append! seen n)
(let
((val (env-get env n)))
(cond
(or (= (type-of val) "component") (= (type-of val) "island"))
(for-each
(fn ((ref :as string)) (transitive-deps-walk ref seen env))
(scan-refs (component-body val)))
(= (type-of val) "macro")
(for-each
(fn ((ref :as string)) (transitive-deps-walk ref seen env))
(scan-refs (macro-body val)))
:else nil)))))
(define
transitive-deps
:effects ()
(fn
((name :as string) (env :as dict))
(let
((seen (list))
(key (if (starts-with? name "~") name (str "~" name))))
(transitive-deps-walk key seen env)
(filter (fn ((x :as string)) (not (= x key))) seen))))
(define
compute-all-deps
:effects (mutation)
(fn
((env :as dict))
(for-each
(fn (fn
((name :as string)) (node (refs :as list))
(let (match
((val (env-get env name))) (type-of node)
(when ("symbol"
(or (= (type-of val) "component") (= (type-of val) "island"))
(component-set-deps! val (transitive-deps name env)))))
(env-components env))))
(define
scan-components-from-source
:effects ()
(fn
((source :as string))
(let
((matches (regex-find-all "\\(~([a-zA-Z_][a-zA-Z0-9_\\-:/]*)" source)))
(map (fn ((m :as string)) (str "~" m)) matches))))
(define
components-needed
:effects ()
(fn
((page-source :as string) (env :as dict))
(let
((direct (scan-components-from-source page-source))
(all-needed (list)))
(for-each
(fn
((name :as string))
(when (not (contains? all-needed name)) (append! all-needed name))
(let
((val (env-get env name)))
(let (let
((deps (if (and (= (type-of val) "component") (not (empty? (component-deps val)))) (component-deps val) (transitive-deps name env)))) ((name (symbol-name node)))
(for-each (when
(fn (starts-with? name "~")
((dep :as string)) (when (not (contains? refs name)) (append! refs name)))))
(when ("list" (for-each (fn (child) (scan-refs-walk child refs)) node))
(not (contains? all-needed dep)) ("dict"
(append! all-needed dep)))
deps))))
direct)
all-needed)))
(define
page-component-bundle
:effects ()
(fn
((page-source :as string) (env :as dict))
(components-needed page-source env)))
(define
page-css-classes
:effects ()
(fn
((page-source :as string) (env :as dict))
(let
((needed (components-needed page-source env)) (classes (list)))
(for-each
(fn
((name :as string))
(let
((val (env-get env name)))
(when
(= (type-of val) "component")
(for-each
(fn
((cls :as string))
(when (not (contains? classes cls)) (append! classes cls)))
(component-css-classes val)))))
needed)
(for-each
(fn
((cls :as string))
(when (not (contains? classes cls)) (append! classes cls)))
(scan-css-classes page-source))
classes)))
(define
scan-io-refs-walk
:effects ()
(fn
(node (io-names :as list) (refs :as list))
(cond
(= (type-of node) "symbol")
(let
((name (symbol-name node)))
(when
(contains? io-names name)
(when (not (contains? refs name)) (append! refs name))))
(= (type-of node) "list")
(for-each (fn (item) (scan-io-refs-walk item io-names refs)) node)
(= (type-of node) "dict")
(for-each
(fn (key) (scan-io-refs-walk (dict-get node key) io-names refs))
(keys node))
:else nil)))
(define
scan-io-refs
:effects ()
(fn
(node (io-names :as list))
(let ((refs (list))) (scan-io-refs-walk node io-names refs) refs)))
(define
transitive-io-refs-walk
:effects ()
(fn
((n :as string)
(seen :as list)
(all-refs :as list)
(env :as dict)
(io-names :as list))
(when
(not (contains? seen n))
(append! seen n)
(let
((val (env-get env n)))
(cond
(= (type-of val) "component")
(do
(for-each (for-each
(fn (fn (key) (scan-refs-walk (dict-get node key) refs))
((ref :as string)) (keys node)))
(when (not (contains? all-refs ref)) (append! all-refs ref))) (_ nil))))
(scan-io-refs (component-body val) io-names)) (define
(for-each transitive-deps-walk
(fn :effects ()
((dep :as string))
(transitive-io-refs-walk dep seen all-refs env io-names))
(scan-refs (component-body val))))
(= (type-of val) "macro")
(do
(for-each
(fn
((ref :as string))
(when (not (contains? all-refs ref)) (append! all-refs ref)))
(scan-io-refs (macro-body val) io-names))
(for-each
(fn
((dep :as string))
(transitive-io-refs-walk dep seen all-refs env io-names))
(scan-refs (macro-body val))))
:else nil)))))
(define
transitive-io-refs
:effects ()
(fn
((name :as string) (env :as dict) (io-names :as list))
(let
((all-refs (list))
(seen (list))
(key (if (starts-with? name "~") name (str "~" name))))
(transitive-io-refs-walk key seen all-refs env io-names)
all-refs)))
(define
compute-all-io-refs
:effects (mutation)
(fn
((env :as dict) (io-names :as list))
(for-each
(fn (fn
((name :as string)) ((n :as string) (seen :as list) (env :as dict))
(let (when
((val (env-get env name))) (not (contains? seen n))
(when (append! seen n)
(= (type-of val) "component")
(component-set-io-refs!
val
(transitive-io-refs name env io-names)))))
(env-components env))))
(define
component-io-refs-cached
:effects ()
(fn
((name :as string) (env :as dict) (io-names :as list))
(let
((key (if (starts-with? name "~") name (str "~" name))))
(let
((val (env-get env key)))
(if
(and
(= (type-of val) "component")
(not (nil? (component-io-refs val)))
(not (empty? (component-io-refs val))))
(component-io-refs val)
(transitive-io-refs name env io-names))))))
(define
component-pure?
:effects ()
(fn
(name (env :as dict) (io-names :as list))
(let
((key (if (starts-with? name "~") name (str "~" name))))
(let
((val (if (env-has? env key) (env-get env key) nil)))
(if
(and
(= (type-of val) "component")
(not (nil? (component-io-refs val)))
(not (empty? (component-io-refs val))))
false
(empty? (transitive-io-refs name env io-names)))))))
(define
render-target
:effects ()
(fn
(name (env :as dict) (io-names :as list))
(let
((key (if (starts-with? name "~") name (str "~" name))))
(let
((val (if (env-has? env key) (env-get env key) nil)))
(if
(not (= (type-of val) "component"))
"server"
(let (let
((affinity (component-affinity val))) ((val (env-get env n)))
(cond (cond
(= affinity "server") (or (= (type-of val) "component") (= (type-of val) "island"))
"server" (for-each
(= affinity "client") (fn ((ref :as string)) (transitive-deps-walk ref seen env))
"client" (scan-refs (component-body val)))
(not (component-pure? name env io-names)) (= (type-of val) "macro")
"server" (for-each
:else "client"))))))) (fn ((ref :as string)) (transitive-deps-walk ref seen env))
(scan-refs (macro-body val)))
(define :else nil)))))
page-render-plan (define
:effects () transitive-deps
(fn :effects ()
((page-source :as string) (env :as dict) (io-names :as list)) (fn
(let ((name :as string) (env :as dict))
((needed (components-needed page-source env)) (let
(comp-targets (dict)) ((seen (list))
(server-list (list)) (key (if (starts-with? name "~") name (str "~" name))))
(client-list (list)) (transitive-deps-walk key seen env)
(io-deps (list))) (filter (fn ((x :as string)) (not (= x key))) seen))))
(for-each (define
(fn compute-all-deps
((name :as string)) :effects (mutation)
(fn
((env :as dict))
(for-each
(fn
((name :as string))
(let
((val (env-get env name)))
(when
(or
(= (type-of val) "component")
(= (type-of val) "island"))
(component-set-deps! val (transitive-deps name env)))))
(env-components env))))
(define
scan-components-from-source
:effects ()
(fn
((source :as string))
(let
((matches (regex-find-all "\\(~([a-zA-Z_][a-zA-Z0-9_\\-:/]*)" source)))
(map (fn ((m :as string)) (str "~" m)) matches))))
(define
components-needed
:effects ()
(fn
((page-source :as string) (env :as dict))
(let
((direct (scan-components-from-source page-source))
(all-needed (list)))
(for-each
(fn
((name :as string))
(when
(not (contains? all-needed name))
(append! all-needed name))
(let
((val (env-get env name)))
(let
((deps (if (and (= (type-of val) "component") (not (empty? (component-deps val)))) (component-deps val) (transitive-deps name env))))
(for-each
(fn
((dep :as string))
(when
(not (contains? all-needed dep))
(append! all-needed dep)))
deps))))
direct)
all-needed)))
(define
page-component-bundle
:effects ()
(fn
((page-source :as string) (env :as dict))
(components-needed page-source env)))
(define
page-css-classes
:effects ()
(fn
((page-source :as string) (env :as dict))
(let
((needed (components-needed page-source env)) (classes (list)))
(for-each
(fn
((name :as string))
(let
((val (env-get env name)))
(when
(= (type-of val) "component")
(for-each
(fn
((cls :as string))
(when
(not (contains? classes cls))
(append! classes cls)))
(component-css-classes val)))))
needed)
(for-each
(fn
((cls :as string))
(when (not (contains? classes cls)) (append! classes cls)))
(scan-css-classes page-source))
classes)))
(define
scan-io-refs-walk
:effects ()
(fn
(node (io-names :as list) (refs :as list))
(match
(type-of node)
("symbol"
(let
((name (symbol-name node)))
(when
(contains? io-names name)
(when (not (contains? refs name)) (append! refs name)))))
("list"
(for-each
(fn (item) (scan-io-refs-walk item io-names refs))
node))
("dict"
(for-each
(fn
(key)
(scan-io-refs-walk (dict-get node key) io-names refs))
(keys node)))
(_ nil))))
(define
scan-io-refs
:effects ()
(fn
(node (io-names :as list))
(let ((refs (list))) (scan-io-refs-walk node io-names refs) refs)))
(define
transitive-io-refs-walk
:effects ()
(fn
((n :as string)
(seen :as list)
(all-refs :as list)
(env :as dict)
(io-names :as list))
(when
(not (contains? seen n))
(append! seen n)
(let (let
((target (render-target name env io-names))) ((val (env-get env n)))
(dict-set! comp-targets name target) (cond
(if (= (type-of val) "component")
(= target "server")
(do (do
(append! server-list name)
(for-each (for-each
(fn (fn
((io-ref :as string)) ((ref :as string))
(when (when
(not (contains? io-deps io-ref)) (not (contains? all-refs ref))
(append! io-deps io-ref))) (append! all-refs ref)))
(component-io-refs-cached name env io-names))) (scan-io-refs (component-body val) io-names))
(append! client-list name)))) (for-each
needed) (fn
{:io-deps io-deps :server server-list :components comp-targets :client client-list}))) ((dep :as string))
(transitive-io-refs-walk dep seen all-refs env io-names))
(define (scan-refs (component-body val))))
env-components (= (type-of val) "macro")
:effects () (do
(fn (for-each
((env :as dict)) (fn
(filter ((ref :as string))
(when
(not (contains? all-refs ref))
(append! all-refs ref)))
(scan-io-refs (macro-body val) io-names))
(for-each
(fn
((dep :as string))
(transitive-io-refs-walk dep seen all-refs env io-names))
(scan-refs (macro-body val))))
:else nil)))))
(define
transitive-io-refs
:effects ()
(fn (fn
((k :as string)) ((name :as string) (env :as dict) (io-names :as list))
(let ((v (env-get env k))) (or (component? v) (macro? v)))) (let
(keys env)))) ((all-refs (list))
(seen (list))
(key (if (starts-with? name "~") name (str "~" name))))
)) ;; end define-library (transitive-io-refs-walk key seen all-refs env io-names)
all-refs)))
(define
compute-all-io-refs
:effects (mutation)
(fn
((env :as dict) (io-names :as list))
(for-each
(fn
((name :as string))
(let
((val (env-get env name)))
(when
(= (type-of val) "component")
(component-set-io-refs!
val
(transitive-io-refs name env io-names)))))
(env-components env))))
(define
component-io-refs-cached
:effects ()
(fn
((name :as string) (env :as dict) (io-names :as list))
(let
((key (if (starts-with? name "~") name (str "~" name))))
(let
((val (env-get env key)))
(if
(and
(= (type-of val) "component")
(not (nil? (component-io-refs val)))
(not (empty? (component-io-refs val))))
(component-io-refs val)
(transitive-io-refs name env io-names))))))
(define
component-pure?
:effects ()
(fn
(name (env :as dict) (io-names :as list))
(let
((key (if (starts-with? name "~") name (str "~" name))))
(let
((val (if (env-has? env key) (env-get env key) nil)))
(if
(and
(= (type-of val) "component")
(not (nil? (component-io-refs val)))
(not (empty? (component-io-refs val))))
false
(empty? (transitive-io-refs name env io-names)))))))
(define
render-target
:effects ()
(fn
(name (env :as dict) (io-names :as list))
(let
((key (if (starts-with? name "~") name (str "~" name))))
(let
((val (if (env-has? env key) (env-get env key) nil)))
(if
(not (= (type-of val) "component"))
"server"
(let
((affinity (component-affinity val)))
(cond
(= affinity "server")
"server"
(= affinity "client")
"client"
(not (component-pure? name env io-names))
"server"
:else "client")))))))
(define
page-render-plan
:effects ()
(fn
((page-source :as string) (env :as dict) (io-names :as list))
(let
((needed (components-needed page-source env))
(comp-targets (dict))
(server-list (list))
(client-list (list))
(io-deps (list)))
(for-each
(fn
((name :as string))
(let
((target (render-target name env io-names)))
(dict-set! comp-targets name target)
(if
(= target "server")
(do
(append! server-list name)
(for-each
(fn
((io-ref :as string))
(when
(not (contains? io-deps io-ref))
(append! io-deps io-ref)))
(component-io-refs-cached name env io-names)))
(append! client-list name))))
needed)
{:io-deps io-deps :server server-list :components comp-targets :client client-list})))
(define
env-components
:effects ()
(fn
((env :as dict))
(filter
(fn
((k :as string))
(let ((v (env-get env k))) (or (component? v) (macro? v))))
(keys env)))))) ;; end define-library
;; Re-export to global namespace for backward compatibility ;; Re-export to global namespace for backward compatibility
(import (web deps)) (import (web deps))

File diff suppressed because it is too large Load Diff