fix the url stripping
[QuestHelper.git] / Development / build
blob5c82904511d7a1ebe2e220de2e15cc9f510e8aca
1 #!/usr/bin/env lua
3 local compile=false
4 local external=true
5 local archive_zip=true
6 local archive_7z=false
7 local icons=true
8 local debug_build=false
9 local changes_lua = true
10 local condense = true
12 function SplitVersion(ver)
13   return string.gsub(ver, " on .*", ""), string.gsub(ver, ".* on ", "")
14 end
16 function AuthorizedVersion(ver)
17   qhv, wowv = SplitVersion(ver)
18   return qhv ~= "Development Version"
19 end
21 function PreWrath(ver)
22   qhv, wowv = SplitVersion(ver)
23   return wowv:sub(1,1) ~= '3'
24 end
26 for i, a in ipairs({...}) do
27   local mode, option = select(3, string.find(a, "^([%+%-]?)(.*)$"))
28   
29   mode = mode ~= "-"
30   
31   if option == "zip" then archive_zip = mode
32   elseif option == "7z" then archive_7z = mode
33   elseif option == "compile" then compile = mode
34   elseif option == "external" then external = mode
35   elseif option == "icons" then icons = mode
36   elseif option == "changes" then changes_lua = mode
37   elseif option == "debug" then debug_build = mode
38   elseif option == "condense" then condense = mode
39   else
40     print("Unknown option: "..option)
41     return 1
42   end
43 end
45 cache = {}
46 local cache_loader = loadfile("build-cache.lua")
47 if cache_loader then
48   cache_loader()
49   cache_loader = nil
50 end
52 if not cache.removed then cache.removed = {} end
53 if not cache.known then cache.known = {} end
54 if not cache.ignored then cache.ignored = {} end
55 if not cache.uid then cache.uid = {} end
57 local file2uid = {}
58 for uid, data in pairs(cache.uid) do file2uid[data.file] = uid end
60 -- so upgrade.lua doesn't freak out
61 GetTime = function () end
62 QuestHelper_File = {}
63 QuestHelper_Loadtime = {}
65 loadfile("dump.lua")()
66 loadfile("fileutil.lua")()
67 loadfile("../upgrade.lua")()
68 loadfile("compiler.lua")()
69 loadfile("pepperfish.lua")()
70 --loadfile("external.lua")()
72 profiler = newProfiler("time", 1000000)
73 --profiler:start()
75 QuestHelper_BuildZoneLookup()
77 if changes_lua then
78   local stream = io.open("../changes.txt", "r")
79   
80   if stream then
81     local function linkparse(a, b)
82       b = b:gsub("^%s*(.-)%s*$", "%1")
83       if b == "" then
84         return a
85       else
86         return b
87       end
88     end
89     
90     local function totext(text)
91       return text:gsub("%[%[http://www.mandible.net|(.*)%]%]", "%1")
92                  :gsub("%[%s*([^%s%]]+)(.-)%]", linkparse)
93                  :gsub("<code>(.-)</code>", "|cff40bbff%1|r")
94                  :gsub("^%s*==%s*(.-)%s*==%s*$", "|cffffff00       %1|r\n")
95                  :gsub("^%*%*%s", "    • ")
96                  :gsub("^%*%s", "• ")
97                  .."\n"
98     end
99     
100     local count = 0
101     local text = ""
102     
103     while true do
104       line = stream:read("*l")
105       if not line then break end
106       if line:find("^%s*==.*==%s*$") then
107         count = count + 1
108         if count == 6 then
109           break
110         end
111         text = text .. (count == 1 and "" or "\n") .. totext(line)
112       elseif not line:find("^%s*$") then
113         text = text .. totext(line)
114       end
115     end
117     io.close(stream)
118     
119     stream = io.open("../changes.lua", "w")
120     if stream then
121       stream:write(("QuestHelper_ChangeLog=%q\n"):format(text))
122       io.close(stream) 
123     else
124       print("Error writing ../changes.lua")
125     end
126   else
127     print("Error opening ../changes.txt")
128   end
131 if external then
133   --print("Updating Astrolabe.")
134   --FileUtil.updateSVNRepo("http://svn.esamynn.org/astrolabe/branches/wotlk_data_temp", "../Astrolabe")
135   print("Updating ChatThrottleLib.")
136   FileUtil.updateSVNRepo("svn://svn.wowace.com/wow/chatthrottlelib/mainline/trunk", "../ChatThrottleLib")
139 if compile then
140   local function saveCache()
141     local stream = io.open("build-cache.lua", "w")
142     local buffer, prebuf = CreateBuffer(), CreateBuffer()
143     DumpVariable(buffer, prebuf, cache, "cache")
144     stream:write(DumpingComplete(buffer, prebuf))
145     io.close(stream)
146   end
147   
148   local function QuestHelper_IsCorrupted(data)
149     if type(data.QuestHelper_Objectives) == "table" then for version, package in pairs(data.QuestHelper_Objectives) do
150       if AuthorizedVersion(version) and type(package) == "table" then for category, objectives in pairs(package) do
151         if type(objectives) == "table" and PreWrath(version) then for name, objective in pairs(objectives) do
152           if type(objective) == "table" and objective.pos and type(objective.pos) == "table" then for i, pos in pairs(objective.pos) do
153             if pos[1] and pos[1] >= 65 then
154               print("SOMETHING IS SERIOUSLY WRONG, found Wrath coordinates in non-Wrath files (version " .. version .. ", continent " .. pos[1] .. ")")
155               return true
156             end
157           end end
158         end end
159       end end
160     end end
161   end
162   
163   -- Download the latest copy of the translated item/npc names.
164   if external then
165     os.execute("wget http://smariot.hopto.org/wowdata.7z -N")
166     
167     if FileUtil.fileExists("wowdata.7z") then
168       local hash = FileUtil.fileHash("wowdata.7z")
169       if cache.wowdata_hash ~= hash then
170         -- If the archive has changed, extract the lua file from the archive.
171         cache.wowdata_hash = hash
172         os.execute("7z e -y wowdata.7z wowdata.lua")
173       end
174     end
175   end
176   
177   if FileUtil.fileExists("wowdata.lua") then
178     loadfile("wowdata.lua")()
179   end
181   local all_input_files, unknown_input_files = {}, {}
183   local since_last_save = 0
185   FileUtil.forEachFile("LocalInput", function (name)
186     if FileUtil.extension(name) == "lua" or 
187        FileUtil.extension(name) == "bak" then
188        
189       local hash = FileUtil.fileHash(name)
190       if cache.ignored[hash] then return end
192       if since_last_save == 100 then
193         print("Collecting garbage and saving . . .")
194         collectgarbage("collect")
195         saveCache()
196         since_last_save = 0
197       end
198       since_last_save = since_last_save + 1
199     
200       cache.ignored[hash] = true
201       
202       local input = loadfile(name)
203       if input then
204         local data = {}
205         setfenv(input, data)
206         if pcall(input) then
207           if not (data.QuestHelper_Locale and data.QuestHelper_Objectives) then
208             print("! "..name.." isn't a QuestHelper SavedVariables file.")
209             return
210           end
211           
212           local upgradable_data = {}
213           setfenv(input, upgradable_data)
214           input()
215           QuestHelper_UpgradeDatabase(upgradable_data)
216           if QuestHelper_IsPolluted(upgradable_data) then
217             print("!! "..name.." is polluted")
218             return
219           end
220           if QuestHelper_IsCorrupted(upgradable_data) then
221             print("!! "..name.." is corrupted")
222             return
223           end
224           if not upgradable_data.QuestHelper_UID then
225             print("!! "..name.." has no UID")
226             return
227           end
228           
229           local tempname = os.tmpname()
230           local stream = io.open(tempname, "w")
231           if stream then
232             print("Copying/Sorting "..name)
233             stream:write(ScanAndDumpVariable(data, nil, true) or "")
234             io.close(stream)
235             hash = FileUtil.fileHash(tempname)
236             if hash then
237               local input_name = "Input/"..hash..".lua"
238               if not cache.removed[input_name] then
239                 if not cache.known[input_name] then
240                   FileUtil.copyFile(tempname, input_name)
241                   unknown_input_files[input_name] = name
242                 else
243                   cache.known[input_name] = name
244                 end
245               else
246                 cache.removed[input_name] = name
247               end
248             else
249               print("!!! Can't get hash of "..tempname..", for "..name)
250             end
251             FileUtil.unlinkFile(tempname)
252           end
253         else
254           print("!!! "..name.." couldn't be executed.")
255         end
256       else
257         print("!!! "..name.." couldn't be loaded.")
258       end
259     end
260   end)
262   collectgarbage("collect")
263   saveCache()
264   
265   FileUtil.forEachFile("Input", function (name)
266     if cache.removed[name] then
267       print("!!! Obsolete: ", cache.removed[name].." ("..name..")")
268       os.remove(name)
269     else
270       if not cache.known[name] then
271         unknown_input_files[name] = unknown_input_files[name] or name
272       end
273       
274       all_input_files[name] = cache.known[name] or unknown_input_files[name]
275     end
276   end)
278   local function ProcessObjective(category, name, objective, result)
279     local istring = "obj."..category.."."..name
280     
281     if category ~= "item" then
282       local seen = 0
283       if objective.pos then for i, pos in pairs(objective.pos) do
284         seen = seen + pos[4]
285       end end
286       
287       result[istring..".seen"] = (result[istring..".seen"] or 0) + seen
288     end
289     
290     if objective.vendor then
291       result[istring..".vend"] = (result[istring..".vend"] or 0) + #objective.vendor
292     end
293     
294     if objective.drop then for monster, count in pairs(objective.drop) do
295       result[istring] = (result[istring] or 0) + count
296     end end
297   end
299   local function ProcessQuest(faction, level, name, quest, result)
300     local qstring = "quest."..faction.."."..level.."."..name
301     result[qstring] = (result[qstring] or 0)+((quest.finish or quest.pos) and 1 or 0)
302     
303     if quest.item then for item_name, data in pairs(quest.item) do
304       ProcessObjective("item", item_name, data, result)
305     end end
306     
307     if quest.alt then for _, quest2 in pairs(quest.alt) do
308       ProcessQuest(faction, level, name, quest2, result)
309     end end
310   end
311   
312   local function LoadFile(file)
313     local data = loadfile(file)
314     local result = {}
315     if data then
316       local loaded = {}
317       if not pcall(setfenv(data, loaded)) then
318         print("!!!!! oh god something is wrong "..file)
319         return
320       end
321          
322       data()
323       
324       QuestHelper_UpgradeDatabase(loaded)
325       
326       if loaded.QuestHelper_UID then
327         result.uid = loaded.QuestHelper_UID
328         result.time = loaded.QuestHelper_SaveDate
329       else
330         if type(loaded.QuestHelper_Quests) == "table" then for version, package in pairs(loaded.QuestHelper_Quests) do
331           if AuthorizedVersion(version) and type(package) == "table" then for faction, levels in pairs(package) do
332             if type(levels) == "table" then for level, quest_list in pairs(levels) do
333               if type(quest_list) == "table" then for name, quest in pairs(quest_list) do
334                 ProcessQuest(faction, level, name, quest, result)
335               end end
336             end end
337           end end
338         end end
339         
340         if type(loaded.QuestHelper_Objectives) == "table" then for version, package in pairs(loaded.QuestHelper_Objectives) do
341           if AuthorizedVersion(version) and type(package) == "table" then for faction, levels in pairs(package) do
342             if type(objectives) == "table" then for name, objective in pairs(objectives) do
343               ProcessObjective(category, name, objective, result)
344             end end
345           end end
346         end end
347       end
348     end
349     
350     return result
351   end
353   local function ObsoletedBy(data1, data2)
354     if data1.uid or data2.uid then
355       return data1.loc == data2.loc and data1.uid == data2.uid and (data1.time or 0) >= (data2.time or 0)
356     end
357     
358     for key, value in pairs(data1) do
359       local value2 = data2[key]
360       if value2 == nil or value2 < value then
361         return false
362       end
363     end
364     return true
365   end
367   local checked = {}
368   local file_data = {}
369   
370   -- This entire mess is built to find obsolete files and remove them.
371   for new_name, original_name in pairs(unknown_input_files) do
372     print("Checking: ", original_name)
373     local data = file_data[new_name]
374     if not data then
375       data = LoadFile(new_name)
376       file_data[new_name] = data
377     end
378     cache.known[new_name] = original_name
379     checked[new_name] = true
380     
381     if data then
382       local uid, last_save = data.uid, data.time
383       
384       if uid then
385         local existing = cache.uid[uid]
386         if not existing then
387           cache.uid[uid] = {file=new_name, save=last_save}
388           file2uid[new_name] = uid
389         else
390           if existing.save >= last_save then
391             print("!!! Obsolete: ", original_name)
392             print("!!!       By: ", all_input_files[existing.file])
393             print("")
394             os.remove(new_name)
395             file_data[new_name] = nil
396             all_input_files[new_name] = nil
397             cache.removed[new_name] = original_name
398             cache.known[new_name] = nil
399             unknown_input_files[new_name] = nil
400           else
401             print("!!! Obsolete: ", all_input_files[existing.file])
402             print("!!!       By: ", original_name)
403             print("")
404             
405             os.remove(existing.file)
406             file_data[existing.file] = nil
407             file2uid[existing.file] = nil
408             file2uid[new_name] = uid
409             cache.removed[existing.file] = all_input_files[existing.file]
410             all_input_files[existing.file] = nil
411             cache.known[existing.file] = nil
412             existing.file = new_name
413           end
414         end
415       else
416         for existing_name, existing_original_name in pairs(all_input_files) do
417           if not checked[existing_name] and not file2uid[existing_name] then
418             local data2 = file_data[existing_name]
419             if not data2 then
420               data2 = LoadFile(existing_name)
421               file_data[existing_name] = data2
422             end
423             
424             if data2 and not data2.uid then
425               if ObsoletedBy(data, data2) then
426                 print("!!! Obsolete: ", original_name)
427                 print("!!!       By: ", existing_original_name)
428                 print("")
429                 
430                 os.remove(new_name)
431                 file_data[new_name] = nil
432                 all_input_files[new_name] = nil
433                 cache.removed[new_name] = original_name
434                 cache.known[new_name] = nil
435                 unknown_input_files[new_name] = nil
436                 break
437               elseif ObsoletedBy(data2, data) then
438                 print("!!! Obsolete: ", existing_original_name)
439                 print("!!!       By: ", original_name)
440                 print("")
441                 
442                 os.remove(existing_name)
443                 file_data[existing_name] = nil
444                 all_input_files[existing_name] = nil
445                 cache.removed[existing_name] = existing_original_name
446                 cache.known[existing_name] = nil
447                 unknown_input_files[existing_name] = nil
448               end
449             end
450           end
451         end
452       end
453     end
454   end
455   
456   checked, file_data = nil, nil
457   
458   saveCache()
459   collectgarbage("collect")
461   --print("Compiling Lightheaded/eql3 data. . .")
462   --ProcessExternal()
463   
464   local total_count = 0
465   local counter = 0
466   local pairfrequencies = {}
467   for name, origin in pairs(all_input_files) do
468     total_count = total_count + 1       -- I fuckin' hate Lua
469   end
470   for name, origin in pairs(all_input_files) do
471     counter = counter + 1
472     print("Compiling " .. counter .. "/" .. total_count .. ": ", origin)
473     --CompileInputFile(name)
474     local ret, msg = pcall(CompileInputFile, name, pairfrequencies)
475     if not ret then
476       print("!!!!! FAILURE")
477       print(msg)
478     end
479   end
480   
481   print("Processing final data")
482   local finaldata = CompileFinish()
483   
484   local finalstatic = io.open("../static.lua", "w")
485   finalstatic:write("QuestHelper_File[\"static.lua\"] = \"Development Version\"\n")
486   finalstatic:write("QuestHelper_Loadtime[\"static.lua\"] = GetTime()\n")
487   finalstatic:write("QuestHelper_StaticData={")
488   
489   for k, v in pairs(finaldata) do
490     collectgarbage("collect")
491     local foloc = string.format("static_%s.lua", k)
492     local finalout = "../" .. foloc
493     local tempout = finalout
494     if condense then tempout = string.format("static_%s.large", k) end
495     
496     print("Writing: " .. tempout)
497     local stream = io.open(tempout, "w")
498     stream:write(string.format("QuestHelper_File[\"%s\"] = \"Development Version\"\n", foloc))
499     stream:write(string.format("QuestHelper_Loadtime[\"%s\"] = GetTime()\n", foloc))
500     stream:write(string.format("if GetLocale() ~= \"%s\" then return end\n", k))  -- wellp
501     stream:write(ScanAndDumpVariable(v, string.format("QuestHelper_StaticData_%s", k)))
502     io.close(stream)
503     
504     if condense then
505       print("Condensing " .. finalout)
506       assert(os.execute(string.format("lua LuaSrcDiet.lua --maximum %s -o %s", tempout, finalout)) ~= -1)
507       if false then
508         local data_loader = loadfile("LuaSrcDiet.lua")
509         local data = {arg={"--maximum", tempout, "-o", finalout}, TEST=true, string=string, print=print, table=table, ipairs=ipairs, error=error, io=io, tonumber=tonumber --[[ assert=assert, FileUtil=FileUtil, tostring=tostring ]]}  -- there has to be a better way to do this
510         setfenv(data_loader, data)
511         data_loader()
512         data.main()
513       end
514     end
515     
516     finalstatic:write(string.format("  %s=QuestHelper_StaticData_%s,", k, k))
517     --assert(FileUtil.fileContains("../QuestHelper.toc", foloc), "didn't contain " .. foloc)
518   end
519   
520   finalstatic:write("}")
521   io.close(finalstatic)
522   
523   local qhwowv = {}
524   local qhfrequencies = {}
525   local wowfrequencies = {}
526   
527   for k, v in pairs(pairfrequencies) do
528     table.insert(qhwowv, {freq = v, tag = k})
529     local qh, wow = SplitVersion(k)
530     qhfrequencies[qh] = (qhfrequencies[qh] or 0) + v
531     wowfrequencies[wow] = (wowfrequencies[wow] or 0) + v
532   end
533   
534   local qhv = {}
535   local wowv = {}
536   
537   for k, v in pairs(qhfrequencies) do table.insert(qhv, {freq = v, tag = k}) end
538   for k, v in pairs(wowfrequencies) do table.insert(wowv, {freq = v, tag = k}) end
539   
540   table.sort(qhwowv, function (a, b) return a.freq < b.freq end)
541   table.sort(qhv, function (a, b) return a.freq < b.freq end)
542   table.sort(wowv, function (a, b) return a.freq < b.freq end)
544   for k, v in pairs(qhwowv) do
545     print(string.format("%d: %s", v.freq, v.tag))
546   end
547   print("")
548   for k, v in pairs(qhv) do
549     print(string.format("%d: %s", v.freq, v.tag))
550   end
551   print("")
552   for k, v in pairs(wowv) do
553     print(string.format("%d: %s", v.freq, v.tag))
554   end
555 elseif not FileUtil.fileExists("../static.lua") then
556   print("../static.lua doesn't exist; you can't skip the compile step.")
557   return 1
560 if icons then
561   print("Creating: Icons.tga")
562   --if not FileUtil.fileExists("../Art/Icons.tga") then
563   --  print("You'll need to manually create Art/Icons.tga, ImageMagick's SVG support seems to have been broken recently.")
564   --end
565   FileUtil.convertImage("Data/art.svg", "../Art/Icons.tga")
568 local version_string = "UNKNOWN"
570 if archive_zip or archive_7z then
571   FileUtil.unlinkDirectory("QuestHelper")
572   
573   FileUtil.createDirectory("QuestHelper")
574   FileUtil.createDirectory("QuestHelper/Art")
575   FileUtil.createDirectory("QuestHelper/Fonts")
576   
577   local info = nil
578   
579   -- Try to coax git to give us something to use for the version.
580   local stream = io.popen("git describe --tags HEAD", "r")
581   if stream then
582     info = stream:read("*line")
583     if info then
584       -- Replace the first dash with a dot.
585       info = string.gsub(info, "^(.*)%-(.*)%-g(.*)$", "%1%.%2%-%3", 1)
586     end
587     io.close(stream)
588   end
589   
590   if not info then
591     -- Didn't get a nice looking tag to use, try to at least get a hash for the version.
592     stream = io.popen("git-log -1 --pretty=\"format:%2-%h\"", "r")
593     if stream then
594       info = stream:read("*line")
595       io.close(stream)
596     end
597   end
598   
599   if not info then
600     -- We have no idea what version this is, will use the string already in the TOC.
601     info = "%2"
602   end
603   
604   FileUtil.copyFile("../QuestHelper.toc", "QuestHelper", "^(##%s-Version%s-):%s*(.*)%s*$",
605     function (a, b)
606       version_string = string.gsub(info, "%%2", b) return a..": "..version_string
607     end)
608   
609   local file_version_pattern = "%1"..string.format("%q", version_string)
610   
611   FileUtil.forEachFile("..", function (file)
612     local ext = FileUtil.extension(file)
613     if ext == "lua" or ext == "xml" then
614       if debug_build then
615         FileUtil.copyFile(file, "QuestHelper")
616       else
617         FileUtil.copyFile(file, "QuestHelper",
618                                                "(assert%s*%b())", "--[[ %1 ]]",
619                                                "(QuestHelper:Assert%s*%b())", "--[[ %1 ]]",
620                                                "(QuestHelper_File%[[^%]]*%]%s*=%s*)\"[^\"]+\"", file_version_pattern)
621       end
622     end
623   end)
624   
625   FileUtil.forEachFile("../Art", function (file)
626     local ext = FileUtil.extension(file)
627     if ext == "blp" or ext == "tga" then
628       FileUtil.copyFile(file, "QuestHelper/Art")
629     end
630   end)
631   
632   FileUtil.forEachFile("../Fonts", function (file)
633     local ext = FileUtil.extension(file)
634     if ext == "txt" or ext == "ttf" then
635       FileUtil.copyFile(file, "QuestHelper/Fonts")
636     end
637   end)
638   
639   for i, dir in ipairs({"AstrolabeQH", "ChatThrottleLib", "LibAboutPanel", "lang"}) do
640     FileUtil.copyDirectoryRecursively("../"..dir, "QuestHelper/"..dir)
641   end
642   
643   FileUtil.copyFile("../MinimapArrow.tga", "QuestHelper")
644   FileUtil.copyFile("../arrow_image.blp", "QuestHelper")
645   FileUtil.copyFile("../arrow_image_down.blp", "QuestHelper")
646   FileUtil.copyFile("../triangle.tga", "QuestHelper")
647   FileUtil.copyFile("../line.tga", "QuestHelper")
648   
649   if archive_zip then
650     local archive = "../QuestHelper-"..version_string..".zip"
651     print("Creating "..archive)
652     FileUtil.unlinkFile(archive)
653     FileUtil.createZipArchive("QuestHelper", archive)
654   end
655   
656   if archive_7z then
657     local archive = "../QuestHelper-"..version_string..".7z"
658     print("Creating "..archive)
659     FileUtil.unlinkFile(archive)
660     FileUtil.create7zArchive("QuestHelper", archive)
661   end
662   
663   FileUtil.unlinkDirectory("QuestHelper")
666 print("Done!")
668 --profiler:stop()
669 --local outfile = io.open("profile.txt", "w+")
670 --profiler:report(outfile)
671 --outfile:close()