texlive[61974] trunk: texlogsieve (10feb22)

commits+karl at tug.org commits+karl at tug.org
Thu Feb 10 22:20:19 CET 2022


Revision: 61974
          http://tug.org/svn/texlive?view=revision&revision=61974
Author:   karl
Date:     2022-02-10 22:20:19 +0100 (Thu, 10 Feb 2022)
Log Message:
-----------
texlogsieve (10feb22)

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/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	2022-02-10 21:19:50 UTC (rev 61973)
+++ trunk/Build/source/texk/texlive/linked_scripts/texlogsieve/texlogsieve	2022-02-10 21:20:19 UTC (rev 61974)
@@ -331,7 +331,7 @@
 -----------------
 
   exampleHandler = {}
-  exampleHandler.pattern = '^%s*L3 programming layer %b<> xparse %b<>'
+  exampleHandler.pattern = '^%s*L3 programming layer %b<>'
 
   function exampleHandler:init()
   end
@@ -532,6 +532,13 @@
 CRITICAL = 3
  UNKNOWN = 4
 
+        RED = '\x1B[31m'
+     YELLOW = '\x1B[33m'
+      GREEN = '\x1B[32m'
+     BRIGHT = '\x1B[37;1m'
+     BGREEN = '\x1B[32;1m'
+RESET_COLOR = '\x1B[0m'
+
 function main(arg)
   initializeKpse()
   processCommandLine(arg)
@@ -695,6 +702,11 @@
   -- detectEngine() may set one of these to true
   LUATEX = false
   XETEX = false
+
+  -- When we print a message that is the first from a given filename,
+  -- we announce the filename first. This is used to detect the change
+  -- in file - used by showFileBanner()
+  lastFileBanner = ""
 end
 
 function initializeKpse()
@@ -722,7 +734,8 @@
   table.insert(beginningOfLineHandlers, labelHandler)
   table.insert(beginningOfLineHandlers, genericLatexHandler)
   table.insert(beginningOfLineHandlers, latex23MessageHandler)
-  table.insert(beginningOfLineHandlers, genericLatexVariantHandler)
+  table.insert(beginningOfLineHandlers, genericLatexVariantIHandler)
+  table.insert(beginningOfLineHandlers, genericLatexVariantIIHandler)
   table.insert(beginningOfLineHandlers, providesHandler)
   table.insert(beginningOfLineHandlers, geometryDetailsHandler)
   table.insert(beginningOfLineHandlers, epilogueHandler)
@@ -737,6 +750,7 @@
   table.insert(anywhereHandlers, anywhereInfoStringsHandler)
   table.insert(anywhereHandlers, anywhereWarningStringsHandler)
   table.insert(anywhereHandlers, anywhereCriticalStringsHandler)
+  table.insert(anywhereHandlers, fpHandler) -- before open/closeParensHandler!
   table.insert(anywhereHandlers, openParensHandler)
   table.insert(anywhereHandlers, closeParensHandler)
   table.insert(anywhereHandlers, openSquareBracketHandler)
@@ -818,18 +832,16 @@
 
 function detectEngine()
   local line = logfile:read("*line")
-  local first = string.find(string.lower(line), '^this is lua')
-  if first ~= nil then
+  if line == nil then return end
+
+  if string.find(string.lower(line), '^this is lua') then
       LUATEX = true
-  else
-      first = string.find(string.lower(line), '^this is xe')
-      if first ~= nil then XETEX = true end
+  elseif string.find(string.lower(line), '^this is xe') then
+      XETEX = true
   end
 
-  local msg = Message:new()
-  msg.content = line
-  msg.severity = DEBUG
-  dispatch(msg)
+  -- leave the line for normal processing
+  Lines:append(line)
 end
 
 function processCommandLine(args)
@@ -842,10 +854,13 @@
   SILENCE_REPETITIONS = true
   MINLEVEL = WARNING
   BE_REDUNDANT = false
+  FILE_BANNER = true
   DETAILED_UNDEROVER_SUMMARY = true
   DETAILED_REFERENCE_SUMMARY = true
   DETAILED_CITATION_SUMMARY = true
 
+  COLOR = false
+
   SILENCE_STRINGS = {}
   SILENCE_PKGS = {} -- just the package names
   SEMISILENCE_FILES = {} -- filenames (without leading path), file globs work
@@ -879,6 +894,7 @@
   --summary, --no-summary                enable/disable final summary
   --only-summary                         no filtering, only final summary
   --shipouts, --no-shipouts              enable/disable reporting shipouts
+  --file-banner, --no-file-banner        Show/suppress "From file ..." banners
   --repetitions, --no-repetitions        allow/prevent repeated messages
   --be-redundant, --no-be-redundant      present/suppress ordinary messages
                                          that will also appear in the summary
@@ -891,6 +907,7 @@
   --summary-detail, --no-summary-detail  toggle box-detail, ref-detail, and
                                          cite-detail at once
   --heartbeat, --no-heartbeat            enable/disable progress gauge
+  --color, --no-color                    enable/disable colored output
   -l LEVEL, --minlevel=LEVEL             filter out messages with severity
                                          level lower than [LEVEL]. Valid
                                          levels are DEBUG, INFO, WARNING,
@@ -933,7 +950,7 @@
 
   --version
   if vars.version then
-      print("texlogsieve 1.0.0-beta-3")
+      print("texlogsieve 1.0.0")
       print("Copyright (C) 2021, 2022 Nelson Lago <lago at ime.usp.br>")
       print("License GPLv3+: GNU GPL version 3 or later "
             .. "<https://gnu.org/licenses/gpl.html>.")
@@ -978,6 +995,7 @@
       SILENCE_REPETITIONS = false
       BE_REDUNDANT = true
       MINLEVEL = DEBUG
+      FILE_BANNER = false
   end
 
   vars.u = nil
@@ -1050,10 +1068,28 @@
       end
   end
 
+  -- When severity is debug, we already output open/close
+  -- file messages; adding these would be confusing.
+  if MINLEVEL == DEBUG then FILE_BANNER = false end
+
   vars.l = nil
   vars.minlevel = nil
 
 
+  --no-file-banner
+  --file-banner
+  --file-banner=true/false
+  if vars['no-file-banner'] or vars['file-banner'] ~= nil
+                        and not vars['file-banner'] then
+
+      FILE_BANNER = false
+  end
+  if vars['file-banner'] then FILE_BANNER = true end
+
+  vars['file-banner'] = nil
+  vars['no-file-banner'] = nil
+
+
   --no-repetitions
   --repetitions
   --repetitions=true/false
@@ -1161,10 +1197,23 @@
   vars['no-heartbeat'] = nil
 
 
+  --no-color
+  --color
+  --color=true/false
+  if vars['no-color'] or vars.color ~= nil and not vars.color then
+      COLOR = false
+  end
+  if vars.color then COLOR = true end
+
+  vars.color = nil
+  vars['no-color'] = nil
+
+
   if vars.filename == nil then
       logfile = io.stdin
   else
       logfile = assert(io.open(vars.filename, "r"))
+      readFls(vars.filename)
   end
 
   vars.filename = nil
@@ -1271,9 +1320,7 @@
       if line == nil then break end
 
       line = trim(line)
-      local first = string.find(line, '^#')
-
-      if first == nil and line ~= "" then
+      if not string.find(line, '^#') and line ~= "" then
           local equals = string.find(line, '=', 1, true)
           if equals ~= nil then
               optname = string.sub(line, 1, equals -1)
@@ -1337,6 +1384,7 @@
     "Tab has been converted to Blank",
     "The morewrites package is unnecessary",
     'Unused \\captionsetup%b[]',
