texlive[74436] trunk: texlogsieve (4mar25)

commits+karl at tug.org commits+karl at tug.org
Tue Mar 4 22:08:30 CET 2025


Revision: 74436
          https://tug.org/svn/texlive?view=revision&revision=74436
Author:   karl
Date:     2025-03-04 22:08:29 +0100 (Tue, 04 Mar 2025)
Log Message:
-----------
texlogsieve (4mar25)

Modified Paths:
--------------
    trunk/Build/source/texk/texlive/linked_scripts/texlogsieve/texlogsieve
    trunk/Master/texmf-dist/doc/man/man1/texlogsieve.1
    trunk/Master/texmf-dist/doc/man/man1/texlogsieve.man1.pdf
    trunk/Master/texmf-dist/doc/support/texlogsieve/README.md
    trunk/Master/texmf-dist/doc/support/texlogsieve/texlogsieve.pdf
    trunk/Master/texmf-dist/doc/support/texlogsieve/texlogsieve.tex
    trunk/Master/texmf-dist/scripts/texlogsieve/texlogsieve

Modified: trunk/Build/source/texk/texlive/linked_scripts/texlogsieve/texlogsieve
===================================================================
--- trunk/Build/source/texk/texlive/linked_scripts/texlogsieve/texlogsieve	2025-03-04 17:01:16 UTC (rev 74435)
+++ trunk/Build/source/texk/texlive/linked_scripts/texlogsieve/texlogsieve	2025-03-04 21:08:29 UTC (rev 74436)
@@ -2,7 +2,7 @@
 
 -- texlogsieve - filter and summarize LaTeX log files
 --
--- Copyright (C) 2021-2024 Nelson Lago <lago at ime.usp.br>
+-- Copyright (C) 2021-2025 Nelson Lago <lago at ime.usp.br>
 --
 -- This program is free software: you can redistribute it and/or modify
 -- it under the terms of the GNU General Public License as published by
@@ -165,26 +165,30 @@
    With option -file-line-error, the exclamation is replaced by an
    indication like "filename:linenum:".
 
-   The "stack trace" is comprised of pairs of lines. By default, LaTeX
-   only shows the last pair (depending on \errorcontextlines) and, when
-   there are more lines, they are replaced by a line with " ..." (right
-   after the line starting with "!"). In each pair of lines, the first
-   one indicates the line content leading up to the error and the
-   second one shows the subsequent content. The first line is at most
-   half_error_line characters long (default 50) and the second line
-   is at most error_line characters long (default 79). The second line
-   is indented to start after the end of the first, as in the example
-   above. If the content of a line does not fit, a part of it (the
-   beginning for the first line and the end for the second line) may
-   be replaced by "...". error_line must be < 255 and half_error_line
-   must be < error_line -15. If error_line >= max_print_line or
-   half_error_line >= max_print_line, TeX does line wrapping as usual
-   (see below). If either is exactly max_print_line (which is true
-   for the default values), things get confusing, so TeX may add a
-   blank line when wrapping.
+   The "stack trace" is comprised of pairs of lines. In each pair,
+   the first line indicates the content in a given line leading up
+   to the error, while the second one shows the subsequent content of
+   that line. The first line is at most half_error_line characters long
+   (default 50) and the second line is at most error_line characters
+   long (default 79). The second line is indented to start after the
+   end of the first, as in the example above. If the content of either
+   segment does not fit, a part of it (the beginning for the first line
+   and the end for the second line) may be replaced by "...". error_line
+   must be < 255 and half_error_line must be < error_line -15. If
+   error_line >= max_print_line or half_error_line >= max_print_line,
+   TeX does line wrapping as usual (see below). If either is exactly
+   max_print_line (which is true for the default values), things get
+   confusing, so TeX may add a blank line when wrapping.
 
+   By default, LaTeX only shows the first and last pairs of lines of the
+   "stack trace" (if only one pair of lines is relevant, then obviously
+   only this pair is shown). The number of additional, intermediate pairs
+   of lines shown is determined by \errorcontextlines: if it is zero, no
+   additional lines are shown, but LaTeX prints "..." indicating they
+   were omitted. If it is -1 (the default), they are not shown and there
+   is no "..." indication.
 
-   LaTeX errors usually follow the format
+   LaTeX errors are similar, but usually follow the format
 
        ! LaTeX/Package/Class BLAH Error: some description
        See the BLAH documentation for explanation.
@@ -627,6 +631,13 @@
   while Lines:numLines() < 15 do
       tmp = logfile:read("*line")
       if tmp == nil then break end
+
+      -- If we are running in a unix-like OS but the files we are
+      -- processing were generated in Windows, lua may leave a \r
+      -- character at the end of the line; if this happens, remove it
+      local _, last = string.find(tmp, '\r$')
+      if last ~= nil then tmp = string.sub(tmp, 1, last -1) end
+
       -- Do not skip blank lines here, we need to do it in Lines:append()
       Lines:append(tmp)
   end
@@ -788,6 +799,7 @@
 end
 
 function registerHandlers()
+  table.insert(beginningOfLineHandlers, pseudoErrorHandler)
   table.insert(beginningOfLineHandlers, errorHandler)
   table.insert(beginningOfLineHandlers, citationHandler)
   table.insert(beginningOfLineHandlers, referenceHandler)
@@ -975,8 +987,8 @@
   --version                              print program version]]
 
 versionmsg = [[
-texlogsieve 1.4.2
-Copyright (C) 2021-2024 Nelson Lago <lago at ime.usp.br>
+texlogsieve 1.5.0
+Copyright (C) 2021-2025 Nelson Lago <lago at ime.usp.br>
 License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
 This is free software: you are free to change and redistribute it.
 There is NO WARRANTY, to the extent permitted by law.]]
@@ -1314,8 +1326,15 @@
   if vars.filename == nil then
       logfile = io.stdin
   else
-      logfile = assert(io.open(vars.filename, "r"))
-      readFls(vars.filename)
+      local filename
+      local exts = {"", ".log", "log"}
+      for _, ext in ipairs(exts) do
+          filename = vars.filename .. ext
+          logfile = io.open(filename, "r")
+          if logfile ~= nil then break end
+      end
+      assert(logfile ~= nil)
+      readFls(filename)
   end
 
   vars.filename = nil
@@ -1728,9 +1747,12 @@
   end
 end
 
-function showMessage(msg)
-  local formatted = msg:toString()
+function showMessage(msg, bypassMostFilters)
+  msg.suppressed = true
+  local formatted = msg:toString(bypassMostFilters)
+
   if trim(formatted) ~= "" then
+      msg.suppressed = false
       local pageinfo = ""
       local spaces = ""
       if not RAW and msg.physicalPage ~= nil then
@@ -1746,6 +1768,7 @@
           for _, summary in ipairs(summaries) do
               if summary:alreadySeen(msg) then
                   alreadySeen = true
+                  msg.suppressed = true
                   break
               end
           end
@@ -1752,6 +1775,22 @@
       end
 
       if not SILENCE_REPETITIONS or not alreadySeen then
+
+          -- For unknown messages, we show the previous one
+          -- if it was suppressed to give some context, unless
+          -- that message is certainly unrelated
+          if msg.severity == UNKNOWN
+                  and lastMessage ~= nil
+                  and msg ~= lastMessage
+                  and lastMessage.suppressed
+                  and not lastMessage.alwaysEnds
+          then
+              local TMP = SILENCE_REPETITIONS
+              SILENCE_REPETITIONS = false
+              showMessage(lastMessage, true)
+              SILENCE_REPETITIONS = TMP
+          end
+
           showFileBanner(msg)
 
           for _, line in ipairs(linesToTable(formatted)) do
@@ -1768,7 +1807,10 @@
       end
   end
 
-  msg:toSummary()
+  if msg ~= lastMessage then
+      lastMessage = msg
+      msg:toSummary()
+  end
 end
 
 function showPageMessages()
@@ -1973,21 +2015,26 @@
 
 -------------------------------------------------------------------------------
 -- errorHandler
+-- pseudoErrorHandler
 --
