summaryrefslogtreecommitdiff
path: root/support/texfindpkg
diff options
context:
space:
mode:
Diffstat (limited to 'support/texfindpkg')
-rw-r--r--support/texfindpkg/README.md66
-rw-r--r--support/texfindpkg/texfindpkg.137
-rw-r--r--support/texfindpkg/texfindpkg.json.gzbin892907 -> 875626 bytes
-rw-r--r--support/texfindpkg/texfindpkg.lua587
-rw-r--r--support/texfindpkg/tfpbuild.lua312
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
index aec9bb158e..db4d301d9c 100644
--- a/support/texfindpkg/texfindpkg.json.gz
+++ b/support/texfindpkg/texfindpkg.json.gz
Binary files differ
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()