+    "Unknown feature `' in font %b`'", -- empty feature, not a problem
   }
 
   DEFAULT_FORCED_CRITICAL = {
@@ -1358,9 +1406,7 @@
   -- defaults (note that there is no "return" in the first two blocks)
   if msg.severity ~= INFO then
       for _, val in ipairs(DEFAULT_FORCED_INFO) do
-          local first = string.find(formatted, val)
-          local other = string.find(msg.content, val)
-          if first ~= nil or other ~= nil then
+          if string.find(formatted, val) or string.find(msg.content, val) then
               msg.severity = INFO
           end
       end
@@ -1368,9 +1414,7 @@
 
   if msg.severity ~= CRITICAL then
       for _, val in ipairs(DEFAULT_FORCED_CRITICAL) do
-          local first = string.find(formatted, val)
-          local other = string.find(msg.content, val)
-          if first ~= nil or other ~= nil then
+          if string.find(formatted, val) or string.find(msg.content, val) then
               msg.severity = CRITICAL
           end
       end
@@ -1378,9 +1422,7 @@
 
   if msg.severity ~= DEBUG then
       for _, val in ipairs(FORCED_DEBUG) do
-          local first = string.find(formatted, val)
-          local other = string.find(msg.content, val)
-          if first ~= nil or other ~= nil then
+          if string.find(formatted, val) or string.find(msg.content, val) then
               msg.severity = DEBUG
               return
           end
@@ -1389,9 +1431,7 @@
 
   if msg.severity ~= INFO then
       for _, val in ipairs(FORCED_INFO) do
-          local first = string.find(formatted, val)
-          local other = string.find(msg.content, val)
-          if first ~= nil or other ~= nil then
+          if string.find(formatted, val) or string.find(msg.content, val) then
               msg.severity = INFO
               return
           end
@@ -1400,9 +1440,7 @@
 
   if msg.severity ~= WARNING then
       for _, val in ipairs(FORCED_WARNING) do
-          local first = string.find(formatted, val)
-          local other = string.find(msg.content, val)
-          if first ~= nil or other ~= nil then
+          if string.find(formatted, val) or string.find(msg.content, val) then
               msg.severity = WARNING
               return
           end
@@ -1411,9 +1449,7 @@
 
   if msg.severity ~= CRITICAL then
       for _, val in ipairs(FORCED_CRITICAL) do
-          local first = string.find(formatted, val)
-          local other = string.find(msg.content, val)
-          if first ~= nil or other ~= nil then
+          if string.find(formatted, val) or string.find(msg.content, val) then
               msg.severity = CRITICAL
               return
           end
@@ -1421,20 +1457,9 @@
   end
 end
 
-function checkRerun(msg)
-  if msg:checkMatch(msg.rerunMessages) then
-      -- Rerun messages should be silenced when
-      -- "--no-be-redundant" is in effect
-      if not BE_REDUNDANT then msg.redundant = true end
-      return true
-  end
 
-  return false
-end
-
 function processMessage(msg)
-  -- can't use short-circuit eval here, we need checkRerun() to always execute
-  SHOULD_RERUN_LATEX = checkRerun(msg) or SHOULD_RERUN_LATEX
+  SHOULD_RERUN_LATEX = SHOULD_RERUN_LATEX or msg:checkMatch(msg.rerunMessages)
 
   adjustSeverity(msg)
 
@@ -1474,6 +1499,26 @@
   if SHOW_SUMMARY then showSummary() end
 end
 
+function showFileBanner(msg)
+  if not FILE_BANNER then return end
+
+  if msg.filename ~= nil
+                and msg.filename ~= ""
+                and msg.filename ~= lastFileBanner
+                -- TODO: "DUMMY" should never happen, but
+                --       what should we do if it does?
+  then
+      lastFileBanner = msg.filename
+      local txt = "From file " .. msg.filename .. ":"
+      if COLOR then
+          txt = yellow(txt)
+      else
+          print(string.rep('-', string.len(txt)))
+      end
+      print(txt)
+  end
+end
+
 function showMessage(msg)
   local formatted = msg:toString()
   if trim(formatted) ~= "" then
@@ -1481,7 +1526,8 @@
       local spaces = ""
       if not RAW and msg.physicalPage ~= nil then
           pageinfo = 'pg ' .. msg.physicalPage .. ': '
-          spaces = string.rep(" ", string.len(pageinfo))
+          spaces = string.rep(" ", string.len(pageinfo)) -- before color
+          if COLOR then pageinfo = bright(pageinfo) end
       end
 
       -- A message is a repetition if it has
@@ -1497,8 +1543,15 @@
       end
 
       if not SILENCE_REPETITIONS or not alreadySeen then
+          showFileBanner(msg)
+
           for _, line in ipairs(linesToTable(formatted)) do
+              if COLOR and msg.severity >= CRITICAL then
+                  line = red(line)
+              end
+
               print(pageinfo .. line)
+
               pageinfo = spaces
           end
       end
@@ -1523,9 +1576,11 @@
   end
 
   if thereIsSomething then
-      print("")
-      print("After last page:")
-      print("")
+      print()
+      local txt = "After last page:"
+      if COLOR then txt = bgreen(txt) end
+      print(txt)
+      print()
   end
 
   -- we always call this, even if there is nothing to show,
@@ -1549,7 +1604,13 @@
 
   if not thereIsSomething then return end
 
-  if not ONLY_SUMMARY then for i = 1, 3 do print("") end end
+  if not ONLY_SUMMARY then
+      print("")
+      local txt = "====  Summary:  ===="
+      if COLOR then txt = bgreen(txt) end
+      print(txt)
+      print("")
+  end
 
   for _, summary in ipairs(summaries) do
       local formatted = summary:toString()
@@ -1564,7 +1625,9 @@
   end
 
   if SHOULD_RERUN_LATEX then
-      print("** LaTeX says you should rerun **")
+      local txt = "** LaTeX says you should rerun **"
+      if COLOR then txt = red(txt) end
+      print(txt)
       print()
   end
 end
@@ -1605,9 +1668,15 @@
 
 -- datepat and filepat will come in handy later on.
 --
--- Note that, in some cases, it may be useful to start filepat with '^[%.]?/'.
+-- Absolute paths may be in the form "/...blah.ext" and "C:\...blah.ext";
+-- Relative paths may be in the form "./blah.ext" or "blah.ext". This last
+-- form makes ordinary words almost indistinguishable from paths. Since it
+-- is also possible for a path to include dots and spaces, this pattern has
+-- to be *very* permissible, which means it may easily match something that
+-- is not really a filename. Don't blindly trust it! guessFilename() uses
+-- this but with added sanity checks.
 --
--- filepat will fail:
+-- filepat will also fail:
 --
 -- 1. If the path or filename includes weird characters, such as ":" or "|"
 -- 2. If the file has no extension or the extension has only one character
@@ -1615,21 +1684,23 @@
 -- 4. If filepat should match the end of the message and the matching line is
 --    wrapped in the middle of the file extension, for example "myfile.pd\nf"
 --    (but we have a hack in unwrapUntilPatternMatches() to work around that)
---
--- More importantly, filepat allows for spaces and multiple dots in filenames,
--- but this means it may match something that is not really a filename. Don't
--- blindly trust it! We do not use this to detect open/close file; for that,
--- check guessFilename().
 
 datepat = '%d%d%d%d[/%-%.]%d%d[/%-%.]%d%d'
 
-filepat = '%a?[:]?'
-          .. '[^%%:;,%=%*%?%|%&%$%#%!%@"%`\'%<%>%[%]%{%}]+'
-          .. '%.'
-          .. '[^/ %-%_%.%%:;,%=%*%?%|%&%$%#%!%@"%`\'%<%>%[%]%{%}]'
-          .. '[^/ %-%_%.%%:;,%=%*%?%|%&%$%#%!%@"%`\'%<%>%[%]%{%}]+'
+-- These charaters should never be part of a path
+unreasonable = '%%:;,%=%*%?%|%&%$%#%!%@"%`\'%<%>%[%]%{%}%(%)'
 
+filepat =    '[^%s%-' .. unreasonable .. ']' -- the first char
+          .. ':?' -- If the first char is a drive letter
+          .. '[^' .. unreasonable .. ']*' -- other (optional) chars
+          .. '%.' -- the extension must exist and be at least two chars long
+          .. '[^/\\ %-%_%.' .. unreasonable .. ']'
+          .. '[^/\\ %-%_%.' .. unreasonable .. ']+'
 
+-- This is even more fragile than filepat, it matches almost anything.
+dirpat =    '[^%s%-' .. unreasonable .. ']'
+         .. '[^' .. unreasonable .. ']*'
+
 -------------------------------------------------------------------------------
 -- HandlerPrototype
 -------------------------------------------------------------------------------
@@ -1715,8 +1786,7 @@
 -- We need to "manually" unwrap the file list because some
 -- lines are wrapped at lengths different from max_print_line
 function epilogueHandler:handleOtherLines()
-  local first = string.find(Lines.current, '^Output written')
-  if first ~= nil then
+  if string.find(Lines.current, '^Output written') then
       self.processingFilelist = false
       self.message.content = '\n' .. self.message.content
   end
@@ -1728,8 +1798,9 @@
       self.message.content = self.message.content .. '\n' .. Lines.current
   end
 
-  first = string.find(Lines.current, '^[%<%{]')
-  if first ~= nil then self.processingFilelist = true end
+  if string.find(Lines.current, '^[%<%{]') then
+      self.processingFilelist = true
+  end
 
   Lines:handledChars()
   nextHandler = self
@@ -1744,6 +1815,132 @@
 
 
 -------------------------------------------------------------------------------
+-- fpHandler
+--
+-- Handles the messages output by the "fp" (fixed point) package, which
+-- look like ( FP-UPN ( FP-MUL ) ( FP-ROUND ) ) etc.
+--
+-- They are \message's, so they may appear anywhere on a line. Usually,
+-- several of such messages appear together, so line wrapping is common.
+-- We handle the parens with a stack independent from the openFiles stack.
+-------------------------------------------------------------------------------
+
+fpHandler = HandlerPrototype:new()
+
+function fpHandler:init()
+  self.stack = Stack:new()
+end
+
+-- The space between the open parens char and "FP" is optional
+-- because it is ommited in case of line wrapping.
+fpHandler.loosePattern = '%( ?FP%-[^%s%)]+'
+fpHandler.strictPattern = '^%s*' .. fpHandler.loosePattern
+fpHandler.pattern = fpHandler.strictPattern
+
+
+function fpHandler:canDoit(position)
+  if position == nil then position = 0 end
+  local line = Lines:get(position)
+  if line == nil then return false, {} end
+
+  -- When we are looking into the future, let's just lie: since this
+  -- handler deals with several similar short messages in sequence,
+  -- preventing unwrapping is a very bad idea, because it will affect the
+  -- processing of the current message. As for other messages looking into
+  -- the future, openParensHandler:canDoit() return value will work fine.
+  if position > 0 then return false, {} end
+
+  while true do
+      local first = string.find(line, self.pattern)
+      if first ~= nil then return true, {first = first} end
+
+      if not Lines:seemsWrapped(position) then return false, {} end
+
+      line = line .. Lines:get(position +1)
+      position = position +1
+  end
+end
+
+function fpHandler:lookahead()
+  self.pattern = self.loosePattern
+  local match, data = self:canDoit()
+  self.pattern = self.strictPattern
+
+  return match, data
+end
+
+function fpHandler:startProcessing()
+  local myTurn, data = self:canDoit()
+  if not myTurn then return false end
+
+  self.message = self:newMessage()
+  self.message.severity = DEBUG
+  self.message.content = ""
+  self.doit = self.process
+  nextHandler = self
+  return true
+end
+
+fpHandler.doit = fpHandler.startProcessing
+
+function fpHandler:process()
+  while true do
+      local _, last = string.find(Lines.current, self.pattern)
+      if last ~= nil then return self:processOpen(last) end
+
+      _, last = string.find(Lines.current, '%s*%)')
+      if last ~= nil then return self:processClose(last) end
+
+      if Lines:seemsWrapped() then
+          Lines:unwrapOneLine()
+      else
+          -- This should never happen, but if it does
+          -- we will probably end up in an endless loop
+          io.stderr:write("    texlogsieve: parsing error in "
+                               .. "fpHandler:process()\n")
+
+          dispatch(self.message)
+          self.doit = self.startProcessing
+          self.stack = Stack:new()
+          return true
+      end
+  end
+end
+
+function fpHandler:processOpen(last)
+  self.message.content = self.message.content
+                         .. string.sub(Lines.current, 1, last)
+
+  Lines:handledChars(last)
+  self.stack:push("DUMMY")
+  nextHandler = self
+
+  return true
+end
+
+function fpHandler:processClose(last)
+  self.message.content = self.message.content
+                         .. string.sub(Lines.current, 1, last)
+
+  Lines:handledChars(last)
+
+  if self.stack:pop() == nil then
+      io.stderr:write("    texlogsieve: parsing error in "
+                       .. "fpHandler:processClose()\n")
+  end
+
+  if self.stack:empty() then
+      dispatch(self.message)
+      self.doit = self.startProcessing
+  else
+      nextHandler = self
+  end
+
+  return true
+end
+
+
+-------------------------------------------------------------------------------
 -- underOverFullBoxHandler
 --
 -- Handles under/overfull multiline messages. There are usually important,
@@ -1783,8 +1980,9 @@
   self.message.verthoriz = data.verthoriz
   self.message.amount = data.amount
   self.message.severity = WARNING
-  local first = string.find(data.amount, 'badness 10000')
-  if first ~= nil then self.message.severity = CRITICAL end
+  if string.find(data.amount, 'badness 10000') then
+      self.message.severity = CRITICAL
+  end
   Lines:handledChars(data.last)
 
   self.doit = self.handleClosing
@@ -1803,7 +2001,9 @@
   end
 
   if last == nil then
-      io.stderr:write("    texlogsieve: parsing error\n")
+      io.stderr:write("    texlogsieve: parsing error in "
+                       .. "underOverFullBoxHandler:handleFirstLine()\n")
+
       self.doit = self.handleFirstLine
       dispatch(self.message)
       return true
@@ -1902,6 +2102,8 @@
 
 stringsHandler = HandlerPrototype:new()
 
+stringsHandler.IHandleAnywhere = false
+
 function stringsHandler:canDoit(position)
   for _, pattern in ipairs(self.patterns) do
       local success, data = self:canDoitRecursive(pattern, position, 0, 0)
@@ -1922,10 +2124,15 @@
   self.message = self:newMessage()
   self.message.severity = self.severity
 
-  -- ignore leading spaces in the first line (in the others,
-  -- they may be indentation or somehow relevant)
-  local _, last = string.find(Lines.current, '^%s+')
-  if last ~= nil then Lines:handledChars(last) end
+  -- If we know the pattern must match at the beginning of a line,
+  -- the pattern may or may not include spaces at the start. If that
+  -- is not the case, however, there may be added spaces before the
+  -- message, so it is better to remove them from the first line (in
+  -- the others, they may be indentation or somehow relevant)
+  if self.IHandleAnywhere then
+      local _, last = string.find(Lines.current, '^%s+')
+      if last ~= nil then Lines:handledChars(last) end
+  end
 
   self.captures = {} -- just in case we want to use captures
   self.patternLines = data.pattern -- the table with the pattern lines
@@ -1955,7 +2162,9 @@
   for _, val in ipairs(tmp) do table.insert(self.captures, val) end
 
   if last == nil then
-      io.stderr:write("    texlogsieve: parsing error\n")
+      io.stderr:write("    texlogsieve: parsing error in "
+                       .. "stringsHandler:handleLines()\n")
+
       dispatch(self.message)
       self.doit = self.handleFirstLine
       return true
@@ -2024,9 +2233,9 @@
 
   while true do
       local first, last = string.find(line, patternLine)
-      local tmp = string.find(nextline, patternLine) -- see comment below
 
-      if first ~= nil and tmp == nil then
+      -- see comment below about "nextline, patternLine"
+      if first ~= nil and not string.find(nextline, patternLine) then
           -- Found it!
           if depth > 2 -- 3 lines matched, that is enough
                        or #patternLines == 1 -- no more pattern lines
@@ -2217,8 +2426,28 @@
   '^`inconsolata%-zi4\' v%S-, ' .. datepat
                    .. ' Text macros for Inconsolata %(msharpe%)',
 
-  '^Requested font ".-" at [%d%.]+pt\n %-> ' .. filepat,
-  '^Requested font ".-" scaled %d+\n %-> ' .. filepat,
+  '^Requested font ".-" at [%d%.]+pt\n ?%-> ' .. filepat,
+  '^Requested font ".-" scaled %d+\n ?%-> ' .. filepat,
+  '^Requested font ".-" at [%d%.]+pt',
+  '^Requested font ".-" scaled %d+',
+
+  -- Usually these are warnings, but for font "nil", why bother?
+  '^luaotfload | aux : font no %d+ %(nil%) does not define feature '
+                            .. '.- for script .- with language %S+',
+
+  '^luaotfload | aux : font no %d+ %(nil%) defines no feature for script %S+',
+
+  -- From IEEEtran.cls
+  '^%-%- This is a[n]? %d+ point document%.',
+  '^%-%- Lines per column: %S+ %(%S+%)%.',
+
+  '^%-%- See the "IEEEtran%_HOWTO" manual for usage information%.\n'
+               .. '%-%- http://www%.michaelshell%.org/tex/ieeetran/',
+
+  '^%-%- Using %S+ x %S+ %b() paper%.',
+  '^%-%- Using %S+ output%.',
+  '^%-%- Verifying Times compatible math font%.',
+  '^%-%- %S+ loaded, OK%.',
 }
 
 
@@ -2226,9 +2455,11 @@
 -- Always start these patterns with "^%s*", see lookahead().
 -- Order matters! The first match wins, so the longer ones should come first.
 anywhereDebugStringsHandler = stringsHandler:new()
+anywhereDebugStringsHandler.IHandleAnywhere = true
 anywhereDebugStringsHandler.severity = DEBUG
 anywhereDebugStringsHandler.patterns = {
-  '^%s*L3 programming layer %b<> xparse %b<>',
+  '^%s*L3 programming layer %b<>',
+  '^%s*xparse %b<>',
   '^%s*%{.*pdftex%.map%}',
 
   '^%s*ABD: EverySelectfont initializing macros',
@@ -2244,6 +2475,14 @@
   '^%s*%<' .. filepat .. '%>', -- <blah.jpg>
 
   "^%s*`Fixed Point Package', .- %(C%) Michael Mehlich",
+
+  "^%s*`newtxmath' v%S+, " .. datepat
+          .. " Math macros based originally on txfonts %(msharpe%)",
+
+  "^%s*`newtxtt' v%S+, " .. datepat
+          .. " Typewriter text macros based on txfonts %(msharpe%)",
+
+  '^%s*%* soulpos %- computing points %- it may take a few seconds %*',
 }
 
 
@@ -2267,12 +2506,66 @@
   "^No file .-%.ind%.",
   "^No file .-%.bbl%.",
   "^No file .-%.gls%.",
+
   "^reledmac reminder:%s*\n"
-    .. "%s*The number of the footnotes in this section "
-    .. "has changed since the last run.\n"
-    .. "%s*You will need to run LaTeX two more times "
-    .. "before the footnote placement\n"
-    .. "%s*and line numbering in this section are correct%.",
+        .. "%s*The number of the footnotes in this section "
+        .. "has changed since the last run.\n"
+        .. "%s*You will need to run LaTeX two more times "
+        .. "before the footnote placement\n"
+        .. "%s*and line numbering in this section are correct%.",
+
+  "^ ?LaTeX document class for Lecture Notes in Computer Science",
+
+  "^%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*\n"
+          .. "%* Local config file " .. filepat .. " used\n"
+          .. "%*",
+
+  "^=== Package selnolig, Version %S+, Date " .. datepat .. " ===",
+
+  -- Package snapshot
+  '^Dependency list written on .-%.dep%.',
+
+  -- These come from IEEEtran.cls
+  '^%*%* Times compatible math font not found, forcing%.',
+  '^%-%- Found %S+, loading%.',
+  '^%-%- Using IEEE %S+ Society mode%.',
+
+  '^%*%* Conference Paper %*%*\n'
+      .. 'Before submitting the final camera ready copy, remember to:\n'
+      .. '1%. Manually equalize the lengths of two columns on the last page\n'
+      .. 'of your paper%;\n'
+      .. '2%. Ensure that any PostScript and/or PDF output post%-processing\n'
+      .. 'uses only Type 1 fonts and that every step in the generation\n'
+      .. 'process uses the appropriate paper size%.',
+
+  '^%*%* ATTENTION: Overriding %S+ to %S+ via %S+%.',
+
+  '^%*%* ATTENTION: Overriding inner side margin to %S+ and '
+                      .. 'outer side margin to %S+ via %S+%.',
+
+  '^%*%* ATTENTION: Overriding top text margin to %S+ and '
+                      .. 'bottom text margin to %S+ via %S+%.',
+
+  '^%*%* ATTENTION: \\IEEEPARstart is disabled in draft mode %(line %S+%)%.',
+  '^%*%* ATTENTION: Overriding command lockouts %(line %S+%)%.',
+
+  "^%*%* ATTENTION: Single column mode is not typically used "
+                                 .. "with IEEE publications%.",
+
+  '^%*%* ATTENTION: Technotes are normally 9pt documents%.',
+
+  -- MiKTeX auto-updates
+  '^======================================================================\n'
+   .. 'starting package maintenance%.%.%.\n'
+   .. 'installation directory: ' .. dirpat .. '\n'
+   .. 'package repository: http.+\n'
+   .. 'package repository digest: [%dabcdef]+\n'
+   .. 'going to download %S+ .*bytes\n'
+   .. 'going to install %d+ file%(s%) %(%d package%(s%)%)\n'
+   .. 'downloading http.-%.%.%.\n'
+   .. '%S+, %S+ Mbit/s\n'
+   .. 'extracting files from ' .. filepat .. '%.%.%.\n'
+   .. '======================================================================',
 }
 
 
@@ -2280,6 +2573,7 @@
 -- Always start these patterns with "^%s*", see lookahead().
 -- Order matters! The first match wins, so the longer ones should come first.
 anywhereInfoStringsHandler = stringsHandler:new()
+anywhereInfoStringsHandler.IHandleAnywhere = true
 anywhereInfoStringsHandler.severity = INFO
 anywhereInfoStringsHandler.patterns = {
   -- TODO: there are other "... patterns for blah blah"
@@ -2322,9 +2616,30 @@
   "^Couldn't patch \\%S+",
   "^Invalid UTF%-8 byte or sequence at line %d+ replaced by U%+FFFD%.",
 
-  '^Requested font ".-" at [%d%.]+pt\n'
-                            .. "Unknown feature %b`' in font %b`'%.\n"
+  "^Unknown feature %b`' in font %b`'%.\n"
                             .. ' %-> ' .. filepat,
+
+  -- From IEEEtran.cls
+  "^%*%* WARNING: %S+ mode specifiers after the first in %b`' "
+                                    .. "ignored %(line %S+%)%.",
+
+  "^%*%* WARNING: IEEEeqnarraybox position specifiers after "
+                .. "the first in %b`' ignored %(line %S+%)%.",
+
+  "^%*%* WARNING: IEEEeqnarray predefined inter%-column glue type "
+                .. "specifiers after the first in %b`' ignored %(line %S+%)%.",
+
+  "^%*%* WARNING: \\and is valid only when in conference or peerreviewca\n"
+                .. "modes %(line %S+%)%.",
+
+  '^%*%* WARNING: Ignoring useless \\section in Appendix %(line %S+%)%.',
+
+  '^%*%* WARNING: IEEEPARstart drop letter has zero height%! %(line %S+%)\n'
+                           .. ' Forcing the drop letter font size to 10pt%.',
+
+  '^%*%* WARNING: \\IEEEPARstart is locked out for technotes %(line %S+%)%.',
+  '^%*%* WARNING: %S+ is locked out when in conference mode %(line %S+%)%.',
+  '^%*%* ATTENTION: %S+ is deprecated %(line %S+%)%. Use %S+ instead%.',
 }
 
 