--- This simply identifies errors and defines "ERRORS_DETECTED" as true. The
--- line with the message is output directly with no further processing, which
--- means the other lines that belong to the error are output as unrecognized
--- messages. We could be more thorough here and handle the whole message, but
--- that is not really necessary, all we want is ERRORS_DETECTED.
---
--- We might get away with just detecting lines that start with "! ", but
--- that might fail with a wrapped line, so we go the extra mile to make
--- sure this is really an error.
+-- errorHandler simply identifies errors and defines "ERRORS_DETECTED" as true.
+-- We might get away with just detecting lines that start with "! ", but that
+-- might fail with a wrapped line, so we go the extra mile to make sure this
+-- is really an error. pseudoErrorHandler deals with some low-level warnings
+-- generated by some engines that have a similar format to error messages.
 -------------------------------------------------------------------------------
 
 errorHandler = HandlerPrototype:new()
 
 errorHandler.patterns = {
+    -- luatex engine errors
+    "error:%s+%(pdf backend%): 'endlink' ended up in different "
+                .. "nesting level than 'startlink'",
+    "error:%s+%(pdf backend%): \\pdfextension endlink cannot be "
+                .. "used in vertical mode",
+    -- pdftex engine errors
+    "pdfTeX error %(ext1%): \\pdfendlink cannot be used in vertical mode%.",
+    "pdfTeX error %(ext5%): cannot open file for embedding%.",
     -- basic LaTeX error messages follow these patterns
     'Package .- Error: ',
     'Class .- Error: ',
@@ -2046,7 +2093,7 @@
   local line = Lines:get(position)
   if line == nil then return false, {} end
 
-  -- This error does not start with "! " or "file:line: "
+  -- Errors that do not start with "! " or "file:line: "
   local last = self:isRunawayLine(line)
   if last then return true, {numLines = 1} end
 
@@ -2053,63 +2100,98 @@
   last = self:isErrorLine(line)
   if not last then return false, {} end
 
-  -- Looks like an error; Let's look ahead to identify the other
-  -- lines that are part of the error message. We do not want to
-  -- look too much ahead, so we just scan the current buffer.
+  -- This is (almost certainly) an error; let's check
+  -- if it matches a known message (we do this to decide
+  -- whether we should unwrap the first line)
+  local identified = false
+  for _, pat in ipairs(self.patterns) do
+      local _, ok = string.find(line, pat)
+      if ok then identified = true break end
+  end
+
+  -- If there were no matches, let's try to unwrap the first line
+  local wrapped = false
+  if not identified then
+      if Lines:seemsWrapped(position) then
+          nextline = Lines:get(position +1)
+          local unwrappedLine = line .. nextline
+
+          for _, pat in ipairs(self.patterns) do
+              local _, ok = string.find(unwrappedLine, pat)
+              if ok then
+                  position = position +1
+                  wrapped = true
+                  break
+              end
+          end
+      end
+  end
+
+  -- if we still could not find a match, it is an unknown error message.
+  -- Things should still work ok, as long as the first line is not wrapped.
+  -- If that is not the case, the remaining lines of this message will
+  -- probably not be identified as such.
+
+  -- Let's look ahead to identify the other lines that are part
+  -- of the error message. We do not want to look too much ahead,
+  -- so we just scan the current buffer.
+
+  local lastline = position
   position = position +1
-  local lastline = nil
+  -- We use "<", not "<=", so we can access the following line too
   while position < Lines:numLines() do
+
       -- if there is a second error, don't look further ahead
       if self:isErrorLine(Lines:get(position))
               or self:isRunawayLine(Lines:get(position))
-
       then break end
 
-      if string.find(Lines:get(position), '^l%.%d+ ') then
-          local length = string.len(Lines:get(position))
+      -- This is always the last line
+      if string.find(Lines:get(position),
+                     '^Type%s+H %<return%>%s+for immediate help%.')
+      then
+          lastline = position
+          break
+      end
+
+      local length = string.len(Lines:get(position))
+      if length > 0 then -- skip empty lines
           if string.find(Lines:get(position +1),
                          "^" .. string.rep(" ", length))
           then
-              lastline = position +1 -- the following line is the last
+              -- this line and the next belong to the message;
+              -- on the next iteration, check the line after that
+              position = position +1
+              lastline = position
+          elseif string.find(Lines:get(position), '^l%.%d+ ') then
+              lastline = position
           else
-              -- the following line is empty, so it was skipped
-              -- when reading the file
-              lastline = position
+              break -- this line belongs to a different message
           end
       end
 
-      if string.find(Lines:get(position),
-                         '^Type%s+H %<return%>%s+for immediate help%.') then
-          lastline = position
-      end
-
       position = position +1
   end
 
-  if lastline then
-      -- position starts at zero, so numlines needs +1
-      return true, {numLines = lastline +1}
-  else
-      -- This looks like an error, but there is no "l.NUM" line following
-      -- it, so it is probably a false positive. Still, let's check for
-      -- some known error messages.
-      local candidateText = string.sub(line, last +1)
-      for _, pat in ipairs(self.patterns) do
-          _, last = string.find(candidateText, pat)
-          if last ~= nil then return true, {numLines = 1} end
-      end
-  end
-
-  return false, {} -- this was really a false positive
+  -- position starts at zero, so numlines needs +1
+  return true, {numLines = lastline +1, wrapped = wrapped}
 end
 
 function errorHandler:handleFirstLine()
   local myTurn, data = self:canDoit()
   if not myTurn then return false end
+  ERRORS_DETECTED = true
+  self:reallyHandleFirstLine(data)
+  return true
+end
 
+function errorHandler:reallyHandleFirstLine(data)
   flushUnrecognizedMessages()
 
-  ERRORS_DETECTED = true
+  if data.wrapped then
+      Lines:unwrapOneLine()
+      data.numLines = data.numLines -1
+  end
 
   self.message = self:newMessage()
   self.message.severity = UNKNOWN
@@ -2120,27 +2202,140 @@
   self.numLines = data.numLines
   self.doit = self.handleLines
   nextHandler = self
-
-  return true
 end
 
 errorHandler.doit = errorHandler.handleFirstLine
 
 function errorHandler:handleLines()
-  if self.processed >= self.numLines then
+  if self.processed >= self.numLines then -- We're done!
       self.doit = self.handleFirstLine
       dispatch(self.message)
-  else
-      self.message.content = self.message.content .. '\n' .. Lines.current
-      Lines:handledChars()
-      self.processed = self.processed +1
-      nextHandler = self
+      return true
   end
 
+  local last = nil
+
+  if self.processed == self.numLines -1 then
+      -- last line; there may be some other messages at the end
+      last = string.len(Lines.current)
+      for _, handler in ipairs(anywhereHandlers) do
+          local match, data = handler:lookahead()
+          if match and data.first -1 < last then last = data.first -1 end
+      end
+  end
+
+  self:outputCurrentLine(last)
+
+  self.processed = self.processed +1
+  nextHandler = self
   return true
 end
 
+function errorHandler:outputCurrentLine(last)
+   -- "last" is nil in all but the last line
+  local line = string.sub(Lines.current, 1, last)
+  self.message.content = self.message.content .. '\n' .. line
+  Lines:handledChars(last)
+end
 
+pseudoErrorHandler = errorHandler:new()
+
+pseudoErrorHandler.patterns = {
+    '^pdfTeX warning %(ext4%): destination with the same identifier %b() '
+               .. 'has been already used, duplicate ignored',
+    '^pdfTeX warning: \\pdfendlink ended up in different nesting level '
+               .. 'than \\pdfstartlink',
+    '^warning%s+%(pdf backend%): ignoring duplicate destination with '
+               .. "the name '.-'",
+}
+
+function pseudoErrorHandler:canDoit(position)
+  if position == nil then position = 0 end
+  local line = Lines:get(position)
+  if line == nil then return false, {} end
+
+  for _, pat in ipairs(self.patterns) do
+      _, last = string.find(line, pat)
+      if last then break end
+  end
+
+  -- Maybe we should unwrap this line?
+  local wrapped = false
+  if not last then
+      if not Lines:seemsWrapped(position) then return false, {} end
+      nextline = Lines:get(position +1)
+      line = line .. nextline
+      position = position +1
+
+      for _, pat in ipairs(self.patterns) do
+          _, last = string.find(line, pat)
+          if last then wrapped = true break end
+      end
+  end
+
+  if not last then return false, {} end
+  local lastline = position
+
+  -- Looks like a pseudoError; Let's look ahead to identify the other
+  -- lines that are part of the message. We do not want to look too
+  -- much ahead, so we just scan the current buffer.
+
+  position = position +1
+  -- We use "<", not "<=", so we can access the following line too
+  while position < Lines:numLines() do
+
+      -- if there is a second error, don't look further ahead
+      if self:isErrorLine(Lines:get(position))
+              or self:isRunawayLine(Lines:get(position))
+      then break end
+
+      local length = string.len(Lines:get(position))
+      if length > 0 then -- skip empty lines
+          if string.find(Lines:get(position +1),
+                         "^" .. string.rep(" ", length))
+          then
+              -- this line and the next belong to the message;
+              -- on the next iteration, check the line after that
+              position = position +1
+              lastline = position
+          elseif string.find(Lines:get(position), '^l%.%d+ ') then
+              lastline = position
+          else
+              break -- this line belongs to a different message
+          end
+      end
+
+      position = position +1
+  end
+
+  -- position starts at zero, so numlines needs +1
+  return true, {numLines = lastline +1, wrapped = wrapped}
+end
+
+function pseudoErrorHandler:handleFirstLine()
+  local myTurn, data = self:canDoit()
+  if not myTurn then return false end
+
+  local terseContent = Lines.current
+  if data.wrapped then terseContent = terseContent .. Lines:get(1) end
+
+  errorHandler.reallyHandleFirstLine(self, data)
+  self.message.severity = WARNING
+  self.message.terseContent = terseContent
+  return true
+end
+
+function pseudoErrorHandler:outputCurrentLine(last)
+   -- "last" is nil in all but the last line
+  local line = string.sub(Lines.current, 1, last)
+  if MINLEVEL <= INFO then
+      self.message.content = self.message.content .. '\n    ' .. line
+  end
+  Lines:handledChars(last)
+end
+
+pseudoErrorHandler.doit = pseudoErrorHandler.handleFirstLine
+
 -------------------------------------------------------------------------------
 -- epilogueHandler
 --
@@ -2762,8 +2957,6 @@
   '^\\[^%s=]+=[^%s=]+', -- "\c at chapter=\count174"
   "^\\openout%d+%s*=%s*`?[^']+'?%.?",
 
-  '^LaTeX2e <' .. datepat .. '>.*',
-
   '^Lua module: lualibs%-extended ' .. datepat
                    .. ' %S+ ConTeXt Lua libraries %-%- extended collection%.',
 
@@ -2921,12 +3114,6 @@
 
   "^`newtxtext' v[%d%.]+, " .. datepat .. " Text macros taking advantage of "
               .. "TeXGyre Termes and its extensions %(msharpe%)",
-
-  -- don't know where this comes from...
-  "^ %*%*%*%*%*%*%*%*%*%*%*\n"
-  .. "LaTeX2e <" .. datepat .. ">\n"
-  .. "L3 programming layer <" .. datepat .. ">\n"
-  .. " %*%*%*%*%*%*%*%*%*%*%*"
 }
 
 
@@ -2937,7 +3124,24 @@
 anywhereDebugStringsHandler.IHandleAnywhere = true
 anywhereDebugStringsHandler.severity = DEBUG
 anywhereDebugStringsHandler.patterns = {
-  '^%s*L3 programming layer %b<>',
+  -- don't know where this comes from...
+  "^%s*%*%*%*%*%*%*%*%*%*%*%*\n"
+  .. "LaTeX2e <" .. datepat .. ">.*\n"
+  .. "%s*L3 programming layer <" .. datepat .. ">.*\n"
+  .. "%s*%*%*%*%*%*%*%*%*%*%*%*",
+
+  '^%s*LaTeX2e <' .. datepat .. '>\n%s*patch.*',
+
+  '^%s*LaTeX2e <' .. datepat .. '> patch.*',
+
+  '^%s*LaTeX2e <' .. datepat .. '>',
+
+  '^%s*L3 programming layer <' .. datepat .. '>\n%s*patch.*',
+
+  '^%s*L3 programming layer <' .. datepat .. '> patch.*',
+
+  '^%s*L3 programming layer <' .. datepat .. '>',
+
   '^%s*xparse %b<>',
   '^%s*%{.*pdftex%.map%}',
 
@@ -2996,6 +3200,10 @@
   "^%s*Comment '[^']+' writing to " .. filepat .. "%.",
   "^%s*Straight input of " .. filepat .. "%.",
   "^%s*Include comment '[^']+' up to level '[^']+'",
+  "^Lua module%: autotype " .. datepat .. " v%S+ automatic "
+                                .. "language%-specific typography",
+  "^Lua module%: pdnm%_nl%_manipulation " .. datepat .. " v%S+ "
+                        .. "pattern driven node list manipulation",
 }
 
 
@@ -3022,6 +3230,7 @@
   "^No file .-%.bbl%.",
   "^No file .-%.gls%.",
 
+  "^runsystem%b()%.%.%.executed safely %(allowed%)%.",
   "^runsystem%b()%.%.%.executed%.?",
 
   'luaotfload | db : Reload initiated %(formats: .-%); reason: Font ".-" not found%.',
@@ -3106,6 +3315,14 @@
    .. "although it is yet undefined",
 
    '^%* %* %* LNI %* %* %*',
+   "^Style `ntheorem', Version %S+ <" .. datepat .. ">",
+   "^`XCharter' v%S+, " .. datepat .. " Text macros for XCharter, "
+                           .. "an extension of Charter %(msharpe%)",
+   "^Document Style algorithmicx %S+ %- a greatly improved `algorithmic' style",
+   "^Applying%: [" .. datepat .. "] Usage of raw or classic option list "
+                                       .. "on input line %S+%.",
+   "^Already applied%: [" .. datepat .. "] Usage of raw or classic "
+                                       .. "option list on input line %S+%.",
 }
 
 
@@ -3219,7 +3436,7 @@
 
   "^ ======================================= \n"
     .. " WARNING WARNING WARNING \n"
-    .. " %-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%- \n"
+    .. " " .. string.rep('%-', 39) .. " \n"
     .. " The ligature suppression macros of the \n"
     .. " selnolig package %*require%* LuaLaTeX%. \n"
     .. " Because you're NOT running this package \n"
@@ -3373,10 +3590,25 @@
       Lines:handledChars()
       nextHandler = self
   else
-      self.doit = self.handleFirstLine
+
       if self.linenum ~= "" then
           self.message.linenum = self.linenum
+
+          tmp = {
+                 " on input line " .. self.linenum .. "%.",
+                 " on line " .. self.linenum .. "$"
+                }
+          for _, pat in ipairs(tmp) do
+              local first = string.find(self.message.content, pat)
+              if first then
+                  self.message.terseContent = string.sub(self.message.content,
+                                                         1, first -1)
+                  break
+              end
+          end
       end
+
+      self.doit = self.handleFirstLine
       dispatch(self.message)
       self.message = nil
   end
@@ -3408,10 +3640,12 @@
   if self.linenum ~= "" then return end
 
   _, _, self.linenum = string.find(Lines.current, "on input line (%d+)%.")
+  if self.linenum == nil then self.linenum = "" end
   if self.linenum ~= "" then return end
 
   -- LaTeX3-style messages (with \msg_something)
   _, _, self.linenum = string.find(Lines.current, "on line (%d+)$")
+  if self.linenum == nil then self.linenum = "" end
 end
 
 function genericLatexHandler:parseSeverity(severity)
@@ -3735,6 +3969,13 @@
 
 openCloseHandlerPrototype = HandlerPrototype:new()
 
