From c7892fe1a11367872de6f3ee0fa074b39824e59c Mon Sep 17 00:00:00 2001 From: Norbert Preining Date: Mon, 31 Oct 2022 03:04:01 +0000 Subject: CTAN sync 202210310303 --- macros/luatex/latex/luaoptions/Contributors.md | 6 + macros/luatex/latex/luaoptions/LICENSE | 21 ++ macros/luatex/latex/luaoptions/README.md | 15 + macros/luatex/latex/luaoptions/latexmkrc | 2 + macros/luatex/latex/luaoptions/luaoptions-lib.lua | 240 +++++++++++++++ macros/luatex/latex/luaoptions/luaoptions.lua | 329 +++++++++++++++++++++ macros/luatex/latex/luaoptions/luaoptions.pdf | Bin 0 -> 28593 bytes macros/luatex/latex/luaoptions/luaoptions.sty | 36 +++ macros/luatex/latex/luaoptions/luaoptions.tex | 188 ++++++++++++ macros/luatex/latex/luaoptions/luaoptionsbase.cls | 37 +++ .../luatex/latex/luaoptions/luaoptionsmanual.cls | 41 +++ 11 files changed, 915 insertions(+) create mode 100644 macros/luatex/latex/luaoptions/Contributors.md create mode 100644 macros/luatex/latex/luaoptions/LICENSE create mode 100644 macros/luatex/latex/luaoptions/README.md create mode 100644 macros/luatex/latex/luaoptions/latexmkrc create mode 100644 macros/luatex/latex/luaoptions/luaoptions-lib.lua create mode 100644 macros/luatex/latex/luaoptions/luaoptions.lua create mode 100644 macros/luatex/latex/luaoptions/luaoptions.pdf create mode 100644 macros/luatex/latex/luaoptions/luaoptions.sty create mode 100644 macros/luatex/latex/luaoptions/luaoptions.tex create mode 100644 macros/luatex/latex/luaoptions/luaoptionsbase.cls create mode 100644 macros/luatex/latex/luaoptions/luaoptionsmanual.cls (limited to 'macros/luatex') diff --git a/macros/luatex/latex/luaoptions/Contributors.md b/macros/luatex/latex/luaoptions/Contributors.md new file mode 100644 index 0000000000..ab09dc720d --- /dev/null +++ b/macros/luatex/latex/luaoptions/Contributors.md @@ -0,0 +1,6 @@ +## Authors + +Luaoptions is developped by: + +- [Fr. Jacques Peron](mailto:cataclop@hotmail.com); [@jperon](https://github.com/jperon) +- [Urs Liska](mailto:git@ursliska.de); [@uliska](https://github.com/uliska), [@openlilylib](https://github.com/openlilylib) diff --git a/macros/luatex/latex/luaoptions/LICENSE b/macros/luatex/latex/luaoptions/LICENSE new file mode 100644 index 0000000000..479e0ec593 --- /dev/null +++ b/macros/luatex/latex/luaoptions/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015--2022 the lualatex-tools Project + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/macros/luatex/latex/luaoptions/README.md b/macros/luatex/latex/luaoptions/README.md new file mode 100644 index 0000000000..19ad0c6770 --- /dev/null +++ b/macros/luatex/latex/luaoptions/README.md @@ -0,0 +1,15 @@ +Main author: [Fr. Jacques Peron](mailto:cataclop@hotmail.com)\nThis material is subject to the MIT license.\n +# `luaoptions` +Option handling for LuaLaTeX packages + +This package has been extracted from +[`lyluatex`](https://github.com/jperon/lyluatex) where it was originally +developed as supporting functionality. However, as it turned out it is useful +as a building block for LuaLaTeX packages in general, and the development of +[`luaformatters`](https://github.com/lualatex-tools/luaformatters) made it +clear that the option handling code had to be moved to a standalone package. + +Initially there is no extensive documentation available since the package wasn't +originally intended to be exposed to third-party packages or even end-users; +the best sources of documentation are `lyluatex` and `luaformatters` codes, +and the docstrings of `luaoptions.lua` and `luaoptions-lib.lua`. diff --git a/macros/luatex/latex/luaoptions/latexmkrc b/macros/luatex/latex/luaoptions/latexmkrc new file mode 100644 index 0000000000..d654412169 --- /dev/null +++ b/macros/luatex/latex/luaoptions/latexmkrc @@ -0,0 +1,2 @@ +$pdf_mode="1"; +$pdflatex="lualatex --shell-escape %O %S"; diff --git a/macros/luatex/latex/luaoptions/luaoptions-lib.lua b/macros/luatex/latex/luaoptions/luaoptions-lib.lua new file mode 100644 index 0000000000..4f797da12e --- /dev/null +++ b/macros/luatex/latex/luaoptions/luaoptions-lib.lua @@ -0,0 +1,240 @@ +-- luacheck: ignore ly log self luatexbase internalversion font fonts tex token kpse status +local err, warn, info, log = luatexbase.provides_module({ + name = "luaoptions-lib", + version = '0.8', --LUAOPTIONS_VERSION + date = "2022/10/30", --LUAOPTIONS_DATE + description = "Module luaoptions-lib.", + author = "The lualatex-tools Project", + copyright = "2015-2022 - the lualatex-tools Project", + license = "MIT", +}) + +local lib = {} +lib.TEX_UNITS = {'bp', 'cc', 'cm', 'dd', 'in', 'mm', 'pc', 'pt', 'sp', 'em', +'ex'} + +------------------------- +-- General tool functions + +function lib.basename(str) +--[[ + Given the full path to a file, return only the file name (without path). + If there is no slash, return the full name. +--]] + return str:gsub(".*/(.*)", "%1") or str +end + + +function lib.contains(table_var, value) +--[[ + Returns the key if the given table contains the given value, or nil. + A value of 'false' (string) is considered equal to false (Boolean). +--]] + for k, v in pairs(table_var) do + if v == value then return k + elseif v == 'false' and value == false then return k + end + end +end + + +function lib.contains_key(table_var, key) +-- Returs true if the given key is present in the table, nil otherwise. + for k in pairs(table_var) do + if k == key then return true end + end +end + + +function lib.convert_unit(value) +--[[ + Convert a LaTeX unit, if possible. + TODO: Understand what this *really* does, what is accepted and returned. +--]] + if not value then return 0 + elseif value == '' then return false + elseif value:match('\\') then + local n, u = value:match('^%d*%.?%d*'), value:match('%a+') + if n == '' then n = 1 end + return tonumber(n) * tex.dimen[u] / tex.sp("1pt") + else return ('%f'):format(tonumber(value) or tex.sp(value) / tex.sp("1pt")) + end +end + + +function lib.current_font_size() +--[[ + Convenience function to return the font size of the current font +--]] + return lib.fontinfo(font.current()).size +end + + +function lib.dirname(str) +--[[ + Given the full path to a file, return only the path (without file name), + including the last slash. If there is no slash, return an empty string. +--]] + return str:gsub("(.*/).*", "%1") or '' +end + + +local fontdata = fonts.hashes.identifiers +function lib.fontinfo(id) +--[[ + Return a LuaTeX font object based on the given ID +--]] + return fontdata[id] or font.getfont(id) +end + + +function lib.max(a, b) + a, b = tonumber(a), tonumber(b) + if a > b then return a else return b end +end + + +function lib.min(a, b) + a, b = tonumber(a), tonumber(b) + if a < b then return a else return b end +end + + +function lib.mkdirs(str) + local path + if str:sub(1, 1) == '/' then path = '' else path = '.' end + for dir in str:gmatch('([^%/]+)') do + path = path .. '/' .. dir + lfs.mkdir(path) + end +end + + +function lib.orderedkeys(t) + local orderedIndex = {} + for k in pairs(t) do table.insert(orderedIndex, k) end + table.sort(orderedIndex) + return orderedIndex +end + + +function lib.orderedpairs(t) + local key + local i = 0 + local orderedIndex = lib.orderedkeys(t) + return function () + i = i + 1 + key = orderedIndex[i] + if key then return key, t[key] end + end +end + + +function lib.print_table(t, indent) +-- Recursively print a table, avoiding recursion loops + indent = indent or ' ' + local visited = {} + local skips = 0 + + local function _print_table(_t, _ind) + local keys = lib.orderedkeys(_t) + local display_keys = {} + local length = 0 + for key, _ in pairs(_t) do + length = lib.max(length, #tostring(key)) + end + for key, _ in pairs(_t) do + display_key = tostring(key) + display_keys[key] = display_key .. string.rep( + ' ', length - #display_key) + end + for k, v in lib.orderedpairs(_t) do + if type(v) == 'table' then + if visited[v] then + skips = skips + 1 + else + visited[v] = true + print(_ind..display_keys[k], v) + if #_ind > 40 then err("k") end + _print_table(v, _ind..indent) + end + else + if v == '' then v = '(empty string)' end + print(_ind..display_keys[k], v) + end + end + end + + print() + visited[t] = true + print("Print table:", t) + _print_table(t, indent) + print() + if skips > 0 then + print(string.format('%s recursive tables skipped', skips)) + end + print("============") + print() +end + +function lib.readlinematching(s, f) + if f then + local result = '' + while result and not result:find(s) do result = f:read() end + f:close() + return result + end +end + + +function lib.splitext(str, ext) +--[[ + If 'ext' is supplied return str stripped of the given extension, + otherwise return the base and extension (if any) +--]] + return ext and (str:match('(.*)%.'..ext..'$') or str) + or (str:match('(.*)%.(%w*)$') or str) +end + + +------------------------------------ +-- Engine, version, TeX distribution + +local function _loaded(ext) +--[[ + Returns a function that checks whether a class or a package is loaded + https://tex.stackexchange.com/questions/501635/ +--]] + local fmt = "ver@%s." .. ext + return function(name) + local macro = token.get_macro(fmt:format(name)) + return macro ~= nil + end +end + +-- Lua implementations of \@ifpackageloaded and \@ifclassloaded +lib.class_loaded = _loaded('cls') +lib.package_loaded = _loaded('sty') + +local tex_engine = {} +setmetatable(tex_engine, tex_engine) + +function tex_engine:__call() +--[[ + Defines the properties extracted from the first line of jobname.log. +--]] + local f = io.open(tex.jobname..'.log') + if not f then return end + self.engine, self.engine_version, self.dist, self.dist_version, self.format, self.format_version = + f:read():match( + 'This is ([^,]*), Version ([^%(]*) %(([^%)]*) ([^%)]*)%)%s+%(format=([^%)]*) ([^)]*)%)' + ) + f:close() + return self +end + +function tex_engine:__index(k) return rawget(self(), k) end + + +lib.tex_engine = tex_engine +return lib diff --git a/macros/luatex/latex/luaoptions/luaoptions.lua b/macros/luatex/latex/luaoptions/luaoptions.lua new file mode 100644 index 0000000000..16f25150bf --- /dev/null +++ b/macros/luatex/latex/luaoptions/luaoptions.lua @@ -0,0 +1,329 @@ +-- luacheck: ignore ly warn info log self luatexbase internalversion font fonts tex token kpse status +local err, warn, info, log = luatexbase.provides_module({ + name = "luaoptions", + version = '0.8', --LUAOPTIONS_VERSION + date = "2022/10/30", --LUAOPTIONS_DATE + description = "Module luaoptions.", + author = "The lualatex-tools Project", + copyright = "2015-2022 - the lualatex-tools Project", + license = "MIT", +}) + +--[[ + This module provides functionality to handle package options and make them + configurable in a fine-grained fashion as + - package options + - local options (for individual instances of commands/environments) + - changed “from here on” within a document. + +-- ]] + +local lib = require(kpse.find_file("luaoptions-lib.lua") or "luaoptions-lib.lua") +local optlib = {} -- namespace for the returned table +local Opts = {options = {}} -- Options class +Opts.__index = function (self, k) return self.options[k] or rawget(Opts, k) end +setmetatable(Opts, Opts) +local clients = {} + +function Opts:new(declarations) +--[[ + Declare an options object along with default and accepted values. + To *some* extent also provide type checking. + - declarations: a definition table stored in the calling module (see below) + Each entry in the 'declarations' table represents one package option, with each + value being an array (table with integer indexes instead of keys). For + details please refer to the manual. +--]] + local o = setmetatable( + { + declarations = declarations, + options = {} + }, + self + ) + for k, v in pairs(declarations) do + o.options[k] = v[1] or '' + end + return o +end + +function Opts:check_local_options(opts, ignore_declarations) +--[[ + Parse the given options (options passed to a command/environment), + sanitize them against the global package options and return a table + with the local options that should then supersede the global options. + If ignore_declaration is given any non-false value the sanitization + step is skipped (i.e. local options are only parsed and duplicates + rejected). +--]] + local options = {} + local next_opt = opts:gmatch('([^,]+)') -- iterator over options + for opt in next_opt do + local k, v = opt:match('([^=]+)=?(.*)') + if k then + if v and v:sub(1, 1) == '{' then -- handle keys with {multiple, values} + while select(2, v:gsub('{', '')) ~= select(2, v:gsub('}', '')) do v = v..','..next_opt() end + v = v:sub(2, -2) -- remove { } + end + if not ignore_declarations then + k, v = self:sanitize_option(k:gsub('^%s', ''), v:gsub('^%s', '')) + end + if k then + if options[k] then err('Option %s is set two times for the same score.', k) + else options[k] = v + end + end + end + end + return options +end + +function Opts:has_option(name) +--[[ + Returns true if the given option is declared +--]] + return self.declarations[name] ~= nil +end + +function Opts:is_neg(k) +--[[ + Type check for a 'negative' option. This is an existing option + name prefixed with 'no' (e.g. 'noalign') +--]] + local _, i = k:find('^no') + return i and lib.contains_key(self.declarations, k:sub(i + 1)) +end + +function Opts:sanitize_option(k, v) +--[[ + Check and (if necessary) adjust the value of a given option. + Reject undefined options + Check 'negative' options + Handle boolean options (empty strings or 'false'), set them to real booleans +--]] + local declarations = self.declarations + if k == '' or k == 'noarg' then return end + if not lib.contains_key(declarations, k) then err('Unknown option: '..k) end + -- aliases + if declarations[k] and declarations[k][2] == optlib.is_alias then + if declarations[k][1] == v then return + else k = declarations[k] end + end + -- boolean + if v == 'false' then v = false end + -- negation (for example, noindent is the negation of indent) + if self:is_neg(k) then + if v ~= nil and v ~= 'default' then + k = k:gsub('^no(.*)', '%1') + v = not v + else return + end + end + return k, v +end + +function Opts:set_option(k, v) +--[[ + Set an option for the given prefix to be in effect from this point on. + Raises an error if the option is not declared or does not meet the + declared expectations. (TODO: The latter has to be integrated by extracting + optlib.validate_option from optlib.validate_options and call it in + sanitize_option). +--]] + k, v = self:sanitize_option(k, v) + if k then + self.options[k] = v + self:validate_option(k) + end +end + +function Opts:use_option(key) +--[[ + Call an option and write its value to the TeX space. + This is a shorthand for accessing options from the TeX side + (rather than having to write tex.print(XX_opts.some_options)) +--]] + local option = self.options[key] + if option then + tex.print(option:explode('\n')) + else + err(string.format("Trying to access non-existent option %s", key)) + end +end + +function Opts:validate_option(key, options_obj) +--[[ + Validate an (already sanitized) option against its expected values. + With options_obj a local options table can be provided, + otherwise the global options stored in OPTIONS are checked. +--]] + local package_opts = self.declarations + local options = options_obj or self.options + local unexpected + if options[key] == 'default' then + -- Replace 'default' with an actual value + options[key] = package_opts[key][1] + unexpected = options[key] == nil + end + if not lib.contains(package_opts[key], options[key]) and package_opts[key][2] then + -- option value is not in the array of accepted values + if type(package_opts[key][2]) == 'function' then package_opts[key][2](key, options[key]) + else unexpected = true + end + end + if unexpected then + err([[ + Unexpected value "%s" for option %s: + authorized values are "%s" + ]], + options[key], key, table.concat(package_opts[key], ', ') + ) + end +end + +function Opts:validate_options(options_obj) +--[[ + Validate the given set of options against the option declaration + table for the given prefix. + With options_obj a local options table can be provided, + otherwise the global options stored in OPTIONS are checked. +--]] + for k, _ in lib.orderedpairs(self.declarations) do + self:validate_option(k, options_obj) + end +end + + +function optlib.is_alias() +--[[ + This function doesn't do anything, but if an option is defined + as an alias, its second parameter will be this function, so the + test declarations[k][2] == optlib.is_alias in Opts:sanitize_options + will return true. +--]] +end + + +function optlib.is_dim(k, v) +--[[ + Type checking for options that accept a LaTeX dimension. + This can be + - a number (integer or float) + - a number with unit + - a (multiplied) TeX length + (see error message in code for examples) +--]] + if v == '' or v == false or tonumber(v) then return true end + local n, sl, u = v:match('^%d*%.?%d*'), v:match('\\'), v:match('%a+') + -- a value of number - backslash - length is a dimension + -- invalid input will be prevented in by the LaTeX parser already + if n and sl and u then return true end + if n and lib.contains(lib.TEX_UNITS, u) then return true end + err([[ +Unexpected value "%s" for dimension %s: +should be either a number (for example "12"), +a number with unit, without space ("12pt"), +or a (multiplied) TeX length (".8\linewidth") +]], + v, k + ) +end + + +function optlib.is_neg(k, _) +--[[ + Type check for a 'negative' option. At this stage, + we only check that it begins with 'no'. +--]] + return k:find('^no') +end + + +function optlib.is_num(_, v) +--[[ + Type check for number options +--]] + return v == '' or tonumber(v) +end + + +function optlib.is_str(_, v) +--[[ + Type check for string options +--]] + return type(v) == 'string' +end + + +function optlib.merge_options(base_opt, super_opt) +--[[ + Merge two tables. + Create a new table as a copy of base_opt, then merge with + super_opt. Entries in super_opt supersede (i.e. overwrite) + colliding entries in base_opt. +--]] + local result = {} + for k, v in pairs(base_opt) do result[k] = v end + for k, v in pairs(super_opt) do result[k] = v end + return result +end + +function optlib.register(client_name, declarations) +--[[ + Register a client as package options. + - Create a new Opts object, + - initialize it, + - store it in a global variable _opts, + - write the code to handle the package options. +--]] + local o = Opts:new(declarations) + local exopt = '' + for k, v in pairs(declarations) do + tex.sprint(string.format([[ +\DeclareOptionX{%s}{\setluaoption{%s}{%s}{#1}}%% +]], + k, client_name, k + )) + exopt = exopt..k..'='..(v[1] or '')..',' + end + clients[client_name] = o + tex.sprint([[\ExecuteOptionsX{]]..exopt..[[}%%]], [[\ProcessOptionsX]]) +end + +function optlib.client(name) +--[[ + Return the FormattersTable instance registered with the given client name. + Return 'nil' if no client is found. +--]] + return clients[name] +end + +function optlib.get_option(client_name, k) +--[[ + Get an option's value from a registered client. + Raises an error if the client hasn't been registered. +--]] + local client = optlib.client(client_name) + return client.options[k] +end + +function optlib.set_option(client_name, k, v) +--[[ + Set an option. + Look up a registered client and set an option. + Produces an error if the client hasn't been registered. +--]] + local client = optlib.client(client_name) + client:set_option(k, v) +end + +function optlib.use_option(client_name, k) +--[[ + Look up an option and write it to LaTex. +--]] + local client = optlib.client(client_name) + client:use_option(k) +end + +optlib.Opts = Opts +return optlib diff --git a/macros/luatex/latex/luaoptions/luaoptions.pdf b/macros/luatex/latex/luaoptions/luaoptions.pdf new file mode 100644 index 0000000000..3339fbeb63 Binary files /dev/null and b/macros/luatex/latex/luaoptions/luaoptions.pdf differ diff --git a/macros/luatex/latex/luaoptions/luaoptions.sty b/macros/luatex/latex/luaoptions/luaoptions.sty new file mode 100644 index 0000000000..559c615ab6 --- /dev/null +++ b/macros/luatex/latex/luaoptions/luaoptions.sty @@ -0,0 +1,36 @@ +% luaoptions support package. +% +% Copyright (C) 2015-2022 the lualatex-tools Project +% License: MIT +% This file is part of luaoptions. + +\NeedsTeXFormat{LaTeX2e}% +\ProvidesPackage{luaoptions}[2022/10/30 v0.8] + +% Dependencies +\RequirePackage{luatexbase} +\RequirePackage{luaotfload} +\RequirePackage{xkeyval} + +\directlua{ + lua_options = require(kpse.find_file("luaoptions.lua") or "luaoptions.lua") +} + +% Set an option in a registered client +\newcommand{\setluaoption}[3]{% + \directlua{ + lua_options.set_option( + '\luatexluaescapestring{#1}', + '\luatexluaescapestring{#2}', + '\luatexluaescapestring{#3}') + } +} + +% Use an option and directly write it to LaTeX +\newcommand{\useluaoption}[2]{% + \directlua{ + lua_options.use_option( + '\luatexluaescapestring{#1}', + '\luatexluaescapestring{#2}') + }% +} diff --git a/macros/luatex/latex/luaoptions/luaoptions.tex b/macros/luatex/latex/luaoptions/luaoptions.tex new file mode 100644 index 0000000000..627583f678 --- /dev/null +++ b/macros/luatex/latex/luaoptions/luaoptions.tex @@ -0,0 +1,188 @@ +% Options for packages loaded elsewhere +\PassOptionsToPackage{unicode}{hyperref} +\PassOptionsToPackage{hyphens}{url} +% +\documentclass[ +]{luaoptionsmanual} +\usepackage{amsmath,amssymb} +\usepackage[]{libertine} +\usepackage{iftex} +\ifPDFTeX + \usepackage[T1]{fontenc} + \usepackage[utf8]{inputenc} + \usepackage{textcomp} % provide euro and other symbols +\else % if luatex or xetex + \usepackage{unicode-math} + \defaultfontfeatures{Scale=MatchLowercase} + \defaultfontfeatures[\rmfamily]{Ligatures=TeX,Scale=1} +\fi +% Use upquote if available, for straight quotes in verbatim environments +\IfFileExists{upquote.sty}{\usepackage{upquote}}{} +\IfFileExists{microtype.sty}{% use microtype if available + \usepackage[]{microtype} + \UseMicrotypeSet[protrusion]{basicmath} % disable protrusion for tt fonts +}{} +\makeatletter +\@ifundefined{KOMAClassName}{% if non-KOMA class + \IfFileExists{parskip.sty}{% + \usepackage{parskip} + }{% else + \setlength{\parindent}{0pt} + \setlength{\parskip}{6pt plus 2pt minus 1pt}} +}{% if KOMA class + \KOMAoptions{parskip=half}} +\makeatother +\usepackage{xcolor} +\IfFileExists{xurl.sty}{\usepackage{xurl}}{} % add URL line breaks if available +\IfFileExists{bookmark.sty}{\usepackage{bookmark}}{\usepackage{hyperref}} +\hypersetup{ + pdfauthor={Fr.~Jacques Peron; Urs Liska}, + hidelinks, + pdfcreator={LaTeX via pandoc}} +\urlstyle{same} % disable monospaced font for URLs +\usepackage{color} +\usepackage{fancyvrb} +\newcommand{\VerbBar}{|} +\newcommand{\VERB}{\Verb[commandchars=\\\{\}]} +\DefineVerbatimEnvironment{Highlighting}{Verbatim}{commandchars=\\\{\}} +% Add ',fontsize=\small' for more characters per line +\newenvironment{Shaded}{}{} +\newcommand{\AlertTok}[1]{\textcolor[rgb]{1.00,0.00,0.00}{\textbf{#1}}} +\newcommand{\AnnotationTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{#1}}}} +\newcommand{\AttributeTok}[1]{\textcolor[rgb]{0.49,0.56,0.16}{#1}} +\newcommand{\BaseNTok}[1]{\textcolor[rgb]{0.25,0.63,0.44}{#1}} +\newcommand{\BuiltInTok}[1]{\textcolor[rgb]{0.00,0.50,0.00}{#1}} +\newcommand{\CharTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{#1}} +\newcommand{\CommentTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textit{#1}}} +\newcommand{\CommentVarTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{#1}}}} +\newcommand{\ConstantTok}[1]{\textcolor[rgb]{0.53,0.00,0.00}{#1}} +\newcommand{\ControlFlowTok}[1]{\textcolor[rgb]{0.00,0.44,0.13}{\textbf{#1}}} +\newcommand{\DataTypeTok}[1]{\textcolor[rgb]{0.56,0.13,0.00}{#1}} +\newcommand{\DecValTok}[1]{\textcolor[rgb]{0.25,0.63,0.44}{#1}} +\newcommand{\DocumentationTok}[1]{\textcolor[rgb]{0.73,0.13,0.13}{\textit{#1}}} +\newcommand{\ErrorTok}[1]{\textcolor[rgb]{1.00,0.00,0.00}{\textbf{#1}}} +\newcommand{\ExtensionTok}[1]{#1} +\newcommand{\FloatTok}[1]{\textcolor[rgb]{0.25,0.63,0.44}{#1}} +\newcommand{\FunctionTok}[1]{\textcolor[rgb]{0.02,0.16,0.49}{#1}} +\newcommand{\ImportTok}[1]{\textcolor[rgb]{0.00,0.50,0.00}{\textbf{#1}}} +\newcommand{\InformationTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{#1}}}} +\newcommand{\KeywordTok}[1]{\textcolor[rgb]{0.00,0.44,0.13}{\textbf{#1}}} +\newcommand{\NormalTok}[1]{#1} +\newcommand{\OperatorTok}[1]{\textcolor[rgb]{0.40,0.40,0.40}{#1}} +\newcommand{\OtherTok}[1]{\textcolor[rgb]{0.00,0.44,0.13}{#1}} +\newcommand{\PreprocessorTok}[1]{\textcolor[rgb]{0.74,0.48,0.00}{#1}} +\newcommand{\RegionMarkerTok}[1]{#1} +\newcommand{\SpecialCharTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{#1}} +\newcommand{\SpecialStringTok}[1]{\textcolor[rgb]{0.73,0.40,0.53}{#1}} +\newcommand{\StringTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{#1}} +\newcommand{\VariableTok}[1]{\textcolor[rgb]{0.10,0.09,0.49}{#1}} +\newcommand{\VerbatimStringTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{#1}} +\newcommand{\WarningTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{#1}}}} +\setlength{\emergencystretch}{3em} % prevent overfull lines +\providecommand{\tightlist}{% + \setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}} +\setcounter{secnumdepth}{-\maxdimen} % remove section numbering +\ifLuaTeX + \usepackage{selnolig} % disable illegal ligatures +\fi + +\title{\texttt{luaoptions}} +\usepackage{etoolbox} +\makeatletter +\providecommand{\subtitle}[1]{% add subtitle to \maketitle + \apptocmd{\@title}{\par {\large #1 \par}}{}{} +} +\makeatother +\subtitle{0.8} +\author{Fr.~Jacques Peron \and Urs Liska} +\date{\luaoptionsmanualdate} + +\begin{document} +\maketitle + +\thispagestyle{empty} +\enlargethispage*{3\baselineskip} + +\hypertarget{introduction}{% +\section{Introduction}\label{introduction}} + +\texttt{luaoptions} is a LuaLaTeX package providing extensive support +for handling \emph{options}, on package level and locally. It is hosted +at \url{https://github.com/lualatex-tools/luaoptions} and maintained by +Jacques Peron +(\href{mailto:cataclop@hotmail.com}{\nolinkurl{cataclop@hotmail.com}}) +and Urs Liska +(\href{mailto:git@ursliska.de}{\nolinkurl{git@ursliska.de}}). The +package was originally developed as part of the \texttt{lyluatex} +package\footnote{\url{https://github.com/jperon/lyluatex}} but has now +been extracted as a standalone package because we realized it can be a +useful tool for arbitrary LuaLaTeX packages having to deal with +\emph{options} -- both \emph{package options} and \emph{optional macro +arguments}. A \texttt{lyluatex} installation should not be a dependency +for packages that want to make use of option handling but are not +interested in music engraving \dots + +\texttt{luaoptions} allows the declaration of sets of options, along +with defaults, expected/allowed values and limited type checking. These +options can be enforced as package options, changed at any point during +a document, or overwritten locally by optional macro arguments. It is +also possible to instantiate an \texttt{Options} object as an +independent Lua object, without linking it to a package. +\texttt{luaoptions} can be used to enforce and prepopulate options, or +it can be used to simply handle the parsing of optional +\texttt{key=value} arguments into proper Lua tables. + +The main use of \texttt{luaoptions} is to define package options as +follows: + +\begin{Shaded} +\begin{Highlighting}[] +\FunctionTok{\textbackslash{}RequirePackage}\NormalTok{\{luaoptions\}} +\FunctionTok{\textbackslash{}directlua}\NormalTok{\{} +\NormalTok{ local \_opt = lua\_options} +\NormalTok{ lua\_options.register(\textquotesingle{}myoptions\textquotesingle{}, \{} +\NormalTok{ [\textquotesingle{}option\textquotesingle{}] = \{\textquotesingle{}default value\textquotesingle{}, \textquotesingle{}other value\textquotesingle{}, \textquotesingle{}\textquotesingle{}\},} +\NormalTok{ [\textquotesingle{}otheroption\textquotesingle{}] = \{\textquotesingle{}default\textquotesingle{}, validator\},} +\NormalTok{ [\textquotesingle{}yetanotheroptions\textquotesingle{}] = \{\},} +\NormalTok{ \})} +\NormalTok{\}} +\end{Highlighting} +\end{Shaded} + +\texttt{validator} is a function that returns \texttt{true} when the +defined option is valid, \texttt{false} otherwise. \texttt{luaoptions} +itself contains predefined such functions. + +After that, defined options are available in a lua table, which can be +used by the mean of the \texttt{client} command: + +\begin{Shaded} +\begin{Highlighting}[] +\KeywordTok{local}\NormalTok{ myopts }\OperatorTok{=}\NormalTok{ lua\_options}\OperatorTok{.}\NormalTok{client}\OperatorTok{(}\StringTok{\textquotesingle{}myoptions\textquotesingle{}}\OperatorTok{)} +\NormalTok{tex}\OperatorTok{.}\NormalTok{sprint}\OperatorTok{(}\NormalTok{myopts}\OperatorTok{.}\NormalTok{option}\OperatorTok{)} +\end{Highlighting} +\end{Shaded} + +This initial release does \emph{not} include documentation beyond this +short introduction; please refer to the docstrings in +\texttt{luaoptions.lua} and \texttt{luaoptions-lib.lua} for more +information. + +For the time being it is possible to look into how some of our own +packages make use of \texttt{luaoptions}: + +\begin{itemize} +\tightlist +\item + \url{https://github.com/jperon/lyluatex/blob/master/lyluatextools.sty} +\item + \url{https://github.com/jperon/lyluatex/blob/master/lyluatex.lua} +\item + \url{https://github.com/lualatex-tools/luaformatters/blob/master/luaformatters.sty} +\item + \url{https://github.com/lualatex-tools/luaformatters/blob/master/luaformatters.lua} +\item + \url{https://github.com/lualatex-tools/luaformatters/blob/master/submodules/luaformatters-formatter.lua} +\end{itemize} + +\end{document} diff --git a/macros/luatex/latex/luaoptions/luaoptionsbase.cls b/macros/luatex/latex/luaoptions/luaoptionsbase.cls new file mode 100644 index 0000000000..3f2ac5726e --- /dev/null +++ b/macros/luatex/latex/luaoptions/luaoptionsbase.cls @@ -0,0 +1,37 @@ +%Luaoptions LaTeX class. +% +% Copyright (C) 2015-2022 the lualatex-tools Project +% License: MIT +% This file is part of lualatex-tools. + +\NeedsTeXFormat{LaTeX2e} +\ProvidesClass{luaoptionsbase}[2022/10/30 v0.8] %%LUAOPTIONS_DATE LUAOPTIONS_VERSION + +\LoadClass[DIV=11]{scrartcl} +\RequirePackage{blindtext} +\RequirePackage{libertine} +\RequirePackage{listings} +\RequirePackage{minted} +\RequirePackage{pgffor} +\RequirePackage{fancyvrb} +\RequirePackage[colorlinks=true]{hyperref} + + +% Common formatting elements for manual and examples +\usepackage{xcolor} +\newcommand{\highlight}[1]{\textcolor{blue}{#1}} +\newcommand{\cmd}[1]{\highlight{\texttt{\textbackslash #1}}} +\newcommand{\option}[1]{\highlight{\texttt{#1}}} +\newcommand{\lyMargin}[1]{% + +\medskip +\hspace*{-1em}% +\noindent% +\highlight{#1} +\nopagebreak +} + +\newcommand{\lyOption}[2]{\lyMargin{\texttt{#1} {\small \emph{(#2)}}\index{#1}}} +\newcommand{\lyCmd}[1]{% +\lyMargin{\texttt{\textbackslash #1}\index{\textbackslash #1}}} +\newcommand{\lyIssue}[1]{\lyMargin{\textbf{\textcolor{red}{#1}}}} diff --git a/macros/luatex/latex/luaoptions/luaoptionsmanual.cls b/macros/luatex/latex/luaoptions/luaoptionsmanual.cls new file mode 100644 index 0000000000..5de9da32b6 --- /dev/null +++ b/macros/luatex/latex/luaoptions/luaoptionsmanual.cls @@ -0,0 +1,41 @@ +%Luaoptions LaTeX class for the manual. +% +% Copyright (C) 2015-2022 the lualatex-tools Project +% License: MIT +% This file is part of luaoptions. + +\NeedsTeXFormat{LaTeX2e} +\ProvidesClass{luaoptionsmanual}[2022/10/30 v0.8] %%LUAOPTIONS_DATE LUAOPTIONS_VERSION + +\LoadClass{luaoptionsbase} + +\RequirePackage{fontspec} +\RequirePackage{microtype} +\RequirePackage{libertine} +\setmonofont[Scale=MatchLowercase,StylisticSet=1]{InconsolataN} +\defaultfontfeatures{ + Ligatures=TeX, + Scale=MatchLowercase, + Numbers=Proportional, + Numbers=OldStyle +} +\frenchspacing + +\RequirePackage{makeidx} +\makeindex + +\newcommand{\linkexample}[2]{ + \addcontentsline{toc}{subsection}{#2} + \hypertarget{#1}{} +} + + +\newcommand{\luaoptionsmanualdate}{ + \directlua{ + local PC = string.char(37) + local date_fmt = PC..'Y-'..PC..'m-'..PC..'d' + local p = io.popen('git log -n1 --date=short --format='..PC..'"ad"', 'r') + tex.sprint(p and p:read('*a'):sub(1,-2) or os.date(date_fmt)) + if p then p:close() end + } +} -- cgit v1.2.3