SkillAgentSearch skills...

ACandy

A sugary Lua module to write HTML

Install / Use

/learn @AmeroHan/ACandy
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

<!-- This file is generated by program. DO NOT edit it directly. Edit docs/README.base.md instead. -->

ACandy: a sugary Lua module for building HTML

<div align="center"> <p>🌏 <strong>English</strong> | <a href="docs/README.zh.md">中文</a></p> <p> This work uses <a href="https://semver.org/">Semantic Versioning</a> </p> </div>

ACandy is a pure Lua module for building HTML. Taking advantage of Lua’s syntactic sugar and metatable, it provides an intuitive DSL to build HTML from Lua.

ACandy 是一个构建 HTML 的纯 Lua 模块。利用 Lua 的语法糖和元表,ACandy 提供了一个易用的 DSL 来从 Lua 构建 HTML。

[!NOTE]

This module even has a specialized version for Scribunto (i.e., the Lua module environment used by MediaWiki’s “Module” namespace). See the branch “scribunto” for details.

本模块还有 Scribunto(即 MediaWiki “Module(模块)”命名空间的 Lua 模块所用环境)的特化版本,详见分支“scribunto”。

Quick look

local acandy = require 'acandy'
local a, some, Fragment = acandy.a, acandy.some, acandy.Fragment

local example = Fragment {
   a.h1['#top heading heading-1'] 'Hello!',
   a.div { class="container", style="margin: 0 auto;",
      a.p {
         'My name is ', a.dfn('ACandy'), ', a module for building HTML.',
         a.br,
         'Thank you for your visit.',
      },
      a.p 'visitors:',
      a.ul / some.li('Alice', 'Bob', 'Carol', '...'),
   },
}
print(example)

Output (formatted):

<h1 id="top" class="heading heading-1">Hello!</h1>
<div style="margin: 0 auto;" class="container">
   <p>
      My name is <dfn>ACandy</dfn>, a module for building HTML.<br>
      Thank you for your visit.
   </p>
   <p>visitors:</p>
   <ul>
      <li>Alice</li>
      <li>Bob</li>
      <li>Carol</li>
      <li>...</li>
   </ul>
</div>

In this documentation, strings related to attributes are enclosed in double quotation marks while others single. It's just my personal preference and you can decide for yourself.

Getting started

Install

ACandy is available on LuaRocks and can be installed with the following command:

luarocks install html

Or you can clone this repository and copy the acandy folder to your Lua module path manually.

Import

local acandy = require('acandy')
local a = acandy.a

a is the entry point for all elements, because:

  • a is ACandy’s first letter;
  • a is short to type;
  • <code>a.xxx</code> can be understood as “a xxx” in English.

Create elements

local elem = a.p {
   class="my-paragraph", style="color: #114514;",
   'This sentence is inside a ', a.code('<p>'), ' element.',
}
print(elem)

In this code, a.p is a function that returns an element. It takes a table as its argument, in which key-value pairs and sequences represent attributes and children of the element respectively, and the same applies to other elements. If there is only one child and no attributes need to be set, the child can be passed directly as the argument of the function, so a.code('...') is equivalent to a.code({ '...' }).

The output of this code, formatted (the same below), is as follows.

<p class="my-paragraph" style="color: #114514;">
   This sentence is inside a <code>&lt;p&gt;</code> element.
</p>

[!TIP]

  • You don’t need to handle HTML escaping in strings. If you don't want automatic escaping, you can put the content in acandy.Raw.
  • Child nodes do not have to be elements or strings—although only these two types are shown here, any value that can be tostring is capable of a child node.

<code>a.xxx</code> is ASCII case-insensitive, thus a.div, a.Div, a.DIV, etc., are the same value (i.e., rawequal(a.div, a.Div) == true and rawequal(a.div, a.DIV) == true) and will all become <div></div>.

Attributes

Attributes are provided to elements through key-value pairs in the table. The attribute values can be:

  • nil and false indicate no such attribute;
  • true indicates a boolean attribute, e.g., a.script { async=true } means <script async></script>;
  • for any other value, try tostring on it, then escape &, <, > and NBSP.

Children

Child nodes are provided to elements through the sequence part of the table. Any value other than nil can be a child node. When serializing, they follow the following rules.

Default case

Elements, strings, numbers, booleans, and all other values not mentioned later are applicable to the following rules.

When serializing, tostring will be tried on these values and then escape &, <, > and NBSP. If you don't want automatic escaping, you can put the content in acandy.Raw.

In the following example, we use three elements (<p>) as child nodes of <article>, and use strings, numbers, and booleans as elements of <p>. It is trivial to guess the result.

