Compare commits

..

No commits in common. "a73af8d936260585e92035827c161ba0bc3075b8" and "7ce7751900ce6eacb9264b3109402bcc17aa40b2" have entirely different histories.

4 changed files with 82 additions and 114 deletions

View file

@ -1,20 +1,16 @@
# HSX # HSX
HSX (Hypertext S-expression) is a simple and powerful HTML (Living Standard) generation library for Common Lisp. 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/).
This project is a fork of [ailisp/flute](https://github.com/ailisp/flute/). ## Introduction
## Warning 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.
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 after the element name, and child elements are nested directly inside. 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.
```lisp ```lisp
(hsx (hsx
@ -32,30 +28,21 @@ This generates:
</div> </div>
``` ```
To convert an HSX object into an HTML string, use the `render-to-string` function: ## Examples
```lisp ### Dynamic Content
(render-to-string
(hsx ...))
```
### Embedding Content HSX allows embedding Common Lisp code directly within your HTML structure, making it easy to generate dynamic 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 (loop :for i :from 1 :to 5 :collect (li (format nil "Item ~a" i))))
:for i :from 1 :to 5 :collect
(hsx (li (format nil "Item ~a" i)))))
(if (> (random 10) 5) (if (> (random 10) 5)
(hsx (p "Condition met!")) (p "Condition met!")
(hsx (p "Condition not met!"))))) (p "Condition not met!"))))
``` ```
This might generate: This might generate:
@ -94,22 +81,22 @@ This generates:
<p>Second paragraph.</p> <p>Second paragraph.</p>
``` ```
### Creating Components ## Creating Components
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. You can define reusable components with the `defcomp` macro. Components are functions that can take keyword arguments and properties.
```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)))
``` ```
Alternatively, you can use a property list: Or using 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))
@ -120,7 +107,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.")))
``` ```
@ -133,6 +120,18 @@ 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.4.0" :version "0.1.0"
:description "Simple and powerful HTML generation library." :description "Hypertext S-expression"
: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,37 +13,21 @@
;;;; hsx macro ;;;; hsx macro
(defmacro hsx (form) (defmacro hsx (form)
"Detect HSX elements and automatically import them." "Detect built-in HSX elements and automatically import them."
(detect-elements form)) (find-builtin-symbols form))
(defun detect-builtin-symbol (sym) (defun find-builtin-symbols (node)
(multiple-value-bind (builtin-sym kind) (if (atom node)
(find-symbol (string sym) :hsx/builtin) (or (and (symbolp node)
(and (eq kind :external) builtin-sym))) (not (keywordp node))
(find-symbol (string node) :hsx/builtin))
(defun start-with-tilde-p (sym) node)
(string= "~" (subseq (string sym) 0 1))) (cons (find-builtin-symbols (car node))
(mapcar (lambda (n)
(defun detect-component-symbol (sym) (if (listp n)
(and (start-with-tilde-p sym) sym)) (find-builtin-symbols n)
n))
(defun detect-elements (form) (cdr node)))))
(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
@ -74,19 +58,16 @@
`(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 an HSX function component. "Define a function component for use in HSX.
The component name must start with a tilde (~). The props must be declared with either &key or &rest (or both).
Component properties must be declared using &key, &rest, or both. The body must return an HSX element."
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 using &key, &rest, or both.")) (error "Component properties must be declared with either &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,79 +1,67 @@
(defpackage #:hsx-test/dsl (defpackage #:hsx-test/dsl
(:use #:cl (:use #:cl
#:rove #:rove
#:hsx/dsl) #:hsx/dsl
(:import-from #:hsx/builtin) #: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 (hsx (div)))) (let ((elm (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 (hsx (div :prop1 "value1" :prop2 "value2")))) (let ((elm (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 (hsx (div props)))) (elm (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 (hsx (div (let ((elm (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 (hsx (div :prop1 "value1" :prop2 "value2" (let ((elm (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 (hsx (div props (elm (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))))))