+function openCloseHandlerPrototype:init()
+    self.openPat = '%' .. self.openChar
+    self.closePat = '%' .. self.closeChar
+    self.openOrClosePat = '[' .. self.openPat .. self.closePat .. ']'
+    self.pattern = self.strictPattern
+end
+
 -- Just like :canDoit(), but does not anchor patterns to the
 -- beginning of the line (used by handleUnrecognizedMessage).
 -- Notice the similarity to stringsHandler:lookahead().
@@ -3748,9 +3989,11 @@
 
 openParensHandler = openCloseHandlerPrototype:new()
 
-openParensHandler.strictPattern = "^(%s*)%("
-openParensHandler.loosePattern = "%s*%("
-openParensHandler.pattern = openParensHandler.strictPattern
+openParensHandler.name = "openParensHandler"
+openParensHandler.openChar = '('
+openParensHandler.closeChar = ')'
+openParensHandler.loosePattern = "(%s*)%" .. openParensHandler.openChar
+openParensHandler.strictPattern = "^" .. openParensHandler.loosePattern
 
 function openParensHandler:canDoit(position)
   if position == nil then position = 0 end
@@ -3775,7 +4018,7 @@
   -- (because of the unwrapping) or we will handle them using the
   -- "DUMMY" entry in the stack as usual.
   if filename == nil and position > 0 then
-      if string.find(line, '%)') then return false, {} end
+      if string.find(line, self.closePat) then return false, {} end
   end
 
   return true, {first = first, filename = filename} -- might be nil
@@ -3786,6 +4029,7 @@
   if not myTurn then return false end
 
   local _, last, spaces = string.find(Lines.current, self.pattern)
+  if spaces == nil then spaces = "" end
   unrecognizedBuffer = unrecognizedBuffer .. spaces
 
   -- skip the spaces and the open parens character
@@ -3797,12 +4041,13 @@
       if last == nil then
           io.stderr:write("    texlogsieve: parsing error near input line "
                                .. Lines.linenum
-                               .. " (openParensHandler:doit)\n")
+                               .. " (" .. self.name .. ":doit)\n")
 
           PARSE_ERROR = true
       else
           Lines:handledChars(last)
       end
+      if openFiles:peek() == "DUMMY" then openFiles:pop() end
       openFiles:push(data.filename)
       mute = currentFileIsSilenced()
       local msg = openFileMessage:new()
@@ -3812,7 +4057,7 @@
       dispatch(msg)
   else
       openFiles:push("DUMMY")
-      unrecognizedBuffer = unrecognizedBuffer .. "("
+      unrecognizedBuffer = unrecognizedBuffer .. self.openChar
   end
 
   return true
@@ -3820,9 +4065,11 @@
 
 closeParensHandler = openCloseHandlerPrototype:new()
 
-closeParensHandler.strictPattern = "^(%s*)%)"
-closeParensHandler.loosePattern = "%s*%)"
-closeParensHandler.pattern = closeParensHandler.strictPattern
+closeParensHandler.name = "closeParensHandler"
+closeParensHandler.openChar = '('
+closeParensHandler.closeChar = ')'
+closeParensHandler.loosePattern = "(%s*)%" .. closeParensHandler.closeChar
+closeParensHandler.strictPattern = "^" .. closeParensHandler.loosePattern
 
 -- In lookahead, when we say "we can do it" we actually mean "well,
 -- we might be able to do it". This is not a problem: it simply
@@ -3908,9 +4155,9 @@
     if line ~= nil then size = string.len(line) end
 
     while i <= size do
-        local j = string.find(line, '[%(%)]', i)
+        local j = string.find(line, self.openOrClosePat, i)
         if j ~= nil then
-            local open = string.find(line, '%(', i)
+            local open = string.find(line, self.openPat, i)
             if open then
                 unpaired = unpaired +1
             elseif unpaired > 0 then
@@ -3939,6 +4186,7 @@
   if not myTurn then return false end
 
   local _, last, spaces = string.find(Lines.current, self.pattern)
+  if spaces == nil then spaces = "" end
   unrecognizedBuffer = unrecognizedBuffer .. spaces
 
   -- skip the spaces and the close parens character
@@ -3946,7 +4194,7 @@
 
   local filename = openFiles:pop()
   if filename == nil or filename == "DUMMY" then
-      unrecognizedBuffer = unrecognizedBuffer .. ")"
+      unrecognizedBuffer = unrecognizedBuffer .. self.closeChar
   else
       flushUnrecognizedMessages()
       local msg = closeFileMessage:new()
@@ -3961,9 +4209,13 @@
 
 openSquareBracketHandler = openCloseHandlerPrototype:new()
 
-openSquareBracketHandler.strictPattern = "^(%s*)%["
-openSquareBracketHandler.loosePattern = "%s*%["
-openSquareBracketHandler.pattern = openSquareBracketHandler.strictPattern
+openSquareBracketHandler.name = "openSquareBracketHandler"
+openSquareBracketHandler.openChar = '['
+openSquareBracketHandler.closeChar = ']'
+openSquareBracketHandler.loosePattern = "(%s*)%"
+                                      .. openSquareBracketHandler.openChar
+openSquareBracketHandler.strictPattern = "^"
+                                       .. openSquareBracketHandler.loosePattern
 
 function openSquareBracketHandler:canDoit(position)
   if position == nil then position = 0 end
@@ -3979,7 +4231,7 @@
 
   -- See the comment "HACK ALERT" in openParensHandler:canDoit()
   if latexPage == nil and position > 0 then
-      if string.find(line, '%]') then return false, {} end
+      if string.find(line, self.closePat) then return false, {} end
   end
 
   return true, {first = first, latexPage = latexPage} -- may be nil
@@ -4001,12 +4253,13 @@
       if last == nil then
           io.stderr:write("    texlogsieve: parsing error near input line "
                                .. Lines.linenum
-                               .. " (openSquareBracketHandler:doit)\n")
+                               .. " (" .. self.name .. ":doit)\n")
 
           PARSE_ERROR = true
       else
           Lines:handledChars(last)
       end
+      if shipouts:peek() == "DUMMY" then shipouts:pop() end
       shipouts:push(data.latexPage)
       numShipouts = numShipouts +1
       table.insert(latexPages, numShipouts, data.latexPage)
@@ -4015,7 +4268,7 @@
       dispatch(msg)
   else
       shipouts:push("DUMMY")
-      unrecognizedBuffer = unrecognizedBuffer .. "["
+      unrecognizedBuffer = unrecognizedBuffer .. self.openChar
   end
 
   return true
@@ -4023,9 +4276,13 @@
 
 closeSquareBracketHandler = openCloseHandlerPrototype:new()
 
-closeSquareBracketHandler.strictPattern = "^(%s*)%]"
-closeSquareBracketHandler.loosePattern = "%s*%]"
-closeSquareBracketHandler.pattern = closeSquareBracketHandler.strictPattern
+closeSquareBracketHandler.name = "closeSquareBracketHandler"
+closeSquareBracketHandler.openChar = '['
+closeSquareBracketHandler.closeChar = ']'
+closeSquareBracketHandler.loosePattern = "(%s*)%"
+                                    .. closeSquareBracketHandler.closeChar
+closeSquareBracketHandler.strictPattern = "^"
+                                    .. closeSquareBracketHandler.loosePattern
 
 -- Read the comment right before "closeParensHandler:lookahead()"
 function closeSquareBracketHandler:lookahead()
@@ -4060,9 +4317,9 @@
     if line ~= nil then size = string.len(line) end
 
     while i <= size do
-        local j = string.find(line, '[%[%]]', i)
+        local j = string.find(line, self.openOrClosePat, i)
         if j ~= nil then
-            local open = string.find(line, '%[', i)
+            local open = string.find(line, self.openPat, i)
             if open then
                 unpaired = unpaired +1
             elseif unpaired > 0 then
@@ -4098,7 +4355,7 @@
 
   local latexPage = shipouts:pop()
   if latexPage == nil or latexPage == "DUMMY" then
-      unrecognizedBuffer = unrecognizedBuffer .. "]"
+      unrecognizedBuffer = unrecognizedBuffer .. self.closeChar
   else
       flushUnrecognizedMessages()
       local msg = endShipoutMessage:new()
@@ -4107,51 +4364,100 @@
   end
 end
 
--- During a shipout, TeX sometimes puts some filenames inside a pair of
--- "{}" or "<>" characters. Since there are no other messages inside these
--- opening and closing characters, this handler may be simple: find the
--- opening character and unwrap lines until finding the closing character.
--- We even check for "{<" and "}>" at the same time, without verifying if
--- they are actually paired, because they really should be.
+-- During a shipout, TeX sometimes puts some filenames
+-- inside a pair of "{}" or "<>" characters.
 shipoutFilesHandler = HandlerPrototype:new()
-shipoutFilesHandler.strictPattern = "^(%s*)[%{%<]"
-shipoutFilesHandler.loosePattern = "%s*[%{%<]"
-shipoutFilesHandler.pattern = shipoutFilesHandler.strictPattern
-shipoutFilesHandler.closingPattern = "[%}%>]"
+shipoutFilesHandler.strictPatterns = {
+    "^(%s*)(%b<>)",
+    "^(%s*)(%b{})",
+}
+shipoutFilesHandler.loosePatterns = {
+    "(%s*)(%b<>)",
+    "(%s*)(%b{})",
+}
+shipoutFilesHandler.patterns = shipoutFilesHandler.strictPatterns
 
--- Read the comment right before "closeParensHandler:lookahead()"
+-- Read the comment right before "closeParensHandler:lookahead()".
+-- That does not work here for two reasons:
 --
