diff options
author | Karl Berry <karl@freefriends.org> | 2024-12-14 21:17:22 +0000 |
---|---|---|
committer | Karl Berry <karl@freefriends.org> | 2024-12-14 21:17:22 +0000 |
commit | 69ddfb12aa76de5eae1e06d0013d01a7bab7537c (patch) | |
tree | 66f1f58c6c522bcd856a5e5777c5e4fb4ec6d0c0 | |
parent | 166a29e6a963e7bba9a711be5a673d455440d46d (diff) |
expltools (14dec24)
git-svn-id: svn://tug.org/texlive/trunk@73119 c570f23f-e606-0410-a88d-b1316a301751
-rw-r--r-- | Master/texmf-dist/doc/support/expltools/CHANGES.md | 31 | ||||
-rw-r--r-- | Master/texmf-dist/doc/support/expltools/README.md | 4 | ||||
-rw-r--r-- | Master/texmf-dist/doc/support/expltools/project-proposal.pdf | bin | 67243 -> 67243 bytes | |||
-rw-r--r-- | Master/texmf-dist/doc/support/expltools/warnings-and-errors.pdf | bin | 2780722 -> 2780738 bytes | |||
-rwxr-xr-x | Master/texmf-dist/scripts/expltools/explcheck-cli.lua | 90 | ||||
-rwxr-xr-x | Master/texmf-dist/scripts/expltools/explcheck-format.lua | 120 |
6 files changed, 178 insertions, 67 deletions
diff --git a/Master/texmf-dist/doc/support/expltools/CHANGES.md b/Master/texmf-dist/doc/support/expltools/CHANGES.md index 1d84a52763d..39d3a6c9c60 100644 --- a/Master/texmf-dist/doc/support/expltools/CHANGES.md +++ b/Master/texmf-dist/doc/support/expltools/CHANGES.md @@ -1,5 +1,33 @@ # Changes +## expltools 2024-12-13 + +### explcheck v0.2.0 + +#### Development + +- Add a command-line option `--porcelain` for machine-readable output. + (suggested by @FrankMittelbach in #8, added in #14) + + See <https://github.com/Witiko/expltools/pull/15#issuecomment-2542418484> + and below for a demonstration of how you might set up your text editor, so + that it automatically navigates you to lines with warnings and errors. + +#### Fixes + +- In the command-line interface, forbid the checking of .ins and .dtx files. + Display messages that direct users to check the generated files instead. + (reported by @josephwright and @FrankMittelbach in #8, fixed in #14) + +- Expect both backslashes and forward slashes when shortening pathnames. (#14) + +- Correctly pluralize "1 file" on the first line of command-line output. (#14) + +#### Documentation + +- Normalize the behavior and documentation of functions `get_*()` across files + `explcheck/build.lua`, `explcheck/test.lua`, and `explcheck-cli.lua`. (#14) + ## expltools 2024-12-09 ### explcheck v0.1.1 @@ -7,7 +35,6 @@ #### Fixes - In LuaTeX, initialize Kpathsea Lua module searchers first. - (reported by @josephwright, Lars Madsen, and Philip Taylor on [tex-live@tug.org][tex-live-02] and by @muzimuzhi in #9, fixed on [tex-live@tug.org][tex-live-03] by @gucci-on-fleek) @@ -23,7 +50,7 @@ - Include explcheck version in the command-line interface. (reported in #10, fixed in #13) -- Hint in the file `README.md` that .dtx are not well supported. +- Hint in the file `README.md` that .dtx files are not well-supported. (reported by @josephwright in #8, added in #13) - Show in the file `README.md` how explcheck can be used from Lua code. (#13) diff --git a/Master/texmf-dist/doc/support/expltools/README.md b/Master/texmf-dist/doc/support/expltools/README.md index 40a0ffff329..94479c74c5f 100644 --- a/Master/texmf-dist/doc/support/expltools/README.md +++ b/Master/texmf-dist/doc/support/expltools/README.md @@ -81,8 +81,8 @@ jobs: You can prepare the expltools bundle for distribution with the following two commands: -- `l3build tag`: Add the current version numbers to the file `explcheck-lua.cli`. -- `l3build ctan`: Run tests, build the documentation, and create a CTAN archive `expltools-ctan.zip`. +1. `l3build tag`: Add the current version numbers to the file `explcheck-lua.cli`. +2. `l3build ctan`: Run tests, build the documentation, and create a CTAN archive `expltools-ctan.zip`. The file `explcheck.lua` should be installed in the TDS directory `scripts/expltools/explcheck`. Furthermore, it should be made executable and either symlinked to system directories as `explcheck` on Unix or have a wrapper `explcheck.exe` installed on Windows. diff --git a/Master/texmf-dist/doc/support/expltools/project-proposal.pdf b/Master/texmf-dist/doc/support/expltools/project-proposal.pdf Binary files differindex 58598e7dfd3..1d831747a9f 100644 --- a/Master/texmf-dist/doc/support/expltools/project-proposal.pdf +++ b/Master/texmf-dist/doc/support/expltools/project-proposal.pdf diff --git a/Master/texmf-dist/doc/support/expltools/warnings-and-errors.pdf b/Master/texmf-dist/doc/support/expltools/warnings-and-errors.pdf Binary files differindex a7b2000188b..56c20e7dfa2 100644 --- a/Master/texmf-dist/doc/support/expltools/warnings-and-errors.pdf +++ b/Master/texmf-dist/doc/support/expltools/warnings-and-errors.pdf diff --git a/Master/texmf-dist/scripts/expltools/explcheck-cli.lua b/Master/texmf-dist/scripts/expltools/explcheck-cli.lua index ecb57f2ed9a..a7b8bebc6b7 100755 --- a/Master/texmf-dist/scripts/expltools/explcheck-cli.lua +++ b/Master/texmf-dist/scripts/expltools/explcheck-cli.lua @@ -24,12 +24,71 @@ local function deduplicate_pathnames(pathnames) return deduplicated_pathnames end +-- Convert a pathname of a file to the suffix of the file. +local function get_suffix(pathname) + return pathname:gsub(".*%.", "."):lower() +end + +-- Convert a pathname of a file to the base name of the file. +local function get_basename(pathname) + return pathname:gsub(".*[\\/]", "") +end + +-- Convert a pathname of a file to the pathname of its parent directory. +local function get_parent(pathname) + if pathname:find("[\\/]") then + return pathname:gsub("(.*)[\\/].*", "%1") + else + return "." + end +end + +-- Check that the pathname specifies a file that we can process. +local function check_pathname(pathname) + local suffix = get_suffix(pathname) + if suffix == ".ins" then + local basename = get_basename(pathname) + if basename:find(" ") then + basename = "'" .. basename .. "'" + end + return + false, + "explcheck can't currently process .ins files directly\n" + .. 'Use a command such as "luatex ' .. basename .. '" ' + .. "to generate .tex, .cls, and .sty files and process these files instead." + elseif suffix == ".dtx" then + local parent = get_parent(pathname) + local basename = "*.ins" + local has_lfs, lfs = pcall(require, "lfs") + if has_lfs then + for candidate_basename in lfs.dir(parent) do + local candidate_suffix = get_suffix(candidate_basename) + if candidate_suffix == ".ins" then + basename = candidate_basename + if basename:find(" ") then + basename = "'" .. candidate_basename .. "'" + end + break + end + end + end + return + false, + "explcheck can't currently process .dtx files directly\n" + .. 'Use a command such as "luatex ' .. basename .. '" ' + .. "to generate .tex, .cls, and .sty files and process these files instead." + end + return true +end + -- Process all input files. -local function main(pathnames, warnings_are_errors, max_line_length) +local function main(pathnames, warnings_are_errors, max_line_length, porcelain) local num_warnings = 0 local num_errors = 0 - print("Checking " .. #pathnames .. " files") + if not porcelain then + print("Checking " .. #pathnames .. " " .. format.pluralize("file", #pathnames)) + end for pathname_number, pathname in ipairs(pathnames) do @@ -53,11 +112,13 @@ local function main(pathnames, warnings_are_errors, max_line_length) ::continue:: num_warnings = num_warnings + #issues.warnings num_errors = num_errors + #issues.errors - format.print_results(pathname, issues, line_starting_byte_numbers, pathname_number == #pathnames) + format.print_results(pathname, issues, line_starting_byte_numbers, pathname_number == #pathnames, porcelain) end -- Print a summary. - format.print_summary(#pathnames, num_warnings, num_errors) + if not porcelain then + format.print_summary(#pathnames, num_warnings, num_errors, porcelain) + end if(num_errors > 0) then return 1 @@ -72,12 +133,13 @@ local function print_usage() print("Usage: " .. arg[0] .. " [OPTIONS] FILENAMES\n") print("Run static analysis on expl3 files.\n") print("Options:") - print("\t--max-line-length=N\tThe maximum line length before the warning S103 (Line too long) is produced.") - print("\t--warnings-are-errors\tProduce a non-zero exit code if any warnings are produced by the analysis.") + print("\t--max-line-length=N The maximum line length before the warning S103 (Line too long) is produced.") + print("\t--porcelain Produce machine-readable output.") + print("\t--warnings-are-errors Produce a non-zero exit code if any warnings are produced by the analysis.") end local function print_version() - print("explcheck (expltools 2024-12-09) v0.1.1") + print("explcheck (expltools 2024-12-13) v0.2.0") print("Copyright (c) 2024 Vít Starý Novotný") print("Licenses: LPPL 1.3 or later, GNU GPL v2 or later") end @@ -91,6 +153,7 @@ else local warnings_are_errors = false local only_pathnames_from_now_on = false local max_line_length = nil + local porcelain = false for _, argument in ipairs(arg) do if only_pathnames_from_now_on then table.insert(pathnames, argument) @@ -104,6 +167,8 @@ else os.exit(0) elseif argument == "--warnings-are-errors" then warnings_are_errors = true + elseif argument == "--porcelain" then + porcelain = true elseif argument:sub(1, 18) == "--max-line-length=" then max_line_length = tonumber(argument:sub(19)) elseif argument:sub(1, 2) == "--" then @@ -114,9 +179,18 @@ else table.insert(pathnames, argument) end end + + -- Deduplicate and check that pathnames specify files that we can process. pathnames = deduplicate_pathnames(pathnames) + for _, pathname in ipairs(pathnames) do + local is_ok, error_message = check_pathname(pathname) + if not is_ok then + print('Failed to process "' .. pathname .. '": ' .. error_message) + os.exit(1) + end + end -- Run the analysis. - local exit_code = main(pathnames, warnings_are_errors, max_line_length) + local exit_code = main(pathnames, warnings_are_errors, max_line_length, porcelain) os.exit(exit_code) end diff --git a/Master/texmf-dist/scripts/expltools/explcheck-format.lua b/Master/texmf-dist/scripts/expltools/explcheck-format.lua index 5b64ab51b90..dfe13b36584 100755 --- a/Master/texmf-dist/scripts/expltools/explcheck-format.lua +++ b/Master/texmf-dist/scripts/expltools/explcheck-format.lua @@ -16,9 +16,9 @@ local function format_pathname(pathname, max_length) while #pathname > max_length do local pattern if first_iteration then - pattern = "([^/]*)/[^/]*/(.*)" + pattern = "([^\\/]*)[\\/][^\\/]*[\\/](.*)" else - pattern = "([^/]*)/%.%.%./[^/]*/(.*)" + pattern = "([^\\/]*)/%.%.%.[\\/][^\\/]*[\\/](.*)" end local prefix_start, _, prefix, suffix = pathname:find(pattern) if prefix_start == nil or prefix_start > 1 then @@ -31,9 +31,9 @@ local function format_pathname(pathname, max_length) if #pathname > max_length then local pattern if first_iteration then - pattern = "([^/]*/?)(.*)" + pattern = "([^\\/]*[\\/]?)(.*)" else - pattern = "([^/]*/%.%.%./)(.*)" + pattern = "([^\\/]*[\\/]%.%.%.[\\/])(.*)" end local prefix_start, _, _, suffix = pathname:find(pattern) if prefix_start == 1 then @@ -81,7 +81,7 @@ local function convert_byte_to_line_and_column(line_starting_byte_numbers, byte_ end -- Print the results of analyzing a file. -local function print_results(pathname, issues, line_starting_byte_numbers, is_last_file) +local function print_results(pathname, issues, line_starting_byte_numbers, is_last_file, porcelain) -- Display an overview. local all_issues = {} local status @@ -95,7 +95,7 @@ local function print_results(pathname, issues, line_starting_byte_numbers, is_la ), 1, 31 ) ) - table.insert(all_issues, issues.errors) + table.insert(all_issues, {issues.errors, "error: "}) if(#issues.warnings > 0) then status = ( status @@ -108,7 +108,7 @@ local function print_results(pathname, issues, line_starting_byte_numbers, is_la ), 1, 33 ) ) - table.insert(all_issues, issues.warnings) + table.insert(all_issues, {issues.warnings, "warning: "}) end elseif(#issues.warnings > 0) then status = colorize( @@ -118,45 +118,50 @@ local function print_results(pathname, issues, line_starting_byte_numbers, is_la .. pluralize("warning", #issues.warnings) ), 1, 33 ) - table.insert(all_issues, issues.warnings) + table.insert(all_issues, {issues.warnings, "warning: "}) else status = colorize("OK", 1, 32) end - local max_overview_length = 72 - local prefix = "Checking " - local formatted_pathname = format_pathname( - pathname, - math.max( - ( - max_overview_length - - #prefix - - #(" ") - - #decolorize(status) - ), 1 - ) - ) - local overview = ( - prefix - .. formatted_pathname - .. (" "):rep( + if not porcelain then + local max_overview_length = 72 + local prefix = "Checking " + local formatted_pathname = format_pathname( + pathname, math.max( ( max_overview_length - #prefix + - #(" ") - #decolorize(status) - - #formatted_pathname ), 1 ) ) - .. status - ) - io.write("\n" .. overview) + local overview = ( + prefix + .. formatted_pathname + .. (" "):rep( + math.max( + ( + max_overview_length + - #prefix + - #decolorize(status) + - #formatted_pathname + ), 1 + ) + ) + .. status + ) + io.write("\n" .. overview) + end -- Display the errors, followed by warnings. if #all_issues > 0 then - for _, warnings_or_errors in ipairs(all_issues) do - print() + for _, warnings_or_errors_and_porcelain_prefix in ipairs(all_issues) do + local warnings_or_errors, porcelain_prefix = table.unpack(warnings_or_errors_and_porcelain_prefix) + if not porcelain then + print() + end -- Display the warnings/errors. for _, issue in ipairs(issues.sort(warnings_or_errors)) do local code = issue[1] @@ -172,40 +177,44 @@ local function print_results(pathname, issues, line_starting_byte_numbers, is_la local reserved_suffix_length = 30 local label_indent = (" "):rep(4) local suffix = code:upper() .. " " .. message - formatted_pathname = format_pathname( - pathname, - math.max( - ( - max_line_length - - #label_indent - - reserved_position_length - - #(" ") - - math.max(#suffix, reserved_suffix_length) - ), 1 - ) - ) - local line = ( - label_indent - .. formatted_pathname - .. position - .. (" "):rep( + if not porcelain then + local formatted_pathname = format_pathname( + pathname, math.max( ( max_line_length - #label_indent - - #formatted_pathname - - #decolorize(position) + - reserved_position_length + - #(" ") - math.max(#suffix, reserved_suffix_length) ), 1 ) ) - .. suffix - .. (" "):rep(math.max(reserved_suffix_length - #suffix, 0)) - ) - io.write("\n" .. line) + local line = ( + label_indent + .. formatted_pathname + .. position + .. (" "):rep( + math.max( + ( + max_line_length + - #label_indent + - #formatted_pathname + - #decolorize(position) + - math.max(#suffix, reserved_suffix_length) + ), 1 + ) + ) + .. suffix + .. (" "):rep(math.max(reserved_suffix_length - #suffix, 0)) + ) + io.write("\n" .. line) + else + print(pathname .. position .. " " .. porcelain_prefix .. suffix) + end end end - if(not is_last_file) then + if not is_last_file and not porcelain then print() end end @@ -228,6 +237,7 @@ end return { convert_byte_to_line_and_column = convert_byte_to_line_and_column, + pluralize = pluralize, print_results = print_results, print_summary = print_summary, } |