Compare commits

..

10 commits

Author SHA1 Message Date
a73af8d936 Update description
Some checks failed
test / tests (ccl-bin) (push) Has been cancelled
test / tests (sbcl-bin) (push) Has been cancelled
2024-12-20 23:41:46 +09:00
fa7fc1605e Modify the wording 2024-12-17 16:14:46 +09:00
f60259ec4a Update system version to v0.4.0 2024-12-13 01:37:10 +09:00
4490c74197 Update README 2024-12-13 01:35:31 +09:00
dfc074ec71 Fix defcomp to detect components in HSX 2024-12-13 01:33:01 +09:00
3193054e04 Update README 2024-12-12 14:00:42 +09:00
33dd8e8205 Update system version to v0.3.0 2024-12-12 13:10:14 +09:00
6abb647246 Update README 2024-12-12 13:09:51 +09:00
a170c58530 Improve find-builtin-symbols 2024-12-12 13:08:52 +09:00
011ccd6b2a Update system version 2024-10-19 00:13:33 +09:00
4 changed files with 114 additions and 82 deletions

View file

@ -1,16 +1,20 @@
# HSX # HSX
HSX (Hypertext S-expression) is a simple yet powerful HTML5 generation library for Common Lisp. It was forked from [flute](https://github.com/ailisp/flute/). HSX (Hypertext S-expression) is a simple and powerful HTML (Living Standard) generation library for Common Lisp.
## Introduction This project is a fork of [ailisp/flute](https://github.com/ailisp/flute/).
HSX allows you to generate HTML using S-expressions, providing a more Lisp-friendly way to create web content. By using the `hsx` macro, you can define HTML elements and their attributes in a concise and readable manner. ## Warning
This software is still in ALPHA quality. The APIs are likely to change.
Please check the [release notes](https://github.com/skyizwhite/hsx/releases) for updates.
## Getting Started ## Getting Started
### Basic Usage ### Basic Usage
Use the `hsx` macro to create HTML elements. Attributes are specified using a property list following the element name, and child elements are nested directly within. Use the `hsx` macro to create HTML elements. Attributes are specified using a property list after the element name, and child elements are nested directly inside.
```lisp ```lisp
(hsx (hsx
@ -28,21 +32,30 @@ This generates:
</div> </div>
``` ```
## Examples To convert an HSX object into an HTML string, use the `render-to-string` function:
### Dynamic Content ```lisp
(render-to-string
(hsx ...))
```
HSX allows embedding Common Lisp code directly within your HTML structure, making it easy to generate dynamic content. ### Embedding Content
HSX allows you to embed Common Lisp forms directly within your HTML structure.
When working with HSX elements inside embedded Lisp forms, you should use the `hsx` macro again.
```lisp ```lisp
(hsx (hsx
(div (div
(p :id (format nil "id-~a" (random 100))) (p :id (format nil "id-~a" (random 100)))
(ul (ul
(loop :for i :from 1 :to 5 :collect (li (format nil "Item ~a" i)))) (loop
:for i :from 1 :to 5 :collect
(hsx (li (format nil "Item ~a" i)))))
(if (> (random 10) 5) (if (> (random 10) 5)
(p "Condition met!") (hsx (p "Condition met!"))
(p "Condition not met!")))) (hsx (p "Condition not met!")))))
``` ```
This might generate: This might generate:
@ -81,22 +94,22 @@ This generates:
<p>Second paragraph.</p> <p>Second paragraph.</p>
``` ```
## Creating Components ### Creating Components
You can define reusable components with the `defcomp` macro. Components are functions that can take keyword arguments and properties. You can define reusable components using the `defcomp` macro. Component names must begin with a tilde (`~`). Properties should be declared using `&key`, `&rest`, or both. The body must return an HSX element.
```lisp ```lisp
(defcomp card (&key title children) (defcomp ~card (&key title children)
(hsx (hsx
(div :class "card" (div :class "card"
(h1 title) (h1 title)
children))) children)))
``` ```
Or using a property list: Alternatively, you can use a property list:
```lisp ```lisp
(defcomp card (&rest props) (defcomp ~card (&rest props)
(hsx (hsx
(div :class "card" (div :class "card"
(h1 (getf props :title)) (h1 (getf props :title))
@ -107,7 +120,7 @@ Usage example:
```lisp ```lisp
(hsx (hsx
(card :title "Card Title" (~card :title "Card Title"
(p "This is a card component."))) (p "This is a card component.")))
``` ```
@ -120,18 +133,6 @@ Generates:
</div> </div>
``` ```
## Rendering HTML
To render HSX to an HTML string, use the `render-to-string` function.
```lisp
(render-to-string
(hsx
(div :class "content"
(h1 "Rendered to String")
(p "This HTML is generated as a string."))))
```
## License ## License
This project is licensed under the MIT License. This project is licensed under the MIT License.

View file

@ -1,6 +1,6 @@
(defsystem "hsx" (defsystem "hsx"
:version "0.1.0" :version "0.4.0"
:description "Hypertext S-expression" :description "Simple and powerful HTML generation library."
:author "skyizwhite, Bo Yao" :author "skyizwhite, Bo Yao"
:maintainer "skyizwhite <paku@skyizwhite.dev>" :maintainer "skyizwhite <paku@skyizwhite.dev>"
:license "MIT" :license "MIT"

View file

@ -13,21 +13,37 @@
;;;; hsx macro ;;;; hsx macro
(defmacro hsx (form) (defmacro hsx (form)
"Detect built-in HSX elements and automatically import them." "Detect HSX elements and automatically import them."
(find-builtin-symbols form)) (detect-elements form))
(defun find-builtin-symbols (node) (defun detect-builtin-symbol (sym)
(if (atom node) (multiple-value-bind (builtin-sym kind)
(or (and (symbolp node) (find-symbol (string sym) :hsx/builtin)
(not (keywordp node)) (and (eq kind :external) builtin-sym)))
(find-symbol (string node) :hsx/builtin))
node) (defun start-with-tilde-p (sym)
(cons (find-builtin-symbols (car node)) (string= "~" (subseq (string sym) 0 1)))
(mapcar (lambda (n)
(if (listp n) (defun detect-component-symbol (sym)
(find-builtin-symbols n) (and (start-with-tilde-p sym) sym))
n))
(cdr node))))) (defun detect-elements (form)
(check-type form cons)
(let* ((head (first form))
(tail (rest form))
(well-formed-p (listp tail))
(detected-sym (and (symbolp head)
(not (keywordp head))
(or (detect-builtin-symbol head)
(detect-component-symbol head)))))
(if (and well-formed-p detected-sym)
(cons detected-sym
(mapcar (lambda (sub-form)
(if (consp sub-form)
(detect-elements sub-form)
sub-form))
tail))
form)))
;;;; defhsx macro ;;;; defhsx macro
@ -58,16 +74,19 @@
`(eval-when (:compile-toplevel :load-toplevel :execute) `(eval-when (:compile-toplevel :load-toplevel :execute)
(defhsx ,name ,(make-keyword name)))) (defhsx ,name ,(make-keyword name))))
(defmacro defcomp (name props &body body) (defmacro defcomp (~name props &body body)
"Define a function component for use in HSX. "Define an HSX function component.
The props must be declared with either &key or &rest (or both). The component name must start with a tilde (~).
The body must return an HSX element." Component properties must be declared using &key, &rest, or both.
The body of the component must produce a valid HSX element."
(unless (start-with-tilde-p ~name)
(error "The component name must start with a tilde (~~)."))
(unless (or (null props) (unless (or (null props)
(member '&key props) (member '&key props)
(member '&rest props)) (member '&rest props))
(error "Component properties must be declared with either &key, &rest, or both.")) (error "Component properties must be declared using &key, &rest, or both."))
(let ((%name (symbolicate '% name))) (let ((%name (symbolicate '% ~name)))
`(eval-when (:compile-toplevel :load-toplevel :execute) `(eval-when (:compile-toplevel :load-toplevel :execute)
(defun ,%name ,props (defun ,%name ,props
,@body) ,@body)
(defhsx ,name (fdefinition ',%name))))) (defhsx ,~name (fdefinition ',%name)))))

View file

@ -1,67 +1,79 @@
(defpackage #:hsx-test/dsl (defpackage #:hsx-test/dsl
(:use #:cl (:use #:cl
#:rove #:rove
#:hsx/dsl #:hsx/dsl)
#:hsx/builtin) (:import-from #:hsx/builtin)
(:import-from #:hsx/element (:import-from #:hsx/element
#:element-props #:element-props
#:element-children)) #:element-children))
(in-package #:hsx-test/dsl) (in-package #:hsx-test/dsl)
(defcomp ~comp1 (&key children)
(hsx (div children)))
(deftest detect-elements-test
(testing "detect-tags"
(ok (expands '(hsx (div div div))
'(hsx/builtin:div div div)))
(ok (expands '(hsx (div (div div (div))))
'(hsx/builtin:div
(hsx/builtin:div
div
(hsx/builtin:div))))))
(testing "detect-components"
(ok (expands '(hsx (~comp1 (div)))
'(~comp1 (hsx/builtin:div)))))
(testing "ignore-malformed-form"
(ok (expands '(hsx (div . div))
'(div . div)))
(ok (expands '(hsx ((div)))
'((div)))))
(testing "ignore-cl-form"
(ok (expands '(hsx (labels ((div () "div"))
(div)))
'(labels ((div () "div"))
(div))))))
(deftest dsl-test (deftest dsl-test
(testing "find-symbols"
(ok (expands
'(hsx (div '(:div "div")
div
(div
'div
(div)
:div)
"div"))
'(hsx/builtin:div '(:div "div")
div
(hsx/builtin:div
'div
(hsx/builtin:div)
:div)
"div"))))
(testing "empty-hsx" (testing "empty-hsx"
(let ((elm (div))) (let ((elm (hsx (div))))
(ok (null (element-props elm))) (ok (null (element-props elm)))
(ok (null (element-children elm))))) (ok (null (element-children elm)))))
(testing "hsx-with-static-props" (testing "hsx-with-static-props"
(let ((elm (div :prop1 "value1" :prop2 "value2"))) (let ((elm (hsx (div :prop1 "value1" :prop2 "value2"))))
(ok (equal '(:prop1 "value1" :prop2 "value2") (ok (equal '(:prop1 "value1" :prop2 "value2")
(element-props elm))) (element-props elm)))
(ok (null (element-children elm))))) (ok (null (element-children elm)))))
(testing "hsx-with-dynamic-props" (testing "hsx-with-dynamic-props"
(let* ((props '(:prop1 "value1" :prop2 "value2")) (let* ((props '(:prop1 "value1" :prop2 "value2"))
(elm (div props))) (elm (hsx (div props))))
(ok (equal props (element-props elm))) (ok (equal props (element-props elm)))
(ok (null (element-children elm))))) (ok (null (element-children elm)))))
(testing "hsx-with-children" (testing "hsx-with-children"
(let ((elm (div (let ((elm (hsx (div
"child1" "child1"
"child2"))) "child2"))))
(ok (null (element-props elm))) (ok (null (element-props elm)))
(ok (equal (list "child1" "child2") (element-children elm))))) (ok (equal (list "child1" "child2") (element-children elm)))))
(testing "hsx-with-static-props-and-children" (testing "hsx-with-static-props-and-children"
(let ((elm (div :prop1 "value1" :prop2 "value2" (let ((elm (hsx (div :prop1 "value1" :prop2 "value2"
"child1" "child1"
"child2"))) "child2"))))
(ok (equal '(:prop1 "value1" :prop2 "value2") (ok (equal '(:prop1 "value1" :prop2 "value2")
(element-props elm))) (element-props elm)))
(ok (equal (list "child1" "child2") (element-children elm))))) (ok (equal (list "child1" "child2") (element-children elm)))))
(testing "hsx-with-dynamic-props-and-children" (testing "hsx-with-dynamic-props-and-children"
(let* ((props '(:prop1 "value1" :prop2 "value2")) (let* ((props '(:prop1 "value1" :prop2 "value2"))
(elm (div props (elm (hsx (div props
"child1" "child1"
"child2"))) "child2"))))
(ok (equal props (element-props elm))) (ok (equal props (element-props elm)))
(ok (equal (list "child1" "child2") (element-children elm)))))) (ok (equal (list "child1" "child2") (element-children elm))))))