--- This handler only processes stuff if there is a pending shipout in the
--- shipouts stack. However, we cannot check for this in lookahead() because
--- the shipout may not have been processed yet. As a result, lookahead()
--- may answer "yes" when in reality the handler won't do anything. This has
--- a nasty consequence: if there is a { or < character at the beginning
--- of a line, doit() may not handle it, but lookahead() may answer "yes",
--- causing an endless loop. We solve this by answering "yes" ONLY if the
--- open character is NOT at the beginning of the line.
+-- 1. We may mess up messages that present content from the document,
+--    which may include "{}" or "<>" characters. An example is the
+--    "pdfTeX warning (ext4):..." message.
+--
+-- 2. This handler should only process stuff if there is a pending shipout
+--    in the shipouts stack. However, we cannot check for this in lookahead()
+--    because the shipout may not have been processed yet. As a result,
+--    lookahead() might answer "yes" when in reality the handler won't do
+--    anything. This would have a nasty consequence: if there is a "{" or
+--    "<" character outside any shipouts at the beginning of a line, doit()
+--    would not handle it, but lookahead() would answer "yes", causing an
+--    endless loop.
+--
+-- The solution is to have lookahead() answer "yes" ONLY if there really
+-- is a filename within the "{}" or "<>" pair. We might also consider only
+-- answering "yes" if the "{" or "<" character is *not* at the beginning
+-- of a line, but that is probably overkill.
+
 function shipoutFilesHandler:lookahead()
-  local line = Lines:get(0)
-  if line == nil then return false, {} end
-
-  local first = string.find(line, self.loosePattern)
-  if first == nil then return false, {} end
-
-  local strictFirst = string.find(line, self.pattern)
-  if first == strictFirst then return false, {} end
-
-  return true, {first = first}
+  shipoutFilesHandler.patterns = shipoutFilesHandler.loosePatterns
+  local ok, data = self:realCanDoit()
+  shipoutFilesHandler.patterns = shipoutFilesHandler.strictPatterns
+  return ok, data
 end
 
 function shipoutFilesHandler:canDoit(position)
   if position == nil then position = 0 end
+  if position == 0 and shipouts:size() == 0 then return false, {} end
+  return self:realCanDoit(position)
+end
+
+function shipoutFilesHandler:realCanDoit(position)
+  if position == nil then position = 0 end
   local line = Lines:get(position)
   if line == nil then return false, {} end
-  if position == 0 and shipouts:size() == 0 then return false, {} end
 
-  local first, last = string.find(line, self.pattern)
+  local first, match
+  for _, pat in ipairs(self.patterns) do
+      first, _, _, match = string.find(line, pat)
+      if first then break end
+  end
+
+  -- let's try unwrapping, but we need to be careful: if we match
+  -- on subsequent lines by themselves, then the match is not the
+  -- result of unwrapping, so we should return false.
+  if first == nil then
+      local offset = 1
+      local subsequentLines = ""
+      while offset < 4 do -- unwrapping the 3 subsequent lines is enough
+          local nextLine = Lines:get(position + offset)
+          if not nextLine then return false, {} end
+          subsequentLines = subsequentLines .. nextLine
+          local nextMatch
+          for _, pat in ipairs(self.patterns) do
+              first, _, _, nextMatch = string.find(subsequentLines, pat)
+              if first then break end
+          end
+          local allLines = line .. subsequentLines
+          for _, pat in ipairs(self.patterns) do
+              first, _, _, match = string.find(allLines, pat)
+              if first then break end
+          end
+          if first then
+              if match == nextMatch then
+                  return false, {}
+              else
+                  break
+              end
+          end
+          offset = offset +1
+      end
+  end
+
   if first == nil then return false, {} end
-
-  return true, {first = first}
+  if checkIfFileExists(string.sub(match, 2, -2)) then
+      return true, {first = first, name = match}
+  end
+  return false, {}
 end
 
 function shipoutFilesHandler:doit()
@@ -4158,35 +4464,18 @@
   local myTurn, data = self:canDoit()
   if not myTurn then return false end
 
-  -- Look for the matching close character. It really
-  -- should be there and there should not be any nesting,
-  -- so no need to be overly cautious.
-  local last
-  for i = 0, 4 do -- 4 lines ahead is plenty!
-      line = Lines:get(i)
-      if line == nil then break end
-      _, last = string.find(line, self.closingPattern)
-      if last ~= nil then break end
+  local _, last, spaces = string.find(Lines.current, "(^%s+)")
+  if last then
+      unrecognizedBuffer = unrecognizedBuffer .. spaces
+      Lines:handledChars(last)
+      flushUnrecognizedMessages()
   end
-  if last == nil then return false end -- should never happen
 
-  local _, last, spaces = string.find(Lines.current, self.pattern)
-  unrecognizedBuffer = unrecognizedBuffer .. spaces
+  last, _ = unwrapUntilStringMatches(data.name) -- this should never fail
 
-  -- skip the spaces and the opening character
-  Lines:handledChars(last)
-  flushUnrecognizedMessages()
-
-  _, last = string.find(Lines.current, self.closingPattern)
-  while last == nil do
-      Lines:unwrapOneLine()
-      _, last = string.find(Lines.current, self.closingPattern)
-  end
-
   local msg = shipoutFilesMessage:new()
-  msg.content = "Loading file at shipout: "
-  -- "-1" so that we do not include the closing character
-  msg.content = msg.content .. string.sub(Lines.current, 1, last -1)
+  msg.content = "Loading file during shipout: "
+  msg.content = msg.content .. string.sub(data.name, 2, -2)
   Lines:handledChars(last)
   dispatch(msg)
 
@@ -4469,6 +4758,11 @@
   },
   {
     WARNING,
+    'LaTeX',
+    'Temporary extra page added at the end%. Rerun to get it removed%.'
+  },
+  {
+    WARNING,
     'longtable',
     'Table %S+s have changed%. Rerun LaTeX%.'
   },
@@ -4489,6 +4783,11 @@
   },
   {
     WARNING,
+    'biblatex',
+    'Please %(re%)run Biber on the file:'
+  },
+  {
+    WARNING,
     'atenddvi',
     'Rerun LaTeX, last page not yet found%.'
   },
@@ -4529,16 +4828,6 @@
   },
   {
     WARNING,
-    'biblatex',
-    'Please rerun LaTeX%. Page breaks have changed%.',
-  },
-  {
-    WARNING,
-    'biblatex',
-    'Please rerun LaTeX%.',
-  },
-  {
-    WARNING,
     'bidi-perpage',
     "Counter %b`' may not have been reset per page%. Rerun to reset counter %b`' per page%.",
   },
@@ -5073,6 +5362,8 @@
   return Message.realToString(self)
 end
 
+openFileMessage.alwaysEnds = true
+
 -- We never want to suppress these repetitions
 function openFileMessage:toSummary()
 end
@@ -5085,6 +5376,8 @@
   return Message.realToString(self)
 end
 
+closeFileMessage.alwaysEnds = true
+
 -- We never want to suppress these repetitions
 function closeFileMessage:toSummary()
 end
@@ -5200,6 +5493,11 @@
   local formatted = msg:toString(self.bypassMostFilters)
   if trim(formatted) == "" then return end
 
+  -- Now that we know whether the message is actually included in
+  -- the output, let's check whether we should use terseContent to
+  -- identify duplicates
+  if msg.terseContent ~= nil then formatted = msg.terseContent end
+
   -- group messages by message content
   if self.messages[formatted] == nil then
       self.messages[formatted] = {}
@@ -5211,6 +5509,12 @@
 function SummaryPrototype:alreadySeen(msg)
   local formatted = msg:toString()
   if trim(formatted) == "" then return false end
+
+  -- Now that we know that the message should actually be included in
+  -- the output, let's check whether we should use terseContent to
+  -- identify duplicates
+  if msg.terseContent ~= nil then formatted = msg.terseContent end
+
   return self.messages[formatted] ~= nil
 end
 
@@ -5356,7 +5660,7 @@
   if #messages > 1 then
       local pages, files = self:pageAndFileList(messages)
 
-      local where
+      local where = ""
       if pages ~= "" and files ~= "" then
           where = 'in ' .. pages .. ' (' .. files .. ') - '
       elseif pages == "" and files ~= "" then
@@ -5570,8 +5874,8 @@
   local i = 1
   local lines = {}
   while i <= size do
-      -- check \r in case the user added to this file a pattern
-      -- with an embedded dos-style "CR LF" sequence.
+      -- check for \r in case the user wrongfully added to this file
+      -- a pattern with an embedded dos-style "CR LF" sequence.
       local first, last, line = string.find(s, '(.-)[\r]?\n', i)
 
       if first == nil then
@@ -5602,7 +5906,7 @@
 
 function listToCommaSeparatedString(list, singular, plural, sep)
   sep = sep or ','
-  if #list == 0 then return end
+  if #list == 0 then return "" end
 
   local tmp
   if #list == 1 then
@@ -6425,8 +6729,8 @@
       local longest = string.len(line)
       local notWrapped = false
 
-      -- if there is a ")", "(", "[", or "]" char, stop before that
-      local first = string.find(line, "[%)%(%[%]]")
+      -- if the line contains one of these chars, stop before it
+      local first = string.find(line, "[%(%)%[%]%{%}%<%>]")
       if first ~= nil then
           notWrapped = true -- this line is obviously not wrapped
           longest = first -1
@@ -6478,45 +6782,50 @@
 
   local flsfilename = string.gsub(logfilename, '%.log$', '.fls')
 
+  -- It is ok for this to fail, so no "assert"
+  local flsfile = io.open(flsfilename, 'r')
+  if flsfile == nil then return end
+
   -- Let's be reasonably sure that we are not dealing
   -- with a stale .fls file from a previous run
   local logdate = lfs.attributes(logfilename, 'modification')
   local flsdate = lfs.attributes(flsfilename, 'modification') or 0
   local timediff = math.abs(logdate - flsdate) -- seconds
+  if timediff > 5 then return end
 
-  -- It is ok for this to fail, so no "assert"
-  local flsfile = io.open(flsfilename, 'r')
+  -- We are good to go!
+  USE_FLS_FILE = true
 
-  if flsfile ~= nil and timediff <= 5 then
-      USE_FLS_FILE = true
+  filelist = {}
+  while true do
+      local line = flsfile:read("*line")
 
-      filelist = {}
-      while true do
-          local line = flsfile:read("*line")
-          if line == nil then
-              io.close(flsfile)
-              return
-          end
+      if line == nil then io.close(flsfile) return end
 
-          local _, last = string.find(line, '^[IO][NU]T?PUT ')
-          if last ~= nil then line = string.sub(line, last +1) end
+      -- If we are running in a unix-like OS but the files we are
+      -- processing were generated in Windows, lua may leave a \r
+      -- character at the end of the line; if this happens, remove it
+      local _, last = string.find(line, '\r$')
+      if last ~= nil then line = string.sub(line, 1, last -1) end
 
-          -- I don't think this ever happens
-          if string.find(line, '^".*"$') then line = string.sub(line, 2, -2) end
+      _, last = string.find(line, '^[IO][NU]T?PUT ')
+      if last ~= nil then line = string.sub(line, last +1) end
 
-          -- No idea what is this, but it happens with the MiKTeX version
-          -- of luatex for Windows. It apparently only appears with font
-          -- files, which are irrelevant here, but let's be safe
-          if string.find(line, '^\\\\%?\\') then line = string.sub(line, 5) end
+      -- I don't think this ever happens
+      if string.find(line, '^".*"$') then line = string.sub(line, 2, -2) end
 
-          line = string.gsub(line, '\\', '/')
+      -- No idea what is this, but it happens with the MiKTeX version
+      -- of luatex for Windows. It apparently only appears with font
+      -- files, which are irrelevant here, but let's be safe
+      if string.find(line, '^\\\\%?\\') then line = string.sub(line, 5) end
 
-          _, last = string.find(line, '^%./')
-          if last ~= nil then line = string.sub(line, last +1) end
+      line = string.gsub(line, '\\', '/')
 
-          -- Save as a Set to eliminate duplicates
-          if not string.find(line, '^PWD') then filelist[line] = true end
-      end
+      _, last = string.find(line, '^%./')
+      if last ~= nil then line = string.sub(line, last +1) end
+
+      -- Save as a Set to eliminate duplicates
+      if not string.find(line, '^PWD') then filelist[line] = true end
   end
 end
 

Modified: trunk/Master/texmf-dist/doc/man/man1/texlogsieve.1
===================================================================
--- trunk/Master/texmf-dist/doc/man/man1/texlogsieve.1	2025-03-04 17:01:16 UTC (rev 74435)
+++ trunk/Master/texmf-dist/doc/man/man1/texlogsieve.1	2025-03-04 21:08:29 UTC (rev 74436)
@@ -1,4 +1,4 @@
-.TH TEXLOGSIEVE "1" "November 2024" "texlogsieve 1.4.2" "User Commands"
+.TH TEXLOGSIEVE "1" "March 2025" "texlogsieve 1.5.0" "User Commands"
 
 .SH NAME
 
@@ -303,7 +303,7 @@
 
 .SH COPYRIGHT
 
-Copyright \[co] 2021-2024 Nelson Lago <lago at ime.usp.br>
+Copyright \[co] 2021-2025 Nelson Lago <lago at ime.usp.br>
 .br
 License GPLv3+: GNU GPL version 3 or later
 .UR https://gnu.org/licenses/gpl.html

Modified: trunk/Master/texmf-dist/doc/man/man1/texlogsieve.man1.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/doc/support/texlogsieve/README.md
===================================================================
--- trunk/Master/texmf-dist/doc/support/texlogsieve/README.md	2025-03-04 17:01:16 UTC (rev 74435)
+++ trunk/Master/texmf-dist/doc/support/texlogsieve/README.md	2025-03-04 21:08:29 UTC (rev 74436)
@@ -57,7 +57,7 @@
 
 Code etc: <https://gitlab.com/lago/texlogsieve>
 
-Copyright (C) 2021-2024 Nelson Lago <lago at ime.usp.br>
+Copyright (C) 2021-2025 Nelson Lago <lago at ime.usp.br>
 
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by

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

