Spec server definition forms (defhandler/defquery/defaction/defpage) in forms.sx
Previously defhandler routed to sf-define which tried to evaluate (&key ...) params as expressions. Now each form has its own spec with parse-key-params and platform constructors. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -106,6 +106,10 @@ class PyEmitter:
|
||||
"make-component": "make_component",
|
||||
"make-macro": "make_macro",
|
||||
"make-thunk": "make_thunk",
|
||||
"make-handler-def": "make_handler_def",
|
||||
"make-query-def": "make_query_def",
|
||||
"make-action-def": "make_action_def",
|
||||
"make-page-def": "make_page_def",
|
||||
"make-symbol": "make_symbol",
|
||||
"make-keyword": "make_keyword",
|
||||
"lambda-params": "lambda_params",
|
||||
@@ -821,6 +825,7 @@ def compile_ref_to_py(adapters: list[str] | None = None) -> str:
|
||||
# Core files always included, then selected adapters
|
||||
sx_files = [
|
||||
("eval.sx", "eval"),
|
||||
("forms.sx", "forms (server definition forms)"),
|
||||
("render.sx", "render (core)"),
|
||||
]
|
||||
for name in ("html", "sx"):
|
||||
@@ -883,6 +888,7 @@ from typing import Any
|
||||
|
||||
from shared.sx.types import (
|
||||
NIL, Symbol, Keyword, Lambda, Component, Macro, StyleValue,
|
||||
HandlerDef, QueryDef, ActionDef, PageDef,
|
||||
)
|
||||
from shared.sx.parser import SxExpr
|
||||
'''
|
||||
@@ -1029,6 +1035,39 @@ def make_macro(params, rest_param, body, env, name=None):
|
||||
closure=dict(env), name=name)
|
||||
|
||||
|
||||
def make_handler_def(name, params, body, env):
|
||||
return HandlerDef(name=name, params=list(params), body=body, closure=dict(env))
|
||||
|
||||
|
||||
def make_query_def(name, params, doc, body, env):
|
||||
return QueryDef(name=name, params=list(params), doc=doc, body=body, closure=dict(env))
|
||||
|
||||
|
||||
def make_action_def(name, params, doc, body, env):
|
||||
return ActionDef(name=name, params=list(params), doc=doc, body=body, closure=dict(env))
|
||||
|
||||
|
||||
def make_page_def(name, slots, env):
|
||||
path = slots.get("path", "")
|
||||
auth_val = slots.get("auth", "public")
|
||||
if isinstance(auth_val, Keyword):
|
||||
auth = auth_val.name
|
||||
elif isinstance(auth_val, list):
|
||||
auth = [item.name if isinstance(item, Keyword) else str(item) for item in auth_val]
|
||||
else:
|
||||
auth = str(auth_val) if auth_val else "public"
|
||||
layout = slots.get("layout")
|
||||
if isinstance(layout, Keyword):
|
||||
layout = layout.name
|
||||
cache = None
|
||||
return PageDef(
|
||||
name=name, path=path, auth=auth, layout=layout, cache=cache,
|
||||
data_expr=slots.get("data"), content_expr=slots.get("content"),
|
||||
filter_expr=slots.get("filter"), aside_expr=slots.get("aside"),
|
||||
menu_expr=slots.get("menu"), closure=dict(env),
|
||||
)
|
||||
|
||||
|
||||
def make_thunk(expr, env):
|
||||
return _Thunk(expr, env)
|
||||
|
||||
|
||||
@@ -143,7 +143,10 @@
|
||||
(= name "defmacro") (sf-defmacro args env)
|
||||
(= name "defstyle") (sf-defstyle args env)
|
||||
(= name "defkeyframes") (sf-defkeyframes args env)
|
||||
(= name "defhandler") (sf-define args env)
|
||||
(= name "defhandler") (sf-defhandler args env)
|
||||
(= name "defpage") (sf-defpage args env)
|
||||
(= name "defquery") (sf-defquery args env)
|
||||
(= name "defaction") (sf-defaction args env)
|
||||
(= name "begin") (sf-begin args env)
|
||||
(= name "do") (sf-begin args env)
|
||||
(= name "quote") (sf-quote args env)
|
||||
|
||||
118
shared/sx/ref/forms.sx
Normal file
118
shared/sx/ref/forms.sx
Normal file
@@ -0,0 +1,118 @@
|
||||
;; ==========================================================================
|
||||
;; forms.sx — Server-side definition forms
|
||||
;;
|
||||
;; Platform-specific special forms for declaring handlers, pages, queries,
|
||||
;; and actions. These parse &key parameter lists and create typed definition
|
||||
;; objects that the server runtime uses for routing and execution.
|
||||
;;
|
||||
;; When SX moves to isomorphic execution, these forms will have different
|
||||
;; platform bindings on client vs server. The spec stays the same — only
|
||||
;; the constructors (make-handler-def, make-query-def, etc.) change.
|
||||
;;
|
||||
;; Platform functions required:
|
||||
;; make-handler-def(name, params, body, env) → HandlerDef
|
||||
;; make-query-def(name, params, doc, body, env) → QueryDef
|
||||
;; make-action-def(name, params, doc, body, env) → ActionDef
|
||||
;; make-page-def(name, slots, env) → PageDef
|
||||
;; ==========================================================================
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Shared: parse (&key param1 param2 ...) → list of param name strings
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(define parse-key-params
|
||||
(fn (params-expr)
|
||||
(let ((params (list))
|
||||
(in-key false))
|
||||
(for-each
|
||||
(fn (p)
|
||||
(when (= (type-of p) "symbol")
|
||||
(let ((name (symbol-name p)))
|
||||
(cond
|
||||
(= name "&key") (set! in-key true)
|
||||
in-key (append! params name)
|
||||
:else (append! params name)))))
|
||||
params-expr)
|
||||
params)))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; defhandler — (defhandler name (&key param...) body)
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(define sf-defhandler
|
||||
(fn (args env)
|
||||
(let ((name-sym (first args))
|
||||
(params-raw (nth args 1))
|
||||
(body (nth args 2))
|
||||
(name (symbol-name name-sym))
|
||||
(params (parse-key-params params-raw)))
|
||||
(let ((hdef (make-handler-def name params body env)))
|
||||
(env-set! env (str "handler:" name) hdef)
|
||||
hdef))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; defquery — (defquery name (&key param...) "docstring" body)
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(define sf-defquery
|
||||
(fn (args env)
|
||||
(let ((name-sym (first args))
|
||||
(params-raw (nth args 1))
|
||||
(name (symbol-name name-sym))
|
||||
(params (parse-key-params params-raw))
|
||||
;; Optional docstring before body
|
||||
(has-doc (and (>= (len args) 4) (= (type-of (nth args 2)) "string")))
|
||||
(doc (if has-doc (nth args 2) ""))
|
||||
(body (if has-doc (nth args 3) (nth args 2))))
|
||||
(let ((qdef (make-query-def name params doc body env)))
|
||||
(env-set! env (str "query:" name) qdef)
|
||||
qdef))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; defaction — (defaction name (&key param...) "docstring" body)
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(define sf-defaction
|
||||
(fn (args env)
|
||||
(let ((name-sym (first args))
|
||||
(params-raw (nth args 1))
|
||||
(name (symbol-name name-sym))
|
||||
(params (parse-key-params params-raw))
|
||||
(has-doc (and (>= (len args) 4) (= (type-of (nth args 2)) "string")))
|
||||
(doc (if has-doc (nth args 2) ""))
|
||||
(body (if has-doc (nth args 3) (nth args 2))))
|
||||
(let ((adef (make-action-def name params doc body env)))
|
||||
(env-set! env (str "action:" name) adef)
|
||||
adef))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; defpage — (defpage name :path "/..." :auth :public :content expr ...)
|
||||
;;
|
||||
;; Keyword-slot form: all values after the name are :key value pairs.
|
||||
;; Values are stored as unevaluated AST — resolved at request time.
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(define sf-defpage
|
||||
(fn (args env)
|
||||
(let ((name-sym (first args))
|
||||
(name (symbol-name name-sym))
|
||||
(slots {}))
|
||||
;; Parse keyword slots from remaining args
|
||||
(let ((i 1)
|
||||
(max-i (len args)))
|
||||
(for-each
|
||||
(fn (idx)
|
||||
(when (and (< idx max-i)
|
||||
(= (type-of (nth args idx)) "keyword"))
|
||||
(when (< (+ idx 1) max-i)
|
||||
(dict-set! slots (keyword-name (nth args idx))
|
||||
(nth args (+ idx 1))))))
|
||||
(range 1 max-i 2)))
|
||||
(let ((pdef (make-page-def name slots env)))
|
||||
(env-set! env (str "page:" name) pdef)
|
||||
pdef))))
|
||||
@@ -18,6 +18,7 @@ from typing import Any
|
||||
|
||||
from shared.sx.types import (
|
||||
NIL, Symbol, Keyword, Lambda, Component, Macro, StyleValue,
|
||||
HandlerDef, QueryDef, ActionDef, PageDef,
|
||||
)
|
||||
from shared.sx.parser import SxExpr
|
||||
|
||||
@@ -163,6 +164,39 @@ def make_macro(params, rest_param, body, env, name=None):
|
||||
closure=dict(env), name=name)
|
||||
|
||||
|
||||
def make_handler_def(name, params, body, env):
|
||||
return HandlerDef(name=name, params=list(params), body=body, closure=dict(env))
|
||||
|
||||
|
||||
def make_query_def(name, params, doc, body, env):
|
||||
return QueryDef(name=name, params=list(params), doc=doc, body=body, closure=dict(env))
|
||||
|
||||
|
||||
def make_action_def(name, params, doc, body, env):
|
||||
return ActionDef(name=name, params=list(params), doc=doc, body=body, closure=dict(env))
|
||||
|
||||
|
||||
def make_page_def(name, slots, env):
|
||||
path = slots.get("path", "")
|
||||
auth_val = slots.get("auth", "public")
|
||||
if isinstance(auth_val, Keyword):
|
||||
auth = auth_val.name
|
||||
elif isinstance(auth_val, list):
|
||||
auth = [item.name if isinstance(item, Keyword) else str(item) for item in auth_val]
|
||||
else:
|
||||
auth = str(auth_val) if auth_val else "public"
|
||||
layout = slots.get("layout")
|
||||
if isinstance(layout, Keyword):
|
||||
layout = layout.name
|
||||
cache = None
|
||||
return PageDef(
|
||||
name=name, path=path, auth=auth, layout=layout, cache=cache,
|
||||
data_expr=slots.get("data"), content_expr=slots.get("content"),
|
||||
filter_expr=slots.get("filter"), aside_expr=slots.get("aside"),
|
||||
menu_expr=slots.get("menu"), closure=dict(env),
|
||||
)
|
||||
|
||||
|
||||
def make_thunk(expr, env):
|
||||
return _Thunk(expr, env)
|
||||
|
||||
@@ -820,7 +854,7 @@ trampoline = lambda val: (lambda result: (trampoline(eval_expr(thunk_expr(result
|
||||
eval_expr = lambda expr, env: _sx_case(type_of(expr), [('number', lambda: expr), ('string', lambda: expr), ('boolean', lambda: expr), ('nil', lambda: NIL), ('symbol', lambda: (lambda name: (env_get(env, name) if sx_truthy(env_has(env, name)) else (get_primitive(name) if sx_truthy(is_primitive(name)) else (True if sx_truthy((name == 'true')) else (False if sx_truthy((name == 'false')) else (NIL if sx_truthy((name == 'nil')) else error(sx_str('Undefined symbol: ', name))))))))(symbol_name(expr))), ('keyword', lambda: keyword_name(expr)), ('dict', lambda: map_dict(lambda k, v: trampoline(eval_expr(v, env)), expr)), ('list', lambda: ([] if sx_truthy(empty_p(expr)) else eval_list(expr, env))), (None, lambda: expr)])
|
||||
|
||||
# eval-list
|
||||
eval_list = lambda expr, env: (lambda head: (lambda args: (map(lambda x: trampoline(eval_expr(x, env)), expr) if sx_truthy((not sx_truthy(((type_of(head) == 'symbol') if sx_truthy((type_of(head) == 'symbol')) else ((type_of(head) == 'lambda') if sx_truthy((type_of(head) == 'lambda')) else (type_of(head) == 'list')))))) else ((lambda name: (sf_if(args, env) if sx_truthy((name == 'if')) else (sf_when(args, env) if sx_truthy((name == 'when')) else (sf_cond(args, env) if sx_truthy((name == 'cond')) else (sf_case(args, env) if sx_truthy((name == 'case')) else (sf_and(args, env) if sx_truthy((name == 'and')) else (sf_or(args, env) if sx_truthy((name == 'or')) else (sf_let(args, env) if sx_truthy((name == 'let')) else (sf_let(args, env) if sx_truthy((name == 'let*')) else (sf_lambda(args, env) if sx_truthy((name == 'lambda')) else (sf_lambda(args, env) if sx_truthy((name == 'fn')) else (sf_define(args, env) if sx_truthy((name == 'define')) else (sf_defcomp(args, env) if sx_truthy((name == 'defcomp')) else (sf_defmacro(args, env) if sx_truthy((name == 'defmacro')) else (sf_defstyle(args, env) if sx_truthy((name == 'defstyle')) else (sf_defkeyframes(args, env) if sx_truthy((name == 'defkeyframes')) else (sf_define(args, env) if sx_truthy((name == 'defhandler')) else (sf_begin(args, env) if sx_truthy((name == 'begin')) else (sf_begin(args, env) if sx_truthy((name == 'do')) else (sf_quote(args, env) if sx_truthy((name == 'quote')) else (sf_quasiquote(args, env) if sx_truthy((name == 'quasiquote')) else (sf_thread_first(args, env) if sx_truthy((name == '->')) else (sf_set_bang(args, env) if sx_truthy((name == 'set!')) else (ho_map(args, env) if sx_truthy((name == 'map')) else (ho_map_indexed(args, env) if sx_truthy((name == 'map-indexed')) else (ho_filter(args, env) if sx_truthy((name == 'filter')) else (ho_reduce(args, env) if sx_truthy((name == 'reduce')) else (ho_some(args, env) if sx_truthy((name == 'some')) else (ho_every(args, env) if sx_truthy((name == 'every?')) else (ho_for_each(args, env) if sx_truthy((name == 'for-each')) else ((lambda mac: make_thunk(expand_macro(mac, args, env), env))(env_get(env, name)) if sx_truthy((env_has(env, name) if not sx_truthy(env_has(env, name)) else is_macro(env_get(env, name)))) else (render_expr(expr, env) if sx_truthy(is_render_expr(expr)) else eval_call(head, args, env)))))))))))))))))))))))))))))))))(symbol_name(head)) if sx_truthy((type_of(head) == 'symbol')) else eval_call(head, args, env))))(rest(expr)))(first(expr))
|
||||
eval_list = lambda expr, env: (lambda head: (lambda args: (map(lambda x: trampoline(eval_expr(x, env)), expr) if sx_truthy((not sx_truthy(((type_of(head) == 'symbol') if sx_truthy((type_of(head) == 'symbol')) else ((type_of(head) == 'lambda') if sx_truthy((type_of(head) == 'lambda')) else (type_of(head) == 'list')))))) else ((lambda name: (sf_if(args, env) if sx_truthy((name == 'if')) else (sf_when(args, env) if sx_truthy((name == 'when')) else (sf_cond(args, env) if sx_truthy((name == 'cond')) else (sf_case(args, env) if sx_truthy((name == 'case')) else (sf_and(args, env) if sx_truthy((name == 'and')) else (sf_or(args, env) if sx_truthy((name == 'or')) else (sf_let(args, env) if sx_truthy((name == 'let')) else (sf_let(args, env) if sx_truthy((name == 'let*')) else (sf_lambda(args, env) if sx_truthy((name == 'lambda')) else (sf_lambda(args, env) if sx_truthy((name == 'fn')) else (sf_define(args, env) if sx_truthy((name == 'define')) else (sf_defcomp(args, env) if sx_truthy((name == 'defcomp')) else (sf_defmacro(args, env) if sx_truthy((name == 'defmacro')) else (sf_defstyle(args, env) if sx_truthy((name == 'defstyle')) else (sf_defkeyframes(args, env) if sx_truthy((name == 'defkeyframes')) else (sf_defhandler(args, env) if sx_truthy((name == 'defhandler')) else (sf_defpage(args, env) if sx_truthy((name == 'defpage')) else (sf_defquery(args, env) if sx_truthy((name == 'defquery')) else (sf_defaction(args, env) if sx_truthy((name == 'defaction')) else (sf_begin(args, env) if sx_truthy((name == 'begin')) else (sf_begin(args, env) if sx_truthy((name == 'do')) else (sf_quote(args, env) if sx_truthy((name == 'quote')) else (sf_quasiquote(args, env) if sx_truthy((name == 'quasiquote')) else (sf_thread_first(args, env) if sx_truthy((name == '->')) else (sf_set_bang(args, env) if sx_truthy((name == 'set!')) else (ho_map(args, env) if sx_truthy((name == 'map')) else (ho_map_indexed(args, env) if sx_truthy((name == 'map-indexed')) else (ho_filter(args, env) if sx_truthy((name == 'filter')) else (ho_reduce(args, env) if sx_truthy((name == 'reduce')) else (ho_some(args, env) if sx_truthy((name == 'some')) else (ho_every(args, env) if sx_truthy((name == 'every?')) else (ho_for_each(args, env) if sx_truthy((name == 'for-each')) else ((lambda mac: make_thunk(expand_macro(mac, args, env), env))(env_get(env, name)) if sx_truthy((env_has(env, name) if not sx_truthy(env_has(env, name)) else is_macro(env_get(env, name)))) else (render_expr(expr, env) if sx_truthy(is_render_expr(expr)) else eval_call(head, args, env))))))))))))))))))))))))))))))))))))(symbol_name(head)) if sx_truthy((type_of(head) == 'symbol')) else eval_call(head, args, env))))(rest(expr)))(first(expr))
|
||||
|
||||
# eval-call
|
||||
eval_call = lambda head, args, env: (lambda f: (lambda evaluated_args: (apply(f, evaluated_args) if sx_truthy((is_callable(f) if not sx_truthy(is_callable(f)) else ((not sx_truthy(is_lambda(f))) if not sx_truthy((not sx_truthy(is_lambda(f)))) else (not sx_truthy(is_component(f)))))) else (call_lambda(f, evaluated_args, env) if sx_truthy(is_lambda(f)) else (call_component(f, args, env) if sx_truthy(is_component(f)) else error(sx_str('Not callable: ', inspect(f)))))))(map(lambda a: trampoline(eval_expr(a, env)), args)))(trampoline(eval_expr(head, env)))
|
||||
@@ -956,6 +990,37 @@ ho_every = lambda args, env: (lambda f: (lambda coll: every_p(lambda item: tramp
|
||||
ho_for_each = lambda args, env: (lambda f: (lambda coll: for_each(lambda item: trampoline(call_lambda(f, [item], env)), coll))(trampoline(eval_expr(nth(args, 1), env))))(trampoline(eval_expr(first(args), env)))
|
||||
|
||||
|
||||
# === Transpiled from forms (server definition forms) ===
|
||||
|
||||
# parse-key-params
|
||||
def parse_key_params(params_expr):
|
||||
_cells = {}
|
||||
params = []
|
||||
_cells['in_key'] = False
|
||||
for p in params_expr:
|
||||
if sx_truthy((type_of(p) == 'symbol')):
|
||||
name = symbol_name(p)
|
||||
if sx_truthy((name == '&key')):
|
||||
_cells['in_key'] = True
|
||||
elif sx_truthy(_cells['in_key']):
|
||||
params.append(name)
|
||||
else:
|
||||
params.append(name)
|
||||
return params
|
||||
|
||||
# sf-defhandler
|
||||
sf_defhandler = lambda args, env: (lambda name_sym: (lambda params_raw: (lambda body: (lambda name: (lambda params: (lambda hdef: _sx_begin(_sx_dict_set(env, sx_str('handler:', name), hdef), hdef))(make_handler_def(name, params, body, env)))(parse_key_params(params_raw)))(symbol_name(name_sym)))(nth(args, 2)))(nth(args, 1)))(first(args))
|
||||
|
||||
# sf-defquery
|
||||
sf_defquery = lambda args, env: (lambda name_sym: (lambda params_raw: (lambda name: (lambda params: (lambda has_doc: (lambda doc: (lambda body: (lambda qdef: _sx_begin(_sx_dict_set(env, sx_str('query:', name), qdef), qdef))(make_query_def(name, params, doc, body, env)))((nth(args, 3) if sx_truthy(has_doc) else nth(args, 2))))((nth(args, 2) if sx_truthy(has_doc) else '')))(((len(args) >= 4) if not sx_truthy((len(args) >= 4)) else (type_of(nth(args, 2)) == 'string'))))(parse_key_params(params_raw)))(symbol_name(name_sym)))(nth(args, 1)))(first(args))
|
||||
|
||||
# sf-defaction
|
||||
sf_defaction = lambda args, env: (lambda name_sym: (lambda params_raw: (lambda name: (lambda params: (lambda has_doc: (lambda doc: (lambda body: (lambda adef: _sx_begin(_sx_dict_set(env, sx_str('action:', name), adef), adef))(make_action_def(name, params, doc, body, env)))((nth(args, 3) if sx_truthy(has_doc) else nth(args, 2))))((nth(args, 2) if sx_truthy(has_doc) else '')))(((len(args) >= 4) if not sx_truthy((len(args) >= 4)) else (type_of(nth(args, 2)) == 'string'))))(parse_key_params(params_raw)))(symbol_name(name_sym)))(nth(args, 1)))(first(args))
|
||||
|
||||
# sf-defpage
|
||||
sf_defpage = lambda args, env: (lambda name_sym: (lambda name: (lambda slots: _sx_begin((lambda i: (lambda max_i: for_each(lambda idx: ((_sx_dict_set(slots, keyword_name(nth(args, idx)), nth(args, (idx + 1))) if sx_truthy(((idx + 1) < max_i)) else NIL) if sx_truthy(((idx < max_i) if not sx_truthy((idx < max_i)) else (type_of(nth(args, idx)) == 'keyword'))) else NIL), range(1, max_i, 2)))(len(args)))(1), (lambda pdef: _sx_begin(_sx_dict_set(env, sx_str('page:', name), pdef), pdef))(make_page_def(name, slots, env))))({}))(symbol_name(name_sym)))(first(args))
|
||||
|
||||
|
||||
# === Transpiled from render (core) ===
|
||||
|
||||
# HTML_TAGS
|
||||
|
||||
@@ -328,6 +328,62 @@ class TestAser:
|
||||
# Macros
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestDefcomp:
|
||||
def test_defcomp_basic(self):
|
||||
"""defcomp should parse &key params without trying to eval &key as a symbol."""
|
||||
env = {}
|
||||
ev('(defcomp ~card (&key title) (div title))', env)
|
||||
assert isinstance(env.get("~card"), Component)
|
||||
|
||||
def test_defcomp_render(self):
|
||||
env = {}
|
||||
ev('(defcomp ~card (&key title) (div title))', env)
|
||||
result = render('(~card :title "hello")', env)
|
||||
assert result == '<div>hello</div>'
|
||||
|
||||
def test_defcomp_with_children(self):
|
||||
env = {}
|
||||
ev('(defcomp ~wrap (&key title &rest children) (div title children))', env)
|
||||
comp = env["~wrap"]
|
||||
assert comp.has_children is True
|
||||
assert "title" in comp.params
|
||||
|
||||
def test_defcomp_multiple_params(self):
|
||||
env = {}
|
||||
ev('(defcomp ~box (&key a b c) (div a b c))', env)
|
||||
result = render('(~box :a "1" :b "2" :c "3")', env)
|
||||
assert result == '<div>123</div>'
|
||||
|
||||
|
||||
class TestDefhandler:
|
||||
def test_defhandler_basic(self):
|
||||
"""defhandler should parse &key params and create a HandlerDef."""
|
||||
from shared.sx.types import HandlerDef
|
||||
env = {}
|
||||
ev('(defhandler link-card (&key slug keys) (div slug))', env)
|
||||
hdef = env.get("handler:link-card")
|
||||
assert isinstance(hdef, HandlerDef)
|
||||
assert hdef.name == "link-card"
|
||||
assert hdef.params == ["slug", "keys"]
|
||||
|
||||
def test_defquery_basic(self):
|
||||
from shared.sx.types import QueryDef
|
||||
env = {}
|
||||
ev('(defquery get-post (&key slug) "Fetch a post" (list slug))', env)
|
||||
qdef = env.get("query:get-post")
|
||||
assert isinstance(qdef, QueryDef)
|
||||
assert qdef.params == ["slug"]
|
||||
assert qdef.doc == "Fetch a post"
|
||||
|
||||
def test_defaction_basic(self):
|
||||
from shared.sx.types import ActionDef
|
||||
env = {}
|
||||
ev('(defaction save-post (&key title body) (list title body))', env)
|
||||
adef = env.get("action:save-post")
|
||||
assert isinstance(adef, ActionDef)
|
||||
assert adef.params == ["title", "body"]
|
||||
|
||||
|
||||
class TestMacros:
|
||||
def test_defmacro_and_expand(self):
|
||||
env = {}
|
||||
|
||||
Reference in New Issue
Block a user