From c580ebb3088a2ef9ca272cbe558847a5152ecd0d Mon Sep 17 00:00:00 2001 From: AmeroHan <72120616+AmeroHan@users.noreply.github.com> Date: Sat, 23 Nov 2024 03:18:26 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit close #6 --- README.md | 61 +++++++++++++++++++++++++++++++++++++++-- acandy.lua | 47 +++++++++++++++---------------- config.lua | 67 +++++++++++++++++++++++++++++++-------------- docs/README.base.md | 65 +++++++++++++++++++++++++++++++++++++++++-- docs/README.zh.md | 61 +++++++++++++++++++++++++++++++++++++++-- utils.lua | 3 ++ 6 files changed, 253 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index d31d65c..225e4e5 100644 --- a/README.md +++ b/README.md @@ -463,7 +463,7 @@ tostring(acandy.Doctype.HTML) --> '' ### `acandy.extend_env` ```lua -function acandy.extend_env(env: table) -> nil +function acandy.extend_env(env: table): () ``` Extend the environment in place with `acandy.a` as `__index`, e.g., `_ENV`. This makes it possible to directly use the tag name rather than tediously type `a.`, unless there is a naming conflict with local variables or global variables. @@ -516,7 +516,7 @@ print( ### `acandy.to_extended_env` ```lua -function acandy.to_extended_env(env: table) -> table +function acandy.to_extended_env(env: table): table ``` Similar to `acandy.extend_env`, but returns a new table instead of modifying the original table. @@ -563,6 +563,63 @@ print(get_article()) ``` +## Configuration + +ACandy defaults to HTML mode (currently only HTML mode, XML will be supported in the future), and has predefined some HTML void elements and raw text elements (see [config.lua](/config.lua)). + +ACandy does not support modifying global configuration. To modify the configuration, create a new configured `ACandy` instance. The function signature is as follows. + +```lua +type Config = { + void_elements: { [string]: true }, + raw_text_elements: { [string]: true }, +} +function acandy.ACandy(output_type: 'html', modify_config?: (config: Config) -> ()): table +``` + +The `output_type` parameter currently only accepts `'html'`. The `modify_config` parameter (optional) is a function that takes a table as a parameter and has no return value. The table passed to this function is the basis of the new configuration, and you can modify this value in the function, for example: + +```lua +local acandy = require('acandy').ACandy('html', function (config) + -- add a void element + config.void_elements['my-void-element'] = true + -- remove `br` from void elements + config.void_elements.br = nil + -- add a raw text element + config.raw_text_elements['my-raw-text-element'] = true + -- remove `script` from raw text elements + config.raw_text_elements.script = nil +end) +local a = acandy.a +print( + acandy.Fragment { + a['my-void-element'], + a.br, + a['my-raw-text-element'] '< > &', + a.script 'let val = 2 > 1', + } +) +``` + +```html + +