@@ -2332,6 +2647,7 @@
 -- Always start these patterns with "^%s*", see lookahead().
 -- Order matters! The first match wins, so the longer ones should come first.
 anywhereWarningStringsHandler = stringsHandler:new()
+anywhereWarningStringsHandler.IHandleAnywhere = true
 anywhereWarningStringsHandler.severity = WARNING
 anywhereWarningStringsHandler.patterns = {
 }
@@ -2343,11 +2659,25 @@
 beginningOfLineCriticalStringsHandler = stringsHandler:new()
 beginningOfLineCriticalStringsHandler.severity = CRITICAL
 beginningOfLineCriticalStringsHandler.patterns = {
-    "^The control sequence at the end of the top line\n"
-      .. "of your error message was never \\def'ed%. If you have\n"
-      .. "misspelled it %(e%.g%., `\\hobx'%), type `I' and the correct\n"
-      .. "spelling %(e%.g%., `I\\hbox'%)%. Otherwise just continue,\n"
-      .. "and I'll forget about whatever was undefined%.",
+  "^The control sequence at the end of the top line\n"
+    .. "of your error message was never \\def'ed%. If you have\n"
+    .. "misspelled it %(e%.g%., `\\hobx'%), type `I' and the correct\n"
+    .. "spelling %(e%.g%., `I\\hbox'%)%. Otherwise just continue,\n"
+    .. "and I'll forget about whatever was undefined%.",
+
+  "^ ======================================= \n"
+    .. " WARNING WARNING WARNING \n"
+    .. " %-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%- \n"
+    .. " The ligature suppression macros of the \n"
+    .. " selnolig package %*require%* LuaLaTeX%. \n"
+    .. " Because you're NOT running this package \n"
+    .. " under LuaLaTeX, ligature suppression \n"
+    .. " %*can not%* be performed%. \n"
+    .. "=========================================",
+
+  -- From IEEEtran.cls
+  "^%*%* No Times compatible math font package found%. "
+                            .. "newtxmath is required%.",
 }
 
 
@@ -2355,6 +2685,7 @@
 -- Always start these patterns with "^%s*", see lookahead().
 -- Order matters! The first match wins, so the longer ones should come first.
 anywhereCriticalStringsHandler = stringsHandler:new()
+anywhereCriticalStringsHandler.IHandleAnywhere = true
 anywhereCriticalStringsHandler.severity = CRITICAL
 anywhereCriticalStringsHandler.patterns = {
 }
@@ -2536,13 +2867,17 @@
   severity = string.lower(severity)
 
    -- tocbibind uses "Note"
+   -- floatflt uses "Message"
   if severity == 'info'
           or severity == 'notification'
           or severity == 'note'
+          or severity == 'message'
   then
       return INFO
+  elseif severity == 'warning' then
+      return WARNING
   else
-      return WARNING
+      return UNKNOWN
   end
 end
 
@@ -2553,13 +2888,11 @@
       -- not know how to handle the next line, but we still need to
       -- check another possibility: the next line might be a "normal"
       -- continuation line
-      local first = string.find(Lines:get(1), '^' .. self.prefix)
-      if first ~= nil then break end
+      if string.find(Lines:get(1), '^' .. self.prefix) then break end
 
       -- Ok, this is almost certainly a wrapped line, but it does
       -- not hurt to also check this just in case
-      first = string.find(Lines.current, 'on input line %d+%.$')
-      if first ~= nil then break end
+      if string.find(Lines.current, 'on input line %d+%.$') then break end
 
       Lines:unwrapOneLine()
   end
@@ -2568,7 +2901,7 @@
 
 -------------------------------------------------------------------------------
 -- latex23MessageHandler
--- genericLatexVariantHandler
+-- genericLatexVariantIHandler
 -- (from genericLatexHandler)
 --
 -- They differ from the prototype by the set of patterns to search for and by
@@ -2601,13 +2934,13 @@
   self.message.content = Lines.current
 end
 
-genericLatexVariantHandler = genericLatexHandler:new()
+genericLatexVariantIHandler = genericLatexHandler:new()
 
-genericLatexVariantHandler.patterns = {
+genericLatexVariantIHandler.patterns = {
   "^(Package) (%S+) (%S+) on input line (%S+): ",
 }
 
-function genericLatexVariantHandler:unpackData(data)
+function genericLatexVariantIHandler:unpackData(data)
   local last = data[1]
   local what = data[2]
   local name = data[3]
@@ -2624,7 +2957,29 @@
   self.message.content = Lines.current
 end
 
+-- Only ever saw "Library (tcolorbox):"
+genericLatexVariantIIHandler = genericLatexHandler:new()
 
+genericLatexVariantIIHandler.patterns = {
+  "^(Library) (%(%S+%)): ",
+}
+
+function genericLatexVariantIIHandler:unpackData(data)
+  local last = data[1]
+  local what = data[2]
+  local name = data[3]
+  self.message.what = what
+  self.message.name = name
+  self.message.severity = INFO
+
+  self:findPrefix(last, name, what)
+  self.message.prefix = self.prefix
+
+  self:unwrapLines()
+  self.message.content = Lines.current
+end
+
+
 -------------------------------------------------------------------------------
 -- citationHandler
 -- referenceHandler
@@ -2731,8 +3086,7 @@
   self.message.content = Lines.current
 
   if not Lines:empty() then
-      local first = string.find(Lines:get(1), 'with kernel methods')
-      if first ~= nil then
+      if string.find(Lines:get(1), 'with kernel methods') then
           self.message.content = self.message.content .. ' ' .. Lines:get(1)
           Lines:gotoNextLine()
       end
@@ -2837,8 +3191,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
-      local first = string.find(line, '%)')
-      if first ~= nil then return false, {} end
+      if string.find(line, '%)') then return false, {} end
   end
 
   return true, {first = first, filename = filename} -- might be nil
@@ -2858,7 +3211,9 @@
       flushUnrecognizedMessages()
       local last = unwrapUntilStringMatches(data.filename)
       if last == nil then
-          io.stderr:write("    texlogsieve: parsing error\n")
+          io.stderr:write("    texlogsieve: parsing error in "
+                           .. "openParensHandler:doit()\n")
+
       else
           Lines:handledChars(last)
       end
@@ -3038,8 +3393,7 @@
 
   -- See the comment "HACK ALERT" in openParensHandler:canDoit()
   if latexPage == nil and position > 0 then
-      local first = string.find(line, '%]')
-      if first ~= nil then return false, {} end
+      if string.find(line, '%]') then return false, {} end
   end
 
   return true, {first = first, latexPage = latexPage} -- may be nil
@@ -3059,7 +3413,9 @@
       flushUnrecognizedMessages()
       local last = unwrapUntilStringMatches(data.latexPage)
       if last == nil then
-          io.stderr:write("    texlogsieve: parsing error\n")
+          io.stderr:write("    texlogsieve: parsing error in "
+                           .. "openSquareBracketHandler:doit()\n")
+
       else
           Lines:handledChars(last)
       end
@@ -3252,10 +3608,9 @@
 utf8FontMapHandler.doit = utf8FontMapHandler.handleFirstLine
 
 function utf8FontMapHandler:handleSecondLine()
-  local first = string.find(Lines.current,
-                      "^%.%.%. no UTF%-8 mapping file for font encoding")
+  if string.find(Lines.current,
+                  "^%.%.%. no UTF%-8 mapping file for font encoding") then
 
-  if first ~= nil then
       self.message.content = self.message.content .. '\n' .. Lines.current
       Lines:handledChars()
       dispatch(self.message)
@@ -3263,10 +3618,9 @@
       return true
   end
 
-  first = string.find(Lines.current,
-                "^%.%.%. processing UTF%-8 mapping file for font encoding")
+  if string.find(Lines.current,
+            "^%.%.%. processing UTF%-8 mapping file for font encoding") then
 
-  if first ~= nil then
       self.message.content = self.message.content .. '\n' .. Lines.current
       Lines:handledChars()
       self.doit = self.handleOtherLines
@@ -3278,7 +3632,9 @@
   -- The second line was neither "no UTF-8 mapping..." nor
   -- "processing UTF-8 mapping" - this should never happen
   dispatch(self.message)
-  io.stderr:write("    texlogsieve: parsing error\n")
+  io.stderr:write("    texlogsieve: parsing error in "
+                   .. "utf8FontMapHandler:handleSecondLine()\n")
+
   Lines:handledChars()
   self.doit = self.handleFirstLine
   return true
@@ -3289,9 +3645,7 @@
 -- Therefore, we try to find the first "...defining Unicode char"
 -- message for the following 4 lines before giving up.
 function utf8FontMapHandler:handleOtherLines()
-  local first = string.find(Lines.current, "^%s*defining Unicode char")
-
-  if first ~= nil then
+  if string.find(Lines.current, "^%s*defining Unicode char") then
       flushUnrecognizedMessages()
       self.foundOtherLines = true
       self.message.content = self.message.content .. '\n' .. Lines.current
@@ -3303,14 +3657,14 @@
   -- this line does not match; why? First possibility: there are no
   -- "...defining Unicode char" lines to be found; instead, there is
   -- another encoding being defined. This obviously should not happen
-  first = string.find(Lines.current,
-                   "^Now handling font encoding (%S+) %.%.%.")
 
-  if first ~=nil then
+  if string.find(Lines.current,
+                   "^Now handling font encoding (%S+) %.%.%.") then
       -- give up and start processing the new message (should not happen)
       dispatch(self.message)
       flushUnrecognizedMessages()
-      io.stderr:write("    texlogsieve: parsing error\n")
+      io.stderr:write("    texlogsieve: parsing error in "
+                       .. "utf8FontMapHandler:handleOtherLines()\n")
 
       self.numTries = 0
       self.foundOtherLines = false
@@ -3363,38 +3717,42 @@
 
 Message.severity = UNKNOWN
 
-function Message:toString(noSilence)
-    -- noSilence may be useful when generating summaries
-    if noSilence then
-        local formatted = self:realToString()
-        if trim(formatted) == "" then return "" else return formatted end
-    end
+function Message:toString(bypassMostFilters)
 
-    -- If we've already been here, just output the previous result
-    if self.formatted ~= nil then return self.formatted end
+    -- We do not want to exclude these in the summaries
+    if not bypassMostFilters then
 
-    if self.mute then self.formatted = "" return "" end
+        -- If we've already been here, just output the previous result
+        if self.formatted ~= nil then return self.formatted end
 
-    if self.severity < MINLEVEL then self.formatted = "" return "" end
+        if self.mute then self.formatted = "" return "" end
 
-    if self:ignoreAsRedundant() then self.formatted = "" return "" end
+        if self.severity < MINLEVEL then self.formatted = "" return "" end
 
-    self.formatted = self:realToString()
-    if trim(self.formatted) == "" then self.formatted = "" return "" end
+        if self:ignoreAsRedundant() then self.formatted = "" return "" end
 
-    for _, val in ipairs(SILENCE_STRINGS) do
-        local first = string.find(self.formatted, val)
-        local other = string.find(self.content, val)
-        if first ~= nil or other ~= nil then self.formatted = "" return "" end
+        if self.name ~= nil then
+            for _, val in ipairs(SILENCE_PKGS) do
+                if self.name == val then self.formatted = "" return "" end
+            end
+        end
+
     end
 
-    if self.name ~= nil then
-        for _, val in ipairs(SILENCE_PKGS) do
-            if self.name == val then self.formatted = "" return "" end
+    local formatted = self:realToString()
+    if trim(formatted) == "" then self.formatted = "" return "" end
+
+    for _, val in ipairs(SILENCE_STRINGS) do
+        if string.find(formatted, val) or string.find(self.content, val)
+        then
+            self.formatted = ""
+            return ""
         end
     end
 
-    return self.formatted
+    if not bypassMostFilters then self.formatted = formatted end
+
+    return formatted
 end
 
 function Message:realToString()
@@ -3434,6 +3792,11 @@
   },
   {
     WARNING,
+    'longtable',
+    'Column widths have changed\nin table'
+  },
+  {
+    WARNING,
     'rerunfilecheck',
     "File %b`' has changed%."
   },
@@ -3442,6 +3805,16 @@
     'biblatex',
     'Please rerun LaTeX%.'
   },
+  {
+    WARNING,
+    'atenddvi',
+    'Rerun LaTeX, last page not yet found%.'
+  },
+  {
+    WARNING,
+    'hyperref',
+    'Rerun to get /PageLabels entry%.'
+  },
 }
 
 function Message:checkMatch(patlist)
@@ -3462,8 +3835,10 @@
           if name == nil then name = self.what end
           if name ~= pkgname then break end
 
-          local first = string.find(self:realToString(), text)
-          if first ~= nil then return true end
+          if string.find(self:realToString(), text)
+                        or string.find(self.content, text)
+          then return true end
+
       until true
   end
 
@@ -3473,9 +3848,11 @@
 function Message:ignoreAsRedundant()
   if BE_REDUNDANT then return false end
 
-  -- this may also be set by checkRerun()
   if self.redundant == nil then
-      if self:checkMatch(self.redundantMessages) then
+      if self:checkMatch(self.redundantMessages)
+              or self:checkMatch(self.rerunMessages) -- these are redundant too
+
+      then
           self.redundant = true
       else
           self.redundant = false
@@ -3492,8 +3869,7 @@
   -- In the rare event that one of these is sent out as
   -- an unrecognizedMessage with no other text, allow for
   -- repetitions
-  local first = string.find(trim(formatted), '^[%(%)%[%]]$')
-  if first ~= nil then return end
+  if string.find(trim(formatted), '^[%(%)%[%]]$') then return end
 
   repetitionsSummary:add(self)
 end
@@ -3506,8 +3882,7 @@
   local _, last = string.find(filename, '^.*/') -- get just the basename
   if last ~= nil then filename = string.sub(filename, last +1) end
   for _, pattern in ipairs(SEMISILENCE_FILES) do
-      local first = string.find(filename, pattern)
-      if first ~= nil then return true end
+      if string.find(filename, pattern) then return true end
   end
 
   -- This is O(n*m) and gets executed for every message,
@@ -3519,8 +3894,7 @@
       if last ~= nil then basename = string.sub(basename, last +1) end
 
       for _, pattern in ipairs(SILENCE_FILES_RECURSIVE) do
-          _, last = string.find(basename, pattern)
-          if last ~= nil then return true end
+          if string.find(basename, pattern) then return true end
       end
   end
 
@@ -3551,6 +3925,8 @@
               .. ', LaTeX page counter ['
               .. latexPages[self.physicalPage] .. ']'
 
+  if COLOR then msg = green(msg) end
+
   return msg
 end
 
@@ -3664,6 +4040,10 @@
 -- specific undefined citation).
 SummaryPrototype = {}
 
+-- Should filtered out messages be included in the summary? For repetitions
+-- this should be false, for most others true is probably better.
+SummaryPrototype.bypassMostFilters = false
+
 function SummaryPrototype:new()
   local o = {}
   setmetatable(o, self)
@@ -3674,10 +4054,10 @@
 end
 
 function SummaryPrototype:add(msg)
-  -- group messages by message content
-  local formatted = msg:toString()
+  local formatted = msg:toString(self.bypassMostFilters)
   if trim(formatted) == "" then return end
 
+  -- group messages by message content
   if self.messages[formatted] == nil then
       self.messages[formatted] = {}
   end
@@ -3705,6 +4085,7 @@
   if text == "" then return "" end -- happens with repetitionsSummary
 
   if self.header ~= "" then
+      if COLOR then self.header = green(self.header) end
       if self:showDetails() then
           self.header = self.header .. '\n'
       else
@@ -3784,6 +4165,7 @@
 
 
 repetitionsSummary = SummaryPrototype:new()
+repetitionsSummary.bypassMostFilters = false
 repetitionsSummary.header = 'Repeated messages:'
 
 function repetitionsSummary:toString()
@@ -3820,6 +4202,7 @@
 
 
 missingCharSummary = SummaryPrototype:new()
