모듈:GameJSONParser: 두 판 사이의 차이

리버티게임, 모두가 만들어가는 자유로운 게임
잔글 (다중장르지원)
(파라미터로 dpl쿼리지원)
95번째 줄: 95번째 줄:
     }
     }
}
}


-- 테이블 관련 유틸리티 함수들
-- 테이블 관련 유틸리티 함수들
141번째 줄: 142번째 줄:
     end,
     end,
}
}
-- 카테고리 관련 유틸리티 함수들
local c = {}
-- #dpl 파서함수로 생성한 HTML에서 페이지 이름을 추출합니다.
function c.getPagenamesFromDpl(html)
    local pageNames = {} -- 반환될 페이지 이름을 담을 배열을 초기화합니다.
   
    for pageName in html:gmatch('<li>%[%[(.-)|') do
        table.insert(pageNames, pageName) -- 추출된 페이지 이름을 배열에 추가합니다.
    end
   
    return pageNames
end
-- #dpl 파서함수로 생성한 스트링인지 확인합니다
function c.isDplString(str)
-- ul li로 시작하는지 확인
if str:sub(1, 6) ~= "<ul>\n" then return false end
return true
end




323번째 줄: 347번째 줄:


function p.getGamecard(frame)
function p.getGamecard(frame)
    local pagename = frame.args[1]
local currentFrame = mw.getCurrentFrame()
local currentFrame = mw.getCurrentFrame()
 
    -- 스키마 획득
    local scheme = p._getSchemaTable(currentFrame)
    -- 첫번째 변수혹은 현재 페이지명으로 게임 메타데이터 페이지 이름 획득
    local gameMeta = p._getGameJsonTable(pagename,currentFrame)
 
    -- 게임 메타데이터가 비어있으면 빈값 반환
    if( gameMeta == nil ) then
        return ""
    end
 
   
   
     local iconFormatter = [[<div class="icon-wrapper">]] ..
     local iconFormatter = [[<div class="icon-wrapper">]] ..
         [[<span class="material-symbols-outlined icon">${icon}</span>]] ..
         [[<span class="material-symbols-outlined icon">${icon}</span>]] ..
366번째 줄: 377번째 줄:
     }
     }


     -- 게임 메타데이터 파싱
 
    local parsed = p._getParsedGameJson(scheme, gameMeta, formatters)
    -- 스키마 획득
    local gameLink = "[[" .. pagename .. "|" .. pagename .. "]]"
    local scheme = p._getSchemaTable(currentFrame)
    local authorLink = ""
 
    if gameMeta.AuthorName ~= nil then
     local paramPagename = "위키낚시" or frame.args[1]
        authorLink = "[[사용자:" .. gameMeta.AuthorName .. "|" .. gameMeta.AuthorName .. "]]"
local pagenames = {}
    end
 
-- dpl쿼리로 생성했으면 pagenames로 변환
if c.isDplString(paramPagename) then
pagenames = c.getDplStringToPagenames(paramPagename)
else
pagenames = {paramPagename}
end
 
local gameCards = {}
 
for i, pagename in ipairs(pagenames) do
-- 첫번째 변수혹은 현재 페이지명으로 게임 메타데이터 페이지 이름 획득
local gameMeta = p._getGameJsonTable(pagename,currentFrame)
 
-- 게임 메타데이터가 비어있으면 빈값 반환
if( gameMeta == nil ) then
return ""
end
-- 게임 메타데이터 파싱
local parsed = p._getParsedGameJson(scheme, gameMeta, formatters)
local gameLink = "[[" .. pagename .. "|" .. pagename .. "]]"
local authorLink = ""
if gameMeta.AuthorName ~= nil then
authorLink = "[[사용자:" .. gameMeta.AuthorName .. "|" .. gameMeta.AuthorName .. "]]"
end




    return [[
local gameCard = [[
        <div class="gamecard" style="border-color:hsl(]].. parsed.genre ..[[,80%);" >
<div class='gamecard' style='border-color:hsl(]].. parsed.genre ..[[,80%);' >
            <div class="theme" style="background-color:hsl(]].. parsed.genre ..[[,92%);"></div>
<div class='theme' style='background-color:hsl(]].. parsed.genre ..[[,92%);'></div>
            <div class="content">
<div class='content'>
            <div class="badges">]] ..
<div class='badges'>]] ..
                    parsed.platform ..   