+< > & + +``` + +To use this configuration throughout the project, you can export the configured `ACandy` instance and import it in other files. + +```lua +-- my_acandy.lua +return require('acandy').ACandy('html', function (config) + -- ... +end) + +-- other files +local acandy = require('my_acandy') +``` + ## Concepts ### Table-like values diff --git a/acandy.lua b/acandy.lua index 1e673d6..3f53390 100644 --- a/acandy.lua +++ b/acandy.lua @@ -23,12 +23,9 @@ local rawset = rawset local tostring = tostring local utils = require('.utils') -local default_void_elems, default_raw_text_elems = (function () - local config = require('.config') - return config.void_elements, config.raw_text_elements -end)() local classes = require('.classes') local node_mts = classes.node_mts +local config_module = require('.config') ---@class Symbol @@ -214,21 +211,23 @@ local function breadcrumb_to_string(self) end -local function error_wrong_index() - error('attempt to access properties of a unbuilt element', 2) -end - -local function error_wrong_newindex() - error('attempt to assign properties of a unbuilt element', 2) +local function ErrorEmitter(msg, level) + return function () + error(msg, level) + end end +local error_emitters = { + unbuilt_elem_index = ErrorEmitter('attempt to access properties of a unbuilt element', 2), + unbuilt_elem_newindex = ErrorEmitter('attempt to assign properties of a unbuilt element', 2), + built_elem_div = ErrorEmitter('attempt to perform division on a built element', 2), +} ----@param config {void_elements: {[string]: boolean}, raw_text_elements: {[string]: boolean}}? +---@param output_type 'html' +---@param modify_config fun(config: Config)? ---@nodiscard -local function ACandy(config) - config = config or {} - local void_elems = utils.list_to_bool_dict(config.void_elements or default_void_elems) - local raw_text_elems = utils.list_to_bool_dict(config.raw_text_elements or default_raw_text_elems) +local function ACandy(output_type, modify_config) + local void_elems, raw_text_elems = config_module.parse_config(output_type, modify_config) ---A BareElement is an Element without any properties except tag name, e.g., ---`acandy.div`. It is immutable and can be cached and reused. @@ -594,22 +593,20 @@ local function ACandy(config) end, __call = new_built_elem_from_props, --> BuiltElement __div = elem_div, --> Breadcrumb | BuiltElement - __newindex = error_wrong_newindex, + __newindex = error_emitters.unbuilt_elem_newindex, } BuildingElement_mt = node_mts:register { __tostring = elem_to_string, --> string __call = new_built_elem_from_props, --> BuiltElement __div = elem_div, --> Breadcrumb | BuiltElement - __index = error_wrong_index, - __newindex = error_wrong_newindex, + __index = error_emitters.unbuilt_elem_index, + __newindex = error_emitters.unbuilt_elem_newindex, } BuiltElement_mt = node_mts:register { __tostring = elem_to_string, --> string __index = get_elem_prop, __newindex = set_elem_prop, - __div = function () - error('attempt to perform division on a built element', 2) - end, + __div = error_emitters.built_elem_div, } Breadcrumb_mt = node_mts:register { __tostring = breadcrumb_to_string, --> string @@ -626,8 +623,8 @@ local function ACandy(config) end return breadcrumb_div(left, right) end, - __index = error_wrong_index, - __newindex = error_wrong_newindex, + __index = error_emitters.unbuilt_elem_index, + __newindex = error_emitters.unbuilt_elem_newindex, } @@ -699,7 +696,7 @@ local function ACandy(config) ---Extend the environment in place with `acandy.a` as `__index`. ---@param env table the environment to be extended, e.g. `_ENV`, `_G` function acandy.extend_env(env) - return utils.extend_env_with_elem_entry(env, a) + utils.extend_env_with_elem_entry(env, a) end ---Return a new environment based on `env` with `acandy.a` as `__index`. @@ -734,7 +731,7 @@ local acandy_module = setmt({ if not ACANDY_EXPORTED_NAMES[k] then return nil end - local default_acandy = ACandy() + local default_acandy = ACandy('html') utils.copy_pairs(default_acandy, self) assert( default_acandy[k] ~= nil, diff --git a/config.lua b/config.lua index e6a009c..8583ec1 100644 --- a/config.lua +++ b/config.lua @@ -1,25 +1,52 @@ -local config = {} - -- ref: https://html.spec.whatwg.org/#elements-2 -config.void_elements = { - 'area', - 'base', 'br', - 'col', - 'embed', - 'hr', - 'img', - 'input', - 'link', - 'meta', - 'param', - 'source', - 'track', - 'wbr', +local DEFAULT_CONFIG = { + html = { + void_elements = { + 'area', + 'base', 'br', + 'col', + 'embed', + 'hr', + 'img', + 'input', + 'link', + 'meta', + 'param', + 'source', + 'track', + 'wbr', + }, + raw_text_elements = { + 'script', 'style', + }, + }, } -config.raw_text_elements = { - 'script', 'style', -} +local utils = require('.utils') + +local module = {} + +---ACandy configuration. +---@class Config +---@field void_elements {[string]: boolean} +---@field raw_text_elements {[string]: boolean} + +---@param output_type 'html' +---@param modify_config fun(config: Config)? +---@nodiscard +function module.parse_config(output_type, modify_config) + local base_config = DEFAULT_CONFIG[output_type] + assert(base_config, 'unsupported output type: '..output_type) + + local config = { + void_elements = utils.list_to_bool_dict(base_config.void_elements), + raw_text_elements = utils.list_to_bool_dict(base_config.raw_text_elements), + } + if modify_config then + modify_config(config) + end + return config.void_elements, config.raw_text_elements +end -return config +return module diff --git a/docs/README.base.md b/docs/README.base.md index 1b300fb..926ce8c 100644 --- a/docs/README.base.md +++ b/docs/README.base.md @@ -527,7 +527,7 @@ tostring(acandy.Doctype.HTML) --> '' ### `acandy.extend_env` ```lua -function acandy.extend_env(env: table) -> nil +function acandy.extend_env(env: table): () ``` Extend the environment in place with `acandy.a` as `__index`, e.g., `_ENV`. This makes it possible to directly use the tag name rather than tediously type `a.`, unless there is a naming conflict with local variables or global variables. @@ -582,7 +582,7 @@ print( ### `acandy.to_extended_env` ```lua -function acandy.to_extended_env(env: table) -> table +function acandy.to_extended_env(env: table): table ``` Similar to `acandy.extend_env`, but returns a new table instead of modifying the original table. @@ -630,6 +630,67 @@ print(get_article()) ``` +## Configuration | 配置 + +ACandy defaults to HTML mode (currently only HTML mode, XML will be supported in the future), and has predefined some HTML void elements and raw text elements (see [config.lua](/config.lua)). +ACandy 默认为 HTML 模式(目前只有 HTML 模式,以后将会支持 XML),并预定义了一些 HTML 空元素和原始文本元素(见 [config.lua](/config.lua))。 + +ACandy does not support modifying global configuration. To modify the configuration, create a new configured `ACandy` instance. The function signature is as follows. +ACandy 不支持修改全局配置,要修改配置,请创建一个配置后的 `ACandy` 实例,其函数签名如下。 + +```lua +type Config = { + void_elements: { [string]: true }, + raw_text_elements: { [string]: true }, +} +function acandy.ACandy(output_type: 'html', modify_config?: (config: Config) -> ()): table +``` + +The `output_type` parameter currently only accepts `'html'`. The `modify_config` parameter (optional) is a function that takes a table as a parameter and has no return value. The table passed to this function is the basis of the new configuration, and you can modify this value in the function, for example: +其中,`output_type` 参数目前只能传入 `'html'`。`modify_config` 参数(可选)是一个函数,接收一个表作为参数,无返回值。传入该函数的表是新配置的基础,你可以在函数中修改这个值,例如: + +```lua +local acandy = require('acandy').ACandy('html', function (config) + -- add a void element + config.void_elements['my-void-element'] = true + -- remove `br` from void elements + config.void_elements.br = nil + -- add a raw text element + config.raw_text_elements['my-raw-text-element'] = true + -- remove `script` from raw text elements + config.raw_text_elements.script = nil +end) +local a = acandy.a +print( + acandy.Fragment { + a['my-void-element'], + a.br, + a['my-raw-text-element'] '< > &', + a.script 'let val = 2 > 1', + } +) +``` + +```html + +