Modified: trunk/Master/texmf-dist/doc/support/texlogsieve/texlogsieve.tex
===================================================================
--- trunk/Master/texmf-dist/doc/support/texlogsieve/texlogsieve.tex	2025-03-04 17:01:16 UTC (rev 74435)
+++ trunk/Master/texmf-dist/doc/support/texlogsieve/texlogsieve.tex	2025-03-04 21:08:29 UTC (rev 74436)
@@ -1,6 +1,6 @@
 % texlogsieve - filter and summarize LaTeX log files
 %
-% Copyright (C) 2021-2024 Nelson Lago <lago at ime.usp.br>
+% Copyright (C) 2021-2025 Nelson Lago <lago at ime.usp.br>
 %
 % This program is free software: you can redistribute it and/or modify
 % it under the terms of the GNU General Public License as published by
@@ -102,12 +102,28 @@
 \changes{1.4.2}{2024/11/22}{Correctly handle ``*full hbox while output\dots``}
 \changes{1.4.2}{2024/11/22}{Add mention to \texttt{pulp}}
 \changes{1.4.2}{2024/11/22}{Add messages from \texttt{comment} pkg}
+\changes{1.5.0}{2025/03/03}{Add .log extension if necessary}
+\changes{1.5.0}{2025/03/03}{Always convert \texttt{CR LF} to \texttt{LF}
+                            when reading the .log and .fls files}
+\changes{1.5.0}{2025/03/03}{When showing an UNKNOWN message, also show the
+                            previous one if it was suppressed, as they may
+                            be related}
+\changes{1.5.0}{2025/03/03}{Improved and refactored handling of error messages}
+\changes{1.5.0}{2025/03/03}{Allow for some messages that differ only in some
+                            details to be identified as the same message}
+\changes{1.5.0}{2025/03/03}{Identify some engine-specific errors and warnings}
+\changes{1.5.0}{2025/03/03}{Greatly improved shipoutFilesHandler (preventing
+                            weird errors)}
+\changes{1.5.0}{2025/03/03}{When adding a file or shipout to the stack,
+                            remove dangling ``DUMMY'' entry}
+\changes{1.5.0}{2025/03/03}{Fixed bug that prevented some summaries from
+                            being printed}
 
 \begin{document}
 
 \title{\textsf{texlogsieve}:\thanks{This document
-corresponds to \textsf{texlogsieve}~1.4.2,
-dated~2024-11-22.}\\[.3\baselineskip]
+corresponds to \textsf{texlogsieve}~1.5.0,
+dated~2025-03-03.}\\[.3\baselineskip]
 {\normalsize(yet another program to)\\[-.6\baselineskip]}
 {\large filter and summarize \LaTeX\ log files}
 }
@@ -555,7 +571,7 @@
 
 \section{License}
 
-Copyright © 2021--2024 Nelson Lago \textless lago at ime.usp.br\textgreater\\
+Copyright © 2021--2025 Nelson Lago \textless lago at ime.usp.br\textgreater\\
 License GPLv3+: GNU GPL version 3 or later
 \url{https://gnu.org/licenses/gpl.html}.\\
 This is free software: you are free to change and redistribute it.\\

Modified: trunk/Master/texmf-dist/scripts/texlogsieve/texlogsieve
===================================================================
--- trunk/Master/texmf-dist/scripts/texlogsieve/texlogsieve	2025-03-04 17:01:16 UTC (rev 74435)
+++ trunk/Master/texmf-dist/scripts/texlogsieve/texlogsieve	2025-03-04 21:08:29 UTC (rev 74436)
@@ -2,7 +2,7 @@
 
 -- texlogsieve - filter and summarize LaTeX log files
 --
--- Copyright (C) 2021-2024 Nelson Lago <lago at ime.usp.br>
+-- Copyright (C) 2021-2025 Nelson Lago <lago at ime.usp.br>
 --
 -- This program is free software: you can redistribute it and/or modify
 -- it under the terms of the GNU General Public License as published by
@@ -165,26 +165,30 @@
    With option -file-line-error, the exclamation is replaced by an
    indication like "filename:linenum:".
 
-   The "stack trace" is comprised of pairs of lines. By default, LaTeX
-   only shows the last pair (depending on \errorcontextlines) and, when
-   there are more lines, they are replaced by a line with " ..." (right
-   after the line starting with "!"). In each pair of lines, the first
-   one indicates the line content leading up to the error and the
-   second one shows the subsequent content. The first line is at most
-   half_error_line characters long (default 50) and the second line
-   is at most error_line characters long (default 79). The second line
-   is indented to start after the end of the first, as in the example
-   above. If the content of a line does not fit, a part of it (the
-   beginning for the first line and the end for the second line) may
-   be replaced by "...". error_line must be < 255 and half_error_line
-   must be < error_line -15. If error_line >= max_print_line or
-   half_error_line >= max_print_line, TeX does line wrapping as usual
-   (see below). If either is exactly max_print_line (which is true
-   for the default values), things get confusing, so TeX may add a
-   blank line when wrapping.
+   The "stack trace" is comprised of pairs of lines. In each pair,
+   the first line indicates the content in a given line leading up
+   to the error, while the second one shows the subsequent content of
+   that line. The first line is at most half_error_line characters long
+   (default 50) and the second line is at most error_line characters
+   long (default 79). The second line is indented to start after the
+   end of the first, as in the example above. If the content of either
+   segment does not fit, a part of it (the beginning for the first line
+   and the end for the second line) may be replaced by "...". error_line
+   must be < 255 and half_error_line must be < error_line -15. If
+   error_line >= max_print_line or half_error_line >= max_print_line,
+   TeX does line wrapping as usual (see below). If either is exactly
+   max_print_line (which is true for the default values), things get
+   confusing, so TeX may add a blank line when wrapping.
 
+   By default, LaTeX only shows the first and last pairs of lines of the
+   "stack trace" (if only one pair of lines is relevant, then obviously
+   only this pair is shown). The number of additional, intermediate pairs
+   of lines shown is determined by \errorcontextlines: if it is zero, no
+   additional lines are shown, but LaTeX prints "..." indicating they
+   were omitted. If it is -1 (the default), they are not shown and there
+   is no "..." indication.
 
-   LaTeX errors usually follow the format
+   LaTeX errors are similar, but usually follow the format
 
        ! LaTeX/Package/Class BLAH Error: some description
        See the BLAH documentation for explanation.
@@ -627,6 +631,13 @@
   while Lines:numLines() < 15 do
       tmp = logfile:read("*line")
       if tmp == nil then break end
+
+      -- If we are running in a unix-like OS but the files we are
+      -- processing were generated in Windows, lua may leave a \r
+      -- character at the end of the line; if this happens, remove it
+      local _, last = string.find(tmp, '\r$')
+      if last ~= nil then tmp = string.sub(tmp, 1, last -1) end
+
       -- Do not skip blank lines here, we need to do it in Lines:append()
       Lines:append(tmp)
   end
@@ -788,6 +799,7 @@
 end
 
 function registerHandlers()
+  table.insert(beginningOfLineHandlers, pseudoErrorHandler)
   table.insert(beginningOfLineHandlers, errorHandler)
   table.insert(beginningOfLineHandlers, citationHandler)
   table.insert(beginningOfLineHandlers, referenceHandler)
@@ -975,8 +987,8 @@
   --version                              print program version]]
 
 versionmsg = [[
-texlogsieve 1.4.2
-Copyright (C) 2021-2024 Nelson Lago <lago at ime.usp.br>
+texlogsieve 1.5.0
+Copyright (C) 2021-2025 Nelson Lago <lago at ime.usp.br>
 License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
 This is free software: you are free to change and redistribute it.
 There is NO WARRANTY, to the extent permitted by law.]]
@@ -1314,8 +1326,15 @@
   if vars.filename == nil then
       logfile = io.stdin
   else
-      logfile = assert(io.open(vars.filename, "r"))
-      readFls(vars.filename)
+      local filename
+      local exts = {"", ".log", "log"}
+      for _, ext in ipairs(exts) do
+          filename = vars.filename .. ext
+          logfile = io.open(filename, "r")
+          if logfile ~= nil then break end
+      end
+      assert(logfile ~= nil)
+      readFls(filename)
   end
 
   vars.filename = nil
@@ -1728,9 +1747,12 @@
   end
 end
 
-function showMessage(msg)
-  local formatted = msg:toString()
+function showMessage(msg, bypassMostFilters)
+  msg.suppressed = true
+  local formatted = msg:toString(bypassMostFilters)
+
   if trim(formatted) ~= "" then
+      msg.suppressed = false
       local pageinfo = ""
       local spaces = ""
       if not RAW and msg.physicalPage ~= nil then
@@ -1746,6 +1768,7 @@
           for _, summary in ipairs(summaries) do
               if summary:alreadySeen(msg) then
                   alreadySeen = true
+                  msg.suppressed = true
                   break
               end
           end
@@ -1752,6 +1775,22 @@
       end
 
       if not SILENCE_REPETITIONS or not alreadySeen then
+
+          -- For unknown messages, we show the previous one
+          -- if it was suppressed to give some context, unless
+          -- that message is certainly unrelated
+          if msg.severity == UNKNOWN
+                  and lastMessage ~= nil
+                  and msg ~= lastMessage
+                  and lastMessage.suppressed
+                  and not lastMessage.alwaysEnds
+          then
+              local TMP = SILENCE_REPETITIONS
+              SILENCE_REPETITIONS = false
+              showMessage(lastMessage, true)
+              SILENCE_REPETITIONS = TMP
+          end
+
           showFileBanner(msg)
 
           for _, line in ipairs(linesToTable(formatted)) do
@@ -1768,7 +1807,10 @@
       end
   end
 
-  msg:toSummary()
+  if msg ~= lastMessage then
+      lastMessage = msg
+      msg:toSummary()
+  end
 end
 
 function showPageMessages()
@@ -1973,21 +2015,26 @@
 
 -------------------------------------------------------------------------------
 -- errorHandler
+-- pseudoErrorHandler
 --