parsed.platform ..   
                    parsed.editpolicy ..   
parsed.editpolicy ..   
                    parsed.progress ..   
parsed.progress ..   
            [[</div>]] ..
[[</div>]] ..
            [[<div class="title">]].. gameLink ..[[</div>
[[<div class='title'>]].. gameLink ..[[</div>
            <div class="summary">]].. (gameMeta.Summary or "")..[[</div>
<div class='summary'>]].. (gameMeta.Summary or "")..[[</div>
            <div class="description">]].. (gameMeta.Description or "") ..[[</div>
<div class='description'>]].. (gameMeta.Description or "") ..[[</div>
            <div class="detail">
<div class='detail'>
                <div class="author">]].. authorLink ..[[</div>
<div class='author'>]].. authorLink ..[[</div>
                <div class="created">]].. (gameMeta.Created or "") ..[[</div>
<div class='created'>]].. (gameMeta.Created or "") ..[[</div>
                <div class="metapage">
<div class='metapage'>
                ]].. "[["..pagename .. "/game.json|<span class='material-symbols-outlined icon'>data_object</span>]]" ..[[
]].. "[["..pagename .. "/game.json|<span class='material-symbols-outlined icon'>data_object</span>]]" ..[[
                </div>
</div>
            </div>
</div>
        </div>
</div>
        </div>
</div>
    ]]
]]
table.insert(gameCards,gameCard)
end


-- join출력
return table.concat(gameCards)
end
end


return p
return p

2023년 8월 1일 (화) 23:59 판


모듈 설명문서[보기] [편집] [역사] [새로 고침]

getGameInfo

이 부분의 본문은 틀:게임 정보입니다.

getGamecard

이 부분의 본문은 틀:게임카드입니다.

getFeaturedCard

이 부분의 본문은 틀:추천평카드입니다.

도보시오


local p = {
    SCHEME_PAGENAME = '리버티게임:게임 메타데이터/스키마.json'
} 
local table = require('table') -- 배열 입출력을 위한 테이블 내부 라이브러리

-- 속성 케이스 검사할 값, 발견시 반환할 값 정의
p.propertyCases = {
    platform = {
        validate = function(gameMeta, scheme)
            local platform = t.walk(gameMeta, {"platform"})
            if platform == nil then return false end
            local platFormDef = t.walk(scheme, {"$defs", "platform", "oneOf"})
            local platFormData = t.find(platFormDef, function(p)
                return p.const == platform
            end)
            if platFormData == nil then return false end
            return platFormData
        end
    },
    abandon = {
        validate = function(gameMeta, scheme)
            return {const = t.walk(gameMeta, {"abandon"}) or false}
        end
    },
    construction = {
        validate = function(gameMeta, scheme)
            local construction = t.walk(gameMeta, {"construction"})
            if not construction then return {const = false} end
            if construction == true then return {const = true} end
            return {const = true, date = construction}
        end
    },
    progress = {
        validate = function(gameMeta, scheme)
            local progress = t.walk(gameMeta, {"progress"})
            if progress == nil then return false end
            local progressDef = t.walk(scheme, {"properties", "progress", "oneOf"})
            local progressData = t.find(progressDef, function(p)
                return p.const == progress
            end)
            if progressData == nil then return false end
            return progressData
        end
    },    
    editpolicy = {
        validate = function(gameMeta, scheme)
            local constKey = t.walk(gameMeta, {"editpolicy"}) or "closed"
            local progressDef = t.walk(scheme, {"properties", "editpolicy", "oneOf"})
            if( progressDef == nil ) then 
                mw.log("editpolicy가 null입니다.")
                return false 
            end
            local progressData = t.find(progressDef, function(p)
                return p.const == constKey
            end) or progressDef[3]
            return progressData
        end
    },
    rating = {
        validate = function(gameMeta, scheme)
            local age = t.walk(gameMeta, {"rating", "libertygame", "age"})
            if age == nil then return false end
            local ageDef = t.walk(scheme, {"properties", "rating", "oneOf", 2, "properties", "libertygame", "properties", "age", "oneOf"})
            local ageData = t.find(ageDef, function(a)
                return a.const == age
            end)
            if ageData == nil then return false end
            return ageData
        end
    },
    repair = {
        validate = function(gameMeta, scheme)
            local repair = t.walk(gameMeta, {"repair"})
            if not repair then return {const = false} end
            if repair == true then return {const = true} end
            return {const = true, date = repair}
        end
    },
    genre = {
        validate = function(gameMeta, scheme)
            local genres = t.walk(gameMeta, {"genre"})
            if genres == nil then return false end
            if type(genres) == "string" then genres = {genres} end
            local genreDef = t.walk(scheme, {"$defs", "genre", "oneOf"})
            local genreData = t.filter(t.map(genres, function(genre)
                return t.find(genreDef, function(genreData)
                    return genreData.const == genre
                end)
            end), function(genreData)
                return genreData ~= nil
            end)

            return genreData
        end
    }
}


