LUA   34
musixtex lua
Guest on 12th February 2023 03:23:00 AM


  1. #!/usr/bin/env texlua  
  2.  
  3. VERSION = "0.23a"
  4.  
  5. --[[
  6.      musixtex.lua: processes MusiXTeX files using xml2pmx and/or prepmx and/or pmxab
  7.      and/or autosp as pre-processors (and deletes intermediate files)
  8.  
  9.      (c) Copyright 2011-2020 Bob Tennent rdt@cs.queensu.ca
  10.                              and Dirk Laurie dirk.laurie@gmail.com
  11.  
  12.      This program is free software; you can redistribute it and/or modify it
  13.      under the terms of the GNU General Public License as published by the
  14.      Free Software Foundation; either version 2 of the License, or (at your
  15.      option) any later version.
  16.  
  17.      This program is distributed in the hope that it will be useful,
  18.      but WITHOUT ANY WARRANTY; without even the implied warranty of
  19.      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
  20.      Public License for more details.
  21.  
  22.      You should have received a copy of the GNU General Public License along
  23.      with this program; if not, write to the Free Software Foundation, Inc.,
  24.      51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  25.  
  26. --]]
  27.  
  28. --[[
  29.  
  30.   ChangeLog:
  31.  
  32.      version 0.23a 2020-08-14  RDT
  33.        pmxprep files now deleted by xml2pmx
  34.  
  35.      version 0.23  2020-05-21 RDT
  36.        added support for xml2pmx pre-preprocessing
  37.  
  38.      version 0.22  2020-03-20 RDT
  39.        add -X option
  40.        add -version, --version, -help, --help options
  41.  
  42.      version 0.21  2018-07-27  RDT
  43.        add -P option.
  44.  
  45.      version 0.20  2018-06-11  RDT
  46.        remove .mx1 file before tex processing
  47.  
  48.      version 0.19   2017-12-10 RDT
  49.        Allow non-standard extensions.
  50.        Add -M and -A options.
  51.  
  52.      version 0.18   2017-06-13 RDT
  53.        Allow autosp to generate .ltx files
  54.  
  55.      version 0.17a   2017-01-08 RDT
  56.        Added -D option.
  57.        Avoid writing or concatenating a nil value.
  58.  
  59.      version 0.16e  2016-03-02 DL
  60.        missing version information (caused by batchmode in 0.16c) fixed
  61.  
  62.      version 0.16d  2016-03-02 RDT
  63.        filename argument in autosp failure message fixed
  64.  
  65.      version 0.16c  2016-02-24 RDT
  66.        -interaction batchmode for -q
  67.        report_error reports only the first error
  68.  
  69.      version 0.16b  2016-02-20 DL
  70.        Improved help message as suggested by Bob Tennent.
  71.  
  72.      version 0.16a  2015-12-30 DL
  73.        Corrects bug in -g option reported by Christian Mondrup.
  74.  
  75.      version 0.16  2015-12-24 DL
  76.        Versions read from tempfile and written to musixtex.log.
  77.  
  78.      version 0.15  2015-12-03 DL
  79.        Option -q added, which redirects most screen output into
  80.        a temporary file. If an error occurs at the TeX stage, processing
  81.        halts immediately and the tail of the log file is sent to stderr.
  82.  
  83.      version 0.14  2015-12-01 DL
  84.        Writes `musixtex.log` and deletes other logfiles (except with -i)
  85.        Hierarchy of extensions emphasized in usage() and consistently
  86.          used elsewhere.
  87.        Unknown option-like arguments reported and ignored, not treated
  88.          as filenames
  89.        Warnings and error messages generated by musixtex.lua start with
  90.          respctively `!` and `!!`.
  91.  
  92.      version 0.13  2015-11-30 RDT
  93.       Process .mtx and .pmx files (suggested by Dirk Laurie)
  94.       exit_code is now an error count
  95.       Use pmxab exit code to distinguish between errors and warnings
  96.  
  97.      version 0.12 2015-11-28 RDT
  98.       Process .ltx files, with -l implied
  99.  
  100.      version 0.11 2015-07-16 RDT
  101.       Automatic autosp preprocessing.
  102.  
  103.      version 0.10 2015-04-23 RDT
  104.       Add -a option to preprocess using autosp
  105.  
  106.      version 0.9 2015-02-13 RDT
  107.       Add an additional latex pass to resolve cross-references.
  108.       Add -e0 option to dvips as given in musixdoc.tex
  109.       Add -x option to call makeindex
  110.  
  111.      version 0.8 2014-05-18 RDT
  112.       Add -g option
  113.  
  114.      version 0.7  2013-12-11 RDT
  115.       Add -F fmt option
  116.  
  117.      version 0.6  2012-09-14 RDT
  118.       Add -1 (one-pass [pdf][la]tex processing) option.
  119.  
  120.      version 0.5  2011-11-28 RDT
  121.       Add -i (retain intermediate files) option.
  122.  
  123.      version 0.4  2011-04-30 RDT
  124.        Allow multiple filenames (and options).
  125.        Add -f (default) and -l (latex) options.
  126.  
  127.      version 0.3  2011-04-25 RDT
  128.        Add -d (dvipdfm)  and -s (stop at dvi) options.
  129.  
  130.      version 0.2  2011-04-21 RDT
  131.        Allow basename.tex as filename.
  132.        Add -p option for pdfetex processing.
  133.        Add standard -v -h options.
  134. --]]
  135. local orig_print = print
  136.  
  137. function usage()
  138.   orig_print
  139. [[
  140. Usage:  [texlua] musixtex.lua { option | basename[ .xml | .mtx | .pmx | .aspc | .tex | .ltx] } ...
  141.         When no extension is given, extensions are tried in the above order
  142.         until a source file is found. Preprocessing goes xml-pmx-tex or mtx-pmx-tex or
  143.         aspc-tex, with the entry point determined by the extension.
  144.         The normal route after preprocessing goes tex-dvi-ps-pdf, but shorter
  145.         routes are also available, see the options. The default 3-pass processing route
  146.         for .tex files is etex-musixflx-etex.
  147. Options: -v, --version  version
  148.          -h, --help   help
  149.          -l  latex source
  150.          -p  direct tex-pdf (pdftex etc)
  151.          -F fmt  use fmt as the TeX processor
  152.          -d  tex-dvi-pdf (using dvipdfm if -D not used)
  153.          -D dvixx  use dvixx as the dvi processor
  154.          -P ps2pdfxx  use ps2pdfxx as the Postscript processor
  155.          -c  preprocess pmx file using pmxchords
  156.          -m  stop at pmx
  157.          -M prepmxx  use prepmxx as the mtx preprocessor
  158.          -A autospx  use autospx as the aspc preprocessor
  159.          -X pmxabx  use pmxabx as the pmx preprocessor
  160.          -L xmlx use xmlx as the xml preprocessor
  161.          -t  stop at tex/mid
  162.          -s  stop at dvi
  163.          -g  stop at ps
  164.          -i  retain intermediate and log files
  165.          -q  quiet mode (redirect screen logs, write summary on musixtex.log)
  166.          -1  one-pass [pdf][la]tex processing
  167.          -x  run makeindex
  168.          -f  restore default processing
  169. Four TeX engines are available via the -l and -p options.
  170.     etex      default
  171.     latex     -l
  172.     pdfetex   -p
  173.     pdflatex  -l -p
  174. If the -F option is used, options -l and -p need to be set if the engine
  175. name does not contain "latex" and "pdf" respectively. For example, the
  176. above four engines can be replaced by:
  177.   -F "luatex --output-format=dvi"
  178.   -F "lualatex --output-format=dvi"
  179.   -F "luatex" -p
  180.   -F "lualatex" -p
  181. ]]
  182. end
  183.  
  184. function whoami ()
  185.   print("This is musixtex.lua version ".. VERSION .. ".")
  186. end
  187.  
  188. function exists (filename, nolog)
  189.   local f = io.open(filename, "r")
  190.   if f then
  191.     f:close()
  192.     if not nolog then musixlog:write("Processing " .. filename,'\n' ) end
  193.     return true
  194.   else
  195.     return false
  196.   end
  197. end
  198.  
  199. --   System commands for the various programs are mostly
  200. --   set to nil if the step is to be omitted, which can be
  201. --   tested by a simple "if" statement.
  202. -- Exceptions:
  203. --    'tex' is the command for processing a TeX file, but it is important
  204. --       to know whether the user has explicitly specified an option that
  205. --       affects this choice, in which case the automatic selection of the
  206. --       command is restricted or disabled.
  207. --         force_engine  "use the command provided"
  208. --         override   records  when 'l' and/or 'p' options are in effect
  209. --    'dvi' is the command for processing a DVI file, but there are two
  210. --       reasons for not needing it and it is important for the automatic
  211. --       selection of a TeX engine to know which is in effect. This is
  212. --       possible by exploiting the the fact that Lua has two false values.
  213. --         dvi == nil    "do not produce a DVI file" (but maybe PDF)
  214. --         dvi == false  "do not process the DVI file" (but stop after TeX)
  215. local dvips = "dvips -e0"
  216. function defaults()
  217.   xml2pmx = "xml2pmx"
  218.   prepmx = "prepmx"
  219.   pmx = "pmxab"
  220.   autosp = "autosp"
  221.   tex = "etex"
  222.   musixflx = "musixflx"
  223.   dvi = dvips
  224.   ps2pdf = "ps2pdf"
  225.   cleanup = true  -- clean up intermediate and log files
  226.   index = false
  227.   latex = false
  228.   force_engine = false -- indicates whether -F is specified
  229.   override = ""  
  230.   passes = 2
  231.   quiet = ""     -- set by "-q"
  232. end  
  233.  
  234. ------------------------------------------------------------------------
  235.  
  236. if #arg == 0 then
  237.   whoami()
  238.   usage()
  239.   os.exit(0)
  240. end
  241.  
  242. -- Logging on `musixtex.log` everything that is printed, all os calls,
  243. --   and warnings from other log files.
  244.  
  245. musixlog = io.open("musixtex.log","w")
  246. if not musixlog then
  247.    print"!! Can't open files for writing in current directory, aborting"
  248.    os.exit(1)
  249. end
  250.  
  251. ------------------------------------------------------------------------
  252.  
  253. print = function(...)
  254.    orig_print(...)
  255.    musixlog:write(...,"\n") -- only the first argument gets written!
  256. end
  257.  
  258. local orig_remove = os.remove
  259. remove = function(filename)  
  260.   if exists(filename,"nolog") then
  261.     musixlog:write("  removing ",filename,"\n")
  262.     return orig_remove(filename)
  263.   end
  264. end
  265.  
  266. local orig_execute = os.execute
  267. execute = function(command)
  268.   musixlog:write("  ",command,"\n")
  269.   return orig_execute(command .. quiet)
  270. end
  271.  
  272. function report_warnings(filename,pattern)
  273.   local log = io.open(filename)
  274.   if not log then
  275.     print("! No file "..filename)
  276.     return
  277.   end
  278.   for line in log:lines() do
  279.     if line:match(pattern) then
  280.       musixlog:write("!  "..line,"\n")
  281.     end
  282.   end
  283. end
  284.  
  285. function report_error(filename)
  286.   local log = io.open(filename)
  287.   if not log then
  288.     print("! No file "..filename)
  289.     return
  290.   end
  291.   local trigger = false
  292.   for line in log:lines() do
  293.     if trigger and line:match"^!" then
  294.       -- report just the first error  
  295.       break
  296.     end
  297.     trigger = trigger or line:match"^!"
  298.     if trigger then
  299.       io.stderr:write("!  "..line,"\n")
  300.     end
  301.   end
  302. end
  303.  
  304. function process_option(this_arg)
  305.   if this_arg == "-v" or this_arg == "-version" or this_arg == "--version" then
  306.     os.exit(0)
  307.   elseif this_arg == "-h" or this_arg == "-help" or this_arg == "--help" then
  308.     usage()
  309.     os.exit(0)
  310.   elseif this_arg == "-l" then
  311.     latex = true
  312.     override = override .. 'l'
  313.   elseif this_arg == "-p" then
  314.     dvi = nil
  315.     override = override .. 'p'
  316.   elseif this_arg == "-d" then
  317.     dvi = "dvipdfm"; ps2pdf = nil
  318.   elseif this_arg == "-D" then
  319.     narg = narg+1
  320.     dvi = arg[narg]
  321.   elseif this_arg == "-c" then
  322.     pmx = "pmxchords"
  323.   elseif this_arg == "-F" then
  324.     narg = narg+1
  325.     tex = arg[narg]
  326.     force_engine = true
  327.   elseif this_arg == "-s" then
  328.     dvi = false; ps2pdf = nil; protect.dvi = true
  329.   elseif this_arg == "-g" then
  330.     dvi = dvips; ps2pdf = nil; protect.ps = true
  331.   elseif this_arg == "-i" then
  332.     cleanup = false
  333.   elseif this_arg == "-x" then
  334.     index = true
  335.   elseif this_arg == "-1" then
  336.     passes = 1
  337.   elseif this_arg == "-f" then
  338.     defaults()
  339.   elseif this_arg == "-t" then
  340.     tex, dvi, ps2pdf = nil,nil,nil
  341.     protect.tex = true
  342.   elseif this_arg == "-m" then
  343.     pmx, tex, dvi, ps2pdf = nil,nil,nil,nil
  344.     protect.pmx = true
  345.   elseif this_arg == "-M" then
  346.     narg = narg+1
  347.     prepmx = arg[narg]
  348.   elseif this_arg == "-A" then
  349.     narg = narg+1
  350.     autosp = arg[narg]
  351.   elseif this_arg == "-L" then
  352.     narg = narg+1
  353.     xml2pmx = arg[narg]
  354.   elseif this_arg == "-q" then
  355.     if not tempname then
  356.       tempname = tempname or os.tmpname()
  357.       print("Redirecting screen output to "..tempname)
  358.     end
  359.     if dvi == dvips then dvi = dvi .. " -q" end
  360.     quiet = " >> " .. tempname
  361.   elseif this_arg == "-P" then
  362.     narg = narg+1
  363.     ps2pdf = arg[narg]
  364.   elseif this_arg == "-X" then
  365.     narg = narg+1
  366.     pmx = arg[narg]
  367.   else
  368.     print("! Unknown option "..this_arg.." ignored")
  369.   end
  370. end
  371.  
  372. function find_file(this_arg)
  373.   basename, extension = this_arg:match"(.*)%.(.*)"  
  374.   extensions = {["xml"] = true, ["mtx"] = true, ["pmx"] = true, ["aspc"] = true, ["tex"] = true, ["ltx"] = true}
  375.   if extensions[extension] then
  376.     return basename, extension
  377.   end
  378.   basename, extension  = this_arg, null
  379.   for ext in ("xml,mtx,pmx,aspc,tex,ltx"):gmatch"[^,]+" do
  380.     if exists (basename .. "." .. ext) then
  381.       extension = ext
  382.       break
  383.     end
  384.   end
  385.   if extension == null then
  386.     print("!! No file " .. basename .. ".[xml|mtx|pmx|aspc|tex|ltx]")
  387.     exit_code = exit_code+1
  388.     return
  389.   end
  390.   return basename, extension
  391. end
  392.  
  393. function preprocess(basename,extension)
  394.   if not (basename and extension) then return end
  395.   if extension == "xml" then
  396.     if execute(xml2pmx .. " " .. basename .. ".xml" .. " " .. basename .. ".pmx" ) == 0 then
  397.       extension = "pmx"
  398.     else
  399.       print ("!! xml2pmx preprocessing of " .. basename .. ".xml fails.")
  400.       return
  401.     end
  402.   elseif extension == "mtx" then
  403.     if execute(prepmx .. " " .. basename ) == 0 then
  404.       extension = "pmx"
  405.     else
  406.       print ("!! prepmx preprocessing of " .. basename .. ".mtx fails.")
  407.       return
  408.     end
  409.   end
  410.   if extension == "pmx" then
  411.     local OK = true
  412.     if pmx then
  413.       local code = execute(pmx .. " " .. basename)
  414.       local pmxaerr = io.open("pmxaerr.dat", "r")
  415.       if (not pmxaerr) then
  416.         OK = false
  417.         print("!! No pmx log file.")
  418.       else
  419.         extension = "tex"
  420.         local linebuf = pmxaerr:read()
  421.         local err = tonumber(linebuf)
  422.         if (code == 0) then
  423.           if err ~=0 then
  424.             print ("!  pmx produced a warning on line "..err..
  425.                 " of "..basename..".pmx")
  426.           end
  427.         else
  428.           OK = false
  429.           print ("!! pmx processing of " .. basename .. ".pmx fails.")
  430.         end
  431.         pmxaerr:close()
  432.       end
  433.     end
  434.     if not OK then
  435.       exit_code = exit_code+1
  436.       return
  437.     end
  438.   end
  439.   if extension == "aspc" then
  440.     if execute (autosp .. " " .. basename .. ".aspc" ) == 0 then
  441.       if exists ( basename .. ".ltx")
  442.         then extension = "ltx"
  443.         else extension = "tex"
  444.       end
  445.     else
  446.       print ("!! autosp preprocessing of " .. basename .. ".aspc fails.")
  447.       exit_code = exit_code+1
  448.       return
  449.     end      
  450.   end
  451.   return extension
  452. end
  453.  
  454. function tex_process(tex,basename,extension)
  455.   if not (extension == "tex" or extension == "ltx") or not tex then return end
  456.   remove(basename .. ".mx1")
  457.   remove(basename .. ".mx2")
  458.   local filename = basename .. "." ..extension
  459. -- .ltx extension re-selects engine only for the current file, and only
  460. -- if default processing is plain TeX
  461.   local latex = latex
  462.   if extension == "ltx" then
  463.     if not force_engine and not latex then
  464.       if dvi then tex = "latex" else tex = "pdflatex" end
  465.     end
  466.     latex = true
  467.   end
  468.   if quiet ~= "" then
  469.     tex = tex .. " -interaction batchmode"
  470.   end
  471.   local OK = (execute(tex .. " " .. filename) == 0)
  472.   if passes ~= 1 then
  473.     OK = OK and (execute(musixflx .. " " .. basename) == 0)
  474.       and (execute(tex .. " " .. filename) == 0)
  475.     if latex and index then
  476.       OK = OK and (execute("makeindex -q " .. basename) == 0)
  477.               and (execute(tex .. " " .. filename) == 0)
  478.       if exists (basename .. ".toc","nolog") then
  479.         -- an extra line for the index will have been added
  480.         OK = OK and (execute(tex .. " " .. filename) == 0)
  481.         -- there is a tiny possibility that the extra line for the index
  482.         -- has changed the pagination. If you feel this should not be
  483.         -- ignored, here are the possibilities:
  484.         --   a. Do an extra TeX pass regardless.
  485.         --   b. Provide a user option -4 to force the extra pass.
  486.         --   c. Compare aux files to find out.
  487.       end
  488.     end
  489.   end
  490.   if (quiet ~= "") and not OK then
  491.     report_error(basename..".log")
  492.   end
  493.   if OK and latex then
  494.     report_warnings(basename..".log","LaTeX.*Warning")
  495.   end
  496.   if dvi then OK = OK and (execute(dvi .. " " .. basename) == 0) end
  497.   if dvi and ps2pdf then
  498.     OK = OK and (execute(ps2pdf .. " " .. basename .. ".ps") == 0)
  499.     if OK then
  500.       print(basename .. ".pdf generated by " .. ps2pdf .. ".")
  501.     end
  502.   end
  503.   if not OK then
  504.     print("!! Processing of " .. filename .. " fails.\n")
  505.     exit_code = exit_code+1
  506.   end
  507. end
  508.  
  509. ---- Report version information on musixtex.log
  510.  
  511. --  File names and message signatures to be looked for in a TeX log file.
  512. logtargets =  {
  513. mtxtex = {"mtx%.tex","mtxTeX"},
  514. mtxlatex = {"mtxlatex%.sty","mtxLaTeX"},
  515. pmxtex = {"pmx%.tex","PMX"},
  516. musixtex = {"musixtex%.tex","MusiXTeX"},
  517. musixltx = {"musixltx%.tex","MusiXLaTeX"},
  518. musixlyr = {"musixlyr%.tex","MusiXLYR"}
  519. }
  520.  
  521. -- Signatures of messages displayed on standard output by programs
  522. capturetargets = {
  523. MTx = "This is (M%-Tx.->)",
  524. PMX = "This is (PMX[^\n]+)",
  525. pdftex = "This is (pdfTeX[^\n]+)",
  526. musixflx = "Musixflx%S+",
  527. autosp = "This is autosp.*$",
  528. index = "autosp,MTx,PMX,musixflx,pdftex"
  529. }
  530.  
  531. function report_texfiles(logname)
  532.   local log = logname and io.open(logname)
  533.   if not log then return end
  534.   local lines =
  535.     {"---   TeX files actually included according to "..logname.."   ---"}
  536.   log = log:read"*a"
  537. -- The following pattern matches filenames input by TeX, even if
  538. -- interrupted by a line break. It may include the first word of
  539. -- an \immediate\write10 message emitted by the file.
  540.   for pos,filename in log:gmatch"%(()(%.?[/]%a[%-/%.%w\n]+)" do
  541.     local hit
  542.     repeat  
  543.       local oldfilename = filename
  544.       filename = oldfilename:match"[^\n]+"   -- up to next line break
  545.       hit = io.open(filename)                -- success if the file exists
  546.       if hit then break end
  547.       filename = oldfilename:gsub("\n","",1) -- remove line break
  548.     until filename==oldfilename
  549.     if hit then
  550.       for target,sig in pairs(logtargets) do
  551.         if filename:match(sig[1]) then
  552.           local i,j = log:find(sig[2].."[^\n]+",pos)          
  553.           if j then lines[#lines+1] = filename.."\n  "..log:sub(i,j) end
  554.         end
  555.       end
  556.     end
  557.   end
  558.   return table.concat(lines,'\n').."\n"
  559. end  
  560.  
  561. function report_versions(tempname)
  562.   if not tempname then return end  -- only available with -q
  563.   local logs = io.open(tempname)
  564.   if not logs then
  565.      musixlog:write ("No version information: could not open "..tempname)
  566.      return
  567.   end
  568.   local versions = {}
  569.   musixlog:write("---   Programs actually executed according to "..tempname.."   ---\n")
  570.   for line in logs:lines() do
  571.     for target in capturetargets.index:gmatch"[^,]+" do
  572.       if not versions[target] then
  573.         local found = line:match(capturetargets[target])
  574.         if found then
  575.           versions[target] = found
  576.           musixlog:write(found,"\n")
  577.         end
  578.       end
  579.     end
  580.   end
  581.   logs:close()
  582.   return
  583. end
  584.  
  585. ------------------------------------------------------------------------
  586.  
  587. whoami()
  588.  
  589. defaults()
  590.  
  591. exit_code = 0
  592. narg = 1
  593. protect = {}  -- extensions not to be deleted, even if cleanup requested
  594.  
  595. repeat
  596.   this_arg = arg[narg]
  597.   if this_arg:match"^%-" then process_option(this_arg)
  598.   else
  599.     basename, extension = find_file(this_arg)  -- nil,nil if not found
  600.     if tex then -- tex output enabled, now select engine
  601.       if tex:match"pdf" then dvi = nil end
  602.       if not dvi then ps2pdf = nil end
  603.       -- .ltx extension will be taken into account later, in `process`
  604.       -- deduce tex/latex from current engine name if -l is not specified
  605.       if not override:match"l" then latex = tex:match"latex" end
  606.       if not force_engine then -- select appropriate default engine
  607.         if latex then
  608.           if dvi==nil then tex = "pdflatex" else tex = "latex" end
  609.         else
  610.           if dvi==nil then tex = "pdfetex" else tex = "etex" end
  611.         end  
  612.       end
  613.     end
  614.     extension = preprocess(basename, extension)
  615.     tex_process(tex,basename,extension)
  616.     if basename and io.open(basename..".log") then -- to be printed later
  617.       versions = report_texfiles(basename..".log")
  618.     end
  619.     if basename and cleanup then
  620.       remove("pmxaerr.dat")
  621.       for ext in ("mx1,mx2,dvi,ps,idx,log,ilg,pml"):gmatch"[^,]+" do
  622.         if not protect[ext] then remove(basename.."."..ext)
  623.         end
  624.       end
  625.     end
  626.     protect = {}
  627.   end
  628.   narg = narg+1
  629. until narg > #arg
  630.  
  631. if versions then musixlog:write(versions) end
  632. report_versions(tempname)
  633. musixlog:close()
  634. os.exit( exit_code )

Raw Paste

Login or Register to edit or fork this paste. It's free.