--- This simply identifies errors and defines "ERRORS_DETECTED" as true. The
--- line with the message is output directly with no further processing, which
--- means the other lines that belong to the error are output as unrecognized
--- messages. We could be more thorough here and handle the whole message, but
--- that is not really necessary, all we want is ERRORS_DETECTED.
---
--- We might get away with just detecting lines that start with "! ", but
--- that might fail with a wrapped line, so we go the extra mile to make
--- sure this is really an error.
+-- errorHandler simply identifies errors and defines "ERRORS_DETECTED" as true.
+-- We might get away with just detecting lines that start with "! ", but that
+-- might fail with a wrapped line, so we go the extra mile to make sure this
+-- is really an error. pseudoErrorHandler deals with some low-level warnings
+-- generated by some engines that have a similar format to error messages.
 -------------------------------------------------------------------------------
 
 errorHandler = HandlerPrototype:new()
 
 errorHandler.patterns = {
+    -- luatex engine errors
+    "error:%s+%(pdf backend%): 'endlink' ended up in different "
+                .. "nesting level than 'startlink'",
+    "error:%s+%(pdf backend%): \\pdfextension endlink cannot be "
+                .. "used in vertical mode",
+    -- pdftex engine errors
+    "pdfTeX error %(ext1%): \\pdfendlink cannot be used in vertical mode%.",
+    "pdfTeX error %(ext5%): cannot open file for embedding%.",
     -- basic LaTeX error messages follow these patterns
     'Package .- Error: ',
     'Class .- Error: ',
@@ -2046,7 +2093,7 @@
   local line = Lines:get(position)
   if line == nil then return false, {} end
 
-  -- This error does not start with "! " or "file:line: "
+  -- Errors that do not start with "! " or "file:line: "
   local last = self:isRunawayLine(line)
   if last then return true, {numLines = 1} end
 
@@ -2053,63 +2100,98 @@
   last = self:isErrorLine(line)
   if not last then return false, {} end
 
-  -- Looks like an error; Let's look ahead to identify the other
-  -- lines that are part of the error message. We do not want to
-  -- look too much ahead, so we just scan the current buffer.
+  -- This is (almost certainly) an error; let's check
+  -- if it matches a known message (we do this to decide
+  -- whether we should unwrap the first line)
+  local identified = false
+  for _, pat in ipairs(self.patterns) do
+      local _, ok = string.find(line, pat)
+      if ok then identified = true break end
+  end
+
+  -- If there were no matches, let's try to unwrap the first line
+  local wrapped = false
+  if not identified then
+      if Lines:seemsWrapped(position) then
+          nextline = Lines:get(position +1)
+          local unwrappedLine = line .. nextline
+
+          for _, pat in ipairs(self.patterns) do
+              local _, ok = string.find(unwrappedLine, pat)
+              if ok then
+                  position = position +1
+                  wrapped = true
+                  break
+              end
+          end
+      end
+  end
+
+  -- if we still could not find a match, it is an unknown error message.
+  -- Things should still work ok, as long as the first line is not wrapped.
+  -- If that is not the case, the remaining lines of this message will
+  -- probably not be identified as such.
+
+  -- Let's look ahead to identify the other lines that are part
+  -- of the error message. We do not want to look too much ahead,
+  -- so we just scan the current buffer.
+
+  local lastline = position
   position = position +1
-  local lastline = nil
+  -- We use "<", not "<=", so we can access the following line too
   while position < Lines:numLines() do
+
       -- if there is a second error, don't look further ahead
       if self:isErrorLine(Lines:get(position))
               or self:isRunawayLine(Lines:get(position))
-
       then break end
 
-      if string.find(Lines:get(position), '^l%.%d+ ') then
-          local length = string.len(Lines:get(position))
+      -- This is always the last line
+      if string.find(Lines:get(position),
+                     '^Type%s+H %<return%>%s+for immediate help%.')
+      then
+          lastline = position
+          break
+      end
+
+      local length = string.len(Lines:get(position))
+      if length > 0 then -- skip empty lines
           if string.find(Lines:get(position +1),
                          "^" .. string.rep(" ", length))
           then
-              lastline = position +1 -- the following line is the last
+              -- this line and the next belong to the message;
+              -- on the next iteration, check the line after that
+              position = position +1
+              lastline = position
+          elseif string.find(Lines:get(position), '^l%.%d+ ') then
+              lastline = position
           else
-              -- the following line is empty, so it was skipped
-              -- when reading the file
-              lastline = position
+              break -- this line belongs to a different message
           end
       end
 
-      if string.find(Lines:get(position),
-                         '^Type%s+H %<return%>%s+for immediate help%.') then
-          lastline = position
-      end
-
       position = position +1
   end
 
-  if lastline then
-      -- position starts at zero, so numlines needs +1
-      return true, {numLines = lastline +1}
-  else
-      -- This looks like an error, but there is no "l.NUM" line following
-      -- it, so it is probably a false positive. Still, let's check for
-      -- some known error messages.
-      local candidateText = string.sub(line, last +1)
-      for _, pat in ipairs(self.patterns) do
-          _, last = string.find(candidateText, pat)
-          if last ~= nil then return true, {numLines = 1} end
-      end
-  end
-
-  return false, {} -- this was really a false positive
+  -- position starts at zero, so numlines needs +1
+  return true, {numLines = lastline +1, wrapped = wrapped}
 end
 
 function errorHandler:handleFirstLine()
   local myTurn, data = self:canDoit()
   if not myTurn then return false end
+  ERRORS_DETECTED = true
+  self:reallyHandleFirstLine(data)
+  return true
+end
 
+function errorHandler:reallyHandleFirstLine(data)
   flushUnrecognizedMessages()
 
-  ERRORS_DETECTED = true
+  if data.wrapped then
+      Lines:unwrapOneLine()
+      data.numLines = data.numLines -1
+  end
 
   self.message = self:newMessage()
   self.message.severity = UNKNOWN
@@ -2120,27 +2202,140 @@
   self.numLines = data.numLines
   self.doit = self.handleLines
   nextHandler = self
-
-  return true
 end
 
 errorHandler.doit = errorHandler.handleFirstLine
 
 function errorHandler:handleLines()
-  if self.processed >= self.numLines then
+  if self.processed >= self.numLines then -- We're done!
       self.doit = self.handleFirstLine
       dispatch(self.message)
-  else
-      self.message.content = self.message.content .. '\n' .. Lines.current
-      Lines:handledChars()
-      self.processed = self.processed +1
-      nextHandler = self
+      return true
   end
 
+  local last = nil
+
+  if self.processed == self.numLines -1 then
+      -- last line; there may be some other messages at the end
+      last = string.len(Lines.current)
+      for _, handler in ipairs(anywhereHandlers) do
+          local match, data = handler:lookahead()
+          if match and data.first -1 < last then last = data.first -1 end
+      end
+  end
+
+  self:outputCurrentLine(last)
+
+  self.processed = self.processed +1
+  nextHandler = self
   return true
 end
 
+function errorHandler:outputCurrentLine(last)
+   -- "last" is nil in all but the last line
+  local line = string.sub(Lines.current, 1, last)
+  self.message.content = self.message.content .. '\n' .. line
+  Lines:handledChars(last)
+end
 
+pseudoErrorHandler = errorHandler:new()
+
+pseudoErrorHandler.patterns = {
+    '^pdfTeX warning %(ext4%): destination with the same identifier %b() '
+               .. 'has been already used, duplicate ignored',
+    '^pdfTeX warning: \\pdfendlink ended up in different nesting level '
+               .. 'than \\pdfstartlink',
+    '^warning%s+%(pdf backend%): ignoring duplicate destination with '
+               .. "the name '.-'",
+}
+
+function pseudoErrorHandler:canDoit(position)
+  if position == nil then position = 0 end
+  local line = Lines:get(position)
+  if line == nil then return false, {} end
+
+  for _, pat in ipairs(self.patterns) do
+      _, last = string.find(line, pat)
+      if last then break end
+  end
+
+  -- Maybe we should unwrap this line?
+  local wrapped = false
+  if not last then
+      if not Lines:seemsWrapped(position) then return false, {} end
+      nextline = Lines:get(position +1)
+      line = line .. nextline
+      position = position +1
+
+      for _, pat in ipairs(self.patterns) do
+          _, last = string.find(line, pat)
+          if last then wrapped = true break end
+      end
+  end
+
+  if not last then return false, {} end
+  local lastline = position
+
+  -- Looks like a pseudoError; Let's look ahead to identify the other
+  -- lines that are part of the message. We do not want to look too
+  -- much ahead, so we just scan the current buffer.
+
+  position = position +1
+  -- We use "<", not "<=", so we can access the following line too
+  while position < Lines:numLines() do
+
+      -- if there is a second error, don't look further ahead
+      if self:isErrorLine(Lines:get(position))
+              or self:isRunawayLine(Lines:get(position))
+      then break end
+
+      local length = string.len(Lines:get(position))
+      if length > 0 then -- skip empty lines
+          if string.find(Lines:get(position +1),
+                         "^" .. string.rep(" ", length))
+          then
+              -- this line and the next belong to the message;
+              -- on the next iteration, check the line after that
+              position = position +1
+              lastline = position
+          elseif string.find(Lines:get(position), '^l%.%d+ ') then
+              lastline = position
+          else
+              break -- this line belongs to a different message
+          end
+      end
+
+      position = position +1
+  end
+
+  -- position starts at zero, so numlines needs +1
+  return true, {numLines = lastline +1, wrapped = wrapped}
+end
+
+function pseudoErrorHandler:handleFirstLine()
+  local myTurn, data = self:canDoit()
+  if not myTurn then return false end
+
+  local terseContent = Lines.current
+  if data.wrapped then terseContent = terseContent .. Lines:get(1) end
+
+  errorHandler.reallyHandleFirstLine(self, data)
+  self.message.severity = WARNING
+  self.message.terseContent = terseContent
+  return true
+end
+
+function pseudoErrorHandler:outputCurrentLine(last)
+   -- "last" is nil in all but the last line
+  local line = string.sub(Lines.current, 1, last)
+  if MINLEVEL <= INFO then
+      self.message.content = self.message.content .. '\n    ' .. line
+  end
+  Lines:handledChars(last)
+end
+
+pseudoErrorHandler.doit = pseudoErrorHandler.handleFirstLine
+
 -------------------------------------------------------------------------------
 -- epilogueHandler
 --
@@ -2762,8 +2957,6 @@
   '^\\[^%s=]+=[^%s=]+', -- "\c at chapter=\count174"
   "^\\openout%d+%s*=%s*`?[^']+'?%.?",
 
-  '^LaTeX2e <' .. datepat .. '>.*',
-
   '^Lua module: lualibs%-extended ' .. datepat
                    .. ' %S+ ConTeXt Lua libraries %-%- extended collection%.',
 
@@ -2921,12 +3114,6 @@
 
   "^`newtxtext' v[%d%.]+, " .. datepat .. " Text macros taking advantage of "
               .. "TeXGyre Termes and its extensions %(msharpe%)",
-
-  -- don't know where this comes from...
-  "^ %*%*%*%*%*%*%*%*%*%*%*\n"
-  .. "LaTeX2e <" .. datepat .. ">\n"
-  .. "L3 programming layer <" .. datepat .. ">\n"
-  .. " %*%*%*%*%*%*%*%*%*%*%*"
 }
 
 
@@ -2937,7 +3124,24 @@
 anywhereDebugStringsHandler.IHandleAnywhere = true
 anywhereDebugStringsHandler.severity = DEBUG
 anywhereDebugStringsHandler.patterns = {
-  '^%s*L3 programming layer %b<>',
+  -- don't know where this comes from...
+  "^%s*%*%*%*%*%*%*%*%*%*%*%*\n"
+  .. "LaTeX2e <" .. datepat .. ">.*\n"
+  .. "%s*L3 programming layer <" .. datepat .. ">.*\n"
+  .. "%s*%*%*%*%*%*%*%*%*%*%*%*",
+
+  '^%s*LaTeX2e <' .. datepat .. '>\n%s*patch.*',
+
+  '^%s*LaTeX2e <' .. datepat .. '> patch.*',
+
+  '^%s*LaTeX2e <' .. datepat .. '>',
+
+  '^%s*L3 programming layer <' .. datepat .. '>\n%s*patch.*',
+
+  '^%s*L3 programming layer <' .. datepat .. '> patch.*',
+
+  '^%s*L3 programming layer <' .. datepat .. '>',
+
   '^%s*xparse %b<>',
   '^%s*%{.*pdftex%.map%}',
 
@@ -2996,6 +3200,10 @@
   "^%s*Comment '[^']+' writing to " .. filepat .. "%.",
   "^%s*Straight input of " .. filepat .. "%.",
   "^%s*Include comment '[^']+' up to level '[^']+'",
+  "^Lua module%: autotype " .. datepat .. " v%S+ automatic "
+                                .. "language%-specific typography",
+  "^Lua module%: pdnm%_nl%_manipulation " .. datepat .. " v%S+ "
+                        .. "pattern driven node list manipulation",
 }
 
 
@@ -3022,6 +3230,7 @@
   "^No file .-%.bbl%.",
   "^No file .-%.gls%.",
 
+  "^runsystem%b()%.%.%.executed safely %(allowed%)%.",
   "^runsystem%b()%.%.%.executed%.?",
 
   'luaotfload | db : Reload initiated %(formats: .-%); reason: Font ".-" not found%.',
@@ -3106,6 +3315,14 @@
    .. "although it is yet undefined",
 
    '^%* %* %* LNI %* %* %*',
+   "^Style `ntheorem', Version %S+ <" .. datepat .. ">",
+   "^`XCharter' v%S+, " .. datepat .. " Text macros for XCharter, "
+                           .. "an extension of Charter %(msharpe%)",
+   "^Document Style algorithmicx %S+ %- a greatly improved `algorithmic' style",
+   "^Applying%: [" .. datepat .. "] Usage of raw or classic option list "
+                                       .. "on input line %S+%.",
+   "^Already applied%: [" .. datepat .. "] Usage of raw or classic "
+                                       .. "option list on input line %S+%.",
 }
 
 