+missingCharSummary.bypassMostFilters = true
 missingCharSummary.header = 'Missing characters:'
 
 function missingCharSummary:processSingleMessageList(messages)
@@ -3836,6 +4219,7 @@
 
 
 citationsSummary = SummaryPrototype:new()
+citationsSummary.bypassMostFilters = true
 citationsSummary.header = 'Undefined citations:'
 
 function citationsSummary:showDetails()
@@ -3843,6 +4227,10 @@
 end
 
 function citationsSummary:add(msg)
+  -- Filter out stuff explicitly excluded by the user
+  local tmp = msg:toString(self.bypassMostFilters)
+  if trim(tmp) == "" then return end
+
   -- group messages by problem key. We do not use msg:toString()
   -- here because some messages may include the page number, making
   -- messages that are otherwise the same appear to be different.
@@ -3902,8 +4290,13 @@
 -- under/overfull boxes in pages X, Y, and Z. So we store messages
 -- directly in self.messages instead of using sublists.
 underOverSummary = SummaryPrototype:new()
+underOverSummary.bypassMostFilters = true
 
 function underOverSummary:add(msg)
+  -- Filter out stuff explicitly excluded by the user
+  local tmp = msg:toString(self.bypassMostFilters)
+  if trim(tmp) == "" then return end
+
   table.insert(self.messages, msg)
 end
 
@@ -3912,18 +4305,28 @@
 
   local pages, files = self:pageAndFileList(self.messages)
 
+  local output
+
   if DETAILED_UNDEROVER_SUMMARY then
-      local output = "Under/overfull boxes:"
+      output = "Under/overfull boxes:"
+      if COLOR then output = green(output) end
       for _, msg in ipairs(self.messages) do
-          output = output .. '\npage ' .. msg.physicalPage
-                   .. ' (file ' .. msg.filename .. '):\n'
-          output = output .. msg:toString(true)
+          local pageinfo = 'page ' .. msg.physicalPage
+          local fileinfo = 'file ' .. msg.filename
+          if COLOR then pageinfo = bright(pageinfo) end
+
+          output = output .. '\n' .. pageinfo .. ' (' .. fileinfo .. '):\n'
+                          .. msg:toString(true)
       end
-      return output
   else
-      return "Under/overfull boxes in pages "
-             .. pages .. " (files " .. files .. ")"
+      output = "Under/overfull boxes"
+      if COLOR then output = green(output) end
+      output = output .. " in pages "
+                      .. pages .. " (files " .. files .. ")"
+
   end
+
+  return output
 end
 
 
@@ -3966,9 +4369,8 @@
 function trimRight(s) return (string.gsub(s, '^(.-)%s*$', '%1')) end
 
 function stringToPattern(s)
-  local first, _ = string.find(s, '^////')
   local pat
-  if first ~= nil then
+  if string.find(s, '^////') then
       pat = string.sub(s, 5)
   else
       pat = protect_metachars(s)
@@ -4027,7 +4429,27 @@
     return tmp
 end
 
+function green(s)
+  return GREEN .. s .. RESET_COLOR
+end
 