+< > & + +``` + +To use this configuration throughout the project, you can export the configured `ACandy` instance and import it in other files. +要想在整个项目使用这个配置,可以导出配置后的 `ACandy` 实例,然后在其他文件中导入这个实例。 + +```lua +-- my_acandy.lua +return require('acandy').ACandy('html', function (config) + -- ... +end) + +-- other files +local acandy = require('my_acandy') +``` + ## Concepts | 概念 ### Table-like values | 类表值 diff --git a/docs/README.zh.md b/docs/README.zh.md index c92fe80..43dff73 100644 --- a/docs/README.zh.md +++ b/docs/README.zh.md @@ -461,7 +461,7 @@ tostring(acandy.Doctype.HTML) --> '' ### `acandy.extend_env` ```lua -function acandy.extend_env(env: table) -> nil +function acandy.extend_env(env: table): () ``` 使用 `acandy.a` 作为 `__index` 来扩展传入的环境,例如 `_ENV`。这使得能够直接使用元素名不需要显式地使用 `a.`,除非与局部变量或全局变量有命名冲突。 @@ -514,7 +514,7 @@ print( ### `acandy.to_extended_env` ```lua -function acandy.to_extended_env(env: table) -> table +function acandy.to_extended_env(env: table): table ``` 类似于 `acandy.extend_env`,但返回一个新表而不是修改原表。 @@ -561,6 +561,63 @@ print(get_article()) ``` +## 配置 + +ACandy 默认为 HTML 模式(目前只有 HTML 模式,以后将会支持 XML),并预定义了一些 HTML 空元素和原始文本元素(见 [config.lua](/config.lua))。 + +ACandy 不支持修改全局配置,要修改配置,请创建一个配置后的 `ACandy` 实例,其函数签名如下。 + +```lua +type Config = { + void_elements: { [string]: true }, + raw_text_elements: { [string]: true }, +} +function acandy.ACandy(output_type: 'html', modify_config?: (config: Config) -> ()): table +``` + +其中,`output_type` 参数目前只能传入 `'html'`。`modify_config` 参数(可选)是一个函数,接收一个表作为参数,无返回值。传入该函数的表是新配置的基础,你可以在函数中修改这个值,例如: + +```lua +local acandy = require('acandy').ACandy('html', function (config) + -- add a void element + config.void_elements['my-void-element'] = true + -- remove `br` from void elements + config.void_elements.br = nil + -- add a raw text element + config.raw_text_elements['my-raw-text-element'] = true + -- remove `script` from raw text elements + config.raw_text_elements.script = nil +end) +local a = acandy.a +print( + acandy.Fragment { + a['my-void-element'], + a.br, + a['my-raw-text-element'] '< > &', + a.script 'let val = 2 > 1', + } +) +``` + +```html + +

+< > & + +``` + +要想在整个项目使用这个配置,可以导出配置后的 `ACandy` 实例,然后在其他文件中导入这个实例。 + +```lua +-- my_acandy.lua +return require('acandy').ACandy('html', function (config) + -- ... +end) + +-- other files +local acandy = require('my_acandy') +``` + ## 概念 ### 类表值 diff --git a/utils.lua b/utils.lua index 852b056..36af33f 100644 --- a/utils.lua +++ b/utils.lua @@ -60,6 +60,9 @@ function utils.map_varargs(func, ...) return t end +---@generic T +---@param list T[] +---@return {[T]: true} function utils.list_to_bool_dict(list) local dict = {} for _, v in ipairs(list) do