모듈:GameJSONParser: 두 판 사이의 차이
(테스트데이터 삭제) |
(dpl파서 개선) |
||
1번째 줄: | 1번째 줄: | ||
local table = require('table') -- 배열 입출력을 위한 테이블 내부 라이브러리 | |||
-- 테이블 관련 유틸리티 함수들 | |||
local 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에서 페이지 이름을 추출합니다. | |||
getPagenamesFromDpl = function(html) | |||
local pageNames = {} -- 반환될 페이지 이름을 담을 배열을 초기화합니다. | |||
for pageName in html:gmatch('<li>%[%[(.-)|') do | |||
table.insert(pageNames, pageName) -- 추출된 페이지 이름을 배열에 추가합니다. | |||
end | |||
return pageNames | |||
end, | |||
-- #dpl 파서함수로 생성한 스트링인지 확인합니다 | |||
isDplString = function(str) | |||
return str:sub(1, 10) == "<ul><li>[[" | |||
end | |||
} | |||
local p = { | local p = { | ||
SCHEME_PAGENAME = '리버티게임:게임 메타데이터/스키마.json' | SCHEME_PAGENAME = '리버티게임:게임 메타데이터/스키마.json' | ||
} | } | ||
-- 속성 케이스 검사할 값, 발견시 반환할 값 정의 | -- 속성 케이스 검사할 값, 발견시 반환할 값 정의 | ||
93번째 줄: | 162번째 줄: | ||
return genreData | return genreData | ||
end | end | ||
} | }, | ||
} | AuthorName = { | ||
validate = function(gameMeta, scheme) | |||
local authorName = t.walk(gameMeta, {"AuthorName"}) | |||
-- authorName이 스트링이면 배열로 반환, [[MTR/game.json]] 케이스의 예외처리 | |||
-- nil일 시 익명 | |||
if authorName == nil then authorName = {"익명"} end | |||
if type(authorName) == "string" then | |||
authorName = {authorName} | |||
end | |||
-- authorName키를 const로 변환 후 반환 | |||
-- | return t.map(authorName, function(name) | ||
return {const = name} | |||
end) | |||
end | |||
} | |||
} | } | ||
-- 게임 메타데이터를 분석하고 각 속성에 대해 적절한 포맷을 적용하는 함수 | -- 게임 메타데이터를 분석하고 각 속성에 대해 적절한 포맷을 적용하는 함수 | ||
237번째 줄: | 251번째 줄: | ||
-- 위키낚시/game.json => :위키낚시/game.json | -- 위키낚시/game.json => :위키낚시/game.json | ||
-- 사:BANIP/위키낚시/game.json => 사:BANIP/위키낚시/game.json | -- 사:BANIP/위키낚시/game.json => 사:BANIP/위키낚시/game.json | ||
if not gameMetaPagename:find(":") then | -- 기존에 : 문자가 포함하는것을 모두 케이스에 포함시켰는데, 비밀4: 악몽/game.json같은 예외사항때문에 사용자 네임스페이스로 한정 | ||
if not ( gameMetaPagename:find("사:") or gameMetaPagename:find("사용자:") ) then | |||
gameMetaPagename = ":" .. gameMetaPagename | gameMetaPagename = ":" .. gameMetaPagename | ||
end | end | ||
344번째 줄: | 359번째 줄: | ||
return math.random(start_val, end_val) | return math.random(start_val, end_val) | ||
end | end | ||
function p.getGamecard(frame) | function p.getGamecard(frame) | ||
374번째 줄: | 388번째 줄: | ||
key = "editpolicy", | key = "editpolicy", | ||
formatter = iconFormatter, -- 파서 구현 필요 | formatter = iconFormatter, -- 파서 구현 필요 | ||
} | }, | ||
author={ | |||
key = "AuthorName", | |||
formatter = "[[사용자:${const}|${const}]]" | |||
} | |||
} | } | ||
386번째 줄: | 404번째 줄: | ||
-- dpl쿼리로 생성했으면 pagenames로 변환 | -- dpl쿼리로 생성했으면 pagenames로 변환 | ||
if c.isDplString(paramPagename) then | if c.isDplString(paramPagename) then | ||
pagenames = c. | pagenames = c.getPagenamesFromDpl(paramPagename) | ||
else | else | ||
pagenames = {paramPagename} | pagenames = {paramPagename} | ||
393번째 줄: | 411번째 줄: | ||
local gameCards = {} | local gameCards = {} | ||
for i, pagename in ipairs(pagenames) do | for i, pagename in ipairs(pagenames) do | ||
-- 첫번째 변수혹은 현재 페이지명으로 게임 메타데이터 페이지 이름 획득 | -- 첫번째 변수혹은 현재 페이지명으로 게임 메타데이터 페이지 이름 획득 | ||
400번째 줄: | 419번째 줄: | ||
if( gameMeta == nil ) then | if( gameMeta == nil ) then | ||
return "" | return "" | ||
end | end | ||
-- gameMeta.AuthorName이 테이블이면 로깅 후 리턴 | |||
local gameCard = [[ | if( type(gameMeta.AuthorName) == "table" ) then | ||
<div class='gamecard' style='border-color:hsl(]].. parsed.genre ..[[,80%);' > | table.insert(gameCards, "게임 메타데이터가 비어있는 게임이 있습니다. " .. pagename) | ||
else | |||
-- 게임 메타데이터 파싱 | |||
local parsed = p._getParsedGameJson(scheme, gameMeta, formatters) | |||
local gameLink = "[[" .. pagename .. "|" .. pagename .. "]]" | |||
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'>]].. parsed.author ..[[</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> | </div> | ||
</div> | </div> | ||
]] | |||
table.insert(gameCards,gameCard) | |||
end | |||
end | end | ||
2023년 8월 2일 (수) 00:49 판
getGameInfo
- 이 부분의 본문은 틀:게임 정보입니다.
getGamecard
- 이 부분의 본문은 틀:게임카드입니다.
getFeaturedCard
- 이 부분의 본문은 틀:추천평카드입니다.
도보시오
- 리버티게임:게임 메타데이터/스키마.json : 메타데이터 스키마
- 모듈:GameJSONParser/연구소
위 설명은 모듈:GameJSONParser/설명문서의 내용을 가져와 보여주고 있습니다. (편집 | 역사) 이 모듈에 대한 수정 연습과 시험은 연습장 (만들기 | 미러)과 시험장 (만들기)에서 할 수 있습니다. 분류는 /설명문서에 넣어주세요. 이 모듈에 딸린 문서. |
local table = require('table') -- 배열 입출력을 위한 테이블 내부 라이브러리
-- 테이블 관련 유틸리티 함수들
local 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에서 페이지 이름을 추출합니다.
getPagenamesFromDpl = function(html)
local pageNames = {} -- 반환될 페이지 이름을 담을 배열을 초기화합니다.
for pageName in html:gmatch('<li>%[%[(.-)|') do
table.insert(pageNames, pageName) -- 추출된 페이지 이름을 배열에 추가합니다.
end
return pageNames
end,
-- #dpl 파서함수로 생성한 스트링인지 확인합니다
isDplString = function(str)
return str:sub(1, 10) == "<ul><li>[["
end
}
local p = {
SCHEME_PAGENAME = '리버티게임:게임 메타데이터/스키마.json'
}
-- 속성 케이스 검사할 값, 발견시 반환할 값 정의
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
},
AuthorName = {
validate = function(gameMeta, scheme)
local authorName = t.walk(gameMeta, {"AuthorName"})
-- authorName이 스트링이면 배열로 반환, [[MTR/game.json]] 케이스의 예외처리
-- nil일 시 익명
if authorName == nil then authorName = {"익명"} end
if type(authorName) == "string" then
authorName = {authorName}
end
-- authorName키를 const로 변환 후 반환
return t.map(authorName, function(name)
return {const = name}
end)
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
-- 기존에 : 문자가 포함하는것을 모두 케이스에 포함시켰는데, 비밀4: 악몽/game.json같은 예외사항때문에 사용자 네임스페이스로 한정
if not ( gameMetaPagename:find("사:") or 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, -- 파서 구현 필요
},
author={
key = "AuthorName",
formatter = "[[사용자:${const}|${const}]]"
}
}
-- 스키마 획득
local scheme = p._getSchemaTable(currentFrame)
local paramPagename = frame.args[1]
local pagenames = {}
-- dpl쿼리로 생성했으면 pagenames로 변환
if c.isDplString(paramPagename) then
pagenames = c.getPagenamesFromDpl(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
-- gameMeta.AuthorName이 테이블이면 로깅 후 리턴
if( type(gameMeta.AuthorName) == "table" ) then
table.insert(gameCards, "게임 메타데이터가 비어있는 게임이 있습니다. " .. pagename)
else
-- 게임 메타데이터 파싱
local parsed = p._getParsedGameJson(scheme, gameMeta, formatters)
local gameLink = "[[" .. pagename .. "|" .. pagename .. "]]"
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'>]].. parsed.author ..[[</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
end
-- join출력
return table.concat(gameCards)
end
return p