From 011ccd6b2aeba7375e2992dc7e58d26c56e760e2 Mon Sep 17 00:00:00 2001 From: paku <paku@skyizwhite.dev> Date: Sat, 19 Oct 2024 00:13:33 +0900 Subject: [PATCH 01/10] Update system version --- hsx.asd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hsx.asd b/hsx.asd index aba1850..dffc354 100644 --- a/hsx.asd +++ b/hsx.asd @@ -1,5 +1,5 @@ (defsystem "hsx" - :version "0.1.0" + :version "0.2.1" :description "Hypertext S-expression" :author "skyizwhite, Bo Yao" :maintainer "skyizwhite <paku@skyizwhite.dev>" From a170c58530fa82b9f9fc8440a14fbe57f466b8fc Mon Sep 17 00:00:00 2001 From: paku <paku@skyizwhite.dev> Date: Thu, 12 Dec 2024 12:57:05 +0900 Subject: [PATCH 02/10] Improve find-builtin-symbols --- src/dsl.lisp | 33 ++++++++++++++--------- tests/dsl.lisp | 73 +++++++++++++++++++++++++++++--------------------- 2 files changed, 63 insertions(+), 43 deletions(-) diff --git a/src/dsl.lisp b/src/dsl.lisp index 680c85a..f196866 100644 --- a/src/dsl.lisp +++ b/src/dsl.lisp @@ -16,18 +16,27 @@ "Detect built-in HSX elements and automatically import them." (find-builtin-symbols form)) -(defun find-builtin-symbols (node) - (if (atom node) - (or (and (symbolp node) - (not (keywordp node)) - (find-symbol (string node) :hsx/builtin)) - node) - (cons (find-builtin-symbols (car node)) - (mapcar (lambda (n) - (if (listp n) - (find-builtin-symbols n) - n)) - (cdr node))))) +(defun get-builtin-symbol (sym) + (multiple-value-bind (builtin-sym kind) + (find-symbol (string sym) :hsx/builtin) + (and (eq kind :external) builtin-sym))) + +(defun find-builtin-symbols (form) + (check-type form cons) + (let* ((head (first form)) + (tail (rest form)) + (well-formed-p (listp tail)) + (builtin-sym (and (symbolp head) + (not (keywordp head)) + (get-builtin-symbol head)))) + (if (and well-formed-p builtin-sym) + (cons builtin-sym + (mapcar (lambda (sub-form) + (if (consp sub-form) + (find-builtin-symbols sub-form) + sub-form)) + tail)) + form))) ;;;; defhsx macro diff --git a/tests/dsl.lisp b/tests/dsl.lisp index d8bce5d..8bd6408 100644 --- a/tests/dsl.lisp +++ b/tests/dsl.lisp @@ -1,67 +1,78 @@ (defpackage #:hsx-test/dsl (:use #:cl #:rove - #:hsx/dsl - #:hsx/builtin) + #:hsx/dsl) + (:import-from #:hsx/builtin) (:import-from #:hsx/element #:element-props #:element-children)) (in-package #:hsx-test/dsl) +(deftest find-builtin-symbols-test + (testing "normal-cases" + (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))))) + (ok (expands '(hsx (div + (labels ((div () "div")) + (hsx (div))))) + '(hsx/builtin:div + (labels ((div () "div")) + (hsx (div))))))) + + (testing "ignore-cases" + (ok (expands '(hsx (div . div)) + '(div . div))) + (ok (expands '(hsx ((div))) + '((div)))) + (ok (expands '(hsx (div + (labels ((div () "div")) + (div)))) + '(hsx/builtin:div + (labels ((div () "div")) + (div))))))) + (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" - (let ((elm (div))) + (let ((elm (hsx (div)))) (ok (null (element-props elm))) (ok (null (element-children elm))))) (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") (element-props elm))) (ok (null (element-children elm))))) (testing "hsx-with-dynamic-props" (let* ((props '(:prop1 "value1" :prop2 "value2")) - (elm (div props))) + (elm (hsx (div props)))) (ok (equal props (element-props elm))) (ok (null (element-children elm))))) (testing "hsx-with-children" - (let ((elm (div - "child1" - "child2"))) + (let ((elm (hsx (div + "child1" + "child2")))) (ok (null (element-props elm))) (ok (equal (list "child1" "child2") (element-children elm))))) (testing "hsx-with-static-props-and-children" - (let ((elm (div :prop1 "value1" :prop2 "value2" - "child1" - "child2"))) + (let ((elm (hsx (div :prop1 "value1" :prop2 "value2" + "child1" + "child2")))) (ok (equal '(:prop1 "value1" :prop2 "value2") (element-props elm))) (ok (equal (list "child1" "child2") (element-children elm))))) (testing "hsx-with-dynamic-props-and-children" (let* ((props '(:prop1 "value1" :prop2 "value2")) - (elm (div props - "child1" - "child2"))) + (elm (hsx (div props + "child1" + "child2")))) (ok (equal props (element-props elm))) (ok (equal (list "child1" "child2") (element-children elm)))))) From 6abb647246b0fe900f7c7f18ee0427282586c8ff Mon Sep 17 00:00:00 2001 From: paku <paku@skyizwhite.dev> Date: Thu, 12 Dec 2024 13:09:51 +0900 Subject: [PATCH 03/10] Update README --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4055a2a..730e943 100644 --- a/README.md +++ b/README.md @@ -39,10 +39,12 @@ HSX allows embedding Common Lisp code directly within your HTML structure, makin (div (p :id (format nil "id-~a" (random 100))) (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) - (p "Condition met!") - (p "Condition not met!")))) + (hsx (p "Condition met!")) + (hsx (p "Condition not met!"))))) ``` This might generate: From 33dd8e82051305840dc3f5bfc480a5f3dd1ec951 Mon Sep 17 00:00:00 2001 From: paku <paku@skyizwhite.dev> Date: Thu, 12 Dec 2024 13:10:14 +0900 Subject: [PATCH 04/10] Update system version to v0.3.0 --- hsx.asd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hsx.asd b/hsx.asd index dffc354..fccc84d 100644 --- a/hsx.asd +++ b/hsx.asd @@ -1,5 +1,5 @@ (defsystem "hsx" - :version "0.2.1" + :version "0.3.0" :description "Hypertext S-expression" :author "skyizwhite, Bo Yao" :maintainer "skyizwhite <paku@skyizwhite.dev>" From 3193054e04052ace1108b34d80116ff85b03d98b Mon Sep 17 00:00:00 2001 From: paku <paku@skyizwhite.dev> Date: Thu, 12 Dec 2024 14:00:42 +0900 Subject: [PATCH 05/10] Update README --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 730e943..4c63e73 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,14 @@ # 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 yet powerful HTML5 generation library for Common Lisp. + +This project is a fork of [ailisp/flute](https://github.com/ailisp/flute/). + +## Warning + +This software is still ALPHA quality. The APIs likely change. + +Please check the [release notes](https://github.com/skyizwhite/hsx/releases). ## Introduction From dfc074ec71a931156e83c86a650447ae783462ab Mon Sep 17 00:00:00 2001 From: paku <paku@skyizwhite.dev> Date: Fri, 13 Dec 2024 01:33:01 +0900 Subject: [PATCH 06/10] Fix defcomp to detect components in HSX --- src/dsl.lisp | 38 ++++++++++++++++++++++++-------------- tests/dsl.lisp | 35 ++++++++++++++++++----------------- 2 files changed, 42 insertions(+), 31 deletions(-) diff --git a/src/dsl.lisp b/src/dsl.lisp index f196866..577ce58 100644 --- a/src/dsl.lisp +++ b/src/dsl.lisp @@ -13,27 +13,34 @@ ;;;; hsx macro (defmacro hsx (form) - "Detect built-in HSX elements and automatically import them." - (find-builtin-symbols form)) + "Detect HSX elements and automatically import them." + (detect-elements form)) (defun get-builtin-symbol (sym) (multiple-value-bind (builtin-sym kind) (find-symbol (string sym) :hsx/builtin) (and (eq kind :external) builtin-sym))) -(defun find-builtin-symbols (form) +(defun start-with-tilde-p (sym) + (string= "~" (subseq (string sym) 0 1))) + +(defun get-component-symbol (sym) + (and (start-with-tilde-p sym) sym)) + +(defun detect-elements (form) (check-type form cons) (let* ((head (first form)) (tail (rest form)) (well-formed-p (listp tail)) - (builtin-sym (and (symbolp head) - (not (keywordp head)) - (get-builtin-symbol head)))) - (if (and well-formed-p builtin-sym) - (cons builtin-sym + (detected-sym (and (symbolp head) + (not (keywordp head)) + (or (get-builtin-symbol head) + (get-component-symbol head))))) + (if (and well-formed-p detected-sym) + (cons detected-sym (mapcar (lambda (sub-form) (if (consp sub-form) - (find-builtin-symbols sub-form) + (detect-elements sub-form) sub-form)) tail)) form))) @@ -67,16 +74,19 @@ `(eval-when (:compile-toplevel :load-toplevel :execute) (defhsx ,name ,(make-keyword name)))) -(defmacro defcomp (name props &body body) - "Define a function component for use in HSX. -The props must be declared with either &key or &rest (or both). +(defmacro defcomp (~name props &body body) + "Define a function component for HSX. +The component name must start with a tilde (~). +Properties must be declared using &key, &rest, or both. The body must return an HSX element." + (unless (start-with-tilde-p ~name) + (error "The component name must start with a tilde (~~).")) (unless (or (null props) (member '&key props) (member '&rest props)) (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) (defun ,%name ,props ,@body) - (defhsx ,name (fdefinition ',%name))))) + (defhsx ,~name (fdefinition ',%name))))) diff --git a/tests/dsl.lisp b/tests/dsl.lisp index 8bd6408..31abea8 100644 --- a/tests/dsl.lisp +++ b/tests/dsl.lisp @@ -8,33 +8,34 @@ #:element-children)) (in-package #:hsx-test/dsl) -(deftest find-builtin-symbols-test - (testing "normal-cases" +(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))))) - (ok (expands '(hsx (div - (labels ((div () "div")) - (hsx (div))))) - '(hsx/builtin:div - (labels ((div () "div")) - (hsx (div))))))) + (hsx/builtin:div)))))) - (testing "ignore-cases" + (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)))) - (ok (expands '(hsx (div - (labels ((div () "div")) - (div)))) - '(hsx/builtin:div - (labels ((div () "div")) - (div))))))) + '((div))))) + + (testing "ignore-cl-form" + (ok (expands '(hsx (labels ((div () "div")) + (div))) + '(labels ((div () "div")) + (div)))))) (deftest dsl-test (testing "empty-hsx" From 4490c741976dc1cfc06e83b9a7a9922a11f6e461 Mon Sep 17 00:00:00 2001 From: paku <paku@skyizwhite.dev> Date: Fri, 13 Dec 2024 01:35:31 +0900 Subject: [PATCH 07/10] Update README --- README.md | 47 +++++++++++++++++++---------------------------- 1 file changed, 19 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 4c63e73..4bd5733 100644 --- a/README.md +++ b/README.md @@ -6,19 +6,15 @@ This project is a fork of [ailisp/flute](https://github.com/ailisp/flute/). ## Warning -This software is still ALPHA quality. The APIs likely change. +This software is still in ALPHA quality. The APIs are likely to change. -Please check the [release notes](https://github.com/skyizwhite/hsx/releases). - -## Introduction - -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. +Please check the [release notes](https://github.com/skyizwhite/hsx/releases) for updates. ## Getting Started ### 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 (hsx @@ -36,11 +32,18 @@ This generates: </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 (hsx @@ -91,22 +94,22 @@ This generates: <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 -(defcomp card (&key title children) +(defcomp ~card (&key title children) (hsx (div :class "card" (h1 title) children))) ``` -Or using a property list: +Alternatively, you can use a property list: ```lisp -(defcomp card (&rest props) +(defcomp ~card (&rest props) (hsx (div :class "card" (h1 (getf props :title)) @@ -117,7 +120,7 @@ Usage example: ```lisp (hsx - (card :title "Card Title" + (~card :title "Card Title" (p "This is a card component."))) ``` @@ -130,18 +133,6 @@ Generates: </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 This project is licensed under the MIT License. From f60259ec4a101de87c5364c3f1b571706448d3a0 Mon Sep 17 00:00:00 2001 From: paku <paku@skyizwhite.dev> Date: Fri, 13 Dec 2024 01:37:10 +0900 Subject: [PATCH 08/10] Update system version to v0.4.0 --- hsx.asd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hsx.asd b/hsx.asd index fccc84d..b1151ae 100644 --- a/hsx.asd +++ b/hsx.asd @@ -1,5 +1,5 @@ (defsystem "hsx" - :version "0.3.0" + :version "0.4.0" :description "Hypertext S-expression" :author "skyizwhite, Bo Yao" :maintainer "skyizwhite <paku@skyizwhite.dev>" From fa7fc1605e14aaca8d449fe21c2ee2d0f23a5ec4 Mon Sep 17 00:00:00 2001 From: paku <paku@skyizwhite.dev> Date: Tue, 17 Dec 2024 16:14:46 +0900 Subject: [PATCH 09/10] Modify the wording --- src/dsl.lisp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/dsl.lisp b/src/dsl.lisp index 577ce58..27d785a 100644 --- a/src/dsl.lisp +++ b/src/dsl.lisp @@ -16,7 +16,7 @@ "Detect HSX elements and automatically import them." (detect-elements form)) -(defun get-builtin-symbol (sym) +(defun detect-builtin-symbol (sym) (multiple-value-bind (builtin-sym kind) (find-symbol (string sym) :hsx/builtin) (and (eq kind :external) builtin-sym))) @@ -24,7 +24,7 @@ (defun start-with-tilde-p (sym) (string= "~" (subseq (string sym) 0 1))) -(defun get-component-symbol (sym) +(defun detect-component-symbol (sym) (and (start-with-tilde-p sym) sym)) (defun detect-elements (form) @@ -34,8 +34,8 @@ (well-formed-p (listp tail)) (detected-sym (and (symbolp head) (not (keywordp head)) - (or (get-builtin-symbol head) - (get-component-symbol head))))) + (or (detect-builtin-symbol head) + (detect-component-symbol head))))) (if (and well-formed-p detected-sym) (cons detected-sym (mapcar (lambda (sub-form) @@ -75,16 +75,16 @@ (defhsx ,name ,(make-keyword name)))) (defmacro defcomp (~name props &body body) - "Define a function component for HSX. + "Define an HSX function component. The component name must start with a tilde (~). -Properties must be declared using &key, &rest, or both. -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) (member '&key 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))) `(eval-when (:compile-toplevel :load-toplevel :execute) (defun ,%name ,props From a73af8d936260585e92035827c161ba0bc3075b8 Mon Sep 17 00:00:00 2001 From: paku <paku@skyizwhite.dev> Date: Fri, 20 Dec 2024 23:38:19 +0900 Subject: [PATCH 10/10] Update description --- README.md | 2 +- hsx.asd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4bd5733..b036ba3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # HSX -HSX (Hypertext S-expression) is a simple yet powerful HTML5 generation library for Common Lisp. +HSX (Hypertext S-expression) is a simple and powerful HTML (Living Standard) generation library for Common Lisp. This project is a fork of [ailisp/flute](https://github.com/ailisp/flute/). diff --git a/hsx.asd b/hsx.asd index b1151ae..c5ab13d 100644 --- a/hsx.asd +++ b/hsx.asd @@ -1,6 +1,6 @@ (defsystem "hsx" :version "0.4.0" - :description "Hypertext S-expression" + :description "Simple and powerful HTML generation library." :author "skyizwhite, Bo Yao" :maintainer "skyizwhite <paku@skyizwhite.dev>" :license "MIT"