Difference between revisions of "Module:Data"

From The Alchemist Code Wiki
Jump to: navigation, search
(Undo test - no change)
Tag: Undo
m (decrease header level.)
Line 115: Line 115:
 
table.sort(fields, function(a, b) return a == "server" or a == "lang" or (b ~= "server" and b ~= "lang" and (a or '') < (b or '')) end)
 
table.sort(fields, function(a, b) return a == "server" or a == "lang" or (b ~= "server" and b ~= "lang" and (a or '') < (b or '')) end)
 
if count == 0 then return nil end
 
if count == 0 then return nil end
if title ~= nil then table.insert(result, "=="..title.."==\n") end
+
if title ~= nil then table.insert(result, "==="..title.."===\n") end
 
table.insert(result, '{| class="wikitable"')
 
table.insert(result, '{| class="wikitable"')
 
for i, col in ipairs(fields) do
 
for i, col in ipairs(fields) do
Line 154: Line 154:
 
end
 
end
 
 
if next(curNode.model) ~= nil then table.insert(result, "=Tables=\nThe following tables have these rows defined by this data entry.\n") end
+
if next(curNode.model) ~= nil then table.insert(result, "==Tables==\nThe following tables have these rows defined by this data entry.\n") end
 
 
 
-- Decode JSON
 
-- Decode JSON
Line 369: Line 369:
 
 
 
if next(jsonEffects) then
 
if next(jsonEffects) then
table.insert(result, '==ConceptCardEffect==\n')
+
table.insert(result, '===ConceptCardEffect===\n')
 
local toInsert = {}
 
local toInsert = {}
 
for k in pairs(jsonEffects) do table.insert(toInsert, k) end
 
for k in pairs(jsonEffects) do table.insert(toInsert, k) end
Line 393: Line 393:
 
end end
 
end end
 
if unused then
 
if unused then
table.insert(result, "==Unused Keys==\nThe following keys were not used by inserted into any table and are thus unused.\n")
+
table.insert(result, "===Unused Keys===\nThe following keys were not used by inserted into any table and are thus unused.\n")
 
local toInsert = {}
 
local toInsert = {}
 
for k in pairs(jsonSets) do table.insert(toInsert, k) end
 
for k in pairs(jsonSets) do table.insert(toInsert, k) end

Revision as of 12:41, 18 November 2020

Documentation for this module may be created at Module:Data/doc

local p = {}
local cargo = require('Module:CargoUtil')
local dataModel = require("Module:Data/Model")
local util_text = require('Module:TextUtil')
local split = util_text.split
local gsplit = util_text.gsplit
local trim = util_text.trim

function getTitlePath()
    local curTitle = mw.title.getCurrentTitle()
    --if curTitle.prefixedText == "Module:Data" then curTitle = mw.title.new("Game/MasterParam/Unit/UN_V2_LOGI", "Data") end
    local titleNodes = {curTitle.subpageText}
    while curTitle.baseText ~= curTitle.text do
        curTitle = curTitle.basePageTitle
        table.insert(titleNodes, 1, curTitle.subpageText)
    end
    return titleNodes
end

-- Will only actually perform the declares on the CargoTable template pages.
function p.init(frame)
	local args = require('Module:Arguments').getArgs(frame, {parentFirst=true})
	return p._init(args)
