diff options
Diffstat (limited to 'Build/source/texk')
6 files changed, 578 insertions, 0 deletions
diff --git a/Build/source/texk/kpathsea/ChangeLog b/Build/source/texk/kpathsea/ChangeLog index 61602772f0e..68be0ec6891 100644 --- a/Build/source/texk/kpathsea/ChangeLog +++ b/Build/source/texk/kpathsea/ChangeLog @@ -1,3 +1,8 @@ +2024-03-17 Karl Berry <karl@tug.org> + + * texmf.cnf (shell_escape_commands): add l3sys-query + after analysis on tex-live. https://ctan.org/pkg/l3sys-query + 2024-03-10 Karl Berry <karl@tug.org> * TL'24 release. diff --git a/Build/source/texk/kpathsea/texmf.cnf b/Build/source/texk/kpathsea/texmf.cnf index 4cb01e45b15..a3fa528268b 100644 --- a/Build/source/texk/kpathsea/texmf.cnf +++ b/Build/source/texk/kpathsea/texmf.cnf @@ -652,6 +652,7 @@ bibtex,bibtex8,\ extractbb,\ gregorio,\ kpsewhich,\ +l3sys-query,\ makeindex,\ memoize-extract.pl,\ memoize-extract.py,\ diff --git a/Build/source/texk/texlive/linked_scripts/Makefile.am b/Build/source/texk/texlive/linked_scripts/Makefile.am index d59131d316f..e6645224af8 100644 --- a/Build/source/texk/texlive/linked_scripts/Makefile.am +++ b/Build/source/texk/texlive/linked_scripts/Makefile.am @@ -162,6 +162,7 @@ texmf_other_scripts = \ kotex-utils/komkindex.pl \ kotex-utils/ttf2kotexfont.pl \ l3build/l3build.lua \ + l3sys-query/l3sys-query.lua \ latex-git-log/latex-git-log \ latex-papersize/latex-papersize.py \ latex2man/latex2man \ diff --git a/Build/source/texk/texlive/linked_scripts/Makefile.in b/Build/source/texk/texlive/linked_scripts/Makefile.in index 5d643ccb129..fe85e5e3e29 100644 --- a/Build/source/texk/texlive/linked_scripts/Makefile.in +++ b/Build/source/texk/texlive/linked_scripts/Makefile.in @@ -379,6 +379,7 @@ texmf_other_scripts = \ kotex-utils/komkindex.pl \ kotex-utils/ttf2kotexfont.pl \ l3build/l3build.lua \ + l3sys-query/l3sys-query.lua \ latex-git-log/latex-git-log \ latex-papersize/latex-papersize.py \ latex2man/latex2man \ diff --git a/Build/source/texk/texlive/linked_scripts/l3sys-query/l3sys-query.lua b/Build/source/texk/texlive/linked_scripts/l3sys-query/l3sys-query.lua new file mode 100755 index 00000000000..03e86296af4 --- /dev/null +++ b/Build/source/texk/texlive/linked_scripts/l3sys-query/l3sys-query.lua @@ -0,0 +1,569 @@ +#!/usr/bin/env texlua + +--[[ + +File l3sys-query.lua Copyright (C) 2024 The LaTeX 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. + +----------------------------------------------------------------------- + +The development version of the bundle can be found at + + https://github.com/latex3/l3sys-query + +for those people who are interested. + +--]] + +-- +-- Details of the script itself, etc. +-- +local copyright = "Copyright (C) 2024 The LaTeX Project" +local release_date = "2024-03-15" +local script_desc = "System queries for LaTeX using Lua\n" +local script_name = "l3sys-query" + +-- +-- Setup for the CLI: commands and options +-- +local cmd_impl = {} +local cmd_desc = {} + +local option_list = + { + exclude = + { + cmds = {"ls"}, + desc = "Exclude entries from directory listing", + type = "string" + }, + ["ignore-case"] = + { + cmds = {"ls"}, + desc = "Ignore case when sorting directory listing", + type = "boolean" + }, + help = + { + desc = "Prints this message and exits", + short = "h", + type = "boolean" + }, + pattern = + { + cmds = {"ls"}, + desc = "Treat (x)args as a Lua pattern", + short = "p", + type = "boolean" + }, + recursive = + { + cmds = {"ls"}, + desc = "Activates recursive directory listing", + short = "r", + type = "boolean" + }, + reverse = + { + cmds = {"ls"}, + desc = "Reversing sorting order", + type = "boolean" + }, + sort = + { + cmds = {"ls"}, + desc = "Method used to sort directory listing", + type = "string" + }, + type = + { + cmds = {"ls"}, + desc = "Selects the type of entry in a directory listing", + type = "string" + }, + version = + { + desc = "Prints version information and exits", + short = "v", + type = "boolean" + } + } + +-- +-- Local copies of globals used here +-- +local io = io +local stderr = io.stderr + +local lfs = lfs +local attributes = lfs.attributes +local currentdir = lfs.currentdir +local dir = lfs.dir + +local os = os +local exit = os.exit + +local string = string +local find = string.find +local gmatch = string.gmatch +local lower = string.lower +local match = string.match +local rep = string.rep +local sub = string.sub + +local table = table +local concat = table.concat +local insert = table.insert +local sort = table.sort + +-- +-- Support functions and data +-- + +-- Remove '...' around an entire text: +-- we need this to support restricted shell escape on Windows +local function dequote(text) + if (match(text,"^'") and match(text,"'$")) then + return sub(text,2,-2) + end + return text +end + + +-- A short auxiliary used whever the script bails out +local function info_and_quit(s) + stderr:write("\n" .. s .. "\nTry '" .. script_name .. " --help' for more information.\n") + exit(1) +end + +-- Convert a file glob into a pattern for use by e.g. string.gub +-- Based on https://github.com/davidm/lua-glob-pattern +-- Simplified substantially: "[...]" syntax not supported as is not +-- required by the file patterns used by the team. Also note style +-- changes to match coding approach in rest of this file. +-- +-- License for original globtopattern +--[[ + + (c) 2008-2011 David Manura. Licensed under the same terms as Lua (MIT). + +--]] +local function glob_to_pattern(glob,skip_convert) + if skip_convert then + return glob + end + + local pattern = "^" -- pattern being built + local i = 0 -- index in glob + local char -- char at index i in glob + + -- escape pattern char + local function escape(char) + return match(char,"^%w$") and char or "%" .. char + end + + -- Convert tokens. + while true do + i = i + 1 + char = sub(glob,i,i) + if char == "" then + pattern = pattern .. "$" + break + elseif char == "?" then + pattern = pattern .. "." + elseif char == "*" then + pattern = pattern .. ".*" + elseif char == "[" then -- ] + -- Ignored + info_and_quit("[...] syntax not supported in globs!") + elseif char == "\\" then + i = i + 1 + char = sub(glob,i,i) + if char == "" then + pattern = pattern .. "\\$" + break + end + pattern = pattern .. escape(char) + else + pattern = pattern .. escape(char) + end + end + return pattern +end + + +-- Initial data for the command line parser +local cmd = "" +local options = {} +local arg_list = "" + +local function parse_args() + -- Turn long/short options into two lookup tables + local long_options = {} + local short_options = {} + for k,v in pairs(option_list) do + if v.short then + short_options[v.short] = k + end + long_options[k] = k + end + + -- Minor speed-up + local arg = arg + + -- arg[1] is a special case: must be a command or a very limited + -- subset (--help|-h or --version|-v) + local a = arg[1] + if a then + -- No options are allowed in position 1, so filter those out + if a == "--version" or a == "-v" then + cmd = "version" + elseif not match(a,"^%-") then + cmd = a + end + end + + -- Stop here if help or version is required + if cmd == "help" or cmd == "version" then + return + end + + -- An auxiliary to grab all file names into a string: + -- this reflects the fact that the use case for l3sys-query doesn't + -- allow quoting spaces + local function tidy(num) + local t = {} + for i = num,#arg do + insert(t,dequote(arg[i])) + end + return concat(t," ") + end + + -- Examine all other arguments + -- Use a while loop rather than for as this makes it easier + -- to grab arg for optionals where appropriate + local i = 2 + while i <= #arg do + local a = arg[i] + -- Terminate search for options + if a == "--" then + arg_list = tidy(i + 1) + return + end + + -- Look for optionals + local opt + local optarg + local opts + -- Look for and option and get it into a variable + if match(a,"^%-") then + if match(a,"^%-%-") then + opts = long_options + local pos = find(a,"=") + if pos then + opt = sub(a,3,pos - 1) + optarg = sub(a,pos + 1) + else + opt = sub(a,3) + end + else + opts = short_options + opt = sub(a,2,2) + -- Only set optarg if it is there + if #a > 2 then + optarg = sub(a,3) + end + end + + -- Now check that the option is valid and sort out the argument + -- if required + local optname = opts[opt] + if optname then + -- Tidy up arguments + if option_list[optname].type == "boolean" then + if optarg then + local opt = "-" .. (match(a,"^%-%-") and "-" or "") .. opt + info_and_quit("Value not allowed for option " .. opt) + end + else + if not optarg then + optarg = arg[i + 1] + if not optarg then + info_and_quit("Missing value for option " .. a) + end + i = i + 1 + end + end + else + info_and_quit("Unknown option " .. a) + end + + -- Store the result + if optarg then + if option_list[optname].type == "string" then + options[optname] = optarg + else + local opts = options[optname] or {} + for hit in gmatch(optarg,"([^,%s]+)") do + insert(opts,hit) + end + options[optname] = opts + end + else + options[optname] = true + end + i = i + 1 + end + + -- Collect up the remaining arguments + if not opt then + arg_list = tidy(i) + break + end + end +end + +parse_args() + +-- +-- The help functions: local only and hard-coded +-- + +local function help() + -- Find the longest entry to pad + local function format_list(list) + local longest = 0 + for k,_ in pairs(list) do + if k:len() > longest then + longest = k:len() + end + end + -- Sort the list + local t = {} + for k,_ in pairs(list) do + insert(t,k) + end + sort(t) + return t,longest + end + + -- 'Header' of fixed info + print("Usage: " .. script_name .. " <cmd> [<options>] [<args>]\n") + print("Valid <cmd>s are:") + + -- Sort the commands, pad the descriptions, print + local t,longest = format_list(cmd_desc) + for _,k in ipairs(t) do + local cmd = cmd_desc[k] + local filler = rep(" ",longest - k:len() + 1) + print(" " .. k .. filler .. cmd) + end + + -- Same for the options + print("\nValid <options> are:") + t,longest = format_list(option_list) + for _,name in ipairs(t) do + local opt = option_list[name] + local filler = rep(" ",longest - name:len() + 1) + if opt.desc then + if opt.short then + print(" --" .. name .. "|-" .. opt.short .. filler .. opt.desc) + else + print(" --" .. name .. " " .. filler .. opt.desc) + end + end + end + + -- Postamble + print("\nFull manual available via 'texdoc " .. script_name .. "'.\n") + print("Repository : https://github.com/latex3/" .. script_name) + print("Bug tracker: https://github.com/latex3/" .. script_name .. "/issues") + print("\n" .. copyright) +end + +local function version() + print(script_name .. " " .. release_date) +end + +-- +-- The functions for commands: all held in the table cmd_impl +-- with docstrings in the table cmd_desc. +-- + +-- The aim here is to convert a user file specification (if given) into a +-- Lua pattern, and then to do a listing. +cmd_desc.ls = "Prints a listing based on the <args> and <options>" +function cmd_impl.ls(arg_list) + if not arg_list or arg_list == "" or arg_list == "." then + arg_list = "*" + end + -- Look for absolute paths or any trying to leave the confines of the current + -- directory: this is not supported. + if match(arg_list,"%.%.") or + match(arg_list,"^/") or + match(arg_list,"^\\") or + match(arg_list,"[a-zA-Z]:") then + return + end + -- Tidy up and convert to a pattern. + path,glob = match(arg_list,"^(.*)/([^/]*)$") + if not path then + path = "." + glob = arg_list + end + if path == "" then + path = "." + end + if not match(path,"^%.") then + path = "./" .. path + end + local conv_pattern = options.pattern + local pattern = glob_to_pattern(glob,conv_pattern) + local exclude_pattern + if options.exclude then + local exclude = dequote(options.exclude) + exclude_pattern = glob_to_pattern(exclude,conv_pattern) + end + -- A lookup table for attributes: map between lfs- and Unix-type naming + local attrib_map = {d = "directory", f = "file"} + + -- A bit of setup to store the sorting data as well as the entry itself + local i = 0 -- If no sorting active, just track the order from lfs + local entries = {} + local sort_mode = options.sort or "none" + local function store(entry,full_entry) + if not match(entry,pattern) + or (exclude_pattern and match(entry,exclude_pattern)) then + return + end + i = i + 1 + local key = i + if sort_mode == "date" then + key = attributes(full_entry,"modification") + elseif sort_mode == "name" then + key = full_entry + end + entries[key] = full_entry + end + + -- Build a table of entries, excluding "." and "..", and return as a string + -- with one entry per line. + local opt = options.type + local is_rec = options.recursive + local function browse(path) + for entry in dir(path) do + if entry ~= "." and entry ~= ".." and not match(entry,"^%.") then + local full_entry = path .. "/" .. entry + local ft = attributes(full_entry,"mode") + if not opt or ft == attrib_map[opt] then + store(entry,full_entry) + end + if is_rec and ft == "directory" then + browse(full_entry) + end + end + end + end + -- Start a search at the top level + browse(path) + + -- Extract keys and sort + local s = {} + for k,_ in pairs(entries) do + insert(s,k) + end + + -- Setup for case-insensitve sorting + local function case(s) + return s + end + if options["ignore-case"] then + function case(s) + return lower(s) + end + end + + if options.reverse then + sort(s,function(a,b) return case(a) > case(b) end) + else + sort(s,function(a,b) return case(a) < case(b) end) + end + + local result = {} + for _,v in ipairs(s) do + insert(result,entries[v]) + end + + return concat(result,"\n") +end + +-- A simple rename +cmd_desc.pwd = "Prints the present working directory" +function cmd_impl.pwd() + return currentdir() +end + +-- +-- Execute the given command +-- + +-- Only 'known' commands do anything at all +if cmd == "version" then + version() + exit(0) +elseif not cmd_impl[cmd] then + if cmd == "" then + help() + else + info_and_quit(script_name .. ": '" .. cmd .. "' is not a " .. script_name .. + " command.") + end + exit(1) +end + +-- Check options are valid for cmd +for k,_ in pairs(options) do + local cmds = option_list[k].cmds + if not cmds then + -- Should not be possible: + -- everything except --help and --version should have an entry + print("Internal error") + exit(1) + else + -- Make a lookup table + local t = {} + for _,v in pairs(cmds) do + t[v] = true + end + if not t[cmd] then + info_and_quit(script_name .. ": Option '" .. k .. + "' does not apply to '" .. cmd .. "'") + end + end +end + +local result = cmd_impl[cmd](arg_list) + +if result then + print(result) + exit(0) +else + exit(1) +end diff --git a/Build/source/texk/texlive/linked_scripts/scripts.lst b/Build/source/texk/texlive/linked_scripts/scripts.lst index efa9be90b7c..12fa2241416 100644 --- a/Build/source/texk/texlive/linked_scripts/scripts.lst +++ b/Build/source/texk/texlive/linked_scripts/scripts.lst @@ -103,6 +103,7 @@ kotex-utils/jamo-normalize.pl kotex-utils/komkindex.pl kotex-utils/ttf2kotexfont.pl l3build/l3build.lua +l3sys-query/l3sys-query.lua latex-git-log/latex-git-log latex-papersize/latex-papersize.py latex2man/latex2man |