util-mrg.lua /size: 7819 b    last modification: 2020-07-01 14:35
1if not modules then modules = { } end modules ['util-mrg'] = {
2    version   = 1.001,
3    comment   = "companion to luat-lib.mkiv",
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-- hm, quite unreadable
10
11local gsub, format = string.gsub, string.format
12local concat = table.concat
13local type, next = type, next
14
15local P, R, S, V, Ct, C, Cs, Cc, Cp, Cmt, Cb, Cg = lpeg.P, lpeg.R, lpeg.S, lpeg.V, lpeg.Ct, lpeg.C, lpeg.Cs, lpeg.Cc, lpeg.Cp, lpeg.Cmt, lpeg.Cb, lpeg.Cg
16local lpegmatch, patterns = lpeg.match, lpeg.patterns
17
18utilities             = utilities or { }
19local merger          = utilities.merger or { }
20utilities.merger      = merger
21merger.strip_comment  = true
22
23local report          = logs.reporter("system","merge")
24utilities.report      = report
25
26local m_begin_merge   = "begin library merge"
27local m_end_merge     = "end library merge"
28local m_begin_closure = "do -- create closure to overcome 200 locals limit"
29local m_end_closure   = "end -- of closure"
30
31local m_pattern =
32    "%c+" ..
33    "%-%-%s+" .. m_begin_merge ..
34    "%c+(.-)%c+" ..
35    "%-%-%s+" .. m_end_merge ..
36    "%c+"
37
38local m_format =
39    "\n\n-- " .. m_begin_merge ..
40    "\n%s\n" ..
41    "-- " .. m_end_merge .. "\n\n"
42
43local m_faked =
44    "-- " .. "created merged file" .. "\n\n" ..
45    "-- " .. m_begin_merge .. "\n\n" ..
46    "-- " .. m_end_merge .. "\n\n"
47
48local m_report = [[
49-- used libraries    : %s
50-- skipped libraries : %s
51-- original bytes    : %s
52-- stripped bytes    : %s
53]]
54
55local m_preloaded = [[package.loaded[%q] = package.loaded[%q] or true]]
56
57local function self_fake()
58    return m_faked
59end
60
61local function self_nothing()
62    return ""
63end
64
65local function self_load(name)
66    local data = io.loaddata(name) or ""
67    if data == "" then
68        report("unknown file %a",name)
69    else
70        report("inserting file %a",name)
71    end
72    return data or ""
73end
74
75-- -- saves some 20K .. scite comments
76-- data = gsub(data,"%-%-~[^\n\r]*[\r\n]","")
77-- -- saves some 20K .. ldx comments
78-- data = gsub(data,"%-%-%[%[ldx%-%-.-%-%-ldx%]%]%-%-","")
79
80local space           = patterns.space
81local eol             = patterns.newline
82local equals          = P("=")^0
83local open            = P("[") * Cg(equals,"init") * P("[") * P("\n")^-1
84local close           = P("]") * C(equals) * P("]")
85local closeeq         = Cmt(close * Cb("init"), function(s,i,a,b) return a == b end)
86local longstring      = open * (1 - closeeq)^0 * close
87
88local quoted          = patterns.quoted
89local digit           = patterns.digit
90local emptyline       = space^0 * eol
91local operator1       = P("<=") + P(">=") + P("~=") + P("..") + S("/^<>=*+%%")
92local operator2       = S("*+/")
93local operator3       = S("-")
94local operator4       = P("..")
95local separator       = S(",;")
96
97local ignore          = (P("]") * space^1 * P("=") * space^1 * P("]")) / "]=[" +
98                        (P("=") * space^1 * P("{")) / "={" +
99                        (P("(") * space^1) / "(" +
100                        (P("{") * (space+eol)^1 * P("}")) / "{}"
101local strings         = quoted --  / function (s) print("<<"..s..">>") return s end
102local longcmt         = (emptyline^0 * P("--") * longstring * emptyline^0) / ""
103local longstr         = longstring
104local comment         = emptyline^0 * P("--") * P("-")^0 * (1-eol)^0 * emptyline^1 / "\n"
105local optionalspaces  = space^0 / ""
106local mandatespaces   = space^1 / ""
107local optionalspacing = (eol+space)^0 / ""
108local mandatespacing  = (eol+space)^1 / ""
109local pack            = digit * space^1 * operator4 * optionalspacing +
110                        optionalspacing * operator1 * optionalspacing +
111                        optionalspacing * operator2 * optionalspaces  +
112                        mandatespacing  * operator3 * mandatespaces   +
113                        optionalspaces  * separator * optionalspaces
114local lines           = emptyline^2 / "\n"
115local spaces          = (space * space) / " "
116local spaces          = (space * space * space * space) / " "
117----- spaces          = ((space+eol)^1 ) / " "
118
119local compact = Cs ( (
120    ignore  +
121    strings +
122    longcmt +
123    longstr +
124    comment +
125    pack    +
126    lines   +
127    spaces  +
128    1
129)^1 )
130
131local strip       = Cs((emptyline^2/"\n" + 1)^0)
132local stripreturn = Cs((1-P("return") * space^1 * P(1-space-eol)^1 * (space+eol)^0 * P(-1))^1)
133
134function merger.compact(data)
135    return lpegmatch(strip,lpegmatch(compact,data))
136end
137
138local function self_compact(data)
139    local delta = 0
140    if merger.strip_comment then
141        local before = #data
142        data = lpegmatch(compact,data)
143        data = lpegmatch(strip,data) -- also strips in longstrings ... alas
144     -- data = string.strip(data)
145        local after = #data
146        delta = before - after
147        report("original size %s, compacted to %s, stripped %s",before,after,delta)
148        data = format("-- original size: %s, stripped down to: %s\n\n%s",before,after,data)
149    end
150    return lpegmatch(stripreturn,data) or data, delta
151end
152
153local function self_save(name, data)
154    if data ~= "" then
155        io.savedata(name,data)
156        report("saving %s with size %s",name,#data)
157    end
158end
159
160local function self_swap(data,code)
161    return data ~= "" and (gsub(data,m_pattern, function() return format(m_format,code) end, 1)) or ""
162end
163
164local function self_libs(libs,list)
165    local result, f, frozen, foundpath = { }, nil, false, nil
166    result[#result+1] = "\n"
167    if type(libs) == 'string' then libs = { libs } end
168    if type(list) == 'string' then list = { list } end
169    for i=1,#libs do
170        local lib = libs[i]
171        for j=1,#list do
172            local pth = gsub(list[j],"\\","/") -- file.clean_path
173            report("checking library path %a",pth)
174            local name = pth .. "/" .. lib
175            if lfs.isfile(name) then
176                foundpath = pth
177            end
178        end
179        if foundpath then break end
180    end
181    if foundpath then
182        report("using library path %a",foundpath)
183        local right, wrong, original, stripped = { }, { }, 0, 0
184        for i=1,#libs do
185            local lib = libs[i]
186            local fullname = foundpath .. "/" .. lib
187            if lfs.isfile(fullname) then
188                report("using library %a",fullname)
189                local preloaded = file.nameonly(lib)
190                local data = io.loaddata(fullname,true)
191                original = original + #data
192                local data, delta = self_compact(data)
193                right[#right+1] = lib
194                result[#result+1] = m_begin_closure
195                result[#result+1] = format(m_preloaded,preloaded,preloaded)
196                result[#result+1] = data
197                result[#result+1] = m_end_closure
198                stripped = stripped + delta
199            else
200                report("skipping library %a",fullname)
201                wrong[#wrong+1] = lib
202            end
203        end
204        right = #right > 0 and concat(right," ") or "-"
205        wrong = #wrong > 0 and concat(wrong," ") or "-"
206        report("used libraries: %a",right)
207        report("skipped libraries: %a",wrong)
208        report("original bytes: %a",original)
209        report("stripped bytes: %a",stripped)
210        result[#result+1] = format(m_report,right,wrong,original,stripped)
211    else
212        report("no valid library path found")
213    end
214    return concat(result, "\n\n")
215end
216
217function merger.selfcreate(libs,list,target)
218    if target then
219        self_save(target,self_swap(self_fake(),self_libs(libs,list)))
220    end
221end
222
223function merger.selfmerge(name,libs,list,target)
224    self_save(target or name,self_swap(self_load(name),self_libs(libs,list)))
225end
226
227function merger.selfclean(name)
228    self_save(name,self_swap(self_load(name),self_nothing()))
229end
230