@@ -3219,7 +3436,7 @@
 
   "^ ======================================= \n"
     .. " WARNING WARNING WARNING \n"
-    .. " %-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%- \n"
+    .. " " .. string.rep('%-', 39) .. " \n"
     .. " The ligature suppression macros of the \n"
     .. " selnolig package %*require%* LuaLaTeX%. \n"
     .. " Because you're NOT running this package \n"
@@ -3373,10 +3590,25 @@
       Lines:handledChars()
       nextHandler = self
   else
-      self.doit = self.handleFirstLine
+
       if self.linenum ~= "" then
           self.message.linenum = self.linenum
+
+          tmp = {
+                 " on input line " .. self.linenum .. "%.",
+                 " on line " .. self.linenum .. "$"
+                }
+          for _, pat in ipairs(tmp) do
+              local first = string.find(self.message.content, pat)
+              if first then
+                  self.message.terseContent = string.sub(self.message.content,
+                                                         1, first -1)
+                  break
+              end
+          end
       end
+
+      self.doit = self.handleFirstLine
       dispatch(self.message)
       self.message = nil
   end
@@ -3408,10 +3640,12 @@
   if self.linenum ~= "" then return end
 
   _, _, self.linenum = string.find(Lines.current, "on input line (%d+)%.")
+  if self.linenum == nil then self.linenum = "" end
   if self.linenum ~= "" then return end
 
   -- LaTeX3-style messages (with \msg_something)
   _, _, self.linenum = string.find(Lines.current, "on line (%d+)$")
+  if self.linenum == nil then self.linenum = "" end
 end
 
 function genericLatexHandler:parseSeverity(severity)
@@ -3735,6 +3969,13 @@
 
 openCloseHandlerPrototype = HandlerPrototype:new()
 
+function openCloseHandlerPrototype:init()
+    self.openPat = '%' .. self.openChar
+    self.closePat = '%' .. self.closeChar
+    self.openOrClosePat = '[' .. self.openPat .. self.closePat .. ']'
+    self.pattern = self.strictPattern
+end
+
 -- Just like :canDoit(), but does not anchor patterns to the
 -- beginning of the line (used by handleUnrecognizedMessage).
 -- Notice the similarity to stringsHandler:lookahead().
@@ -3748,9 +3989,11 @@
 
 openParensHandler = openCloseHandlerPrototype:new()
 
-openParensHandler.strictPattern = "^(%s*)%("
-openParensHandler.loosePattern = "%s*%("
-openParensHandler.pattern = openParensHandler.strictPattern
+openParensHandler.name = "openParensHandler"
+openParensHandler.openChar = '('
+openParensHandler.closeChar = ')'
+openParensHandler.loosePattern = "(%s*)%" .. openParensHandler.openChar
+openParensHandler.strictPattern = "^" .. openParensHandler.loosePattern
 
 function openParensHandler:canDoit(position)
   if position == nil then position = 0 end
@@ -3775,7 +4018,7 @@
   -- (because of the unwrapping) or we will handle them using the
   -- "DUMMY" entry in the stack as usual.
   if filename == nil and position > 0 then
-      if string.find(line, '%)') then return false, {} end
+      if string.find(line, self.closePat) then return false, {} end
   end
 
   return true, {first = first, filename = filename} -- might be nil
@@ -3786,6 +4029,7 @@
   if not myTurn then return false end
 
   local _, last, spaces = string.find(Lines.current, self.pattern)
+  if spaces == nil then spaces = "" end
   unrecognizedBuffer = unrecognizedBuffer .. spaces
 
   -- skip the spaces and the open parens character
@@ -3797,12 +4041,13 @@
       if last == nil then
           io.stderr:write("    texlogsieve: parsing error near input line "
                                .. Lines.linenum
-                               .. " (openParensHandler:doit)\n")
+                               .. " (" .. self.name .. ":doit)\n")
 
           PARSE_ERROR = true
       else
           Lines:handledChars(last)
       end
+      if openFiles:peek() == "DUMMY" then openFiles:pop() end
       openFiles:push(data.filename)
       mute = currentFileIsSilenced()
       local msg = openFileMessage:new()
@@ -3812,7 +4057,7 @@
       dispatch(msg)
   else
       openFiles:push("DUMMY")
-      unrecognizedBuffer = unrecognizedBuffer .. "("
+      unrecognizedBuffer = unrecognizedBuffer .. self.openChar
   end
 
   return true
@@ -3820,9 +4065,11 @@
 
 closeParensHandler = openCloseHandlerPrototype:new()
 
-closeParensHandler.strictPattern = "^(%s*)%)"
-closeParensHandler.loosePattern = "%s*%)"
-closeParensHandler.pattern = closeParensHandler.strictPattern
+closeParensHandler.name = "closeParensHandler"
+closeParensHandler.openChar = '('
+closeParensHandler.closeChar = ')'
+closeParensHandler.loosePattern = "(%s*)%" .. closeParensHandler.closeChar
+closeParensHandler.strictPattern = "^" .. closeParensHandler.loosePattern
 
 -- In lookahead, when we say "we can do it" we actually mean "well,
 -- we might be able to do it". This is not a problem: it simply
@@ -3908,9 +4155,9 @@
     if line ~= nil then size = string.len(line) end
 
     while i <= size do
-        local j = string.find(line, '[%(%)]', i)
+        local j = string.find(line, self.openOrClosePat, i)
         if j ~= nil then
-            local open = string.find(line, '%(', i)
+            local open = string.find(line, self.openPat, i)
             if open then
                 unpaired = unpaired +1
             elseif unpaired > 0 then
@@ -3939,6 +4186,7 @@
   if not myTurn then return false end
 
   local _, last, spaces = string.find(Lines.current, self.pattern)
+  if spaces == nil then spaces = "" end
   unrecognizedBuffer = unrecognizedBuffer .. spaces
 
   -- skip the spaces and the close parens character
@@ -3946,7 +4194,7 @@
 
   local filename = openFiles:pop()
   if filename == nil or filename == "DUMMY" then
-      unrecognizedBuffer = unrecognizedBuffer .. ")"
+      unrecognizedBuffer = unrecognizedBuffer .. self.closeChar
   else
       flushUnrecognizedMessages()
       local msg = closeFileMessage:new()
@@ -3961,9 +4209,13 @@
 
 openSquareBracketHandler = openCloseHandlerPrototype:new()
 
-openSquareBracketHandler.strictPattern = "^(%s*)%["
-openSquareBracketHandler.loosePattern = "%s*%["
-openSquareBracketHandler.pattern = openSquareBracketHandler.strictPattern
+openSquareBracketHandler.name = "openSquareBracketHandler"
+openSquareBracketHandler.openChar = '['
+openSquareBracketHandler.closeChar = ']'
+openSquareBracketHandler.loosePattern = "(%s*)%"
+                                      .. openSquareBracketHandler.openChar
+openSquareBracketHandler.strictPattern = "^"
+                                       .. openSquareBracketHandler.loosePattern
 
 function openSquareBracketHandler:canDoit(position)
   if position == nil then position = 0 end
@@ -3979,7 +4231,7 @@
 
   -- See the comment "HACK ALERT" in openParensHandler:canDoit()
   if latexPage == nil and position > 0 then
-      if string.find(line, '%]') then return false, {} end
+      if string.find(line, self.closePat) then return false, {} end
   end
 
   return true, {first = first, latexPage = latexPage} -- may be nil
@@ -4001,12 +4253,13 @@
       if last == nil then
           io.stderr:write("    texlogsieve: parsing error near input line "
                                .. Lines.linenum
-                               .. " (openSquareBracketHandler:doit)\n")
+                               .. " (" .. self.name .. ":doit)\n")
 
           PARSE_ERROR = true
       else
           Lines:handledChars(last)
       end
+      if shipouts:peek() == "DUMMY" then shipouts:pop() end
       shipouts:push(data.latexPage)
       numShipouts = numShipouts +1
       table.insert(latexPages, numShipouts, data.latexPage)
@@ -4015,7 +4268,7 @@
       dispatch(msg)
   else
       shipouts:push("DUMMY")
-      unrecognizedBuffer = unrecognizedBuffer .. "["
+      unrecognizedBuffer = unrecognizedBuffer .. self.openChar
   end
 
   return true
@@ -4023,9 +4276,13 @@
 
 closeSquareBracketHandler = openCloseHandlerPrototype:new()
 
-closeSquareBracketHandler.strictPattern = "^(%s*)%]"
-closeSquareBracketHandler.loosePattern = "%s*%]"
-closeSquareBracketHandler.pattern = closeSquareBracketHandler.strictPattern
+closeSquareBracketHandler.name = "closeSquareBracketHandler"
+closeSquareBracketHandler.openChar = '['
+closeSquareBracketHandler.closeChar = ']'
+closeSquareBracketHandler.loosePattern = "(%s*)%"
+                                    .. closeSquareBracketHandler.closeChar
+closeSquareBracketHandler.strictPattern = "^"
+                                    .. closeSquareBracketHandler.loosePattern
 
 -- Read the comment right before "closeParensHandler:lookahead()"
 function closeSquareBracketHandler:lookahead()
@@ -4060,9 +4317,9 @@
     if line ~= nil then size = string.len(line) end
 
     while i <= size do
-        local j = string.find(line, '[%[%]]', i)
+        local j = string.find(line, self.openOrClosePat, i)
         if j ~= nil then
-            local open = string.find(line, '%[', i)
+            local open = string.find(line, self.openPat, i)
             if open then
                 unpaired = unpaired +1
             elseif unpaired > 0 then
@@ -4098,7 +4355,7 @@
 
   local latexPage = shipouts:pop()
   if latexPage == nil or latexPage == "DUMMY" then
-      unrecognizedBuffer = unrecognizedBuffer .. "]"
+      unrecognizedBuffer = unrecognizedBuffer .. self.closeChar
   else
       flushUnrecognizedMessages()
       local msg = endShipoutMessage:new()
@@ -4107,51 +4364,100 @@
   end
 end
 
--- During a shipout, TeX sometimes puts some filenames inside a pair of
--- "{}" or "<>" characters. Since there are no other messages inside these
--- opening and closing characters, this handler may be simple: find the
--- opening character and unwrap lines until finding the closing character.
--- We even check for "{<" and "}>" at the same time, without verifying if
--- they are actually paired, because they really should be.
+-- During a shipout, TeX sometimes puts some filenames
+-- inside a pair of "{}" or "<>" characters.
 shipoutFilesHandler = HandlerPrototype:new()
-shipoutFilesHandler.strictPattern = "^(%s*)[%{%<]"
-shipoutFilesHandler.loosePattern = "%s*[%{%<]"
-shipoutFilesHandler.pattern = shipoutFilesHandler.strictPattern
-shipoutFilesHandler.closingPattern = "[%}%>]"
+shipoutFilesHandler.strictPatterns = {
+    "^(%s*)(%b<>)",
+    "^(%s*)(%b{})",
+}
+shipoutFilesHandler.loosePatterns = {
+    "(%s*)(%b<>)",
+    "(%s*)(%b{})",
+}
+shipoutFilesHandler.patterns = shipoutFilesHandler.strictPatterns
 
--- Read the comment right before "closeParensHandler:lookahead()"
+-- Read the comment right before "closeParensHandler:lookahead()".
+-- That does not work here for two reasons:
 --