-- 테이블 관련 유틸리티 함수들
t = {
    -- 자바스크립트의 find 함수와 동일
    -- 주어진 함수를 만족하는 첫 번째 요소를 반환
    find = function(tb, func)
        for _, value in ipairs(tb) do
            if func(value) then
                return value
            end
        end
        return nil
    end,
    -- 자바스크립트의 map 함수와 동일
    -- 주어진 함수를 이용하여 테이블의 모든 요소를 변환
    map = function(tb, func)
        local newTable = {}
        for i, value in ipairs(tb) do
            newTable[i] = func(value)
        end
        return newTable
    end,
    -- 자바스크립트의 filter 함수와 동일
    -- 주어진 함수를 만족하는 요소만으로 새 테이블 생성
    filter = function(tb, func)
        local newTable = {}
        for _, value in ipairs(tb) do
            if func(value) then
                table.insert(newTable, value)
            end
        end
        return newTable
    end,
    -- gameMeta.rating.libertygame.age와 같이 다단계 키로 이루어진 테이블 값을 가져오는 함수
    -- 키의 경로 중간에 nil이 있는 경우 nil 반환
    walk = function(tbl, keys)
        local value = tbl
        for i, key in ipairs(keys) do
            value = value[key]
            if value == nil or type(value) == 'number' or type(value) == 'string' then
                return value
            end
        end
        return value
    end,
}


-- 카테고리 관련 유틸리티 함수들
local c = {}

-- #dpl 파서함수로 생성한 HTML에서 페이지 이름을 추출합니다.
function c.getPagenamesFromDpl(html)
    local pageNames = {} -- 반환될 페이지 이름을 담을 배열을 초기화합니다.
    
    for pageName in html:gmatch('<li>%[%[(.-)|') do
        table.insert(pageNames, pageName) -- 추출된 페이지 이름을 배열에 추가합니다.
    end
    
    return pageNames
end

-- #dpl 파서함수로 생성한 스트링인지 확인합니다
function c.isDplString(str)
	-- ul li로 시작하는지 확인
	if str:sub(1, 6) ~= "<ul>\n" then return false end
	return true

end


