-- lua-placeholders-types.lua -- Copyright 2024 E. Nijenhuis -- -- This work may be distributed and/or modified under the -- conditions of the LaTeX Project Public License, either version 1.3c -- of this license or (at your option) any later version. -- The latest version of this license is in -- http://www.latex-project.org/lppl.txt -- and version 1.3c or later is part of all distributions of LaTeX -- version 2005/12/01 or later. -- -- This work has the LPPL maintenance status ‘maintained’. -- -- The Current Maintainer of this work is E. Nijenhuis. -- -- This work consists of the files lua-placeholders.sty -- lua-placeholders-manual.pdf lua-placeholders.lua -- lua-placeholders-common.lua lua-placeholders-namespace.lua -- lua-placeholders-parser.lua and lua-placeholders-types.lua require('lua-placeholders-common') function table.copy(t) local u = { } for k, v in pairs(t) do u[k] = v end return setmetatable(u, getmetatable(t)) end base_param = {} function base_param:new(o) o = o or {} setmetatable(o, self) self.__index = self return o end function base_param:is_set() return self and ((self.values or self.fields or self.value) ~= nil) end function base_param:raw_val() return self.value or self.values or self.default end function base_param:val() return self:raw_val() end function base_param:to_upper() local val = self:val() if type(val) == 'string' then return val:upper() elseif type(self.placeholder) == 'string' then return '[' .. self.placeholder:upper() .. ']' end end function base_param:print_val() local value = self:val() if value ~= nil then tex.sprint(value) else tex.sprint(lua_placeholders_toks.placeholder_format, '{', self.placeholder or self.key, '}') end end bool_param = base_param:new{ type = 'bool' } function bool_param:new(key, _o) local o = { key = key, default = _o.default } setmetatable(o, self) self.__index = self tex.sprint(lua_placeholders_toks.new_bool, '{', o.key, '}') return o end function bool_param:raw_val() local value if self.value ~= nil then value = tostring(self.value) elseif self.default ~= nil then value = tostring(self.default) else value = 'false' end return value end function bool_param:set_bool(key) tex.sprint(lua_placeholders_toks.set_bool, '{', key, '}{', self:val(), '}') end str_param = base_param:new{ type = 'string' } function str_param:new(key, _o) local o = { key = key, placeholder = _o.placeholder, default = _o.default } setmetatable(o, self) self.__index = self return o end function str_param:val() local value = self:raw_val() if value then if type(value) == 'table' and type(value.year) == 'number' then -- tinyyaml turns YYYY-MM-DD scalars into a Lua date table. Hand -- the three components to a TeX-side wrapper so the engine/format -- decides how to render (LaTeX side uses \printdateTeX from -- isodate; plain LuaTeX falls back to Y/M/D text). value = '\\paramdateformat{' .. value.year .. '}{' .. value.month .. '}{' .. value.day .. '}' end local formatted, _ = string.gsub(value, '\n', ' ') return formatted end end number_param = base_param:new{ type = 'number' } function number_param:new(key, _o) local o = { key = key, placeholder = _o.placeholder, default = _o.default } setmetatable(o, self) self.__index = self return o end function number_param:raw_val() if self.value ~= nil or self.default ~= nil then return self.value or self.default end end function number_param:val() local val = self:raw_val() if val ~= nil then -- Hand the raw value to a TeX-side formatter. LaTeX side wraps it -- in \numprint when numprint is loaded; plain LuaTeX prints as-is. return '\\paramnumberformat{' .. tostring(val) .. '}' end end function number_param:print_num() texio.write_nl('Warning: number_param:print_num is deprecated. Use number_param:print_val instead') self:print_val() end list_param = base_param:new{ type = 'list' } function list_param:new(key, _o) -- Two ways to spell the item shape: -- item type: -- short form for primitive items -- item: { type: , fields: {...} } -- full nested spec, needed for -- object/list item types local item_spec if type(_o.item) == 'table' then item_spec = _o.item else item_spec = { type = (_o["item type"] or 'string') } end local o = { key = key, item_type = base_param.define('list_item', item_spec), default = {} } if _o.default then for _, default_val in ipairs(_o.default) do local v = table.copy(o.item_type) v:load('list_item', default_val) table.insert(o.default, v) end end setmetatable(o, self) self.__index = self return o end function list_param:val() return self.values or self.default or {} end function list_param:print_val() local list = self:val() if #list > 0 then if not self.values then tex.sprint(lua_placeholders_toks.placeholder_format, '{') end tex.sprint(list[1]:val()) for i = 2, #list do tex.sprint(lua_placeholders_toks.list_conj, list[i]:val()) end if not self.values then tex.sprint('}') end end end object_param = base_param:new{ type = 'object' } function object_param:new(key, _o) local o = { key = key, fields = {}, default = _o.default } for _key, field in pairs(_o.fields) do o.fields[_key] = base_param.define(_key, field) end setmetatable(o, self) self.__index = self return o end table_param = base_param:new{ type = 'table' } function table_param:new(key, _o) local o = { key = key, columns = {} } for col_key, col in pairs(_o.columns) do o.columns[col_key] = base_param.define(col_key, col) end setmetatable(o, self) self.__index = self return o end function base_param.define(key, o) if o.type then if o.type == 'bool' then return bool_param:new(key, o) elseif o.type == 'string' then return str_param:new(key, o) elseif o.type == 'number' then return number_param:new(key, o) elseif o.type == 'list' then return list_param:new(key, o) elseif o.type == 'object' then return object_param:new(key, o) elseif o.type == 'table' then return table_param:new(key, o) else texio.write_nl('Warning: no such parameter type ' .. o.type) end else error('ERROR: parameter must have a "type" field') end end function base_param:load(key, value) if self.type == 'list' then self.values = {} for _, val in ipairs(value) do local param = table.copy(self.item_type) param:load('list-item', val) table.insert(self.values, param) end elseif self.type == 'table' then self.values = {} for _, row_vals in ipairs(value) do local row = {} for col_key, col in pairs(self.columns) do local cell = table.copy(col) cell:load(col.key, row_vals[col_key]) row[col_key] = cell end table.insert(self.values, row) end elseif self.type == 'object' then -- Make per-instance field copies before loading. Without this, copying -- an object column to use as a row cell shares the field params with -- the column spec, so loading row 2 mutates the same field instance -- and silently overwrites row 1's values. local own_fields = {} for field_key, field in pairs(self.fields) do local field_copy = table.copy(field) field_copy.value = nil local field_val = value and value[field_key] if field_val ~= nil then field_copy:load(field_key, field_val) end own_fields[field_key] = field_copy end self.fields = own_fields else self.value = value end if self.type == 'bool' then self:set_bool(key) end end