texlive[73119] Master/texmf-dist: expltools (14dec24)

commits+karl at tug.org commits+karl at tug.org
Sat Dec 14 22:17:22 CET 2024


Revision: 73119
          https://tug.org/svn/texlive?view=revision&revision=73119
Author:   karl
Date:     2024-12-14 22:17:22 +0100 (Sat, 14 Dec 2024)
Log Message:
-----------
expltools (14dec24)

Modified Paths:
--------------
    trunk/Master/texmf-dist/doc/support/expltools/CHANGES.md
    trunk/Master/texmf-dist/doc/support/expltools/README.md
    trunk/Master/texmf-dist/doc/support/expltools/project-proposal.pdf
    trunk/Master/texmf-dist/doc/support/expltools/warnings-and-errors.pdf
    trunk/Master/texmf-dist/scripts/expltools/explcheck-cli.lua
    trunk/Master/texmf-dist/scripts/expltools/explcheck-format.lua

Modified: trunk/Master/texmf-dist/doc/support/expltools/CHANGES.md
===================================================================
--- trunk/Master/texmf-dist/doc/support/expltools/CHANGES.md	2024-12-14 00:42:01 UTC (rev 73118)
+++ trunk/Master/texmf-dist/doc/support/expltools/CHANGES.md	2024-12-14 21:17:22 UTC (rev 73119)
@@ -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 at tug.org][tex-live-02] and by @muzimuzhi in #9,
   fixed on [tex-live at 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)

Modified: trunk/Master/texmf-dist/doc/support/expltools/README.md
===================================================================
--- trunk/Master/texmf-dist/doc/support/expltools/README.md	2024-12-14 00:42:01 UTC (rev 73118)
+++ trunk/Master/texmf-dist/doc/support/expltools/README.md	2024-12-14 21:17:22 UTC (rev 73119)
@@ -81,8 +81,8 @@
 
 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.
 

Modified: trunk/Master/texmf-dist/doc/support/expltools/project-proposal.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/doc/support/expltools/warnings-and-errors.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/scripts/expltools/explcheck-cli.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/expltools/explcheck-cli.lua	2024-12-14 00:42:01 UTC (rev 73118)
+++ trunk/Master/texmf-dist/scripts/expltools/explcheck-cli.lua	2024-12-14 21:17:22 UTC (rev 73119)
@@ -24,12 +24,71 @@
   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 @@
     ::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 @@
   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 @@
   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 @@
       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 @@
       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

Modified: trunk/Master/texmf-dist/scripts/expltools/explcheck-format.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/expltools/explcheck-format.lua	2024-12-14 00:42:01 UTC (rev 73118)
+++ trunk/Master/texmf-dist/scripts/expltools/explcheck-format.lua	2024-12-14 21:17:22 UTC (rev 73119)
@@ -16,9 +16,9 @@
   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 @@
   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 @@
 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 @@
         ), 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 @@
           ), 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 @@
         .. 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 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 @@
 
 return {
   convert_byte_to_line_and_column = convert_byte_to_line_and_column,
+  pluralize = pluralize,
   print_results = print_results,
   print_summary = print_summary,
 }



More information about the tex-live-commits mailing list.