-- 게임 메타데이터를 분석하고 각 속성에 대해 적절한 포맷을 적용하는 함수
function p._getParsedGameJson(scheme, gameMeta, formatters)
    local results = {}

    -- 각 속성 케이스를 순회합니다.
    for resultKey, formatterItem in pairs(formatters) do
        local formatter = formatterItem.formatter

        local propertyKey = formatterItem.key
        -- propertyCase에 없는 키면 contiuue
        if p.propertyCases[propertyKey] == nil then 
            mw.log("파싱하려는 " .. propertyKey .. "키는 propertyCase에 없습니다.")
        else 
            -- 현재 속성의 유효성을 확인합니다.
            local validatedGroup = p.propertyCases[propertyKey].validate(gameMeta,scheme)

            if validatedGroup ~= false and validatedGroup ~= nil then

                -- 배열이 아닌 경우 기본 배열로 변환 
                if type(validatedGroup) ~= 'table' then
                    validatedGroup = {}
                end

                -- 1차원일 경우 2차원으로 변환
                if type(validatedGroup[1]) ~= 'table' then
                    validatedGroup = {validatedGroup}
                end

                local resultItem = ""
                
                for propIndex, props in ipairs(validatedGroup) do
                    local isEnable = formatterItem.enable or true
                    if type(isEnable) == 'function' then
                        isEnable = isEnable(props, propIndex)
                    end

                    if isEnable then
                        local thisItemString = formatter
                        -- formatter가 함수면 prop 파라미터로 넣어서 실행
                        if type(formatter) == 'function' then
                            thisItemString = formatter(props)
                        end

                        -- props foreach
                        for propKey, propValue in pairs(props) do
                            thisItemString = thisItemString:gsub("${" .. propKey .. "}", propValue)
                        end
                        resultItem = resultItem .. thisItemString
                    end
                end

                --results[resultKey]에 resultItem입력 
                results[resultKey] = resultItem
            end
        end
    end
    return results
end

function p._getGameJsonTable(pagename, frame)
    frame = frame or mw.getCurrentFrame()
    local gameMetaPagename = pagename or mw.title.getCurrentTitle().prefixedText

    -- /game.json으로 안끝나면 붙여주기
    if not gameMetaPagename:find("/game.json$") then
        gameMetaPagename = gameMetaPagename .. "/game.json"
    end

    -- 위키낚시/game.json => :위키낚시/game.json
    -- 사:BANIP/위키낚시/game.json => 사:BANIP/위키낚시/game.json
    if not gameMetaPagename:find(":") then
        gameMetaPagename = ":" .. gameMetaPagename
    end

    -- gameMetaPagename 페이지가 존재하는지 확인 없으면 null 반환
    if not mw.title.new(gameMetaPagename).exists then
        return nil
    end
    
    -- -- 게임 메타데이터 획득 후 테이블로 변환
    local gameMetaRaw = frame:expandTemplate{title = gameMetaPagename} 
    local gameMeta = mw.text.jsonDecode(gameMetaRaw)
    return gameMeta

end

function p._getSchemaTable(frame)
    frame = frame or mw.getCurrentFrame()

    -- schemePagename 페이지가 존재하는지 확인 없으면 null 반환
    if not mw.title.new(p.SCHEME_PAGENAME).exists then
        return nil
    end

    -- 메타데이터 스키마 획득 후 테이블로 변환
    local schemeRaw = frame:expandTemplate{title = p.SCHEME_PAGENAME}
    local scheme = mw.text.jsonDecode(schemeRaw)

    return scheme
end
    

function p.getGameCategories(frame)
	-- 틀 호출하기 위해서 필요한 현재 페이지 프레임 변수 획득
	local currentFrame = mw.getCurrentFrame()

    -- 스키마 획득
    local scheme = p._getSchemaTable(currentFrame)
    -- 첫번째 변수혹은 현재 페이지명으로 게임 메타데이터 페이지 이름 획득
    local gameMeta = p._getGameJsonTable(frame.args[1],currentFrame)

    -- schemePagename 페이지가 존재하는지 확인 없으면 오류반환
    if scheme == nil then
        return "자동 분류에 필요한 [[" .. p.SCHEME_PAGENAME .. "]] 페이지가 존재하지 않습니다. 관리자에게 알려주세요. "
    end
    -- game.json 페이지 있는지 확인 및 없으면 오류 반환
    if gameMeta == nil then
        return "[[분류:게임 메타데이터가 없는 게임]]"
    end

    local formatters = {
        {
            key = "platform",
            formatter = "[[분류:${title} 게임]]",
        }, {
           key = "abandon",
           formatter = "[[분류:버려진 게임]]",
           enable = function(prop)
               return prop.const
           end
        }, {
           key = "construction",
           formatter = "[[분류:공사중인 게임]]",
           enable = function(prop)
               return prop.const
           end
        }, {
            key = "repair",
            formatter = "[[분류:수리중인 게임]]",
            enable = function(prop)
                return prop.const
            end
         }, {
           key = "progress",
           formatter = "[[분류:${title}]]",
        }, {
           key = "rating",
           formatter = "[[분류:${title} 게임]]",
        }, {
           key = "genre",
           formatter = "[[분류:${title}]]",
        }
    }

    -- 게임 메타데이터 파싱
    local parsed = p._getParsedGameJson(scheme, gameMeta, formatters)


    

    -- 파싱 결과 join후 반환
    return table.concat(parsed)
