mtx-tools.lua /size: 8257 b    last modification: 2020-07-01 14:35
1if not modules then modules = { } end modules ['mtx-tools'] = {
2    version   = 1.002,
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
9local find, format, sub, rep, gsub, lower = string.find, string.format, string.sub, string.rep, string.gsub, string.lower
10
11local helpinfo = [[
12<?xml version="1.0"?>
13<application>
14 <metadata>
15  <entry name="name">mtx-tools</entry>
16  <entry name="detail">Some File Related Goodies</entry>
17  <entry name="version">1.01</entry>
18 </metadata>
19 <flags>
20  <category name="basic">
21   <subcategory>
22    <flag name="disarmutfbomb"><short>remove utf bomb if present</short></flag>
23    <flag name="force"><short>remove indeed</short></flag>
24   </subcategory>
25   <subcategory>
26    <flag name="dirtoxml"><short>glob directory into xml</short></flag>
27    <flag name="pattern"><short>glob pattern (default: *)</short></flag>
28    <flag name="url"><short>url attribute (no processing)</short></flag>
29    <flag name="root"><short>the root of the globbed path (default: .)</short></flag>
30    <flag name="output"><short>output filename (console by default)</short></flag>
31    <flag name="recurse"><short>recurse into subdirecories</short></flag>
32    <flag name="stripname"><short>take pathpart of given pattern</short></flag>
33    <flag name="longname"><short>set name attributes to full path name</short></flag>
34    <flag name="downcase"><short>lowercase names</short></flag>
35   </subcategory>
36   <subcategory>
37    <flag name="showstring"><short>show unicode characters in given string</short></flag>
38    <flag name="showfile"><short>show unicode characters in given file</short></flag>
39   </subcategory>
40   <subcategory>
41    <flag name="pattern"><short>glob pattern (default: *)</short></flag>
42    <flag name="recurse"><short>recurse into subdirecories</short></flag>
43    <flag name="force"><short>downcase indeed</short></flag>
44   </subcategory>
45  </category>
46 </flags>
47</application>
48]]
49
50local application = logs.application {
51    name     = "mtx-tools",
52    banner   = "Some File Related Goodies 1.01",
53    helpinfo = helpinfo,
54}
55
56local report  = application.report
57local writeln = (logs and logs.writer) or (texio and texio.write_nl) or print
58
59scripts       = scripts       or { }
60scripts.tools = scripts.tools or { }
61
62local bomb_1, bomb_2 = "^\254\255", "^\239\187\191"
63
64function scripts.tools.disarmutfbomb()
65    local force, done = environment.argument("force"), false
66    local files = environment.files
67    for i=1,#files do
68        local name = files[i]
69        if lfs.isfile(name) then
70            local data = io.loaddata(name)
71            if not data then
72                -- just skip
73            elseif find(data,bomb_1) then
74                report("file '%s' has a 2 character utf bomb",name)
75                if force then
76                    io.savedata(name,(gsub(data,bomb_1,"")))
77                end
78                done = true
79            elseif find(data,bomb_2) then
80                report("file '%s' has a 3 character utf bomb",name)
81                if force then
82                    io.savedata(name,(gsub(data,bomb_2,"")))
83                end
84                done = true
85            else
86            --  report("file '%s' has no utf bomb",name)
87            end
88        end
89    end
90    if done and not force then
91        report("use --force to do a real disarming")
92    end
93end
94
95function scripts.tools.downcase()
96    local pattern = environment.argument('pattern') or "*"
97    local recurse = environment.argument('recurse')
98    local force   = environment.argument('force')
99    local n = 0
100    if recurse and not find(pattern,"^%*%*%/") then
101        pattern = "**/*" .. pattern
102    end
103    dir.glob(pattern,function(name)
104        local basename = file.basename(name)
105        if lower(basename) ~= basename then
106            n = n + 1
107            local low = lower(name)
108            if n == 1 then
109                report()
110            end
111            report("%a renamed to %a",name,low)
112            if force then
113                os.rename(name,low)
114            end
115        end
116    end)
117    if n > 0 then
118        report()
119        if force then
120            report("%s files renamed",n)
121        else
122            report("use --force to do a real rename (%s files involved)",n)
123        end
124    else
125        report("nothing to do")
126    end
127end
128
129function scripts.tools.dirtoxml()
130
131    local join, removesuffix, suffixonly, date = file.join, file.removesuffix, file.suffixonly, os.date
132
133    local xmlns      = "http://www.pragma-ade.com/rlg/xmldir.rng"
134    local timestamp  = "%Y-%m-%d %H:%M"
135
136    local pattern    = environment.argument('pattern') or ".*"
137    local url        = environment.argument('url')     or "no-url"
138    local root       = environment.argument('root')    or "."
139    local outputfile = environment.argument('output')
140
141    local recurse    = environment.argument('recurse') or false
142    local stripname  = environment.argument('stripname')
143    local longname   = environment.argument('longname')
144
145    local function flush(list,result,n,path)
146        n, result = n or 1, result or { }
147        local d = rep("  ",n)
148        for name, attr in table.sortedhash(list) do
149            local mode = attr.mode
150            if mode == "file" then
151                result[#result+1] = format("%s<file name='%s'>",d,(longname and path and join(path,name)) or name)
152                result[#result+1] = format("%s  <base>%s</base>",d,removesuffix(name))
153                result[#result+1] = format("%s  <type>%s</type>",d,suffixonly(name))
154                result[#result+1] = format("%s  <size>%s</size>",d,attr.size)
155                result[#result+1] = format("%s  <permissions>%s</permissions>",d,sub(attr.permissions,7,9))
156                result[#result+1] = format("%s  <date>%s</date>",d,date(timestamp,attr.modification))
157                result[#result+1] = format("%s</file>",d)
158            elseif mode == "directory" then
159                result[#result+1] = format("%s<directory name='%s'>",d,name)
160                flush(attr.list,result,n+1,(path and join(path,name)) or name)
161                result[#result+1] = format("%s</directory>",d)
162            end
163        end
164    end
165
166    if not pattern or pattern == ""  then
167        report('provide --pattern=')
168        return
169    end
170
171    if stripname then
172        pattern = file.dirname(pattern)
173    end
174
175    local luapattern = string.topattern(pattern,true)
176
177    lfs.chdir(root)
178
179    local list = dir.collectpattern(root,luapattern,recurse)
180
181    if list[outputfile] then
182        list[outputfile] = nil
183    end
184
185    local result = { "<?xml version='1.0'?>" }
186    result[#result+1] = format("<files url=%q root=%q pattern=%q luapattern=%q xmlns='%s' timestamp='%s'>",url,root,pattern,luapattern,xmlns,date(timestamp))
187    flush(list,result)
188    result[#result+1] = "</files>"
189
190    result = table.concat(result,"\n")
191
192    if not outputfile or outputfile == "" then
193        writeln(result)
194    else
195        io.savedata(outputfile,result)
196    end
197
198end
199
200local function showstring(s)
201    if not characters or not characters.data then
202        require("char-def")
203    end
204    local d = characters.data
205    local f = string.formatters["%U  %s  %-30s  %c"]
206    for c in string.utfvalues(s) do
207        local cs = d[c]
208        print(f(c,cs.category or "",cs.description or "",c))
209    end
210end
211
212function scripts.tools.showstring()
213    local files = environment.files
214    for i=1,#files do
215        showstring(files[i])
216    end
217end
218
219function scripts.tools.showfile()
220    local files = environment.files
221    for i=1,#files do
222        showstring(io.loaddata(files[i]) or "")
223    end
224end
225
226if environment.argument("disarmutfbomb") then
227    scripts.tools.disarmutfbomb()
228elseif environment.argument("dirtoxml") then
229    scripts.tools.dirtoxml()
230elseif environment.argument("downcase") then
231    scripts.tools.downcase()
232elseif environment.argument("exporthelp") then
233    application.export(environment.argument("exporthelp"),environment.files[1])
234elseif environment.argument("showstring") then
235    scripts.tools.showstring()
236elseif environment.argument("showfile") then
237    scripts.tools.showfile()
238else
239    application.help()
240end
241