mtx-ctan.lua /size: 10 Kb    last modification: 2023-12-21 09:43
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 [--field=key|name|caption]</short></flag>
38    <flag name="topics"><short>list available topics [--field=key|name|details]</short></flag>
39    <flag name="details"><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. Maybe 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        pattern = string.topattern(pattern,true)
145        return lower(shaped(pattern))
146    end
147end
148
149local validdata = json and
150
151    function(data)
152        if data then
153            data = jsontolua(data)
154            if type(data) == "table" then
155                return data
156            else
157                report("unable to handle this json data")
158            end
159        else
160            report("unable to fetch packages")
161        end
162    end
163
164or
165
166    function(data)
167        if data then
168            data = xmlconvert(data)
169            if data.error then
170                report("unable to handle this json data")
171            else
172                return data
173            end
174        else
175            report("unable to fetch packages")
176        end
177    end
178
179scripts.ctan.details = json and
180
181    function(name)
182        if name then
183            local data = validdata(fetched("pkg/" .. name))
184            if data then
185                report("")
186             -- report("key     : %s",data.key or "-")
187                report("name    : %s",data.name or "-")
188                report("caption : %s",data.caption or "-")
189                report("path    : %s",data.ctan and data.ctan.path or "-")
190                report("")
191            end
192        end
193    end
194
195or
196
197    function (name)
198        if name then
199            local data = validdata(fetched("pkg/" .. name))
200            report("")
201         -- report("key     : %s",data.key or "-")
202            report("name    : %s",xmltext(data,"/entry/name"))
203            report("caption : %s",xmltext(data,"/entry/caption"))
204            report("path    : %s",xmlattr(data,"/entry/ctan","path"))
205            report("")
206        end
207    end
208
209scripts.ctan.packages = json and
210
211    function(pattern,field)
212        local data = validdata(fetched("packages"))
213        if data then
214            local found = {
215                { "key", "name", "caption" },
216                { "",    "",     ""        },
217            }
218            pattern = checkedpattern(pattern)
219            for i=1,#data do
220                local entry   = data[i]
221                local key     = entry.key
222                local name    = entry.name
223                local caption = entry.caption
224                local where
225                if field == "name" then
226                    where = name
227                elseif field == "caption" then
228                    where = caption
229                elseif field == "key" then
230                    where = key
231                end
232                if where then
233                    if strfound(pattern,where) then
234                        found[#found+1] = { key, name, caption }
235                    end
236                else
237                    if strfound(pattern,name) or strfound(pattern,caption) then
238                        found[#found+1] = { key, name, caption }
239                    end
240                end
241            end
242            showresult(found)
243        end
244    end
245
246or
247
248    function(pattern)
249        local data = validdata(fetched("packages"))
250        if data then
251            local found = {
252                { "key", "name", "caption" },
253                { "",    "",     ""        },
254            }
255            pattern = checkedpattern(pattern)
256            for c in xmlcollected(data,"/packages/package") do
257                local at = c.at
258                if strfound(pattern,at.caption) then
259                    found[#found+1] = { at.key, at.name, at.caption }
260                end
261            end
262            showresult(found)
263        end
264    end
265
266scripts.ctan.topics = json and
267
268    function (pattern)
269        local data = validdata(fetched("topics"))
270        if data then
271            local found = {
272                { "key", "details" },
273                { "",    ""        },
274            }
275            pattern = checkedpattern(pattern)
276            for i=1,#data do
277                -- inconsistency between json and xml
278                local entry   = data[i]
279                local key     = entry.key
280                local name    = entry.name
281                local details = entry.details
282                local where
283                if field == "name" then
284                    where = name or key
285                elseif field == "details" then
286                    where = details
287                elseif field == "key" then
288                    where = key or name
289                end
290                if where then
291                    if strfound(pattern,where) then
292                        found[#found+1] = { key or name, details }
293                    end
294                else
295                    if strfound(pattern,key or name) or strfound(pattern,details) then
296                        found[#found+1] = { key or name, details }
297                    end
298                end
299            end
300            showresult(found)
301        end
302    end
303
304or
305
306    function(pattern)
307        local data = validdata(fetched("topics"))
308        if data then
309            local found = {
310                { "name", "details" },
311                { "",     ""        },
312            }
313            pattern = checkedpattern(pattern)
314            for c in xmlcollected(data,"/topics/topic") do
315                local at = c.at
316                if strfound(pattern,at.caption) then
317                    found[#found+1] = { at.key or at.name, at.details } -- inconsistency between json and xml
318                end
319            end
320            showresult(found)
321        end
322    end
323
324local function whatever()
325    report("")
326    report("using %s interface", json and "json"    or "xml")
327    report("using curl %s",      curl and "library" or "binary")
328    report("")
329end
330
331-- scripts.ctan.packages(environment.argument("pattern") or environment.files[1])
332-- scripts.ctan.packages("font")
333-- scripts.ctan.details("tex")
334-- scripts.ctan.details("ipaex")
335
336-- scripts.ctan.packages("Półtawskiego")
337-- scripts.ctan.packages("Poltawskiego")
338
339-- scripts.ctan.topics("font")
340-- scripts.ctan.topics()
341
342if environment.argument("packages") then
343    whatever()
344    scripts.ctan.packages(environment.argument("pattern") or environment.files[1], environment.argument("field"))
345elseif environment.argument("topics") then
346    whatever()
347    scripts.ctan.topics(environment.argument("pattern") or environment.files[1])
348elseif environment.argument("details") then
349    whatever()
350    scripts.ctan.details(environment.files[1])
351elseif environment.argument("exporthelp") then
352    application.export(environment.argument("exporthelp"),environment.files[1])
353else
354    application.help()
355end
356