diff options
Diffstat (limited to 'support/texfindpkg')
-rw-r--r-- | support/texfindpkg/README.md | 66 | ||||
-rw-r--r-- | support/texfindpkg/texfindpkg.1 | 37 | ||||
-rw-r--r-- | support/texfindpkg/texfindpkg.json.gz | bin | 892907 -> 875626 bytes | |||
-rw-r--r-- | support/texfindpkg/texfindpkg.lua | 587 | ||||
-rw-r--r-- | support/texfindpkg/tfpbuild.lua | 312 |
5 files changed, 719 insertions, 283 deletions
diff --git a/support/texfindpkg/README.md b/support/texfindpkg/README.md index d0822b625d..06c2d44bc2 100644 --- a/support/texfindpkg/README.md +++ b/support/texfindpkg/README.md @@ -1,27 +1,32 @@ -# TeXFindPkg tool for installing TeX packages +# TeXFindPkg tool for querying or installing TeX packages ``` -Description: Install TeX packages and their dependencies +Description: Query or Install TeX packages and their dependencies Copyright: 2023 (c) Jianrui Lyu <tolvjr@163.com> Repository: https://github.com/lvjr/texfindpkg License: GNU General Public License v3.0 ``` -## Introduction +## 1\. Introduction -TeXFindPkg makes it easy to install TeX packages and their dependencies by file names, command names or environment names. +TeXFindPkg makes it easy to query or install TeX packages and their dependencies by file names, command names or environment names. +It supports both TeXLive and MiKTeX distributions. At present it focuses mainly on LaTeX packages, +but may extend to ConTeXt packages if anyone would like to contribute. -- To install a package by its file name you can run `texfindpkg install array.sty`; -- To install a package by some command name you can run `texfindpkg install \fakeverb`; -- To install a package by some environment name you can run `texfindpkg install {frame}`. +## 2\. Installation -TeXFindPkg supports both TeXLive and MiKTeX distributions. At present it focuses mainly on LaTeX packages, -but may extend to ConTeXt packages if anyone would like to contribute. +Normally your TeX distribution will copy TeXFindPkg files and create a binary file `texfindpkg` when you install this package. +If a manual installation is needed, you could copy TeXFindPkg files to TEXMF tree as follows: -## Installation +| Package file | Where to install it | +| :------ | :------ | +| texfindpkg.1 | TEXMF/doc/man/man1/texfindpkg.1 | +| README.md | TEXMF/doc/support/texfindpkg/README.md | +| texfindpkg.lua | TEXMF/scripts/texfindpkg/texfindpkg.lua | +| texfindpkg.json.gz | TEXMF/scripts/texfindpkg/texfindpkg.json.gz | +| tfpbuild.lua | TEXMF/source/texfindpkg/tfpbuild.lua | -Your TeX distribution should have created a binary file `texfindpkg` when you install this package. -If not, you could create a symbolic link from `/usr/local/bin/texfindpkg` to `texfindpkg.lua` on Linux or MacOS, +Then you could create a symbolic link from `/usr/local/bin/texfindpkg` to `path/to/texfindpkg.lua` on Linux or MacOS, or create a batch file `texfindpkg.bat` in binary folder of the TeX distribution with these lines on Windows: ``` @@ -29,30 +34,37 @@ or create a batch file `texfindpkg.bat` in binary folder of the TeX distribution texlua path\to\texfindpkg.lua %* ``` -## Usage +## 3\. Usage + +After installing TeXFindPkg, you can run it by executing ``` -texfindpkg <action> [<options>] [<name>] +texfindpkg <action> [<option>] [<name>] ``` -where `<action>` could be `install` or `query`, and `<name>` could be file name, command name or environment name. -For example: +The `<action>` could be `query`, `install`, `help` or `version`. +The leading dashes in any `<action>` will be removed first. -``` -texfindpkg install array.sty -texfindpkg install \fakeverb -texfindpkg install {frame} -texfindpkg query array.sty -texfindpkg query \fakeverb -texfindpkg query {frame} -``` +For `query` action, you pass `-f`, `-c` or `-e` as `<option>` +to indicate the following `<name>` is a file name, command name or environment name. +For example, + +- `texfindpkg query -f array.sty` means querying package with file `array.sty`; +- `texfindpkg query -c fakeverb` means querying package with command `\fakeverb`; +- `texfindpkg query -e frame` means querying package with environment `{frame}`. + +The only difference between `query` and `install` actions is that +with `install` action TeXFindPkg will install missing TeXLive or MiKTeX packages at the end. + +With `help` action, TeXFindPkg prints help message and exit. +With `version` action, TeXFindPkg prints version information and exit. -## Building +## 4\. Building TeXFindPkg uses completion files of TeXstudio editor which are in `completion` folder of TeXstudio [repository](https://github.com/texstudio-org/texstudio). -After putting `completion` folder into current folder, you can run `texlua texfindpkg.lua generate` to generate `texfindpkg.json` and `texfindpkg.json.gz` files. +After putting `completion` folder into current folder, you can execute `texlua tfpbuild.lua generate` to generate `texfindpkg.json` and `texfindpkg.json.gz` files. -## Contributing +## 5\. Contributing Any updates of dependencies, commands or environments for packages should be contributed directly to TeXstudio project. diff --git a/support/texfindpkg/texfindpkg.1 b/support/texfindpkg/texfindpkg.1 index 22d7c8534c..f0ad1dd921 100644 --- a/support/texfindpkg/texfindpkg.1 +++ b/support/texfindpkg/texfindpkg.1 @@ -1,26 +1,29 @@ -.TH texfindpkg 1 "2023-04-05" "2023D" +.TH texfindpkg 1 "2023-05-05" "2023E" .SH NAME -TexFindPkg \- Install TeX packages and their dependencies +TexFindPkg \- Query or install TeX packages and their dependencies .SH SYNOPSIS -texfindpkg <action> [<options>] [<name>] +texfindpkg <action> [<option>] [<name>] .SH DESCRIPTION -TeXFindPkg makes it easy to install TeX packages and their dependencies -by file names, command names or environment names. +TeXFindPkg tool makes it easy to query or install some TeX packages and +their dependencies by file names, command names or environment names. .PP The most commonly used TeXFindPkg actions are: -.IP install -Install some package and its dependencies -.IP query +.IP "query, \-\-query" 6 Query dependencies for some package -.SH OPTIONS -Various options apply -.IP --help +.IP "install, \-\-install" 6 +Install some package and its dependencies +.IP "help, \-\-help" 6 Print a help message and exit -.IP --version +.IP "version, \-\-version" 6 Print version information and exit +.SH OPTIONS +Various options apply +.IP -f 6 +Query or install packages by file name +.IP -c 6 +Query or install packages by command name +.IP -e 6 +Query or install packages by environment name .SH BUGS -.SH AUTHOR -Jianrui Lyu <tolvjr@163.com> -.PP -Please log issues on the GitHub homepage: -https://github.com/lvjr/texfindpkg/issues. +Please log issues on +https://github.com/lvjr/texfindpkg/issues diff --git a/support/texfindpkg/texfindpkg.json.gz b/support/texfindpkg/texfindpkg.json.gz Binary files differindex aec9bb158e..db4d301d9c 100644 --- a/support/texfindpkg/texfindpkg.json.gz +++ b/support/texfindpkg/texfindpkg.json.gz diff --git a/support/texfindpkg/texfindpkg.lua b/support/texfindpkg/texfindpkg.lua index f2845f9d90..160684474b 100644 --- a/support/texfindpkg/texfindpkg.lua +++ b/support/texfindpkg/texfindpkg.lua @@ -5,16 +5,29 @@ -- Repository: https://github.com/lvjr/texfindpkg -- License: GNU General Public License v3.0 -local tfpversion = "2023D" -local tfpdate = "2023-04-05" +local tfp = tfp or {} + +tfp.version = "2023E" +tfp.date = "2023-05-05" + +local building = tfp.building +local tfpresult = "" ------------------------------------------------------------ --> \section{Some variables and functions} ------------------------------------------------------------ local lfs = require("lfs") + local insert = table.insert -local match = string.match +local remove = table.remove +local concat = table.concat +local gmatch = string.gmatch +local match = string.match +local find = string.find +local gsub = string.gsub +local sub = string.sub +local rep = string.rep local lookup = kpse.lookup kpse.set_program_name("kpsewhich") @@ -24,7 +37,18 @@ local json = utilities.json -- for json.tostring and json.tolua local gzip = gzip -- for gzip.compress and gzip.decompress local function tfpPrint(msg) - print("[tfp] " .. msg) + msg = "[tfp] " .. msg + if building then + tfpresult = tfpresult .. msg .. "\n" + else + print(msg) + end +end + +local function tfpRealPrint(msg) + if not building then + print("[tfp] " .. msg) + end end local showdbg = false @@ -85,6 +109,7 @@ end ------------------------------------------------------------ local tlpkgtext +local tlinspkgtext local function tlReadPackageDB() local tlroot = kpse.var_value("TEXMFROOT") @@ -97,36 +122,52 @@ local function tlReadPackageDB() if #list > 0 then tlpkgtext = fileRead(tlroot .. "/" .. list[1]) if not tlpkgtext then - tfpPrint("error in reading texlive package database!") + tfpPrint("error in reading texlive.tlpdb.main file!") end else -- no texlive.tlpdb.main file in a fresh TeX live tfpPrint("error in finding texlive package database!") tfpPrint("please run 'tlmgr update --self' first.") end + tlinspkgtext = fileRead(tlroot .. "/texlive.tlpdb") + if not tlinspkgtext then + tfpPrint("error in reading texlive.tlpdb file!") + end end local tlpkgdata = {} +local tlinspkgdata = {} local function tlExtractFiles(name, desc) -- ignore binary packages -- also ignore latex-dev packages - if name:find("%.") or name:find("^latex%-[%a]-%-dev") then + if find(name, "%.") or find(name, "^latex%-[%a]-%-dev") then --print(name) return end -- ignore package files in doc folder - desc = desc:match("\nrunfiles .+") or "" - for base, ext in desc:gmatch("/([%a%d%-]+)%.([%a%d]+)\n") do - if ext == "sty" or ext == "cls" then + desc = match(desc, "\nrunfiles .+") or "" + for base, ext in gmatch(desc, "/([%a%d%-%.]+)%.([%a%d]+)\n") do + if ext == "sty" or ext == "cls" or ext == "tex" or ext == "ltx" then dbgPrint(name, base .. "." .. ext) tlpkgdata[base .. "." .. ext] = name end end end -local function tlParsePackageDB() - tlpkgtext:gsub("name (.-)\n(.-)\n\n", tlExtractFiles) +local function tlExtractPackages(name, desc) + tlinspkgdata[name] = true +end + +local function tlParsePackageDB(tlpkgtext) + gsub(tlpkgtext, "name (.-)\n(.-)\n\n", tlExtractFiles) + return tlpkgdata +end + +local function tlParseTwoPackageDB() + gsub(tlpkgtext, "name (.-)\n(.-)\n\n", tlExtractFiles) + -- texlive.tlpdb might use different eol characters + gsub(tlinspkgtext, "name (.-)\r?\n(.-)\r?\n\r?\n", tlExtractPackages) end ------------------------------------------------------------ @@ -134,186 +175,75 @@ end ------------------------------------------------------------ local mtpkgtext +local mtinspkgtext local function mtReadPackageDB() - local mtvar = kpse.var_value("TEXMFVAR") + local mtvar = kpse.var_value("TEXMFDIST") if mtvar then - mtpdb = mtvar .. "/miktex/cache/packages/miktex-zzdb3-2.9/package-manifests.ini" + mtpkgtext = fileRead(mtvar .. "/miktex/config/package-manifests.ini") + if not mtpkgtext then + tfpPrint("error in reading package-manifests.ini file!") + end + mtinspkgtext = fileRead(mtvar .. "/miktex/config/packages.ini") + if not mtinspkgtext then + tfpPrint("error in reading packages.ini file!") + end else tfpPrint("error in finding texmf root!") end - mtpkgtext = fileRead(mtpdb) - if not mtpkgtext then - tfpPrint("error in reading miktex package database!") - end end local mtpkgdata = {} +local mtinspkgdata = {} local function mtExtractFiles(name, desc) -- ignore package files in source or doc folders -- also ignore latex-dev packages - if name:find("_") or name:find("^latex%-[%a]-%-dev") then + if find(name, "_") or find(name, "^latex%-[%a]-%-dev") then --print(name) return end - for base, ext in desc:gmatch("/([%a%d%-]+)%.([%a%d]+)\r?\n") do - if ext == "sty" or ext == "cls" then + for base, ext in gmatch(desc, "/([%a%d%-%.]+)%.([%a%d]+)\r?\n") do + if ext == "sty" or ext == "cls" or ext == "tex" or ext == "ltx" then dbgPrint(name, base .. "." .. ext) mtpkgdata[base .. "." .. ext] = name end end end -local function mtParsePackageDB() - -- package-manifests.ini might use different eol characters - mtpkgtext:gsub("%[(.-)%]\r?\n(.-)\r?\n\r?\n", mtExtractFiles) -end - ------------------------------------------------------------- ---> \section{Compare TeX Live and MiKTeX packages} ------------------------------------------------------------- - -local tlpkgname = "download/texlive.tlpdb" -local mtpkgname = "download/package-manifests.ini" - -local function compareDistributions() - tlpkgtext = fileRead(tlpkgname) - if tlpkgtext then - tlParsePackageDB() - else - tfpPrint("error in reading texlive package database!") - end - mtpkgtext = fileRead(mtpkgname) - if mtpkgtext then - mtParsePackageDB() - else - tfpPrint("error in reading miktex package database!") - end - local tlmissing, mkmissing = {}, {} - for k, vt in pairs(tlpkgdata) do - local vm = mtpkgdata[k] -- or "[none]" - if vm then - mtpkgdata[k] = nil -- remove it - if vm ~= vt then - print("texlive->" .. vt, "miktex->" .. vm, k) - end - else - insert(mkmissing, {vt, k}) - end - end - for k, v in pairs(mtpkgdata) do - print("texlive doesn't include " .. v .. " -> " .. k) - end - for _, v in ipairs(mkmissing) do - print("miktex doesn't include " .. v[1] .. " -> " .. v[2]) - end -end - ------------------------------------------------------------- ---> \section{Generate json file from cwl files} ------------------------------------------------------------- - -local function insertNewValue(tbl, val) - if not valueExists(tbl, val) then - insert(tbl, val) - end -end - -local function extractFileData(cwl) - -- the cwl files have different eol characters - local deps = {} - for d in cwl:gmatch("\n#include:(.-)[\r\n]") do - --dbgPrint(d) - n = d:match("^class-(.+)$") - if n then - insertNewValue(deps, n .. ".cls") - else - insertNewValue(deps, d .. ".sty") - end - end - local envs = {} - for b, e in cwl:gmatch("\n\\begin{(.-)}.-\n\\end{(.-)}") do - if b == e then - --dbgPrint("{" .. e .. "}") - insert(envs, e) - end - end - local cmds = {} - for c in cwl:gmatch("\n\\(%a+)") do - if c ~= "begin" and c ~= "end" then - --dbgPrint("\\" .. c) - if not valueExists(cmds, c) then - insert(cmds, c) - end - end - end - --return {deps, envs, cmds} - return {deps = deps, envs = envs, cmds = cmds} +local function mtExtractPackages(name, desc) + mtinspkgdata[name] = true end -local function writeJson(cwldata) - tfpPrint("writing json database to file...") - local tbl1 = {} - for k, v in pairs(cwldata) do - table.insert(tbl1, {k, v}) - end - table.sort(tbl1, function(a, b) - if a[1] < b[1] then return true end - end) - local tbl2 = {} - for _, v in ipairs(tbl1) do - local item = '"' .. v[1] .. '":' .. json.tostring(v[2]) - table.insert(tbl2, item) - end - local text = "{\n" .. table.concat(tbl2, "\n,\n") .. "\n}" - fileWrite(text, "texfindpkg.json") - fileWrite(gzip.compress(text), "texfindpkg.json.gz") +local function mtParsePackageDB(mtpkgtext) + -- package-manifests.ini might use different eol characters + gsub(mtpkgtext, "%[(.-)%]\r?\n(.-)\r?\n\r?\n", mtExtractFiles) + return mtpkgdata end -local cwlpath = "completion" - -local function generateJsonData() - local list = getFiles(cwlpath, "%.cwl$") - local fname - local cwldata = {} - for _, v in ipairs(list) do - local a, b = match(v, "^([^%-]-)%-(.+)%.cwl") - if a == "class" then - fname = b .. ".cls" - else - b = match(v, "^(.+)%.cwl") - fname = b .. ".sty" - end - --print(fname) - local cwl = fileRead(cwlpath .. "/" .. v) - --print(cwl) - if cwl then - local item = extractFileData(cwl) - dbgPrint(item) - cwldata[fname] = item - else - tfpPrint("error in reading " .. v) - end - end - writeJson(cwldata) +local function mtParseTwoPackageDB() + -- package-manifests.ini and packages.ini might use different eol characters + gsub(mtpkgtext, "%[(.-)%]\r?\n(.-)\r?\n\r?\n", mtExtractFiles) + gsub(mtinspkgtext, "%[(.-)%]\r?\n(.-)\r?\n\r?\n", mtExtractPackages) end ------------------------------------------------------------ --> \section{Install packages in current TeX distribution} ------------------------------------------------------------ -local dist -- name of current tex distribution +local dist -- name of current tex distribution +local totalinslist = {} -- list of all missing packages +local filecount = 0 -- total number of files found local function initPackageDB() dist = testDistribution() tfpPrint("you are using " .. dist) if dist == "texlive" then tlReadPackageDB() - tlParsePackageDB() + tlParseTwoPackageDB() else mtReadPackageDB() - mtParsePackageDB() + mtParseTwoPackageDB() end end @@ -325,32 +255,86 @@ local function findOnePackage(fname) end end -local function tfpExecute(c) - if os.type == "windows" then - os.execute(c) +local function checkInsPakage(pkg) + if dist == "texlive" then + return tlinspkgdata[pkg] else - os.execute('sudo env "PATH=$PATH" ' .. c) + return mtinspkgdata[pkg] + end +end + +local function tfpExecute(c) + if not building then + if os.type == "windows" then + os.execute(c) + else + os.execute('sudo env "PATH=$PATH" ' .. c) + end end end local function installSomePackages(list) if not list then return end if dist == "texlive" then - local p = table.concat(list, " ") - tfpPrint("installing package " .. p) - tfpExecute("tlmgr install " .. p) + local pkgs = concat(list, " ") + if #list > 1 then + tfpRealPrint("installing texlive packages: " .. pkgs) + else + tfpRealPrint("installing texlive package: " .. pkgs) + end + tfpExecute("tlmgr install " .. pkgs) else for _, p in ipairs(list) do - tfpPrint("installing package " .. p) + tfpRealPrint("installing miktex package: " .. p) tfpExecute("miktex packages install " .. p) end end end +local function updateTotalInsList(inslist) + if #totalinslist == 0 then + totalinslist = inslist + else + for _, pkg in ipairs(inslist) do + if not valueExists(totalinslist, pkg) then + insert(totalinslist, pkg) + end + end + end +end + local function listSomePackages(list) - if not list then return end - local p = table.concat(list, " ") - tfpPrint("please install " .. dist .. " package " .. p) + if not list then return {} end + if #list > 0 then + filecount = filecount + 1 + end + local pkgs = concat(list, " ") + if #list == 1 then + tfpPrint(dist .. " package needed: " .. pkgs) + else + tfpPrint(dist .. " packages needed: " .. pkgs) + end + local inslist = {} + for _, p in ipairs(list) do + if not checkInsPakage(p) then + insert(inslist, p) + end + end + if #inslist == 0 then + if #list == 1 then + tfpRealPrint("this package is already installed") + else + tfpRealPrint("these packages are already installed") + end + else + local pkgs = concat(inslist, " ") + if #inslist == 1 then + tfpRealPrint(dist .. " package not yet installed: " .. pkgs) + else + tfpRealPrint(dist .. " packages not yet installed: " .. pkgs) + end + end + updateTotalInsList(inslist) end ------------------------------------------------------------ @@ -359,7 +343,8 @@ end local tfptext = "" -- the json text local tfpdata = {} -- the lua object -local fnlist = {} -- file name list +local fnlist = {} -- file name list +local pkglist = {} -- package name list local function initDependencyDB() local ziptext = fileRead(lookup("texfindpkg.json.gz")) @@ -372,67 +357,87 @@ local function initDependencyDB() end end -local function findDependencies(fname) +local function printDependency(fname, level) + local msg = fname + local pkg = findOnePackage(fname) + if pkg then + msg = msg .. " (from " .. pkg .. ")" + if not valueExists(pkglist, pkg) then + insert(pkglist, pkg) + end + else + msg = msg .. " (not found)" + end + if level == 0 then + tfpPrint(msg) + else + tfpPrint(rep(" ", level - 1) .. "|- " .. msg) + end +end + +local function findDependencies(fname, level) --print(fname) if valueExists(fnlist, fname) then return end local item = tfpdata[fname] if not item then - tfpPrint("no dependency info for " .. fname) + -- no dependency info for fname + printDependency(fname, level) return end - tfpPrint("finding dependencies for " .. fname) - table.insert(fnlist, fname) + -- finding dependencies for fname + printDependency(fname, level) + insert(fnlist, fname) local deps = item.deps if deps then for _, dname in ipairs(deps) do - findDependencies(dname) + findDependencies(dname, level + 1) end end end local function queryByFileName(fname) - fnlist = {} -- reset the list - findDependencies(fname) + fnlist, pkglist = {}, {} -- reset the list + tfpPrint("building dependency tree for " .. fname .. ":") + tfpPrint(rep("-", 24)) + findDependencies(fname, 0) + tfpPrint(rep("-", 24)) if #fnlist == 0 then tfpPrint("could not find any package with file " .. fname) return end - local pkglist = {} - for _, fn in ipairs(fnlist) do - local pkg = findOnePackage(fn) - --print(fn, pkg) - if pkg then - table.insert(pkglist, pkg) - end - end - if not pkglist then + if #pkglist == 0 then tfpPrint("error in finding package in " .. dist) return end - return pkglist + listSomePackages(pkglist) end local function getFileNameFromCmdEnvName(cmdenv, name) --print(name) - for line in tfptext:gmatch("(.-)\n[,}]") do - if line:find('"' .. name .. '"') then + local flist = {} + for line in gmatch(tfptext, "(.-)\n[,}]") do + if find(line, '"' .. name .. '"') then --print(line) - local fname, fspec = line:match('"(.-)":(.+)') + local fname, fspec = match(line, '"(.-)":(.+)') --print(fname, fspec) local item = json.tolua(fspec) - if valueExists(item[cmdenv], name) then - tfpPrint("found package file " .. fname) - return fname + if item[cmdenv] and valueExists(item[cmdenv], name) then + insert(flist, fname) end end end + return flist end local function queryByCommandName(cname) --print(cname) - local fname = getFileNameFromCmdEnvName("cmds", cname) - if fname then - return queryByFileName(fname) + local flist = getFileNameFromCmdEnvName("cmds", cname) + if #flist > 0 then + for _, fname in ipairs(flist) do + tfpPrint(rep("=", 48)) + tfpPrint("found package file " .. fname .. " with command \\" .. cname) + queryByFileName(fname) + end else tfpPrint("could not find any package with command \\" .. cname) end @@ -440,83 +445,187 @@ end local function queryByEnvironmentName(ename) --print(ename) - local fname = getFileNameFromCmdEnvName("envs", ename) - if fname then - return queryByFileName(fname) + local flist = getFileNameFromCmdEnvName("envs", ename) + if #flist > 0 then + for _, fname in ipairs(flist) do + tfpPrint(rep("=", 48)) + tfpPrint("found package file " .. fname .. " with environment {" .. ename .. "}") + queryByFileName(fname) + end else tfpPrint("could not find any package with environment {" .. ename .. "}") end end -local function query(name) - local h = name:sub(1,1) +local function queryOne(t, name) + if t == "cmd" then + queryByCommandName(name) + elseif t == "env" then + queryByEnvironmentName(name) + elseif t == "file" then + tfpPrint(rep("=", 48)) + queryByFileName(name) + else + -- do something for t == "pkg" + end +end + +local function query(namelist) + for _, v in ipairs(namelist) do + queryOne(v[1], v[2]) + end + if filecount > 1 then + tfpRealPrint(rep("=", 48)) + local pkgs = concat(totalinslist, " ") + if #totalinslist == 1 then + tfpRealPrint(dist .. " package not yet installed in total: " .. pkgs) + else + tfpRealPrint(dist .. " packages not yet installed in total: " .. pkgs) + end + end +end + +local function install(namelist) + query(namelist) + if #totalinslist > 0 then + installSomePackages(totalinslist) + end +end + +------------------------------------------------------------ +--> \section{Parse query or install arguments} +------------------------------------------------------------ + +local function parseName(name) + local h = sub(name, 1, 1) if h == "\\" then - local b = name:sub(2) - return queryByCommandName(b) + local b = sub(name, 2) + return({"cmd", b}) elseif h == "{" then - if name:sub(-1) == "}" then - local b = name:sub(2,-2) - return queryByEnvironmentName(b) + if sub(name, -1) == "}" then + local b = sub(name, 2, -2) + return({"env", b}) + else + error("invalid name '" .. name .. "'") + end + elseif find(name, "%.") then + return({"file", name}) + else + return({"pkg", name}) + end +end + +local function parseArgList(arglist) + local namelist = {} + local nametype = nil + for _, v in ipairs(arglist) do + if v == "-f" then + nametype = "file" + elseif v == "-c" then + nametype = "cmd" + elseif v == "-e" then + nametype = "env" else - tfpPrint("invalid input " .. name) + if nametype then + insert(namelist, {nametype, v}) + else + insert(namelist, parseName(v)) + end end + end + if #namelist == 0 then + error("missing the name of file/cmd/env!") else - return queryByFileName(name) + return namelist end end -local function install(name) - local list = query(name) - installSomePackages(list) +local function doQuery(arglist) + local namelist = parseArgList(arglist) + initPackageDB() + initDependencyDB() + query(namelist) +end + +local function doInstall(arglist) + local namelist = parseArgList(arglist) + initPackageDB() + initDependencyDB() + install(namelist) end ------------------------------------------------------------ ---> \section{Respond to user input} +--> \section{Print help or version text} ------------------------------------------------------------ local helptext = [[ usage: texfindpkg <action> [<options>] [<name>] -Valid actions are: +valid actions are: install Install some package and its dependencies query Query dependencies for some package + help Print this message and exit + version Print version information and exit + +valid options are: + -f Query or install by file name + -c Query or install by command name + -e Query or install by environment name -Valid options are: - --help Print this message and exit - --version Print version information and exit +please report bug at https://github.com/lvjr/texfindpkg ]] -local function main() - if arg[1] == nil then - print(helptext) - elseif arg[1] == "--help" then - print(helptext) - elseif arg[1] == "--version" then - print("TeXFindPkg Version " .. tfpversion .. " (" .. tfpdate .. ")\n") - elseif arg[1] == "install" then - if arg[2] then - initPackageDB() - initDependencyDB() - install(arg[2]) - else - tfpPrint("missing the name of file/cmd/env!") - end - elseif arg[1] == "query" then - if arg[2] then - initPackageDB() - initDependencyDB() - local list = query(arg[2]) - listSomePackages(list) - else - tfpPrint("missing the name of file/cmd/env!") - end - elseif arg[1] == "generate" then - generateJsonData() - elseif arg[1] == "compare" then - compareDistributions() +local function help() + print(helptext) +end + +local function version() + print("TeXFindPkg Version " .. tfp.version .. " (" .. tfp.date .. ")\n") +end + +------------------------------------------------------------ +--> \section{Respond to user input} +------------------------------------------------------------ + +local function tfpMain(tfparg) + tfpresult = "" + if tfparg[1] == nil then return help() end + local action = remove(tfparg, 1) + action = match(action, "^%-*(.*)$") -- remove leading dashes + --print(action) + if action == "query" then + doQuery(tfparg) + elseif action == "install" then + doInstall(tfparg) + elseif action == "help" then + help() + elseif action == "version" then + version() else - tfpPrint("unknown option " .. arg[1]) + tfpPrint("unknown action '" .. action .. "'") + help() end + return tfpresult end -main() +local function main() + tfpMain(arg) +end + +if building then + tfp.tfpMain = tfpMain + tfp.showdbg = showdbg + tfp.dbgPrint = dbgPrint + tfp.tfpPrint = tfpPrint + tfp.fileRead = fileRead + tfp.fileWrite = fileWrite + tfp.getFiles = getFiles + tfp.valueExists = valueExists + tfp.json = json + tfp.gzip = gzip + tfp.tlParsePackageDB = tlParsePackageDB + tfp.mtParsePackageDB = mtParsePackageDB + return tfp +else + main() +end diff --git a/support/texfindpkg/tfpbuild.lua b/support/texfindpkg/tfpbuild.lua new file mode 100644 index 0000000000..c0ccac47aa --- /dev/null +++ b/support/texfindpkg/tfpbuild.lua @@ -0,0 +1,312 @@ +#!/usr/bin/env texlua + +-- Description: Install TeX packages and their dependencies +-- Copyright: 2023 (c) Jianrui Lyu <tolvjr@163.com> +-- Repository: https://github.com/lvjr/texfindpkg +-- License: GNU General Public License v3.0 + +tfp = { building = true } + +kpse.set_program_name("kpsewhich") +local tfp = require(kpse.lookup("texfindpkg.lua")) + +------------------------------------------------------------ +--> \section{Some variables and functions} +------------------------------------------------------------ + +local tfpMain = tfp.tfpMain +local showdbg = tfp.showdbg +local dbgPrint = tfp.dbgPrint +local fileRead = tfp.fileRead +local fileWrite = tfp.fileWrite +local getFiles = tfp.getFiles +local valueExists = tfp.valueExists +local json = tfp.json +local gzip = tfp.gzip +local tlParsePackageDB = tfp.tlParsePackageDB +local mtParsePackageDB = tfp.mtParsePackageDB + +local insert = table.insert +local remove = table.remove +local concat = table.concat +local gmatch = string.gmatch +local match = string.match + +------------------------------------------------------------ +--> \section{Compare TeX Live and MiKTeX packages} +------------------------------------------------------------ + +local tlpkgname = "download/texlive.tlpdb" +local mtpkgname = "download/package-manifests.ini" + +local function compareDistributions() + tlpkgtext = fileRead(tlpkgname) + if tlpkgtext then + tlpkgdata = tlParsePackageDB(tlpkgtext) + else + print("error in reading texlive package database!") + end + mtpkgtext = fileRead(mtpkgname) + if mtpkgtext then + mtpkgdata = mtParsePackageDB(mtpkgtext) + else + print("error in reading miktex package database!") + end + local tlmissing, mkmissing = {}, {} + for k, vt in pairs(tlpkgdata) do + local vm = mtpkgdata[k] -- or "[none]" + if vm then + mtpkgdata[k] = nil -- remove it + if vm ~= vt then + print("texlive->" .. vt, "miktex->" .. vm, k) + end + else + insert(mkmissing, {vt, k}) + end + end + for k, v in pairs(mtpkgdata) do + print("texlive doesn't include " .. v .. " -> " .. k) + end + for _, v in ipairs(mkmissing) do + print("miktex doesn't include " .. v[1] .. " -> " .. v[2]) + end +end + +------------------------------------------------------------ +--> \section{Generate json file from cwl files} +------------------------------------------------------------ + +local function insertNewValue(tbl, val) + if not valueExists(tbl, val) then + insert(tbl, val) + end +end + +local latexcwls = {"tex", "xetex", "luatex", "plaintex", "latex-document", "latex-dev"} +local islatexcwl = false + +local function cwlToRealName(base) + if valueExists(latexcwls, base) then return end + local n = match(base, "^tikzlibrary(.+)$") + if n then return base .. ".code.tex" end + local n = match(base, "^tcolorboxlibrary(.+)$") + if n then + if n == "many" or n == "most" or n == "all" then + return nil + else + return "tcb" .. n .. ".code.tex" + end + end + n = match(base, "^class%-(.+)$") + if n then return n .. ".cls" end + return base .. ".sty" +end + +local cwldata = {} + +local function extractFileData(cwl) + -- the cwl files have different eol characters + local deps = {} + for base in gmatch(cwl, "\n#include:(.-)[\r\n]") do + --dbgPrint(base) + local n = cwlToRealName(base) + if n then insertNewValue(deps, n) end + end + local envs = {} + if islatexcwl and cwldata["latex.ltx"] then + envs = cwldata["latex.ltx"].envs or {} + end + for e in gmatch(cwl, "\n\\begin{(.-)}") do + --dbgPrint("{" .. e .. "}") + insertNewValue(envs, e) + end + local cmds = {} + if islatexcwl and cwldata["latex.ltx"] then + cmds = cwldata["latex.ltx"].cmds or {} + end + for c in gmatch(cwl, "\n\\(%a+)") do + if c ~= "begin" and c ~= "end" then + --dbgPrint("\\" .. c) + if not valueExists(cmds, c) then + insert(cmds, c) + end + end + end + --return {deps, envs, cmds} + if #deps == 0 then deps = nil end + if #cmds == 0 then cmds = nil end + if #envs == 0 then envs = nil end + return {deps = deps, envs = envs, cmds = cmds} +end + +local function writeJson() + print("writing json database to file...") + local tbl1 = {} + for k, v in pairs(cwldata) do + insert(tbl1, {k, v}) + end + table.sort(tbl1, function(a, b) + -- make latex.ltx the first item in the json file + local aa, bb + if a[1] == "latex.ltx" then aa = "001" else aa = a[1] end + if b[1] == "latex.ltx" then bb = "001" else bb = b[1] end + return aa < bb + end) + local tbl2 = {} + for _, v in ipairs(tbl1) do + local item = '"' .. v[1] .. '":' .. json.tostring(v[2]) + insert(tbl2, item) + end + local text = "{\n" .. concat(tbl2, "\n,\n") .. "\n}" + fileWrite(text, "texfindpkg.json") + fileWrite(gzip.compress(text), "texfindpkg.json.gz") +end + +local cwlpath = "completion" + +local ignoredcwls = {"optex"} + +local function generateJsonData() + local list = getFiles(cwlpath, "%.cwl$") + local fname + for _, v in ipairs(list) do + local base = match(v, "^(.+)%.cwl") + if not valueExists(ignoredcwls, base) then + if valueExists(latexcwls, base) then + islatexcwl = true + fname = "latex.ltx" + else + islatexcwl = false + fname = cwlToRealName(base) + end + if fname then + --print(fname) + local cwl = fileRead(cwlpath .. "/" .. v) + --print(cwl) + if cwl then + local item = extractFileData(cwl) + dbgPrint(item) + cwldata[fname] = item + else + print("error in reading " .. v) + end + end + end + end + writeJson() +end + +------------------------------------------------------------ +--> \section{Print help or version text} +------------------------------------------------------------ + +local helptext = [[ +usage: texfindpkg <action> [<options>] [<name>] + +valid actions are: + generate Generate texfindpkg.json and texfindpkg.json.gz + compare Compare packages in TeXLive and MiKTeX + check Check test files and report the results + save Check test files and save the results + help Print this message and exit + version Print version information and exit + +please report bug at https://github.com/lvjr/texfindpkg +]] + +local function help() + print(helptext) +end + +local function version() + print("TeXFindPkg Version " .. tfp.version .. " (" .. tfp.date .. ")\n") +end + +------------------------------------------------------------ +--> \section{Check or save test files} +------------------------------------------------------------ + +local function cmdLineToArgList(cmdline) + local tfparg = {} + for v in gmatch(cmdline, "([^ ]+)") do + insert(tfparg, v) + end + remove(tfparg, 1) + return tfparg +end + +local testpath = "test" + +local function readTestFiles(v) + local tests = {} + local text = fileRead(testpath .. "/" .. v) + for i, o in gmatch(text, "<<<<+\n(.-)\n%-%-%-%-+\n(.-)\n>>>>+") do + --print(i, o) + insert(tests, {i, o}) + end + return tests +end + +local function checkTestFiles() + local files = getFiles(testpath, "%.tfp$") + for _, v in ipairs(files) do + print("checking test file " .. v) + local tests = readTestFiles(v) + for _, v in ipairs(tests) do + local i, o = v[1], v[2] .. "\n" + local result = tfpMain(cmdLineToArgList(i)) + if o == result then + dbgPrint("test passed for '" .. i .. "'") + else + print("test failed for '" .. i .. "'") + end + end + end +end + +local function saveTestFiles() + local files = getFiles(testpath, "%.tfp$") + for _, v in ipairs(files) do + print("reading test file " .. v) + local tests = readTestFiles(v) + local text = "" + for _, v in ipairs(tests) do + local i = v[1] + dbgPrint("running test '" .. i .. "'") + local o = tfpMain(cmdLineToArgList(i)) + local sect = "<<<<<<<<\n" .. i .. "\n--------\n" .. o .. ">>>>>>>>\n" + text = text .. sect + end + print("saving test file " .. v) + fileWrite(text, testpath .. "/" .. v) + end +end + +------------------------------------------------------------ +--> \section{Respond to user input} +------------------------------------------------------------ + +local function main() + if arg[1] == nil then return help() end + local action = remove(arg, 1) + action = match(action, "^%-*(.*)$") -- remove leading dashes + --print(action) + if action == "help" then + help() + elseif action == "version" then + version() + elseif action == "generate" then + generateJsonData() + elseif action == "compare" then + compareDistributions() + elseif action == "check" then + checkTestFiles() + elseif action == "save" then + saveTestFiles() + else + print("unknown action '" .. action .. "'") + help() + end +end + +main() |