mtx-ctan.lua /size: 9467 b    last modification: 2021-10-28 13:50
1if not modules then modules = { } end modules ['mtx-ctan'] = {
2    version   = 1.00,
3    comment   = "companion to mtxrun.lua",
4    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5    copyright = "PRAGMA ADE / ConTeXt Development Team",
6    license   = "see context related readme files"
7}
8
9-- This is just an experiment. Some day I want to be able to install fonts this way
10-- but maybe fetching tex live packages is also an option (I need to check if there
11-- is an api for that ... in wintertime). Normally fonts come from the web but I had
12-- to fetch newcm from ctan, so ...
13--
14-- mtxrun --script ctan --packages --pattern=computermodern
15
16-- http://www.ctan.org/json/2.0/packages
17-- http://www.ctan.org/json/2.0/pkg/name
18-- http://www.ctan.org/json/2.0/topics              : key details
19-- http://www.ctan.org/json/2.0/topic/name          : key details
20-- http://www.ctan.org/json/2.0/topic/name?ref=true : key details packages
21
22local lower, find, gsub = string.lower, string.find, string.gsub
23local write_nl = (logs and logs.writer) or (texio and texio.write_nl) or print
24local xmlconvert, xmltext, xmlattr, xmlcollected = xml.convert, xml.text, xml.attribute, xml.collected
25
26local helpinfo = [[
27<?xml version="1.0"?>
28<application>
29 <metadata>
30  <entry name="name">mtx-ctan</entry>
31  <entry name="detail">Dealing with CTAN</entry>
32  <entry name="version">1.00</entry>
33 </metadata>
34 <flags>
35  <category name="basic">
36   <subcategory>
37    <flag name="packages"><short>list available packages</short></flag>
38    <flag name="topics"><short>list available topics</short></flag>
39    <flag name="detail"><short>show details about package</short></flag>
40    <flag name="pattern" value="string"><short>use this pattern, otherwise first argument</short></flag>
41   </subcategory>
42  </category>
43 </flags>
44</application>
45]]
46
47local application = logs.application {
48    name     = "mtx-ctan",
49    banner   = "Dealing with CTAN",
50    helpinfo = helpinfo,
51}
52
53local report = application.report
54
55scripts      = scripts      or { }
56scripts.ctan = scripts.ctan or { }
57
58local okay, json = pcall(require,"util-jsn")
59local okay, curl = pcall(require,"libs-imp-curl")
60                   pcall(require,"char-ini")
61
62local jsontolua = json and json.tolua
63local shaped    = characters and characters.shaped or lower
64
65-- local ignore = {
66--     "latex",
67--     "plain",
68--     "xetex",
69-- }
70
71-- what is the url to fetch a zip
72
73-- We cannot use the socket library because we don't compile that massive amount of
74-- ssl code into lua(meta)tex. aybe some day one fo these small embedded libraries
75-- makes sense but there are so many changes in all that security stuff that it
76-- defeats long term stability of the ecosystem anyway ... just like some of my old
77-- devices suddenly are no longer accessible with modern browsers I expect it to
78-- happen everywhere. I'm not sure why ctan can't support http because I see no
79-- added value in the 's' here.
80
81local ctanurl = "https://www.ctan.org/" .. (json and "json" or "xml") .. "/2.0/"
82
83local fetched = curl and
84
85    function(str)
86        local data, message = curl.fetch {
87            url           = ctanurl .. str,
88            sslverifyhost = false,
89            sslverifypeer = false,
90        }
91        if not data then
92            report("some error: %s",message)
93        end
94        return data
95    end
96
97or
98
99    function(str)
100        -- So, no redirect to http, which means that we cannot use the built in socket
101        -- library. What if the client is happy with http?
102        local data = os.resultof("curl -sS " .. ctanurl .. str)
103        -- print(data)
104        return data
105    end
106
107-- for j=1,#ignore do
108--     if find(str,ignore[j]) then
109--         return false
110--     end
111-- end
112
113local function strfound(pattern,str)
114    if not pattern then
115        return true
116    else
117        local str = lower(shaped(str))
118        if find(str,pattern) then
119            return true
120        else
121            str = gsub(str,"[^a-zA-Z0-9]","")
122            if find(str,pattern) then
123                return true
124            else
125                return false
126            end
127        end
128    end
129end
130
131local function showresult(found)
132    if #found > 2 then
133        utilities.formatters.formatcolumns(found)
134        report("")
135        for k=1,#found do
136            report(found[k])
137        end
138        report("")
139    end
140end
141
142local function checkedpattern(pattern)
143    if pattern then
144        return lower(shaped(pattern))
145    end
146end
147
148local validdata = json and
149
150    function(data)
151        if data then
152            data = jsontolua(data)
153            if type(data) == "table" then
154                return data
155            else
156                report("unable to handle this json data")
157            end
158        else
159            report("unable to fetch packages")
160        end
161    end
162
163or
164
165    function(data)
166        if data then
167            data = xmlconvert(data)
168            if data.error then
169                report("unable to handle this json data")
170            else
171                return data
172            end
173        else
174            report("unable to fetch packages")
175        end
176    end
177
178scripts.ctan.details = json and
179
180    function(name)
181        if name then
182            local data = validdata(fetched("pkg/" .. name))
183            if data then
184                report("")
185             -- report("key     : %s",data.key or "-")
186                report("name    : %s",data.name or "-")
187                report("caption : %s",data.caption or "-")
188                report("path    : %s",data.ctan.path or "-")
189                report("")
190            end
191        end
192    end
193
194or
195
196    function (name)
197        if name then
198            local data = validdata(fetched("pkg/" .. name))
199            report("")
200         -- report("key     : %s",data.key or "-")
201            report("name    : %s",xmltext(data,"/entry/name"))
202            report("caption : %s",xmltext(data,"/entry/caption"))
203            report("path    : %s",xmlattr(data,"/entry/ctan","path"))
204            report("")
205        end
206    end
207
208scripts.ctan.packages = json and
209
210    function(pattern)
211        local data = validdata(fetched("packages"))
212        if data then
213            local found = {
214                { "key", "name", "caption" },
215                { "",    "",     ""        },
216            }
217            pattern = checkedpattern(pattern)
218            for i=1,#data do
219                local entry = data[i]
220                if strfound(pattern,entry.caption) then
221                    found[#found+1] = { entry.key, entry.name, entry.caption }
222                end
223            end
224            showresult(found)
225        end
226    end
227
228or
229
230    function(pattern)
231        local data = validdata(fetched("packages"))
232        if data then
233            local found = {
234                { "key", "name", "caption" },
235                { "",    "",     ""        },
236            }
237            pattern = checkedpattern(pattern)
238            for c in xmlcollected(data,"/packages/package") do
239                local at = c.at
240                if strfound(pattern,at.caption) then
241                    found[#found+1] = { at.key, at.name, at.caption }
242                end
243            end
244            showresult(found)
245        end
246    end
247
248scripts.ctan.topics = json and
249
250    function (pattern)
251        local data = validdata(fetched("topics"))
252        if data then
253            local found = {
254                { "key", "details" },
255                { "",    ""        },
256            }
257            pattern = checkedpattern(pattern)
258            for i=1,#data do
259                local entry = data[i]
260                if strfound(pattern,entry.details) then
261                    found[#found+1] = { entry.key or entry.name, entry.details } -- inconsistency between json and xml
262                end
263            end
264            showresult(found)
265        end
266    end
267
268or
269
270    function(pattern)
271        local data = validdata(fetched("topics"))
272        if data then
273            local found = {
274                { "name", "details" },
275                { "",     ""        },
276            }
277            pattern = checkedpattern(pattern)
278            for c in xmlcollected(data,"/topics/topic") do
279                local at = c.at
280                if strfound(pattern,at.caption) then
281                    found[#found+1] = { at.key or at.name, at.details } -- inconsistency between json and xml
282                end
283            end
284            showresult(found)
285        end
286    end
287
288local function whatever()
289    report("")
290    report("using %s interface", json and "json"    or "xml")
291    report("using curl %s",      curl and "library" or "binary")
292    report("")
293end
294
295if environment.argument("packages") then
296    whatever()
297    scripts.ctan.packages(environment.argument("pattern") or environment.files[1])
298elseif environment.argument("topics") then
299    whatever()
300    scripts.ctan.topics(environment.argument("pattern") or environment.files[1])
301elseif environment.argument("details") then
302    whatever()
303    scripts.ctan.details(environment.files[1])
304elseif environment.argument("exporthelp") then
305    application.export(environment.argument("exporthelp"),environment.files[1])
306else
307    application.help()
308end
309
310-- scripts.ctan.packages(environment.argument("pattern") or environment.files[1])
311-- scripts.ctan.packages("font")
312-- scripts.ctan.details("tex")
313-- scripts.ctan.details("ipaex")
314
315-- scripts.ctan.packages("Półtawskiego")
316-- scripts.ctan.packages("Poltawskiego")
317
318-- scripts.ctan.topics("font")
319-- scripts.ctan.topics()
320