+function bgreen(s)
+  return BGREEN .. s .. RESET_COLOR
+end
+
+function red(s)
+  return RED .. s .. RESET_COLOR
+end
+
+function yellow(s)
+  return YELLOW .. s .. RESET_COLOR
+end
+
+function bright(s)
+  return BRIGHT .. s .. RESET_COLOR
+end
+
+
 --[[ ##### STACK ##### ]]--
 
 Stack = {}
@@ -4540,7 +4962,10 @@
       if Lines:seemsWrapped() then
           Lines:unwrapOneLine()
       else
-          io.stderr:write("    texlogsieve: parsing error\n")
+          io.stderr:write("    texlogsieve: parsing error in "
+                           .. "unwrapUntilPatternMatches()\n")
+
+          break
       end
   end
 
@@ -4682,29 +5107,25 @@
 function guessQuotedFilename(line, position)
   -- luatex puts quotes around filenames with spaces, which makes things easier
   while true do
-      local _, last = string.find(line, '^"')
-      if line ~= "" and last == nil then return false end -- no quotes
 
-      last = string.find(line, '^"[%.]?/') -- relative path or unix-style path
-      if last == nil then
-          last = string.find(line, '^"%a:/') -- windows-style path
-      end
-      -- there are quotes, but what follows is not a filename
-      if string.len(line) >= 4 and last == nil then return true, nil end
+      -- no quotes
+      if line ~= "" and not string.find(line, '^"') then return false end
 
-      _, last = string.find(line, '^"%a?[:]?[%.]?/[^"%(%)%[%]]+"')
+      local _, last = string.find(line, '^"' .. filepat .. '"')
       if last ~= nil then
-          local filename = string.sub(line, 2, last -1) -- remove quotes
-          if kpse.find_file(filename, 'other text files') == nil then
+          local filename = string.sub(line, 1, last)
+          if checkIfFileExists(filename) then
+              return true, filename
+          else
               return true, nil -- there are quotes, but no filename
-          else
-              return true, '"' .. filename .. '"'
           end
       end
 
       -- no closing quote or the line is too short; can we unwrap this line?
       if not Lines:seemsWrapped(position) then
-          io.stderr:write("    texlogsieve: parsing error\n")
+          io.stderr:write("    texlogsieve: parsing error in "
+                           .. "guessQuotedFilename()\n")
+
           return true, nil
       end
 
@@ -4724,24 +5145,27 @@
 
   local filename
   while true do
-      local first = string.find(line, '^[%.]?/') -- relative or unix-style path
-      if first == nil then
-          first = string.find(line, '^%a:/') -- windows-style path
-      end
-      -- this does not look like a filename
-      if string.len(line) >= 3 and first == nil then return nil end
 
       local longest = string.len(line)
+      local notWrapped = false
 
       -- if there is a ")", "(", "[", or "]" char, stop before that
-      first = string.find(line, "[%)%(%[%]]")
-      if first ~= nil then longest = first -1 end
+      local first = string.find(line, "[%)%(%[%]]")
+      if first ~= nil then
+          notWrapped = true -- this line is obviously not wrapped
+          longest = first -1
+      end
 
       -- From longest to shortest, to avoid problems if there is a
       -- substring in the filename that matches some other filename.
       for i = longest, alreadyCheckedIdx +1, -1 do
           local candidate = string.sub(line, 1, i)
-          if kpse.find_file(candidate, 'other text files') ~= nil then
+
+          -- We are gradually removing chars from the end of the
+          -- string; if we reach a slash, only the directories remain
+          if string.sub(candidate, #candidate) == "/" then break end
+
+          if checkIfFileExists(candidate) then
               filename = candidate
               break
           end
@@ -4753,9 +5177,9 @@
       -- 2. If we did find the filename, we might find a different
       --    (longer) filename, in which case we should stick with
       --    it (yes, I have seen it happen!)
-      if not Lines:seemsWrapped(position) then return filename end
-      first = string.find(line, "[%)%(%[%]]")
-      if first ~= nil then return filename end
+      if notWrapped or not Lines:seemsWrapped(position) then
+          return filename
+      end
 
       alreadyCheckedIdx = longest
       line = line .. Lines:get(position +1)
@@ -4763,6 +5187,107 @@
   end
 end
 
+function readFls(logfilename)
+  local flsfilename = string.gsub(logfilename, '%.log$', '.fls')
+
+  -- 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
+
+  -- It is ok for this to fail, so no "assert"
+  local flsfile = io.open(flsfilename, 'r')
+
+  if flsfile ~= nil and timediff <= 5 then
+      USE_FLS_FILE = true
+
+      filelist = {}
+      while true do
+          local line = flsfile:read("*line")
+          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
+
+          -- I don't think this ever happens
+          if string.find(line, '^".*"$') then line = string.sub(line, 2, -2) 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
+
+          line = string.gsub(line, '\\', '/')
+
+          _, 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
+end
+
+function checkIfFileExistsWithFls(filename)
+  if string.find(filename, '^%./') then filename = string.sub(filename, 3) end
+
+  for tmp, _ in pairs(filelist) do -- not ipairs!
+      if tmp == filename then return true end
+  end
+
+  return false
+end
+
+function checkIfFileExistsWithKpse(filename)
+  -- Is this something like "blah.ext"? If so, make it "./blah.ext",
+  -- otherwise kpse.find_file may find some file in the TeX path that
+  -- has nothing to do with us.
+  local onlyName = true
+
+  -- "C:/"
+  if string.find(filename, '^%a%:/') then onlyName = false end
+
+  -- "./" or just "/"
+  if string.find(filename, '^[%.]?/') then onlyName = false end
+
+  if onlyName then filename = './' .. filename end
+
+  if kpse.find_file(filename, 'other text files') ~= nil then
+      return true
+  else
+      return false
+  end
+end
+
+function checkIfFileExists(filename)
+  -- If there are quotes, remove them
+  if string.find(filename, '^".*"$') then
+      filename = string.sub(filename, 2, -2)
+  end
+
+  -- MiKTeX for windows does not necessarily use the same type of
+  -- slashes in paths inside the log file and the .fls file. I also
+  -- do not know if kpse.find_file works ok with backslashes. Let's
+  -- just turn everything to forward slashes and be done with it.
+  filename = string.gsub(filename, '\\', '/')
+
+  -- If we are reading a logfile (not stdin) and there is a
+  -- corresponding .fls file, use it. Maybe this is slightly more
+  -- reliable, but the real advantage is that we can process a
+  -- logfile even if we are not on the same environment that
+  -- created it, which is nice for testing, reporting bugs, etc.
+  if USE_FLS_FILE then
+      return checkIfFileExistsWithFls(filename)
+  else
+      return checkIfFileExistsWithKpse(filename)
+  end
+end
+
+
 --[[ ###################################################################### ]]--
 --[[ ######################## BEGINNING OF SCRIPT ######################### ]]--
 --[[ ###################################################################### ]]--

Modified: trunk/Master/texmf-dist/doc/man/man1/texlogsieve.1
===================================================================
--- trunk/Master/texmf-dist/doc/man/man1/texlogsieve.1	2022-02-10 21:19:50 UTC (rev 61973)
+++ trunk/Master/texmf-dist/doc/man/man1/texlogsieve.1	2022-02-10 21:20:19 UTC (rev 61974)
@@ -1,4 +1,4 @@
-.TH TEXLOGSIEVE "1" "February 2022" "texlogsieve 1.0.0-beta-3" "User Commands"
+.TH TEXLOGSIEVE "1" "February 2022" "texlogsieve 1.0.0" "User Commands"
 
 .SH NAME
 
@@ -66,6 +66,11 @@
 enabled with no-page-delay).
 
 .TP
+\fB\-\-file\-banner\fR, \fB\-\-no\-file\-banner\fR
+Show/don't show the \[lq]From file...\[rq] banner messages (default
+enabled, except with level DEBUG as that would be redundant and confusing).
+
+.TP
 \fB\-\-repetitions\fR, \fB\-\-no\-repetitions\fR
 Allow/prevent repeated messages (default disabled, i.e., repeated messages
 are supressed).
@@ -104,6 +109,11 @@
 Enable/disable progress gauge in page-delay mode (default enabled).
 
 .TP
+\fB\-\-color\fR, \fB\-\-no\-color\fR
+Enable/disable colored output. On Windows, this will only work with
+an up-to-date Windows 10 or later (default disabled).
+
+.TP
 \fB\-l\fR \fI\,LEVEL\/\fR, \fB\-\-minlevel\fR=\fI\,LEVEL\/\fR
 Filter out messages with severity level lower than LEVEL. Valid levels
 are DEBUG (no filtering), INFO, WARNING, CRITICAL, and UNKNOWN (default
@@ -115,13 +125,13 @@
 wrapped lines. The output should be very similar (but not equal) to the
 input file, but with wrapped lines reconstructed. This activates \-l debug,
 \-\-no\-summary, \-\-no\-page\-delay, \-\-repetitions, \-\-be\-redundant,
-and \-\-shipouts, and also supresses the verbose \[lq]open/close file\[rq]
-and \[lq]shipout\[rq] messages, simulating instead the TeX format, with parens
-and square brackets. This is useful if you prefer the reports generated
-by some other tool but want to benefit from texlogsieve's line unwrapping
-algorithm; the output generated by this option should be parseable by other
-tools (but you probably need to coerce the other tool not to try to unwrap
-lines).
+\-\-shipouts, and \-\-no\-file\-banner, and also supresses the verbose
+\[lq]open/close file\[rq] and \[lq]shipout\[rq] messages, simulating
+instead the TeX format, with parens and square brackets. This is useful if
+you prefer the reports generated by some other tool but want to benefit
+from texlogsieve's line unwrapping algorithm; the output generated by this
+option should be parseable by other tools (but you probably need to coerce
+the other tool not to try to unwrap lines).
 
 .TP
 \fB\-\-silence\-package\fR=\fI\,PKGNAME\/\fR
@@ -260,7 +270,8 @@
 
 .SH SEE ALSO
 
-The pdf documentation (in TeXLive, try \fI\,texdoc texlogsieve\/\fR).
+The pdf documentation (in TeXLive, try \fI\,texdoc texlogsieve\/\fR)
+includes a \fBTIPS\fR section you may find useful.
 
 If you want to know more about the TeX log file and the workings of the
 program, check the initial comments in the code.

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

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	2022-02-10 21:19:50 UTC (rev 61973)
+++ trunk/Master/texmf-dist/doc/support/texlogsieve/texlogsieve.tex	2022-02-10 21:20:19 UTC (rev 61974)
@@ -39,34 +39,40 @@
 
 \RecordChanges
 
-\changes{v1.0.0-beta-1}{2021/12/16}{First public prerelease}
-\changes{v1.0.0-beta-2}{2022/01/04}{Automatically read \texttt{texlogsieverc}
+\changes{1.0.0-beta-1}{2021/12/16}{First public prerelease}
+\changes{1.0.0-beta-2}{2022/01/04}{Automatically read \texttt{texlogsieverc}
                                     if it exists}
-\changes{v1.0.0-beta-2}{2022/01/04}{Add options \texttt{-\/-be-redundant}
+\changes{1.0.0-beta-2}{2022/01/04}{Add options \texttt{-\/-be-redundant}
                                     and \texttt{-\/-box-detail}}
-\changes{v1.0.0-beta-2}{2022/01/04}{Add options
+\changes{1.0.0-beta-2}{2022/01/04}{Add options
                                     \texttt{-\/-set-to-level-[levelname]}}
-\changes{v1.0.0-beta-2}{2022/01/04}{Include silenced messages in summaries}
-\changes{v1.0.0-beta-2}{2022/01/04}{Substitute empty citation/label keys
+\changes{1.0.0-beta-2}{2022/01/04}{Include silenced messages in summaries}
+\changes{1.0.0-beta-2}{2022/01/04}{Substitute empty citation/label keys
                                     for ``???''}
-\changes{v1.0.0-beta-2}{2022/01/04}{Fix bug that prevented
+\changes{1.0.0-beta-2}{2022/01/04}{Fix bug that prevented
                                     \texttt{-\/-add-[info\textbar
                                     warning]-message} from working}
-\changes{v1.0.0-beta-2}{2022/01/04}{Misc small bugfixes}
-\changes{v1.0.0-beta-3}{2022/02/02}{Add options
+\changes{1.0.0-beta-2}{2022/01/04}{Misc small bugfixes}
+\changes{1.0.0-beta-3}{2022/02/02}{Add options
                                     \texttt{-\/-summary-detail},
                                     \texttt{-\/-ref-detail},
                                     \texttt{-\/-cite-detail}}
-\changes{v1.0.0-beta-3}{2022/02/02}{Abort on invalid command-line options}
-\changes{v1.0.0-beta-3}{2022/02/02}{Detect ``please rerun`` messages and
+\changes{1.0.0-beta-3}{2022/02/02}{Abort on invalid command-line options}
+\changes{1.0.0-beta-3}{2022/02/02}{Detect ``please rerun`` messages and
                                     add them to the summary}
-\changes{v1.0.0-beta-3}{2022/02/02}{Fix line unwrapping with \XeTeX}
+\changes{1.0.0-beta-3}{2022/02/02}{Fix line unwrapping with \XeTeX}
+\changes{1.0.0-final={1.0.0}}{2022/02/09}{Better compatibility with MiK\TeX\ for
+                                 Windows}
+\changes{1.0.0-final={1.0.0}}{2022/02/09}{If possible, use the \texttt{.fls} file}
+\changes{1.0.0-final={1.0.0}}{2022/02/09}{Add options \texttt{-\/-file-banner}
+                                 and \texttt{-\/-color}}
+\changes{1.0.0-final={1.0.0}}{2022/02/09}{Changed the effect of filters on the summary}
 
 \begin{document}
 
 \title{\textsf{texlogsieve}:\thanks{This document
-corresponds to \textsf{texlogsieve}~1.0.0-beta-3,
-dated~2022-02-02.}\\[.3\baselineskip]
+corresponds to \textsf{texlogsieve}~1.0.0,
+dated~2022-02-09.}\\[.3\baselineskip]
 {\normalsize(yet another program to)\\[-.6\baselineskip]}
 {\large filter and summarize \LaTeX\ log files}
 }
@@ -203,10 +209,11 @@
 
 \begin{quote}
 \begin{verbatim}
+no-summary-detail
 no-page-delay
 # no-page-delay enables shipouts, but we do not want that
 no-shipouts
-silence-string = Hyperreferences in rotated content will be misplaced
+set-to-level-info=Hyperreferences in rotated content will be misplaced
 # no need to escape the "\" (or any other) character
 silence-string = Using \overbracket and \underbracket from `mathtools'
 # silence a string using lua pattern matching
@@ -241,6 +248,12 @@
 \end{description}
 
 \begin{description}
+\item[\texttt{-\/-file-banner}, \texttt{-\/-no-file-banner}]~\\
+Show/don't show the ``From file\dots'' banner messages (default enabled,
+except with level \texttt{DEBUG} as that would be redundant and confusing).
+\end{description}
+
+\begin{description}
 \item[\texttt{-\/-repetitions}, \texttt{-\/-no-repetitions}]~\\
 Allow/prevent repeated messages (default disabled, i.e., repeated messages
 are supressed).
@@ -288,6 +301,12 @@
 \end{description}
 
 \begin{description}
+\item[\texttt{-\/-color}, \texttt{-\/-no-color}]~\\
+Enable/disable colored output. On Windows, this will only work with
+an up-to-date Windows 10 or later (default disabled).
+\end{description}
+
+\begin{description}
 \item[\texttt{-l LEVEL}, \texttt{-\/-minlevel=LEVEL}]~\\
 Filter out messages with severity level lower than \texttt{LEVEL}. Valid
 levels are \texttt{DEBUG} (no filtering), \texttt{INFO}, \texttt{WARNING},
@@ -300,9 +319,9 @@
 wrapped lines. The output should be very similar (but not equal) to the
 input file, but with wrapped lines reconstructed. This activates
 \texttt{-l debug}, \texttt{-\/-no-summary}, \texttt{-\/-no-page-delay},
-\texttt{-\/-repetitions}, \texttt{-\/-be-redundant}, and
-\texttt{-\/-shipouts}, and also supresses the verbose ``open/close file''
-and ``shipout'' messages, simulating instead the \TeX{} format, with
+\texttt{-\/-repetitions}, \texttt{-\/-be-redundant}, \texttt{-\/-shipouts},
+and \texttt{-\/-no-file-banner}, and also supresses the verbose ``open/close
+file'' and ``shipout'' messages, simulating instead the \TeX{} format, with
 parens and square brackets. This is useful if you prefer the reports
 generated by some other tool but want to benefit from texlogsieve's line
 unwrapping algorithm; the output generated by this option should be
@@ -397,6 +416,78 @@
 Print program version.
 \end{description}
 
+\section{Tips}
+
+\begin{itemize}
+  \item If the program output is still too verbose to your liking, resist
+  the urge to use \texttt{-l critical} or \texttt{-\/-only-summary}. Instead,
+  create a configuration file with \texttt{no-summary-detail} and appropriate
+  \texttt{silence-[something]} and \texttt{set-to-level-[something]} options:
+  You will get reasonably quiet output but still be notified of problems. Since
+  you probably always use essentially the same set of packages and classes,
+  the file will be useable in other \LaTeX\ projects too (you may even put
+  it somewhere in your \TeX\ path, like \texttt{\$HOME/texmf/tex/latex} on
+  Unix-like systems).
+
+  \item \texttt{texlogsieve} has no smarts to deal with error messages, but
+  you may use options \texttt{-l unknown -\/-no-page-delay -\/-no-summary
+  -\/-no-shipouts -\/-no-file-banner} to get almost nothing but errors.
+
+  \item With \texttt{latexmk}, it is enough to put something like this in
+  \texttt{latexmkrc}:
+
+  \bgroup\small
+  \begin{verbatim}
+set_tex_cmds("-halt-on-error -interaction nonstopmode %O %S|texlogsieve");\end{verbatim}
+  \egroup
+
+  However, this means you get to see the repeated output of all iterations
+  needed to produce the document. To only see the output of the last iteration,
+  you may do something like this:
+
+  \bgroup\small
+  \begin{verbatim}
+set_tex_cmds("-halt-on-error %O %S");
+
+$silent = 1; # This adds "-interaction batchmode"
+$silence_logfile_warnings = 1;
+
+END {
+  if (-s "$root_filename.blg"
+              and open my $bibfile, '<', "$root_filename.blg") {
+
+      print("**********************\n");
+      print("bibtex/biber messages:\n");
+      while(my $line = <$bibfile>) {
+          if ($line =~ /You.ve used/) {
+              last;
+          } else {
+              print($line);
+          };
+      };
+      close($bibfile);
+  };
+
+  if (-s "$root_filename.ilg"
+              and open my $indfile, '<', "$root_filename.ilg") {
+
+      print("*************************\n");
+      print("makeindex/xindy messages:\n");
+      while(my $line = <$indfile>) {
+          print($line);
+      };
+      close($indfile);
+  };
+
+  if (-s "$root_filename.log") {
+      print("***************\n");
+      print("LaTeX messages:\n");
+      Run_subst("texlogsieve %R.log");
+  };
+};\end{verbatim}
+  \egroup
+\end{itemize}
+
 \section{License}
 
 Copyright © 2021, 2022 Nelson Lago \textless lago at ime.usp.br\textgreater\\

Modified: trunk/Master/texmf-dist/scripts/texlogsieve/texlogsieve
===================================================================
--- trunk/Master/texmf-dist/scripts/texlogsieve/texlogsieve	2022-02-10 21:19:50 UTC (rev 61973)
+++ trunk/Master/texmf-dist/scripts/texlogsieve/texlogsieve	2022-02-10 21:20:19 UTC (rev 61974)
@@ -331,7 +331,7 @@
 -----------------
 
   exampleHandler = {}
-  exampleHandler.pattern = '^%s*L3 programming layer %b<> xparse %b<>'
+  exampleHandler.pattern = '^%s*L3 programming layer %b<>'
 
   function exampleHandler:init()
   end
@@ -532,6 +532,13 @@
 CRITICAL = 3
  UNKNOWN = 4
 
+        RED = '\x1B[31m'
+     YELLOW = '\x1B[33m'
+      GREEN = '\x1B[32m'
+     BRIGHT = '\x1B[37;1m'
+     BGREEN = '\x1B[32;1m'
+RESET_COLOR = '\x1B[0m'
+
 function main(arg)
   initializeKpse()
   processCommandLine(arg)
@@ -695,6 +702,11 @@
   -- detectEngine() may set one of these to true
   LUATEX = false
   XETEX = false
+
+  -- When we print a message that is the first from a given filename,
+  -- we announce the filename first. This is used to detect the change
+  -- in file - used by showFileBanner()
+  lastFileBanner = ""
 end
 
 function initializeKpse()
@@ -722,7 +734,8 @@
   table.insert(beginningOfLineHandlers, labelHandler)
   table.insert(beginningOfLineHandlers, genericLatexHandler)
   table.insert(beginningOfLineHandlers, latex23MessageHandler)
-  table.insert(beginningOfLineHandlers, genericLatexVariantHandler)
+  table.insert(beginningOfLineHandlers, genericLatexVariantIHandler)
+  table.insert(beginningOfLineHandlers, genericLatexVariantIIHandler)
   table.insert(beginningOfLineHandlers, providesHandler)
   table.insert(beginningOfLineHandlers, geometryDetailsHandler)
   table.insert(beginningOfLineHandlers, epilogueHandler)
@@ -737,6 +750,7 @@
   table.insert(anywhereHandlers, anywhereInfoStringsHandler)
   table.insert(anywhereHandlers, anywhereWarningStringsHandler)
   table.insert(anywhereHandlers, anywhereCriticalStringsHandler)
+  table.insert(anywhereHandlers, fpHandler) -- before open/closeParensHandler!
   table.insert(anywhereHandlers, openParensHandler)
   table.insert(anywhereHandlers, closeParensHandler)
   table.insert(anywhereHandlers, openSquareBracketHandler)
@@ -818,18 +832,16 @@
 
 function detectEngine()
   local line = logfile:read("*line")
-  local first = string.find(string.lower(line), '^this is lua')
-  if first ~= nil then
+  if line == nil then return end
+
+  if string.find(string.lower(line), '^this is lua') then
       LUATEX = true
-  else
-      first = string.find(string.lower(line), '^this is xe')
-      if first ~= nil then XETEX = true end
+  elseif string.find(string.lower(line), '^this is xe') then
+      XETEX = true
   end
 
-  local msg = Message:new()
-  msg.content = line
-  msg.severity = DEBUG
-  dispatch(msg)
+  -- leave the line for normal processing
+  Lines:append(line)
 end
 
 function processCommandLine(args)
@@ -842,10 +854,13 @@
   SILENCE_REPETITIONS = true
   MINLEVEL = WARNING
   BE_REDUNDANT = false
+  FILE_BANNER = true
   DETAILED_UNDEROVER_SUMMARY = true
   DETAILED_REFERENCE_SUMMARY = true
   DETAILED_CITATION_SUMMARY = true
 
+  COLOR = false
+
   SILENCE_STRINGS = {}
   SILENCE_PKGS = {} -- just the package names
   SEMISILENCE_FILES = {} -- filenames (without leading path), file globs work
@@ -879,6 +894,7 @@
   --summary, --no-summary                enable/disable final summary
   --only-summary                         no filtering, only final summary
   --shipouts, --no-shipouts              enable/disable reporting shipouts
+  --file-banner, --no-file-banner        Show/suppress "From file ..." banners
   --repetitions, --no-repetitions        allow/prevent repeated messages
   --be-redundant, --no-be-redundant      present/suppress ordinary messages
                                          that will also appear in the summary
@@ -891,6 +907,7 @@
   --summary-detail, --no-summary-detail  toggle box-detail, ref-detail, and
                                          cite-detail at once
   --heartbeat, --no-heartbeat            enable/disable progress gauge
+  --color, --no-color                    enable/disable colored output
   -l LEVEL, --minlevel=LEVEL             filter out messages with severity
                                          level lower than [LEVEL]. Valid
                                          levels are DEBUG, INFO, WARNING,
@@ -933,7 +950,7 @@
 
   --version
   if vars.version then
-      print("texlogsieve 1.0.0-beta-3")
+      print("texlogsieve 1.0.0")
       print("Copyright (C) 2021, 2022 Nelson Lago <lago at ime.usp.br>")
       print("License GPLv3+: GNU GPL version 3 or later "
             .. "<https://gnu.org/licenses/gpl.html>.")
@@ -978,6 +995,7 @@
       SILENCE_REPETITIONS = false
       BE_REDUNDANT = true
       MINLEVEL = DEBUG
+      FILE_BANNER = false
   end
 
   vars.u = nil
@@ -1050,10 +1068,28 @@
       end
   end
 
+  -- When severity is debug, we already output open/close
+  -- file messages; adding these would be confusing.
+  if MINLEVEL == DEBUG then FILE_BANNER = false end
+
   vars.l = nil
   vars.minlevel = nil
 
 
+  --no-file-banner
+  --file-banner
+  --file-banner=true/false
+  if vars['no-file-banner'] or vars['file-banner'] ~= nil
+                        and not vars['file-banner'] then
+
+      FILE_BANNER = false
+  end
+  if vars['file-banner'] then FILE_BANNER = true end
+
+  vars['file-banner'] = nil
+  vars['no-file-banner'] = nil
+
+
   --no-repetitions
   --repetitions
   --repetitions=true/false
@@ -1161,10 +1197,23 @@
   vars['no-heartbeat'] = nil
 
 
+  --no-color
+  --color
+  --color=true/false
+  if vars['no-color'] or vars.color ~= nil and not vars.color then
+      COLOR = false
+  end
+  if vars.color then COLOR = true end
+
+  vars.color = nil
+  vars['no-color'] = nil
+
+
   if vars.filename == nil then
       logfile = io.stdin
   else
       logfile = assert(io.open(vars.filename, "r"))
+      readFls(vars.filename)
   end
 
   vars.filename = nil
@@ -1271,9 +1320,7 @@
       if line == nil then break end
 
       line = trim(line)
-      local first = string.find(line, '^#')
-
-      if first == nil and line ~= "" then
+      if not string.find(line, '^#') and line ~= "" then
           local equals = string.find(line, '=', 1, true)
           if equals ~= nil then
               optname = string.sub(line, 1, equals -1)
@@ -1337,6 +1384,7 @@
     "Tab has been converted to Blank",
     "The morewrites package is unnecessary",
     'Unused \\captionsetup%b[]',
+    "Unknown feature `' in font %b`'", -- empty feature, not a problem
   }
 
   DEFAULT_FORCED_CRITICAL = {
@@ -1358,9 +1406,7 @@
   -- defaults (note that there is no "return" in the first two blocks)
   if msg.severity ~= INFO then
       for _, val in ipairs(DEFAULT_FORCED_INFO) do
-          local first = string.find(formatted, val)
-          local other = string.find(msg.content, val)
-          if first ~= nil or other ~= nil then
+          if string.find(formatted, val) or string.find(msg.content, val) then
               msg.severity = INFO
           end
       end
@@ -1368,9 +1414,7 @@
 
   if msg.severity ~= CRITICAL then
       for _, val in ipairs(DEFAULT_FORCED_CRITICAL) do
-          local first = string.find(formatted, val)
-          local other = string.find(msg.content, val)
-          if first ~= nil or other ~= nil then
+          if string.find(formatted, val) or string.find(msg.content, val) then
               msg.severity = CRITICAL
           end
       end
@@ -1378,9 +1422,7 @@
 
   if msg.severity ~= DEBUG then
       for _, val in ipairs(FORCED_DEBUG) do
-          local first = string.find(formatted, val)
-          local other = string.find(msg.content, val)
-          if first ~= nil or other ~= nil then
+          if string.find(formatted, val) or string.find(msg.content, val) then
               msg.severity = DEBUG
               return
           end
@@ -1389,9 +1431,7 @@
 
   if msg.severity ~= INFO then
       for _, val in ipairs(FORCED_INFO) do
-          local first = string.find(formatted, val)
-          local other = string.find(msg.content, val)
-          if first ~= nil or other ~= nil then
+          if string.find(formatted, val) or string.find(msg.content, val) then
               msg.severity = INFO
               return
           end
@@ -1400,9 +1440,7 @@
 
   if msg.severity ~= WARNING then
       for _, val in ipairs(FORCED_WARNING) do
-          local first = string.find(formatted, val)
-          local other = string.find(msg.content, val)
-          if first ~= nil or other ~= nil then
+          if string.find(formatted, val) or string.find(msg.content, val) then
               msg.severity = WARNING
               return
           end
@@ -1411,9 +1449,7 @@
 
   if msg.severity ~= CRITICAL then
       for _, val in ipairs(FORCED_CRITICAL) do
-          local first = string.find(formatted, val)
-          local other = string.find(msg.content, val)
-          if first ~= nil or other ~= nil then
+          if string.find(formatted, val) or string.find(msg.content, val) then
               msg.severity = CRITICAL
               return
           end
@@ -1421,20 +1457,9 @@
   end
 end
 
-function checkRerun(msg)
-  if msg:checkMatch(msg.rerunMessages) then
-      -- Rerun messages should be silenced when
-      -- "--no-be-redundant" is in effect
-      if not BE_REDUNDANT then msg.redundant = true end
-      return true
-  end
 
-  return false
-end
-
 function processMessage(msg)
-  -- can't use short-circuit eval here, we need checkRerun() to always execute
-  SHOULD_RERUN_LATEX = checkRerun(msg) or SHOULD_RERUN_LATEX
+  SHOULD_RERUN_LATEX = SHOULD_RERUN_LATEX or msg:checkMatch(msg.rerunMessages)
 
   adjustSeverity(msg)
 
@@ -1474,6 +1499,26 @@
   if SHOW_SUMMARY then showSummary() end
 end
 
+function showFileBanner(msg)
+  if not FILE_BANNER then return end
+
+  if msg.filename ~= nil
+                and msg.filename ~= ""
+                and msg.filename ~= lastFileBanner
+                -- TODO: "DUMMY" should never happen, but
+                --       what should we do if it does?
+  then
+      lastFileBanner = msg.filename
+      local txt = "From file " .. msg.filename .. ":"
+      if COLOR then
+          txt = yellow(txt)
+      else
+          print(string.rep('-', string.len(txt)))
+      end
+      print(txt)
+  end
+end
+
 function showMessage(msg)
   local formatted = msg:toString()
   if trim(formatted) ~= "" then
@@ -1481,7 +1526,8 @@
       local spaces = ""
       if not RAW and msg.physicalPage ~= nil then
           pageinfo = 'pg ' .. msg.physicalPage .. ': '
-          spaces = string.rep(" ", string.len(pageinfo))
+          spaces = string.rep(" ", string.len(pageinfo)) -- before color
+          if COLOR then pageinfo = bright(pageinfo) end
       end
 
       -- A message is a repetition if it has
@@ -1497,8 +1543,15 @@
       end
 
       if not SILENCE_REPETITIONS or not alreadySeen then
+          showFileBanner(msg)
+
           for _, line in ipairs(linesToTable(formatted)) do
+              if COLOR and msg.severity >= CRITICAL then
+                  line = red(line)
+              end
+
               print(pageinfo .. line)
+
               pageinfo = spaces
           end
       end
@@ -1523,9 +1576,11 @@
   end
 
   if thereIsSomething then
-      print("")
-      print("After last page:")
-      print("")
+      print()
+      local txt = "After last page:"
+      if COLOR then txt = bgreen(txt) end
+      print(txt)
+      print()
   end
 
   -- we always call this, even if there is nothing to show,
@@ -1549,7 +1604,13 @@
 
   if not thereIsSomething then return end
 
-  if not ONLY_SUMMARY then for i = 1, 3 do print("") end end
+  if not ONLY_SUMMARY then
+      print("")
+      local txt = "====  Summary:  ===="
+      if COLOR then txt = bgreen(txt) end
+      print(txt)
+      print("")
+  end
 
   for _, summary in ipairs(summaries) do
       local formatted = summary:toString()
@@ -1564,7 +1625,9 @@
   end
 
   if SHOULD_RERUN_LATEX then
-      print("** LaTeX says you should rerun **")
+      local txt = "** LaTeX says you should rerun **"
+      if COLOR then txt = red(txt) end
+      print(txt)
       print()
   end
 end
@@ -1605,9 +1668,15 @@
 
 -- datepat and filepat will come in handy later on.
 --
--- Note that, in some cases, it may be useful to start filepat with '^[%.]?/'.
+-- Absolute paths may be in the form "/...blah.ext" and "C:\...blah.ext";
+-- Relative paths may be in the form "./blah.ext" or "blah.ext". This last
+-- form makes ordinary words almost indistinguishable from paths. Since it
+-- is also possible for a path to include dots and spaces, this pattern has
+-- to be *very* permissible, which means it may easily match something that
+-- is not really a filename. Don't blindly trust it! guessFilename() uses
+-- this but with added sanity checks.
 --
--- filepat will fail:
+-- filepat will also fail:
 --
 -- 1. If the path or filename includes weird characters, such as ":" or "|"
 -- 2. If the file has no extension or the extension has only one character
@@ -1615,21 +1684,23 @@
 -- 4. If filepat should match the end of the message and the matching line is
 --    wrapped in the middle of the file extension, for example "myfile.pd\nf"
 --    (but we have a hack in unwrapUntilPatternMatches() to work around that)
---
--- More importantly, filepat allows for spaces and multiple dots in filenames,
--- but this means it may match something that is not really a filename. Don't
--- blindly trust it! We do not use this to detect open/close file; for that,
--- check guessFilename().
 
 datepat = '%d%d%d%d[/%-%.]%d%d[/%-%.]%d%d'
 
-filepat = '%a?[:]?'
-          .. '[^%%:;,%=%*%?%|%&%$%#%!%@"%`\'%<%>%[%]%{%}]+'
-          .. '%.'
-          .. '[^/ %-%_%.%%:;,%=%*%?%|%&%$%#%!%@"%`\'%<%>%[%]%{%}]'
-          .. '[^/ %-%_%.%%:;,%=%*%?%|%&%$%#%!%@"%`\'%<%>%[%]%{%}]+'
+-- These charaters should never be part of a path
+unreasonable = '%%:;,%=%*%?%|%&%$%#%!%@"%`\'%<%>%[%]%{%}%(%)'
 
+filepat =    '[^%s%-' .. unreasonable .. ']' -- the first char
+          .. ':?' -- If the first char is a drive letter
+          .. '[^' .. unreasonable .. ']*' -- other (optional) chars
+          .. '%.' -- the extension must exist and be at least two chars long
+          .. '[^/\\ %-%_%.' .. unreasonable .. ']'
+          .. '[^/\\ %-%_%.' .. unreasonable .. ']+'
 
+-- This is even more fragile than filepat, it matches almost anything.
+dirpat =    '[^%s%-' .. unreasonable .. ']'
+         .. '[^' .. unreasonable .. ']*'
+
 -------------------------------------------------------------------------------
 -- HandlerPrototype
 -------------------------------------------------------------------------------
@@ -1715,8 +1786,7 @@
 -- We need to "manually" unwrap the file list because some
 -- lines are wrapped at lengths different from max_print_line
 function epilogueHandler:handleOtherLines()
-  local first = string.find(Lines.current, '^Output written')
-  if first ~= nil then
+  if string.find(Lines.current, '^Output written') then
       self.processingFilelist = false
       self.message.content = '\n' .. self.message.content
   end
@@ -1728,8 +1798,9 @@
       self.message.content = self.message.content .. '\n' .. Lines.current
   end
 
-  first = string.find(Lines.current, '^[%<%{]')
-  if first ~= nil then self.processingFilelist = true end
+  if string.find(Lines.current, '^[%<%{]') then
+      self.processingFilelist = true
+  end
 
   Lines:handledChars()
   nextHandler = self
@@ -1744,6 +1815,132 @@
 
 
 -------------------------------------------------------------------------------
+-- fpHandler
+--
+-- Handles the messages output by the "fp" (fixed point) package, which
+-- look like ( FP-UPN ( FP-MUL ) ( FP-ROUND ) ) etc.
+--
+-- They are \message's, so they may appear anywhere on a line. Usually,
+-- several of such messages appear together, so line wrapping is common.
+-- We handle the parens with a stack independent from the openFiles stack.
+-------------------------------------------------------------------------------
+
+fpHandler = HandlerPrototype:new()
+
+function fpHandler:init()
+  self.stack = Stack:new()
+end
+
+-- The space between the open parens char and "FP" is optional
+-- because it is ommited in case of line wrapping.
+fpHandler.loosePattern = '%( ?FP%-[^%s%)]+'
+fpHandler.strictPattern = '^%s*' .. fpHandler.loosePattern
+fpHandler.pattern = fpHandler.strictPattern
+
+
+function fpHandler:canDoit(position)
+  if position == nil then position = 0 end
+  local line = Lines:get(position)
+  if line == nil then return false, {} end
+
+  -- When we are looking into the future, let's just lie: since this
+  -- handler deals with several similar short messages in sequence,
+  -- preventing unwrapping is a very bad idea, because it will affect the
+  -- processing of the current message. As for other messages looking into
+  -- the future, openParensHandler:canDoit() return value will work fine.
+  if position > 0 then return false, {} end
+
+  while true do
+      local first = string.find(line, self.pattern)
+      if first ~= nil then return true, {first = first} end
+
+      if not Lines:seemsWrapped(position) then return false, {} end
+
+      line = line .. Lines:get(position +1)
+      position = position +1
+  end
+end
+
+function fpHandler:lookahead()
+  self.pattern = self.loosePattern
+  local match, data = self:canDoit()
+  self.pattern = self.strictPattern
+
+  return match, data
+end
+
+function fpHandler:startProcessing()
+  local myTurn, data = self:canDoit()
+  if not myTurn then return false end
+
+  self.message = self:newMessage()
+  self.message.severity = DEBUG
+  self.message.content = ""
+  self.doit = self.process
+  nextHandler = self
+  return true
+end
+
+fpHandler.doit = fpHandler.startProcessing
+
+function fpHandler:process()
+  while true do
+      local _, last = string.find(Lines.current, self.pattern)
+      if last ~= nil then return self:processOpen(last) end
+
+      _, last = string.find(Lines.current, '%s*%)')
+      if last ~= nil then return self:processClose(last) end
+
+      if Lines:seemsWrapped() then
+          Lines:unwrapOneLine()
+      else
+          -- This should never happen, but if it does
+          -- we will probably end up in an endless loop
+          io.stderr:write("    texlogsieve: parsing error in "
+                               .. "fpHandler:process()\n")
+
+          dispatch(self.message)
+          self.doit = self.startProcessing
+          self.stack = Stack:new()
+          return true
+      end
+  end
+end
+
+function fpHandler:processOpen(last)
+  self.message.content = self.message.content
+                         .. string.sub(Lines.current, 1, last)
+
+  Lines:handledChars(last)
+  self.stack:push("DUMMY")
+  nextHandler = self
+
+  return true
+end
+
+function fpHandler:processClose(last)
+  self.message.content = self.message.content
+                         .. string.sub(Lines.current, 1, last)
+
+  Lines:handledChars(last)
+
+  if self.stack:pop() == nil then
+      io.stderr:write("    texlogsieve: parsing error in "
+                       .. "fpHandler:processClose()\n")
+  end
+
+  if self.stack:empty() then
+      dispatch(self.message)
+      self.doit = self.startProcessing
+  else
+      nextHandler = self
+  end
+
+  return true
+end
+
+
+-------------------------------------------------------------------------------
 -- underOverFullBoxHandler
 --
 -- Handles under/overfull multiline messages. There are usually important,
@@ -1783,8 +1980,9 @@
   self.message.verthoriz = data.verthoriz
   self.message.amount = data.amount
   self.message.severity = WARNING
-  local first = string.find(data.amount, 'badness 10000')
-  if first ~= nil then self.message.severity = CRITICAL end
+  if string.find(data.amount, 'badness 10000') then
+      self.message.severity = CRITICAL
+  end
   Lines:handledChars(data.last)
 
   self.doit = self.handleClosing
@@ -1803,7 +2001,9 @@
   end
 
   if last == nil then
-      io.stderr:write("    texlogsieve: parsing error\n")
+      io.stderr:write("    texlogsieve: parsing error in "
+                       .. "underOverFullBoxHandler:handleFirstLine()\n")
+
       self.doit = self.handleFirstLine
       dispatch(self.message)
       return true
@@ -1902,6 +2102,8 @@
 
 stringsHandler = HandlerPrototype:new()
 
+stringsHandler.IHandleAnywhere = false
+
 function stringsHandler:canDoit(position)
   for _, pattern in ipairs(self.patterns) do
       local success, data = self:canDoitRecursive(pattern, position, 0, 0)
@@ -1922,10 +2124,15 @@
   self.message = self:newMessage()
   self.message.severity = self.severity
 
-  -- ignore leading spaces in the first line (in the others,
-  -- they may be indentation or somehow relevant)
-  local _, last = string.find(Lines.current, '^%s+')
-  if last ~= nil then Lines:handledChars(last) end
+  -- If we know the pattern must match at the beginning of a line,
+  -- the pattern may or may not include spaces at the start. If that
+  -- is not the case, however, there may be added spaces before the
+  -- message, so it is better to remove them from the first line (in
+  -- the others, they may be indentation or somehow relevant)
+  if self.IHandleAnywhere then
+      local _, last = string.find(Lines.current, '^%s+')
+      if last ~= nil then Lines:handledChars(last) end
+  end
 
   self.captures = {} -- just in case we want to use captures
   self.patternLines = data.pattern -- the table with the pattern lines
@@ -1955,7 +2162,9 @@
   for _, val in ipairs(tmp) do table.insert(self.captures, val) end
 
   if last == nil then
-      io.stderr:write("    texlogsieve: parsing error\n")
+      io.stderr:write("    texlogsieve: parsing error in "
+                       .. "stringsHandler:handleLines()\n")
+
       dispatch(self.message)
       self.doit = self.handleFirstLine
       return true
@@ -2024,9 +2233,9 @@
 
   while true do
       local first, last = string.find(line, patternLine)
-      local tmp = string.find(nextline, patternLine) -- see comment below
 
-      if first ~= nil and tmp == nil then
+      -- see comment below about "nextline, patternLine"
+      if first ~= nil and not string.find(nextline, patternLine) then
           -- Found it!
           if depth > 2 -- 3 lines matched, that is enough
                        or #patternLines == 1 -- no more pattern lines
@@ -2217,8 +2426,28 @@
   '^`inconsolata%-zi4\' v%S-, ' .. datepat
                    .. ' Text macros for Inconsolata %(msharpe%)',
 
-  '^Requested font ".-" at [%d%.]+pt\n %-> ' .. filepat,
-  '^Requested font ".-" scaled %d+\n %-> ' .. filepat,
+  '^Requested font ".-" at [%d%.]+pt\n ?%-> ' .. filepat,
+  '^Requested font ".-" scaled %d+\n ?%-> ' .. filepat,
+  '^Requested font ".-" at [%d%.]+pt',
+  '^Requested font ".-" scaled %d+',
+
+  -- Usually these are warnings, but for font "nil", why bother?
+  '^luaotfload | aux : font no %d+ %(nil%) does not define feature '
+                            .. '.- for script .- with language %S+',
+
+  '^luaotfload | aux : font no %d+ %(nil%) defines no feature for script %S+',
+
+  -- From IEEEtran.cls
+  '^%-%- This is a[n]? %d+ point document%.',
+  '^%-%- Lines per column: %S+ %(%S+%)%.',
+
+  '^%-%- See the "IEEEtran%_HOWTO" manual for usage information%.\n'
+               .. '%-%- http://www%.michaelshell%.org/tex/ieeetran/',
+
+  '^%-%- Using %S+ x %S+ %b() paper%.',
+  '^%-%- Using %S+ output%.',
+  '^%-%- Verifying Times compatible math font%.',
+  '^%-%- %S+ loaded, OK%.',
 }
 
 
@@ -2226,9 +2455,11 @@
 -- Always start these patterns with "^%s*", see lookahead().
 -- Order matters! The first match wins, so the longer ones should come first.
 anywhereDebugStringsHandler = stringsHandler:new()
+anywhereDebugStringsHandler.IHandleAnywhere = true
 anywhereDebugStringsHandler.severity = DEBUG
 anywhereDebugStringsHandler.patterns = {
-  '^%s*L3 programming layer %b<> xparse %b<>',
+  '^%s*L3 programming layer %b<>',
+  '^%s*xparse %b<>',
   '^%s*%{.*pdftex%.map%}',
 
   '^%s*ABD: EverySelectfont initializing macros',
@@ -2244,6 +2475,14 @@
   '^%s*%<' .. filepat .. '%>', -- <blah.jpg>
 
   "^%s*`Fixed Point Package', .- %(C%) Michael Mehlich",
+
+  "^%s*`newtxmath' v%S+, " .. datepat
+          .. " Math macros based originally on txfonts %(msharpe%)",
+
+  "^%s*`newtxtt' v%S+, " .. datepat
+          .. " Typewriter text macros based on txfonts %(msharpe%)",
+
+  '^%s*%* soulpos %- computing points %- it may take a few seconds %*',
 }
 
 
@@ -2267,12 +2506,66 @@
   "^No file .-%.ind%.",
   "^No file .-%.bbl%.",
   "^No file .-%.gls%.",
+
   "^reledmac reminder:%s*\n"
-    .. "%s*The number of the footnotes in this section "
-    .. "has changed since the last run.\n"
-    .. "%s*You will need to run LaTeX two more times "
-    .. "before the footnote placement\n"
-    .. "%s*and line numbering in this section are correct%.",
+        .. "%s*The number of the footnotes in this section "
+        .. "has changed since the last run.\n"
+        .. "%s*You will need to run LaTeX two more times "
+        .. "before the footnote placement\n"
+        .. "%s*and line numbering in this section are correct%.",
+
+  "^ ?LaTeX document class for Lecture Notes in Computer Science",
+
+  "^%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*\n"
+          .. "%* Local config file " .. filepat .. " used\n"
+          .. "%*",
+
+  "^=== Package selnolig, Version %S+, Date " .. datepat .. " ===",
+
+  -- Package snapshot
+  '^Dependency list written on .-%.dep%.',
+
+  -- These come from IEEEtran.cls
+  '^%*%* Times compatible math font not found, forcing%.',
+  '^%-%- Found %S+, loading%.',
+  '^%-%- Using IEEE %S+ Society mode%.',
+
+  '^%*%* Conference Paper %*%*\n'
+      .. 'Before submitting the final camera ready copy, remember to:\n'
+      .. '1%. Manually equalize the lengths of two columns on the last page\n'
+      .. 'of your paper%;\n'
+      .. '2%. Ensure that any PostScript and/or PDF output post%-processing\n'
+      .. 'uses only Type 1 fonts and that every step in the generation\n'
+      .. 'process uses the appropriate paper size%.',
+
+  '^%*%* ATTENTION: Overriding %S+ to %S+ via %S+%.',
+
+  '^%*%* ATTENTION: Overriding inner side margin to %S+ and '
+                      .. 'outer side margin to %S+ via %S+%.',
+
+  '^%*%* ATTENTION: Overriding top text margin to %S+ and '
+                      .. 'bottom text margin to %S+ via %S+%.',
+
+  '^%*%* ATTENTION: \\IEEEPARstart is disabled in draft mode %(line %S+%)%.',
+  '^%*%* ATTENTION: Overriding command lockouts %(line %S+%)%.',
+
+  "^%*%* ATTENTION: Single column mode is not typically used "
+                                 .. "with IEEE publications%.",
+
+  '^%*%* ATTENTION: Technotes are normally 9pt documents%.',
+
+  -- MiKTeX auto-updates
+  '^======================================================================\n'
+   .. 'starting package maintenance%.%.%.\n'
+   .. 'installation directory: ' .. dirpat .. '\n'
+   .. 'package repository: http.+\n'
+   .. 'package repository digest: [%dabcdef]+\n'
+   .. 'going to download %S+ .*bytes\n'
+   .. 'going to install %d+ file%(s%) %(%d package%(s%)%)\n'
+   .. 'downloading http.-%.%.%.\n'
+   .. '%S+, %S+ Mbit/s\n'
+   .. 'extracting files from ' .. filepat .. '%.%.%.\n'
+   .. '======================================================================',
 }
 
 
@@ -2280,6 +2573,7 @@
 -- Always start these patterns with "^%s*", see lookahead().
 -- Order matters! The first match wins, so the longer ones should come first.
 anywhereInfoStringsHandler = stringsHandler:new()
+anywhereInfoStringsHandler.IHandleAnywhere = true
 anywhereInfoStringsHandler.severity = INFO
 anywhereInfoStringsHandler.patterns = {
   -- TODO: there are other "... patterns for blah blah"
@@ -2322,9 +2616,30 @@
   "^Couldn't patch \\%S+",
   "^Invalid UTF%-8 byte or sequence at line %d+ replaced by U%+FFFD%.",
 
-  '^Requested font ".-" at [%d%.]+pt\n'
-                            .. "Unknown feature %b`' in font %b`'%.\n"
+  "^Unknown feature %b`' in font %b`'%.\n"
                             .. ' %-> ' .. filepat,
+
+  -- From IEEEtran.cls
+  "^%*%* WARNING: %S+ mode specifiers after the first in %b`' "
+                                    .. "ignored %(line %S+%)%.",
+
+  "^%*%* WARNING: IEEEeqnarraybox position specifiers after "
+                .. "the first in %b`' ignored %(line %S+%)%.",
+
+  "^%*%* WARNING: IEEEeqnarray predefined inter%-column glue type "
+                .. "specifiers after the first in %b`' ignored %(line %S+%)%.",
+
+  "^%*%* WARNING: \\and is valid only when in conference or peerreviewca\n"
+                .. "modes %(line %S+%)%.",
+
+  '^%*%* WARNING: Ignoring useless \\section in Appendix %(line %S+%)%.',
+
+  '^%*%* WARNING: IEEEPARstart drop letter has zero height%! %(line %S+%)\n'
+                           .. ' Forcing the drop letter font size to 10pt%.',
+
+  '^%*%* WARNING: \\IEEEPARstart is locked out for technotes %(line %S+%)%.',
+  '^%*%* WARNING: %S+ is locked out when in conference mode %(line %S+%)%.',
+  '^%*%* ATTENTION: %S+ is deprecated %(line %S+%)%. Use %S+ instead%.',
 }
 
 
@@ -2332,6 +2647,7 @@
 -- Always start these patterns with "^%s*", see lookahead().
 -- Order matters! The first match wins, so the longer ones should come first.
 anywhereWarningStringsHandler = stringsHandler:new()
+anywhereWarningStringsHandler.IHandleAnywhere = true
 anywhereWarningStringsHandler.severity = WARNING
 anywhereWarningStringsHandler.patterns = {
 }
@@ -2343,11 +2659,25 @@
 beginningOfLineCriticalStringsHandler = stringsHandler:new()
 beginningOfLineCriticalStringsHandler.severity = CRITICAL
 beginningOfLineCriticalStringsHandler.patterns = {
-    "^The control sequence at the end of the top line\n"
-      .. "of your error message was never \\def'ed%. If you have\n"
-      .. "misspelled it %(e%.g%., `\\hobx'%), type `I' and the correct\n"
-      .. "spelling %(e%.g%., `I\\hbox'%)%. Otherwise just continue,\n"
-      .. "and I'll forget about whatever was undefined%.",
+  "^The control sequence at the end of the top line\n"
+    .. "of your error message was never \\def'ed%. If you have\n"
+    .. "misspelled it %(e%.g%., `\\hobx'%), type `I' and the correct\n"
+    .. "spelling %(e%.g%., `I\\hbox'%)%. Otherwise just continue,\n"
+    .. "and I'll forget about whatever was undefined%.",
+
+  "^ ======================================= \n"
+    .. " WARNING WARNING WARNING \n"
+    .. " %-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%- \n"
+    .. " The ligature suppression macros of the \n"
+    .. " selnolig package %*require%* LuaLaTeX%. \n"
+    .. " Because you're NOT running this package \n"
+    .. " under LuaLaTeX, ligature suppression \n"
+    .. " %*can not%* be performed%. \n"
+    .. "=========================================",
+
+  -- From IEEEtran.cls
+  "^%*%* No Times compatible math font package found%. "
+                            .. "newtxmath is required%.",
 }
 
 
@@ -2355,6 +2685,7 @@
 -- Always start these patterns with "^%s*", see lookahead().
 -- Order matters! The first match wins, so the longer ones should come first.
 anywhereCriticalStringsHandler = stringsHandler:new()
+anywhereCriticalStringsHandler.IHandleAnywhere = true
 anywhereCriticalStringsHandler.severity = CRITICAL
 anywhereCriticalStringsHandler.patterns = {
 }
@@ -2536,13 +2867,17 @@
   severity = string.lower(severity)
 
    -- tocbibind uses "Note"
+   -- floatflt uses "Message"
   if severity == 'info'
           or severity == 'notification'
           or severity == 'note'
+          or severity == 'message'
   then
       return INFO
+  elseif severity == 'warning' then
+      return WARNING
   else
-      return WARNING
+      return UNKNOWN
   end
 end
 
@@ -2553,13 +2888,11 @@
       -- not know how to handle the next line, but we still need to
       -- check another possibility: the next line might be a "normal"
       -- continuation line
-      local first = string.find(Lines:get(1), '^' .. self.prefix)
-      if first ~= nil then break end
+      if string.find(Lines:get(1), '^' .. self.prefix) then break end
 
       -- Ok, this is almost certainly a wrapped line, but it does
       -- not hurt to also check this just in case
-      first = string.find(Lines.current, 'on input line %d+%.$')
-      if first ~= nil then break end
+      if string.find(Lines.current, 'on input line %d+%.$') then break end
 
       Lines:unwrapOneLine()
   end
@@ -2568,7 +2901,7 @@
 
 -------------------------------------------------------------------------------
 -- latex23MessageHandler
--- genericLatexVariantHandler
+-- genericLatexVariantIHandler
 -- (from genericLatexHandler)
 --
 -- They differ from the prototype by the set of patterns to search for and by
@@ -2601,13 +2934,13 @@
   self.message.content = Lines.current
 end
 
-genericLatexVariantHandler = genericLatexHandler:new()
+genericLatexVariantIHandler = genericLatexHandler:new()
 
-genericLatexVariantHandler.patterns = {
+genericLatexVariantIHandler.patterns = {
   "^(Package) (%S+) (%S+) on input line (%S+): ",
 }
 
-function genericLatexVariantHandler:unpackData(data)
+function genericLatexVariantIHandler:unpackData(data)
   local last = data[1]
   local what = data[2]
   local name = data[3]
@@ -2624,7 +2957,29 @@
   self.message.content = Lines.current
 end
 
+-- Only ever saw "Library (tcolorbox):"
+genericLatexVariantIIHandler = genericLatexHandler:new()
 
+genericLatexVariantIIHandler.patterns = {
+  "^(Library) (%(%S+%)): ",
+}
+
+function genericLatexVariantIIHandler:unpackData(data)
+  local last = data[1]
+  local what = data[2]
+  local name = data[3]
+  self.message.what = what
+  self.message.name = name
+  self.message.severity = INFO
+
+  self:findPrefix(last, name, what)
+  self.message.prefix = self.prefix
+
+  self:unwrapLines()
+  self.message.content = Lines.current
+end
+
+
 -------------------------------------------------------------------------------
 -- citationHandler
 -- referenceHandler
@@ -2731,8 +3086,7 @@
   self.message.content = Lines.current
 
   if not Lines:empty() then
-      local first = string.find(Lines:get(1), 'with kernel methods')
-      if first ~= nil then
+      if string.find(Lines:get(1), 'with kernel methods') then
           self.message.content = self.message.content .. ' ' .. Lines:get(1)
           Lines:gotoNextLine()
       end
@@ -2837,8 +3191,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
-      local first = string.find(line, '%)')
-      if first ~= nil then return false, {} end
+      if string.find(line, '%)') then return false, {} end
   end
 
   return true, {first = first, filename = filename} -- might be nil
@@ -2858,7 +3211,9 @@
       flushUnrecognizedMessages()
       local last = unwrapUntilStringMatches(data.filename)
       if last == nil then
-          io.stderr:write("    texlogsieve: parsing error\n")
+          io.stderr:write("    texlogsieve: parsing error in "
+                           .. "openParensHandler:doit()\n")
+
       else
           Lines:handledChars(last)
       end
@@ -3038,8 +3393,7 @@
 
   -- See the comment "HACK ALERT" in openParensHandler:canDoit()
   if latexPage == nil and position > 0 then
-      local first = string.find(line, '%]')
-      if first ~= nil then return false, {} end
+      if string.find(line, '%]') then return false, {} end
   end
 
   return true, {first = first, latexPage = latexPage} -- may be nil
@@ -3059,7 +3413,9 @@
       flushUnrecognizedMessages()
       local last = unwrapUntilStringMatches(data.latexPage)
       if last == nil then
-          io.stderr:write("    texlogsieve: parsing error\n")
+          io.stderr:write("    texlogsieve: parsing error in "
+                           .. "openSquareBracketHandler:doit()\n")
+
       else
           Lines:handledChars(last)
       end
@@ -3252,10 +3608,9 @@
 utf8FontMapHandler.doit = utf8FontMapHandler.handleFirstLine
 
 function utf8FontMapHandler:handleSecondLine()
-  local first = string.find(Lines.current,
-                      "^%.%.%. no UTF%-8 mapping file for font encoding")
+  if string.find(Lines.current,
+                  "^%.%.%. no UTF%-8 mapping file for font encoding") then
 
-  if first ~= nil then
       self.message.content = self.message.content .. '\n' .. Lines.current
       Lines:handledChars()
       dispatch(self.message)
@@ -3263,10 +3618,9 @@
       return true
   end
 
-  first = string.find(Lines.current,
-                "^%.%.%. processing UTF%-8 mapping file for font encoding")
+  if string.find(Lines.current,
+            "^%.%.%. processing UTF%-8 mapping file for font encoding") then
 
-  if first ~= nil then
       self.message.content = self.message.content .. '\n' .. Lines.current
       Lines:handledChars()
       self.doit = self.handleOtherLines
@@ -3278,7 +3632,9 @@
   -- The second line was neither "no UTF-8 mapping..." nor
   -- "processing UTF-8 mapping" - this should never happen
   dispatch(self.message)
-  io.stderr:write("    texlogsieve: parsing error\n")
+  io.stderr:write("    texlogsieve: parsing error in "
+                   .. "utf8FontMapHandler:handleSecondLine()\n")
+
   Lines:handledChars()
   self.doit = self.handleFirstLine
   return true
@@ -3289,9 +3645,7 @@
 -- Therefore, we try to find the first "...defining Unicode char"
 -- message for the following 4 lines before giving up.
 function utf8FontMapHandler:handleOtherLines()
-  local first = string.find(Lines.current, "^%s*defining Unicode char")
-
-  if first ~= nil then
+  if string.find(Lines.current, "^%s*defining Unicode char") then
       flushUnrecognizedMessages()
       self.foundOtherLines = true
       self.message.content = self.message.content .. '\n' .. Lines.current
@@ -3303,14 +3657,14 @@
   -- this line does not match; why? First possibility: there are no
   -- "...defining Unicode char" lines to be found; instead, there is
   -- another encoding being defined. This obviously should not happen
-  first = string.find(Lines.current,
-                   "^Now handling font encoding (%S+) %.%.%.")
 
-  if first ~=nil then
+  if string.find(Lines.current,
+                   "^Now handling font encoding (%S+) %.%.%.") then
       -- give up and start processing the new message (should not happen)
       dispatch(self.message)
       flushUnrecognizedMessages()
-      io.stderr:write("    texlogsieve: parsing error\n")
+      io.stderr:write("    texlogsieve: parsing error in "
+                       .. "utf8FontMapHandler:handleOtherLines()\n")
 
       self.numTries = 0
       self.foundOtherLines = false
@@ -3363,38 +3717,42 @@
 
 Message.severity = UNKNOWN
 
-function Message:toString(noSilence)
-    -- noSilence may be useful when generating summaries
-    if noSilence then
-        local formatted = self:realToString()
-        if trim(formatted) == "" then return "" else return formatted end
-    end
+function Message:toString(bypassMostFilters)
 
-    -- If we've already been here, just output the previous result
-    if self.formatted ~= nil then return self.formatted end
+    -- We do not want to exclude these in the summaries
+    if not bypassMostFilters then
 
-    if self.mute then self.formatted = "" return "" end
+        -- If we've already been here, just output the previous result
+        if self.formatted ~= nil then return self.formatted end
 
-    if self.severity < MINLEVEL then self.formatted = "" return "" end
+        if self.mute then self.formatted = "" return "" end
 
-    if self:ignoreAsRedundant() then self.formatted = "" return "" end
+        if self.severity < MINLEVEL then self.formatted = "" return "" end
 
-    self.formatted = self:realToString()
-    if trim(self.formatted) == "" then self.formatted = "" return "" end
+        if self:ignoreAsRedundant() then self.formatted = "" return "" end
 
-    for _, val in ipairs(SILENCE_STRINGS) do
-        local first = string.find(self.formatted, val)
-        local other = string.find(self.content, val)
-        if first ~= nil or other ~= nil then self.formatted = "" return "" end
+        if self.name ~= nil then
+            for _, val in ipairs(SILENCE_PKGS) do
+                if self.name == val then self.formatted = "" return "" end
+            end
+        end
+
     end
 
-    if self.name ~= nil then
-        for _, val in ipairs(SILENCE_PKGS) do
-            if self.name == val then self.formatted = "" return "" end
+    local formatted = self:realToString()
+    if trim(formatted) == "" then self.formatted = "" return "" end
+
+    for _, val in ipairs(SILENCE_STRINGS) do
+        if string.find(formatted, val) or string.find(self.content, val)
+        then
+            self.formatted = ""
+            return ""
         end
     end
 
-    return self.formatted
+    if not bypassMostFilters then self.formatted = formatted end
+
+    return formatted
 end
 
 function Message:realToString()
@@ -3434,6 +3792,11 @@
   },
   {
     WARNING,
+    'longtable',
+    'Column widths have changed\nin table'
+  },
+  {
+    WARNING,
     'rerunfilecheck',
     "File %b`' has changed%."
   },
@@ -3442,6 +3805,16 @@
     'biblatex',
     'Please rerun LaTeX%.'
   },
+  {
+    WARNING,
+    'atenddvi',
+    'Rerun LaTeX, last page not yet found%.'
+  },
+  {
+    WARNING,
+    'hyperref',
+    'Rerun to get /PageLabels entry%.'
+  },
 }
 
 function Message:checkMatch(patlist)
@@ -3462,8 +3835,10 @@
           if name == nil then name = self.what end
           if name ~= pkgname then break end
 
-          local first = string.find(self:realToString(), text)
-          if first ~= nil then return true end
+          if string.find(self:realToString(), text)
+                        or string.find(self.content, text)
+          then return true end
+
       until true
   end
 
@@ -3473,9 +3848,11 @@
 function Message:ignoreAsRedundant()
   if BE_REDUNDANT then return false end
 
-  -- this may also be set by checkRerun()
   if self.redundant == nil then
-      if self:checkMatch(self.redundantMessages) then
+      if self:checkMatch(self.redundantMessages)
+              or self:checkMatch(self.rerunMessages) -- these are redundant too
+
+      then
           self.redundant = true
       else
           self.redundant = false
@@ -3492,8 +3869,7 @@
   -- In the rare event that one of these is sent out as
   -- an unrecognizedMessage with no other text, allow for
   -- repetitions
-  local first = string.find(trim(formatted), '^[%(%)%[%]]$')
-  if first ~= nil then return end
+  if string.find(trim(formatted), '^[%(%)%[%]]$') then return end
 
   repetitionsSummary:add(self)
 end
@@ -3506,8 +3882,7 @@
   local _, last = string.find(filename, '^.*/') -- get just the basename
   if last ~= nil then filename = string.sub(filename, last +1) end
   for _, pattern in ipairs(SEMISILENCE_FILES) do
-      local first = string.find(filename, pattern)
-      if first ~= nil then return true end
+      if string.find(filename, pattern) then return true end
   end
 
   -- This is O(n*m) and gets executed for every message,
@@ -3519,8 +3894,7 @@
       if last ~= nil then basename = string.sub(basename, last +1) end
 
       for _, pattern in ipairs(SILENCE_FILES_RECURSIVE) do
-          _, last = string.find(basename, pattern)
-          if last ~= nil then return true end
+          if string.find(basename, pattern) then return true end
       end
   end
 
@@ -3551,6 +3925,8 @@
               .. ', LaTeX page counter ['
               .. latexPages[self.physicalPage] .. ']'
 
+  if COLOR then msg = green(msg) end
+
   return msg
 end
 
@@ -3664,6 +4040,10 @@
 -- specific undefined citation).
 SummaryPrototype = {}
 
+-- Should filtered out messages be included in the summary? For repetitions
+-- this should be false, for most others true is probably better.
+SummaryPrototype.bypassMostFilters = false
+
 function SummaryPrototype:new()
   local o = {}
   setmetatable(o, self)
@@ -3674,10 +4054,10 @@
 end
 
 function SummaryPrototype:add(msg)
-  -- group messages by message content
-  local formatted = msg:toString()
+  local formatted = msg:toString(self.bypassMostFilters)
   if trim(formatted) == "" then return end
 
+  -- group messages by message content
   if self.messages[formatted] == nil then
       self.messages[formatted] = {}
   end
@@ -3705,6 +4085,7 @@
   if text == "" then return "" end -- happens with repetitionsSummary
 
   if self.header ~= "" then
+      if COLOR then self.header = green(self.header) end
       if self:showDetails() then
           self.header = self.header .. '\n'
       else
@@ -3784,6 +4165,7 @@
 
 
 repetitionsSummary = SummaryPrototype:new()
+repetitionsSummary.bypassMostFilters = false
 repetitionsSummary.header = 'Repeated messages:'
 
 function repetitionsSummary:toString()
@@ -3820,6 +4202,7 @@
 
 
 missingCharSummary = SummaryPrototype:new()
+missingCharSummary.bypassMostFilters = true
 missingCharSummary.header = 'Missing characters:'
 
 function missingCharSummary:processSingleMessageList(messages)
@@ -3836,6 +4219,7 @@
 
 
 citationsSummary = SummaryPrototype:new()
+citationsSummary.bypassMostFilters = true
 citationsSummary.header = 'Undefined citations:'
 
 function citationsSummary:showDetails()
@@ -3843,6 +4227,10 @@
 end
 
 function citationsSummary:add(msg)
+  -- Filter out stuff explicitly excluded by the user
+  local tmp = msg:toString(self.bypassMostFilters)
+  if trim(tmp) == "" then return end
+
   -- group messages by problem key. We do not use msg:toString()
   -- here because some messages may include the page number, making
   -- messages that are otherwise the same appear to be different.
@@ -3902,8 +4290,13 @@
 -- under/overfull boxes in pages X, Y, and Z. So we store messages
 -- directly in self.messages instead of using sublists.
 underOverSummary = SummaryPrototype:new()
+underOverSummary.bypassMostFilters = true
 
 function underOverSummary:add(msg)
+  -- Filter out stuff explicitly excluded by the user
+  local tmp = msg:toString(self.bypassMostFilters)
+  if trim(tmp) == "" then return end
+
   table.insert(self.messages, msg)
 end
 
@@ -3912,18 +4305,28 @@
 
   local pages, files = self:pageAndFileList(self.messages)
 
+  local output
+
   if DETAILED_UNDEROVER_SUMMARY then
-      local output = "Under/overfull boxes:"
+      output = "Under/overfull boxes:"
+      if COLOR then output = green(output) end
       for _, msg in ipairs(self.messages) do
-          output = output .. '\npage ' .. msg.physicalPage
-                   .. ' (file ' .. msg.filename .. '):\n'
-          output = output .. msg:toString(true)
+          local pageinfo = 'page ' .. msg.physicalPage
+          local fileinfo = 'file ' .. msg.filename
+          if COLOR then pageinfo = bright(pageinfo) end
+
+          output = output .. '\n' .. pageinfo .. ' (' .. fileinfo .. '):\n'
+                          .. msg:toString(true)
       end
-      return output
   else
-      return "Under/overfull boxes in pages "
-             .. pages .. " (files " .. files .. ")"
+      output = "Under/overfull boxes"
+      if COLOR then output = green(output) end
+      output = output .. " in pages "
+                      .. pages .. " (files " .. files .. ")"
+
   end
+
+  return output
 end
 
 
@@ -3966,9 +4369,8 @@
 function trimRight(s) return (string.gsub(s, '^(.-)%s*$', '%1')) end
 
 function stringToPattern(s)
-  local first, _ = string.find(s, '^////')
   local pat
-  if first ~= nil then
+  if string.find(s, '^////') then
       pat = string.sub(s, 5)
   else
       pat = protect_metachars(s)
@@ -4027,7 +4429,27 @@
     return tmp
 end
 
+function green(s)
+  return GREEN .. s .. RESET_COLOR
+end
 
+function bgreen(s)
+  return BGREEN .. s .. RESET_COLOR
+end
+
+function red(s)
+  return RED .. s .. RESET_COLOR
+end
+
+function yellow(s)
+  return YELLOW .. s .. RESET_COLOR
+end
+
+function bright(s)
+  return BRIGHT .. s .. RESET_COLOR
+end
+
+
 --[[ ##### STACK ##### ]]--
 
 Stack = {}
@@ -4540,7 +4962,10 @@
       if Lines:seemsWrapped() then
           Lines:unwrapOneLine()
       else
-          io.stderr:write("    texlogsieve: parsing error\n")
+          io.stderr:write("    texlogsieve: parsing error in "
+                           .. "unwrapUntilPatternMatches()\n")
+
+          break
       end
   end
 
@@ -4682,29 +5107,25 @@
 function guessQuotedFilename(line, position)
   -- luatex puts quotes around filenames with spaces, which makes things easier
   while true do
-      local _, last = string.find(line, '^"')
-      if line ~= "" and last == nil then return false end -- no quotes
 
-      last = string.find(line, '^"[%.]?/') -- relative path or unix-style path
-      if last == nil then
-          last = string.find(line, '^"%a:/') -- windows-style path
-      end
-      -- there are quotes, but what follows is not a filename
-      if string.len(line) >= 4 and last == nil then return true, nil end
+      -- no quotes
+      if line ~= "" and not string.find(line, '^"') then return false end
 
-      _, last = string.find(line, '^"%a?[:]?[%.]?/[^"%(%)%[%]]+"')
+      local _, last = string.find(line, '^"' .. filepat .. '"')
       if last ~= nil then
-          local filename = string.sub(line, 2, last -1) -- remove quotes
-          if kpse.find_file(filename, 'other text files') == nil then
+          local filename = string.sub(line, 1, last)
+          if checkIfFileExists(filename) then
+              return true, filename
+          else
               return true, nil -- there are quotes, but no filename
-          else
-              return true, '"' .. filename .. '"'
           end
       end
 
       -- no closing quote or the line is too short; can we unwrap this line?
       if not Lines:seemsWrapped(position) then
-          io.stderr:write("    texlogsieve: parsing error\n")
+          io.stderr:write("    texlogsieve: parsing error in "
+                           .. "guessQuotedFilename()\n")
+
           return true, nil
       end
 
@@ -4724,24 +5145,27 @@
 
   local filename
   while true do
-      local first = string.find(line, '^[%.]?/') -- relative or unix-style path
-      if first == nil then
-          first = string.find(line, '^%a:/') -- windows-style path
-      end
-      -- this does not look like a filename
-      if string.len(line) >= 3 and first == nil then return nil end
 
       local longest = string.len(line)
+      local notWrapped = false
 
       -- if there is a ")", "(", "[", or "]" char, stop before that
-      first = string.find(line, "[%)%(%[%]]")
-      if first ~= nil then longest = first -1 end
+      local first = string.find(line, "[%)%(%[%]]")
+      if first ~= nil then
+          notWrapped = true -- this line is obviously not wrapped
+          longest = first -1
+      end
 
       -- From longest to shortest, to avoid problems if there is a
       -- substring in the filename that matches some other filename.
       for i = longest, alreadyCheckedIdx +1, -1 do
           local candidate = string.sub(line, 1, i)
-          if kpse.find_file(candidate, 'other text files') ~= nil then
+
+          -- We are gradually removing chars from the end of the
+          -- string; if we reach a slash, only the directories remain
+          if string.sub(candidate, #candidate) == "/" then break end
+
+          if checkIfFileExists(candidate) then
               filename = candidate
               break
           end
@@ -4753,9 +5177,9 @@
       -- 2. If we did find the filename, we might find a different
       --    (longer) filename, in which case we should stick with
       --    it (yes, I have seen it happen!)
-      if not Lines:seemsWrapped(position) then return filename end
-      first = string.find(line, "[%)%(%[%]]")
-      if first ~= nil then return filename end
+      if notWrapped or not Lines:seemsWrapped(position) then
+          return filename
+      end
 
       alreadyCheckedIdx = longest
       line = line .. Lines:get(position +1)
@@ -4763,6 +5187,107 @@
   end
 end
 
+function readFls(logfilename)
+  local flsfilename = string.gsub(logfilename, '%.log$', '.fls')
+
+  -- 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
+
+  -- It is ok for this to fail, so no "assert"
+  local flsfile = io.open(flsfilename, 'r')
+
+  if flsfile ~= nil and timediff <= 5 then
+      USE_FLS_FILE = true
+
+      filelist = {}
+      while true do
+          local line = flsfile:read("*line")
+          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
+
+          -- I don't think this ever happens
+          if string.find(line, '^".*"$') then line = string.sub(line, 2, -2) 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
+
+          line = string.gsub(line, '\\', '/')
+
+          _, 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
+end
+
+function checkIfFileExistsWithFls(filename)
+  if string.find(filename, '^%./') then filename = string.sub(filename, 3) end
+
+  for tmp, _ in pairs(filelist) do -- not ipairs!
+      if tmp == filename then return true end
+  end
+
+  return false
+end
+
+function checkIfFileExistsWithKpse(filename)
+  -- Is this something like "blah.ext"? If so, make it "./blah.ext",
+  -- otherwise kpse.find_file may find some file in the TeX path that
+  -- has nothing to do with us.
+  local onlyName = true
+
+  -- "C:/"
+  if string.find(filename, '^%a%:/') then onlyName = false end
+
+  -- "./" or just "/"
+  if string.find(filename, '^[%.]?/') then onlyName = false end
+
+  if onlyName then filename = './' .. filename end
+
+  if kpse.find_file(filename, 'other text files') ~= nil then
+      return true
+  else
+      return false
+  end
+end
+
+function checkIfFileExists(filename)
+  -- If there are quotes, remove them
+  if string.find(filename, '^".*"$') then
+      filename = string.sub(filename, 2, -2)
+  end
+
+  -- MiKTeX for windows does not necessarily use the same type of
+  -- slashes in paths inside the log file and the .fls file. I also
+  -- do not know if kpse.find_file works ok with backslashes. Let's
+  -- just turn everything to forward slashes and be done with it.
+  filename = string.gsub(filename, '\\', '/')
+
+  -- If we are reading a logfile (not stdin) and there is a
+  -- corresponding .fls file, use it. Maybe this is slightly more
+  -- reliable, but the real advantage is that we can process a
+  -- logfile even if we are not on the same environment that
+  -- created it, which is nice for testing, reporting bugs, etc.
+  if USE_FLS_FILE then
+      return checkIfFileExistsWithFls(filename)
+  else
+      return checkIfFileExistsWithKpse(filename)
+  end
+end
+
+
 --[[ ###################################################################### ]]--
 --[[ ######################## BEGINNING OF SCRIPT ######################### ]]--
 --[[ ###################################################################### ]]--



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