end


local stringToNumber = function(input)
    local sum = 0
    for i = 1, #input do
        sum = sum + string.byte(input, i)
    end
    return sum
end

local rand = function(start_val, end_val,seed)
    math.randomseed(stringToNumber(seed))
    return math.random(start_val, end_val)
end


function p.getGamecard(frame)
	local currentFrame = mw.getCurrentFrame()
	
    local iconFormatter = [[<div class="icon-wrapper">]] ..
        [[<span class="material-symbols-outlined icon">${icon}</span>]] ..
        [[<span class="description">${description}</span>]] ..
    [[</div>]]
    local formatters = {
        genre={
            key = "genre",
            formatter = function(props)
                return rand(0,360,props.const) ..",".. rand(50,100,props.const) .."%" 
            end,
            enable = function(props,index)
                return index == 1
            end
        }, 
        platform={
            key = "platform",
            formatter = iconFormatter,
        }, 
        progress={
            key = "progress",
            formatter = iconFormatter,
        }, 
        editpolicy={
           key = "editpolicy",
           formatter = iconFormatter, -- 파서 구현 필요
        }
    }


    -- 스키마 획득
    local scheme = p._getSchemaTable(currentFrame)

    local paramPagename = "위키낚시" or frame.args[1]
	local pagenames = {}

	-- dpl쿼리로 생성했으면 pagenames로 변환
	if c.isDplString(paramPagename) then
		pagenames = c.getDplStringToPagenames(paramPagename)
	else
		pagenames = {paramPagename}
	end

	local gameCards = {}

	for i, pagename in ipairs(pagenames) do
		-- 첫번째 변수혹은 현재 페이지명으로 게임 메타데이터 페이지 이름 획득
		local gameMeta = p._getGameJsonTable(pagename,currentFrame)

		-- 게임 메타데이터가 비어있으면 빈값 반환
		if( gameMeta == nil ) then
			return ""
		end
		
		-- 게임 메타데이터 파싱
		local parsed = p._getParsedGameJson(scheme, gameMeta, formatters)
		local gameLink = "[[" .. pagename .. "|" .. pagename .. "]]"
		local authorLink = ""
		if gameMeta.AuthorName ~= nil then
			authorLink = "[[사용자:" .. gameMeta.AuthorName .. "|" .. gameMeta.AuthorName .. "]]"
		end


		local gameCard = [[
<div class='gamecard' style='border-color:hsl(]].. parsed.genre ..[[,80%);' >
	<div class='theme' style='background-color:hsl(]].. parsed.genre ..[[,92%);'></div>
	<div class='content'>
		<div class='badges'>]] ..
				parsed.platform ..  
				parsed.editpolicy ..  
				parsed.progress ..  
		[[</div>]] ..
		[[<div class='title'>]].. gameLink ..[[</div>
		<div class='summary'>]].. (gameMeta.Summary or "")..[[</div>
		<div class='description'>]].. (gameMeta.Description or "") ..[[</div>
		<div class='detail'>
			<div class='author'>]].. authorLink ..[[</div>
			<div class='created'>]].. (gameMeta.Created or "") ..[[</div>
			<div class='metapage'>
				]].. "[["..pagename .. "/game.json|<span class='material-symbols-outlined icon'>data_object</span>]]" ..[[
			</div>
		</div>
	</div>
</div>
		]]
		table.insert(gameCards,gameCard)
	end

	-- join출력
	return table.concat(gameCards)
end

return p