local elem = a.article {
   a.p 'Lorem ipsum...',  -- or `a.p { 'Lorem ipsum...' }`
   a.p(2),  -- or `a.p { 2 }`
   a.p(true),  -- or `a.p { true }`
}
print(elem)
<article>
   <p>Lorem ipsum...</p>
   <p>2</p>
   <p>true</p>
</article>

Lists

When serializing, if a node is list-like, ACandy will recursively serialize the child nodes inside it.

By the way, tables returned by acandy.Fragment (e.g., Fragment { 1, 2, 3 }) are list-like, as their metatable has the '__acandy_list_like' field set to true.

Particularly, if a node has a table type but not considered list-like (e.g., table returned by a.p { 1, 2, 3 }), it will be directly converted to string according to the default rule, so make sure __tostring metamethod is implemented.

local list1 = { '3', '4' }
local list2 = { '2', list1 }
local elem = a.div { '1', list2 }
print(elem)
<p>1234</p>

Functions

Functions can be used as child nodes, which is equivalent to calling the function and using the return value as a child node, with the only difference being that the function will be deferred until tostring is called.

local elem = a.ul {
   a.li 'item 1',
   a.li {
      function ()  -- returns string
         return 'item 2'
      end,
   },
   function ()  -- returns element
      return a.li 'item 3'
   end,
   function ()  -- returns list
      local list = {}
      for i = 4, 6 do
         list[#list+1] = a.li('item '..i)
      end
      return list
   end,
}
print(elem)
<ul>
   <li>item 1</li>
   <li>item 2</li>
   <li>item 3</li>
   <li>item 4</li>
   <li>item 5</li>
   <li>item 6</li>
</ul>

[!TIP]

Child nodes are processed recursively, so you can return functions within functions.

Bracket syntax (setting element attributes)

Placing a string in brackets can quickly set id and class.

local elem = a.div['#my-id my-class-1 my-class-2'] {
   a.p 'You know what it is.',
}
print(elem)

Placing a table-like value in brackets can set element attributes, not limited to id and class. This makes reusing attributes more convenient.

local attr = {
   id="my-id",
   class="my-class-1 my-class-2",
}
local elem = a.div[attr] {
   a.p 'You know what it is.',
}
print(elem)

Both of the above code snippets output:

<div id="my-id" class="my-class-1 my-class-2">
   <p>You know what it is.</p>
</div>

Slash syntax (breadcrumbs)

elem1 / elem2 / ... / elemN / tail_value

is equivalent to:

elem1(
   elem2(
      ...(
         elemN(tail_value)
      )
   )
)

Kind of like CSS’s child combinator >, except that it is used to compose elements rather than select elements.

The premise is that elem1..elemN are not void elements or constructed elements.

Example:

local link_item = a.li / a.a
local text = 'More coming soon...'
local elem = (
   a.header['site-header'] / a.nav / a.ul {
      link_item { href="/home", 'Home' },
      link_item { href="/posts", 'Posts' },
      link_item { href="/about", 'About' },
      a.li / text,
   }
)
print(elem)
<header class="site-header">
   <nav>
      <ul>
         <li><a href="/home">Home</a></li>
         <li><a href="/posts">Posts</a></li>
         <li><a href="/about">About</a></li>
         <li>More coming soon...</li>
      </ul>
   </nav>
</header>

[!TIP]

breadcrumbs can be cached, just like link_item in the above example.

acandy.some

local frag1 = some.xxx(arg1, arg2, ...)
local frag2 = some.xxx[attr](arg1, arg2, ...)

is equivalent to:

local frag1 = Fragment {
   a.xxx(arg1),
   a.xxx(arg2),
   ...,
}
local frag2 = Fragment {
   a.xxx[attr](arg1),
   a.xxx[attr](arg2),
   ...,
}

Example:

local some = acandy.some
local items = a.ul {
   some.li['my-li']('item 1', 'item 2'),
   some.li('item 3', 'item 4'),
}
print(items)
<ul>
   <li class="my-li">item 1</li>
   <li class="my-li">item 2</li>
   <li>item 3</li>
   <li>item 4</li>
</ul>

Element instance

If an element is obtained by calling functions like a.div(...), a.div[...](...), it is called (tentatively) a "constructed element"; when a constructed element is the end of a breadcrumb, the breadcrumb also returns a constructed element; while a.div, a.div[...] are not constructed elements.

A constructed element elem has the following properties:

  • elem.tag_name: the tag name of the element, reassignable.
  • elem.attributes: a table that stores all the attributes of the element, changes to this table will take effect on the el
View on GitHub
GitHub Stars12
CategoryDevelopment
Updated1mo ago
Forks1

Languages

Lua

Security Score

95/100

Audited on Mar 2, 2026

No findings