--- This handler only processes stuff if there is a pending shipout in the
--- shipouts stack. However, we cannot check for this in lookahead() because
--- the shipout may not have been processed yet. As a result, lookahead()
--- may answer "yes" when in reality the handler won't do anything. This has
--- a nasty consequence: if there is a { or < character at the beginning
--- of a line, doit() may not handle it, but lookahead() may answer "yes",
--- causing an endless loop. We solve this by answering "yes" ONLY if the
--- open character is NOT at the beginning of the line.
+-- 1. We may mess up messages that present content from the document,
+--    which may include "{}" or "<>" characters. An example is the
+--    "pdfTeX warning (ext4):..." message.
+--
+-- 2. This handler should only process stuff if there is a pending shipout
+--    in the shipouts stack. However, we cannot check for this in lookahead()
+--    because the shipout may not have been processed yet. As a result,
+--    lookahead() might answer "yes" when in reality the handler won't do
+--    anything. This would have a nasty consequence: if there is a "{" or
+--    "<" character outside any shipouts at the beginning of a line, doit()
+--    would not handle it, but lookahead() would answer "yes", causing an
+--    endless loop.
+--
+-- The solution is to have lookahead() answer "yes" ONLY if there really
+-- is a filename within the "{}" or "<>" pair. We might also consider only
+-- answering "yes" if the "{" or "<" character is *not* at the beginning
+-- of a line, but that is probably overkill.
+
 function shipoutFilesHandler:lookahead()
-  local line = Lines:get(0)
-  if line == nil then return false, {} end
-
-  local first = string.find(line, self.loosePattern)
-  if first == nil then return false, {} end
-
-  local strictFirst = string.find(line, self.pattern)
-  if first == strictFirst then return false, {} end
-
-  return true, {first = first}
+  shipoutFilesHandler.patterns = shipoutFilesHandler.loosePatterns
+  local ok, data = self:realCanDoit()
+  shipoutFilesHandler.patterns = shipoutFilesHandler.strictPatterns
+  return ok, data
 end
 
 function shipoutFilesHandler:canDoit(position)
   if position == nil then position = 0 end
+  if position == 0 and shipouts:size() == 0 then return false, {} end
+  return self:realCanDoit(position)
+end
+
+function shipoutFilesHandler:realCanDoit(position)
+  if position == nil then position = 0 end
   local line = Lines:get(position)
   if line == nil then return false, {} end
-  if position == 0 and shipouts:size() == 0 then return false, {} end
 
-  local first, last = string.find(line, self.pattern)
+  local first, match
+  for _, pat in ipairs(self.patterns) do
+      first, _, _, match = string.find(line, pat)
+      if first then break end
+  end
+
+  -- let's try unwrapping, but we need to be careful: if we match
+  -- on subsequent lines by themselves, then the match is not the
+  -- result of unwrapping, so we should return false.
+  if first == nil then
+      local offset = 1
+      local subsequentLines = ""
+      while offset < 4 do -- unwrapping the 3 subsequent lines is enough
+          local nextLine = Lines:get(position + offset)
+          if not nextLine then return false, {} end
+          subsequentLines = subsequentLines .. nextLine
+          local nextMatch
+          for _, pat in ipairs(self.patterns) do
+              first, _, _, nextMatch = string.find(subsequentLines, pat)
+              if first then break end
+          end
+          local allLines = line .. subsequentLines
+          for _, pat in ipairs(self.patterns) do
+              first, _, _, match = string.find(allLines, pat)
+              if first then break end
+          end
+          if first then
+              if match == nextMatch then
+                  return false, {}
+              else
+                  break
+              end
+          end
+          offset = offset +1
+      end
+  end
+
   if first == nil then return false, {} end
-
-  return true, {first = first}
+  if checkIfFileExists(string.sub(match, 2, -2)) then
+      return true, {first = first, name = match}
+  end
+  return false, {}
 end
 
 function shipoutFilesHandler:doit()
@@ -4158,35 +4464,18 @@
   local myTurn, data = self:canDoit()
   if not myTurn then return false end
 
-  -- Look for the matching close character. It really
-  -- should be there and there should not be any nesting,
-  -- so no need to be overly cautious.
-  local last
-  for i = 0, 4 do -- 4 lines ahead is plenty!
-      line = Lines:get(i)
-      if line == nil then break end
-      _, last = string.find(line, self.closingPattern)
-      if last ~= nil then break end
+  local _, last, spaces = string.find(Lines.current, "(^%s+)")
+  if last then
+      unrecognizedBuffer = unrecognizedBuffer .. spaces
+      Lines:handledChars(last)
+      flushUnrecognizedMessages()
   end
-  if last == nil then return false end -- should never happen
 
-  local _, last, spaces = string.find(Lines.current, self.pattern)
-  unrecognizedBuffer = unrecognizedBuffer .. spaces
+  last, _ = unwrapUntilStringMatches(data.name) -- this should never fail
 
-  -- skip the spaces and the opening character
-  Lines:handledChars(last)
-  flushUnrecognizedMessages()
-
-  _, last = string.find(Lines.current, self.closingPattern)
-  while last == nil do
-      Lines:unwrapOneLine()
-      _, last = string.find(Lines.current, self.closingPattern)
-  end
-
   local msg = shipoutFilesMessage:new()
-  msg.content = "Loading file at shipout: "
-  -- "-1" so that we do not include the closing character
-  msg.content = msg.content .. string.sub(Lines.current, 1, last -1)
+  msg.content = "Loading file during shipout: "
+  msg.content = msg.content .. string.sub(data.name, 2, -2)
   Lines:handledChars(last)
   dispatch(msg)
 
@@ -4469,6 +4758,11 @@
   },
   {
     WARNING,
+    'LaTeX',
+    'Temporary extra page added at the end%. Rerun to get it removed%.'
+  },
+  {
+    WARNING,
     'longtable',
     'Table %S+s have changed%. Rerun LaTeX%.'
   },
@@ -4489,6 +4783,11 @@
   },
   {
     WARNING,
+    'biblatex',
+    'Please %(re%)run Biber on the file:'
+  },
+  {
+    WARNING,
     'atenddvi',
     'Rerun LaTeX, last page not yet found%.'
   },
@@ -4529,16 +4828,6 @@
   },
   {
     WARNING,
-    'biblatex',
-    'Please rerun LaTeX%. Page breaks have changed%.',
-  },
-  {
-    WARNING,
-    'biblatex',
-    'Please rerun LaTeX%.',
-  },
-  {
-    WARNING,
     'bidi-perpage',
     "Counter %b`' may not have been reset per page%. Rerun to reset counter %b`' per page%.",
   },
@@ -5073,6 +5362,8 @@
   return Message.realToString(self)
 end
 
+openFileMessage.alwaysEnds = true
+
 -- We never want to suppress these repetitions
 function openFileMessage:toSummary()
 end
@@ -5085,6 +5376,8 @@
   return Message.realToString(self)
 end
 
+closeFileMessage.alwaysEnds = true
+
 -- We never want to suppress these repetitions
 function closeFileMessage:toSummary()
 end
@@ -5200,6 +5493,11 @@
   local formatted = msg:toString(self.bypassMostFilters)
   if trim(formatted) == "" then return end
 
+  -- Now that we know whether the message is actually included in
+  -- the output, let's check whether we should use terseContent to
+  -- identify duplicates
+  if msg.terseContent ~= nil then formatted = msg.terseContent end
+
   -- group messages by message content
   if self.messages[formatted] == nil then
       self.messages[formatted] = {}
@@ -5211,6 +5509,12 @@
 function SummaryPrototype:alreadySeen(msg)
   local formatted = msg:toString()
   if trim(formatted) == "" then return false end
+
+  -- Now that we know that the message should actually be included in
+  -- the output, let's check whether we should use terseContent to
+  -- identify duplicates
+  if msg.terseContent ~= nil then formatted = msg.terseContent end
+
   return self.messages[formatted] ~= nil
 end
 
@@ -5356,7 +5660,7 @@
   if #messages > 1 then
       local pages, files = self:pageAndFileList(messages)
 
-      local where
+      local where = ""
       if pages ~= "" and files ~= "" then
           where = 'in ' .. pages .. ' (' .. files .. ') - '
       elseif pages == "" and files ~= "" then
@@ -5570,8 +5874,8 @@
   local i = 1
   local lines = {}
   while i <= size do
-      -- check \r in case the user added to this file a pattern
-      -- with an embedded dos-style "CR LF" sequence.
+      -- check for \r in case the user wrongfully added to this file
+      -- a pattern with an embedded dos-style "CR LF" sequence.
       local first, last, line = string.find(s, '(.-)[\r]?\n', i)
 
       if first == nil then
@@ -5602,7 +5906,7 @@
 
 function listToCommaSeparatedString(list, singular, plural, sep)
   sep = sep or ','
-  if #list == 0 then return end
+  if #list == 0 then return "" end
 
   local tmp
   if #list == 1 then
@@ -6425,8 +6729,8 @@
       local longest = string.len(line)
       local notWrapped = false
 
-      -- if there is a ")", "(", "[", or "]" char, stop before that
-      local first = string.find(line, "[%)%(%[%]]")
+      -- if the line contains one of these chars, stop before it
+      local first = string.find(line, "[%(%)%[%]%{%}%<%>]")
       if first ~= nil then
           notWrapped = true -- this line is obviously not wrapped
           longest = first -1
@@ -6478,45 +6782,50 @@
 
   local flsfilename = string.gsub(logfilename, '%.log$', '.fls')
 
+  -- It is ok for this to fail, so no "assert"
+  local flsfile = io.open(flsfilename, 'r')
+  if flsfile == nil then return end
+
   -- Let's be reasonably sure that we are not dealing
   -- with a stale .fls file from a previous run
   local logdate = lfs.attributes(logfilename, 'modification')
   local flsdate = lfs.attributes(flsfilename, 'modification') or 0
   local timediff = math.abs(logdate - flsdate) -- seconds
+  if timediff > 5 then return end
 
-  -- It is ok for this to fail, so no "assert"
-  local flsfile = io.open(flsfilename, 'r')
+  -- We are good to go!
+  USE_FLS_FILE = true
 
-  if flsfile ~= nil and timediff <= 5 then
-      USE_FLS_FILE = true
+  filelist = {}
+  while true do
+      local line = flsfile:read("*line")
 
-      filelist = {}
-      while true do
-          local line = flsfile:read("*line")
-          if line == nil then
-              io.close(flsfile)
-              return
-          end
+      if line == nil then io.close(flsfile) return end
 
-          local _, last = string.find(line, '^[IO][NU]T?PUT ')
-          if last ~= nil then line = string.sub(line, last +1) end
+      -- If we are running in a unix-like OS but the files we are
+      -- processing were generated in Windows, lua may leave a \r
+      -- character at the end of the line; if this happens, remove it
+      local _, last = string.find(line, '\r$')
+      if last ~= nil then line = string.sub(line, 1, last -1) end
 
-          -- I don't think this ever happens
-          if string.find(line, '^".*"$') then line = string.sub(line, 2, -2) end
+      _, last = string.find(line, '^[IO][NU]T?PUT ')
+      if last ~= nil then line = string.sub(line, last +1) end
 
-          -- No idea what is this, but it happens with the MiKTeX version
-          -- of luatex for Windows. It apparently only appears with font
-          -- files, which are irrelevant here, but let's be safe
-          if string.find(line, '^\\\\%?\\') then line = string.sub(line, 5) end
+      -- I don't think this ever happens
+      if string.find(line, '^".*"$') then line = string.sub(line, 2, -2) end
 
-          line = string.gsub(line, '\\', '/')
+      -- No idea what is this, but it happens with the MiKTeX version
+      -- of luatex for Windows. It apparently only appears with font
+      -- files, which are irrelevant here, but let's be safe
+      if string.find(line, '^\\\\%?\\') then line = string.sub(line, 5) end
 
-          _, last = string.find(line, '^%./')
-          if last ~= nil then line = string.sub(line, last +1) end
+      line = string.gsub(line, '\\', '/')
 
-          -- Save as a Set to eliminate duplicates
-          if not string.find(line, '^PWD') then filelist[line] = true end
-      end
+      _, last = string.find(line, '^%./')
+      if last ~= nil then line = string.sub(line, last +1) end
+
+      -- Save as a Set to eliminate duplicates
+      if not string.find(line, '^PWD') then filelist[line] = true end
   end
 end
 



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