end
function p._init(args)
	local yesno = require('Module:Yesno')
    local declareStatements = {}
    local recFunc
    function recFunc(node)
        if node.nodes ~= nil then for k, child in pairs(node.nodes) do recFunc(child) end end
        if node.model ~= nil then for tbl, model in pairs(node.model) do
            local statement = {"", _table = tbl, server = "String"}
            for col, colModel in pairs(model) do
                if type(colModel) == "string" then colModel = {colModel} end
                local modelType = colModel[1]
                if colModel.list ~= nil then modelType = "List ("..colModel.list..") of "..modelType end
                statement[col] = modelType
            end
            declareStatements[tbl] = statement
        end end
    end
    recFunc(dataModel)

    local curTitle = mw.title.getCurrentTitle()
	if args[1] then
		curTitle = mw.title.new(args[1])
	end
    if curTitle.basePageTitle.prefixedText == "Template:CargoTable" then
        local statement = declareStatements[curTitle.subpageText]
        if statement == nil then return "Invalid table "..curTitle.subpageText end
    	local tbl = {'{{#cargo_declare:', '}}'}
    	for k,v in pairs(statement) do
    		if k ~= 1 then
    			tbl[#tbl+1] = '| ' .. k..' = '..v
    		end
    	end
    	table.sort(tbl)
    	local declare = table.concat(tbl, '\n')
    	if yesno(args.debug) then
        	return tostring(mw.html.create('pre'):wikitext(declare))
        else
        	mw.log(declare)
	        return mw.getCurrentFrame():callParserFunction('#cargo_declare', statement)
	    end
    end
    if mw.getCurrentFrame():getParent() == nil then
        return nil
    end
    return "asdf"
end

-- Called from Data pages
function p.insert(frame)
	local args = require('Module:Arguments').getArgs(frame, {parentFirst = true})
	return p._insert(args)
end
function p._insert(args)
    local curNode = {nodes = {Game = dataModel}}
    local titlePath = getTitlePath()
    for i, pathItem in ipairs(titlePath) do
        if curNode == nil then return nil end
        if i == #titlePath then break end
        if curNode.nodes == nil then return nil end
        curNode = curNode.nodes[pathItem]
    end
    
    if curNode.model == nil then return nil end

    -- Now that we know this is a valid page
    local result = {}
    
    -- Generates table for data page
    local insertQueryResult
    function insertQueryResult(title, tbl)
        local fields = {}
        local found = {}
        local count = 0
        local hideCols = true
        for i, row in pairs(tbl) do
            count = count + 1
            for col in ipairs(row) do 
                if found[col] == nil then
                    table.insert(fields, col)
                    found[col] = true
                end
            end
            for col in pairs(row) do 
                if found[col] == nil then
                    table.insert(fields, col)
                    found[col] = true
                    hideCols = false
                end
            end
        end
        table.sort(fields, function(a, b) return a == "server" or a == "lang" or (b ~= "server" and b ~= "lang" and (a or '') < (b or '')) end)
        if count == 0 then return nil end
        if title ~= nil then table.insert(result, "==="..title.."===\n") end
        table.insert(result, '{| class="wikitable"')
        for i, col in ipairs(fields) do
            table.insert(result, "\n|-")
            if not hideCols then
                table.insert(result, "\n! ")
                table.insert(result, col)
            end
            
            -- Merge matching rows
            local allSame = true
            local testVal = tbl[next(tbl)][col]
            for j, row in pairs(tbl) do
                if type(testVal) == 'table' and type(row[col]) == 'table' then
                    for k, v in pairs(testVal) do if v ~= row[col][k] then allSame = false end end
                    for k, v in pairs(row[col]) do if v ~= testVal[k] then allSame = false end end
                elseif row[col] ~= testVal then allSame = false end
            end
            
            local function writeVal(val)
                if type(val) == "table" then
                    table.insert(result, "\n")
                    insertQueryResult(nil, {val})
                else table.insert(result, tostring(val or '')) end
            end
            
            if allSame then
                table.insert(result, '\n| colspan="'..count..'" | ')
                writeVal(testVal)
            else
                for j, row in pairs(tbl) do
                    table.insert(result, "\n| ")
                    writeVal(row[col])
                end
            end
        end
        table.insert(result, "\n|}\n")
    end
    
    if next(curNode.model) ~= nil then table.insert(result, "==Tables==\nThe following tables have these rows defined by this data entry.\n") end
    
    -- Decode JSON
    local jsonDecode = mw.text.jsonDecode
    local unstripNoWiki = mw.text.unstripNoWiki
    local jsonSets = {}
    local content_sha256s = {}
    local suffix = '_sha256'
    for server, json in pairs(args) do
        json = unstripNoWiki(json)
    	if server == 'gl' or server == 'jp' or server == 'loc' then
    	    jsonSets[server] = jsonDecode(json)
    	elseif string.sub(server, -string.len(suffix)) == suffix then
    		-- TODO: Store this in a cargo table somewhere.
    		content_sha256s[string.sub(server, 1, -string.len(suffix))] = json
    	end
	end
    
    -- Convert Functions
    local convertFrom = {
        table = function(val, model)
            if model.list ~= nil then
                return table.concat(val, model.list)
            end
            error("JSON data for "..model[1].." is a table but is not modeled to handle it.")
        end,
    }
    
    -- Insert Rows
    local where = '_pageId='..mw.title.getCurrentTitle().id
    local errors = {}
    
    -- Do loc first
    local locSets = jsonSets.loc or {}
    jsonSets.loc = nil
    local expected = 0
    table.insert(result, mw.getCurrentFrame():expandTemplate{ title = "CargoTable/Loc" })
    for param, json in pairs(locSets) do
    	if type(json) == "table" then
	        for lang, value in pairs(json) do
	            table.insert(result, cargo.store{
	            	_table = 'Loc',
	            	lang = lang,
	            	param = param,
	            	value = value,
	            })
	            expected = expected + 1
	        end
        end
    end
    -- Query loc table
    local rows = cargo.query{
    	tables = 'Loc',
    	fields = 'lang, param, value',
    	where = where,
    	orderBy = 'lang'
    }
	-- Check if expected row count matches actual row count.
    local actual = #rows
    if actual ~= expected then
        table.insert(result, string.format('Entry count mismatch in table %q (%s expected, got %s)\n', 'Loc', expected, actual))
        errors.cargoStore = true
    end
    
    -- Generate loc tables from queried data
    local queryResult = {}
    for i, row in ipairs(rows) do
        queryResult[i] = row
    end
    insertQueryResult(tbl, queryResult)
    
    local iname
    for server, json in pairs(jsonSets) do
    	if not iname and json.iname then
    		iname = json.iname
    	end
	end

	local actualTitle = table.concat(titlePath, '/', 1, 3)
	mw.logObject(actualTitle, 'actualTitle')
    if actualTitle == 'Game/MasterParam/Buff' then
        for server, json in pairs(jsonSets) do
            for i=1,11 do
            	local calc = tonumber(json['calc'..i])
            	local _type = tonumber(json['type'..i]) or 0
            	local tktag = (json['tktag'..i] or '') ~= '' and json['tktag'..i] or nil
            	local vini = tonumber(json['vini'..i])
            	local vmax = tonumber(json['vmax'..i])
            	local vone = tonumber(json['vone'..i])
                local values = {
                    _table = 'BuffDetail',
                    idx = i,
                    buff_iname = iname,
                    server = server,
                    calc = calc,
                    tktag = tktag,
                    ['type'] = _type,
                    vini = vini,
                    vmax = vmax,
                    vone = vone,
                }
            	if _type <= 0 then
            		mw.logObject(values, "Skipped insert into BuffDetail values")
                else
                    table.insert(result, cargo.store(values))
                    -- json['calc'..i] = nil
                    -- json['tktag'..i] = nil
                    -- json['type'..i] = nil
                    -- json['vini'..i] = nil
                    -- json['vmax'..i] = nil
                    -- json['vone'..i] = nil
                end
            end
        end
    end

    -- Now get the data
    for tbl, model in pairs(curNode.model) do
        table.insert(result, mw.getCurrentFrame():expandTemplate{ title = "CargoTable/"..tbl })
        expected = 0
        for server, json in pairs(jsonSets) do
            local statement = {_table = tbl, server = server}
            for col, colModel in pairs(model) do
                if type(colModel) == "string" then colModel = {colModel} end
                -- Extract value
                local jsonVal = json[colModel.from or col]
                json[colModel.from or col] = nil
                -- Convert datatypes here
                local converter = convertFrom[type(jsonVal)]
                if converter ~= nil then jsonVal = converter(jsonVal, colModel) end
                -- Append to statement
                statement[col] = jsonVal
            end
            mw.logObject(statement, 'statement')
            table.insert(result, cargo.store(statement))
            expected = expected + 1
        end

		-- Query the data
        local query = {
        	tables = tbl,
        	fields = {"server"},
        	where = where,
        	orderBy = 'server',
        }
        for col in pairs(model) do
        	table.insert(query.fields, col)
        end
        local rows = cargo.query(query)

        local actual = #rows
        if actual ~= expected then
            table.insert(result, string.format('Entry count mismatch in table %q (%s expected, got %s)\n', tbl, expected, actual))
            errors.cargoStore = true
        end
        
        -- Generate tables from queried data
        queryResult = {}
        for i, row in ipairs(rows) do
            for col, val in pairs(row) do
                if type(model[col]) == "table" then
                    if model[col].list ~= nil then
                        val = #val == 0 and {} or split(val, model[col].list, true)
                    end
                    if model[col].dataRef ~= nil then
                        if type(val) == "table" then
                            for k, v in pairs(val) do
                                val[k] = "[[Data:Game/"..model[col].dataRef.."/"..v.."|"..v.."]]"
                            end
                        elseif #val > 0 then
                            val = "[[Data:Game/"..model[col].dataRef.."/"..val.."|"..val.."]]"
                        end
                    end
                end
                row[col] = val
            end
            queryResult[i] = row
        end
        insertQueryResult(tbl, queryResult)
    end

	local hasEffects = false
	local jsonEffects = {}
    for server, json in pairs(jsonSets) do
		if json.effects then
			hasEffects = true
			jsonEffects[server] = {effects = {}}
			for i,effect in ipairs(json.effects) do
				local values = {}
				values._table = 'ConceptCardEffect'
				values.server = server
				values.cc_iname = iname
				jsonEffects[server].effects[i] = {}
				for k,v in pairs(effect) do
					values[k] = v
					jsonEffects[server].effects[i][k] = v
					effect[k] = nil
				end
				table.insert(result, cargo.store(values))
				jsonEffects[server].server = server
			end
			-- A table of failed effect insertions.
			local errorEffects = {}
			for i, effect in ipairs(json.effects) do
				local empty = true
				for k, v in pairs(effect) do empty = false end
				if not empty then
					errorEffects[#errorEffects+1] = effect
				end
			end
			json.effects = #errorEffects > 0 and errorEffects or nil
		end
	end
	
	if next(jsonEffects) then
		table.insert(result, '===ConceptCardEffect===\n')
	    local toInsert = {}
	    for k in pairs(jsonEffects) do table.insert(toInsert, k) end
	    table.sort(toInsert)
	    for i, k in ipairs(toInsert) do toInsert[i] = jsonEffects[k] end
	    insertQueryResult(nil, toInsert)
	end

    local buffDetailRows = cargo.query{
    	tables = 'BuffDetail',
    	fields = 'buff_iname, server, idx, calc, type, tktag, vini, vmax, vone',
    	where = where,
    	orderBy = 'idx'
    }
    insertQueryResult('BuffDetail', buffDetailRows)

	
    -- Display unused keys here
    local unused = false
    for server, json in pairs(jsonSets) do if next(json) ~= nil then
        unused = true
        json.server = server
    end end
    if unused then
        table.insert(result, "===Unused Keys===\nThe following keys were not used by inserted into any table and are thus unused.\n")
        local toInsert = {}
        for k in pairs(jsonSets) do table.insert(toInsert, k) end
        table.sort(toInsert)
        for i, k in ipairs(toInsert) do toInsert[i] = jsonSets[k] end
        insertQueryResult(nil, toInsert)
    end
    
    table.insert(result, '[[Category:Data pages]]') -- Tracking category.
    -- Handle errors encountered
    if errors.cargoStore then
    	table.insert(result, '[[Category:Data pages that failed a table insert]]' )
	end
	if hasEffects then
	    table.insert(result, '[[Category:Data pages with effects]]')
	end

    return table.concat(result)
end

-- Add functions to model
p.model = (function()
    dataModel.tables = {}
    local recFunc
    function recFunc(node)
        if node.nodes ~= nil then for k, child in pairs(node.nodes) do recFunc(child) end end
        if node.model ~= nil then for tbl, model in pairs(node.model) do dataModel.tables[tbl] = model end end
    end
    recFunc(dataModel)
    
    -----------
    -- Query --
    -----------
    dataModel.query = function(tables, fields, args)
    	local query = args
    	query.tables = tables
    	query.fields = fields
        local result = cargo.query(query)
        local toCast = {}
        if type(query.tables) == 'string' then
        	query.tables = split(query.tables, ',', true)
        end
        if type(query.fields) == 'string' then
        	query.fields = split(query.fields, ',', true)
        end
        for _, field in ipairs(query.fields) do
            -- Extract alias
            local t = split(field, '=', true)
            local alias = trim(t[2] or t[1])
            
            local fnMatch = string.match(t[1], "([^()]*)[(](.*)[)](.*)")
            if fnMatch ~= nil then
                mw.log(fnMatch)
                toCast[alias] = {"String"}
            else
                -- Extract table
                local t2 = split(t[1], '.', true)
                local tbl = #t2 > 1 and trim(t2[1]) or nil
                local key = trim(t2[#t2])
                if tbl == nil then
                    for _, tblName in ipairs(query.tables) do
                        tblName = trim(tblName)
                        if (dataModel.tables[tblName] or {})[key] ~= nil then
                            tbl = tblName
                            break
                        end
                    end
                end
                local model = (dataModel.tables[tbl] or {})[key]
                toCast[alias] = type(model) == "string" and {model} or model
            end
        end
        local castFuncs = {
            Integer = tonumber
        }
        for i, row in ipairs(result) do
            for k, castTo in pairs(toCast) do
                local castFunc = castFuncs[castTo[1]] or function(v) return v end
                if castTo.list ~= nil then
                    local list = {}
                    if row[k] and row[k] ~= '' then
                        for s in gsplit(row[k], castTo.list, true) do
                            list[#list+1] = castFunc(s)
                        end
                    end
                    row[k] = list
                else
                    row[k] = castFunc(row[k])
                end
            end
        end
        return result
    end
    
    ------------
    -- getLoc --
    ------------
    dataModel.getLoc = function(_pageName, param, lang)
        if _pageName == nil then return '???' end
        local where = "_pageName='".._pageName.."' and param='"..param.."' and value<>'' and lang='"
        local thisLang = cargo.query{tables='Loc',fields='value',where=where..(lang or "english'")}
        if #thisLang == 0 then thisLang = cargo.query{tables='Loc',fields='value',where=where.."english'"} end
        if #thisLang == 0 then thisLang = cargo.query{tables='Loc',fields='value',where=where.."japanese'"} end
        if #thisLang == 0 then thisLang = {{value = ''}} end
        return thisLang[1].value
    end

    -------------
    -- Sources --
    -------------
    dataModel.sources = require("Module:Data/Sources")(dataModel)
    
    return dataModel
end)()

return p