Module:Lorebook/Debug
Documentation for this module may be created at Module:Lorebook/Debug/doc
local p = {}
-- 调试模式标志
local DEBUG = true
local function debug_log(msg)
if DEBUG then
return "<!-- DEBUG: " .. msg .. " -->\n"
end
return ""
end
local function splitCSV(s)
if not s or s == '' then return {} end
local t = {}
for token in mw.text.gsplit(s, ',', true) do
token = mw.text.trim(token)
if token ~= '' then table.insert(t, token:lower()) end
end
return t
end
local function containsAny(haystack, needles)
local s = ' ' .. haystack:lower() .. ' '
for _, n in ipairs(needles) do
if n ~= '' then
local escaped = n:gsub('[%^%$%(%)%%%.%[%]%*%+%-%?]', '%%%1')
local patterns = {
'%f[%w]' .. escaped .. '%f[%W]',
'[%W]' .. escaped .. '[%W]',
'^' .. escaped .. '[%W]',
'[%W]' .. escaped .. '$',
'^' .. escaped .. '$'
}
for _, pattern in ipairs(patterns) do
if s:find(pattern) then
return true
end
end
end
end
return false
end
local function anyMatch(haystack, primary, _secondary)
return containsAny(haystack, primary)
end
local function andMatch(haystack, primary, secondary)
return containsAny(haystack, primary) and containsAny(haystack, secondary)
end
local function notMatch(haystack, primary, secondary)
return containsAny(haystack, primary) and not containsAny(haystack, secondary)
end
local logicDispatch = {
ANY = anyMatch,
AND = andMatch,
NOT = notMatch,
}
local function normalizeValue(val)
if type(val) == 'table' then
return val[1] or ''
end
return val or ''
end
local function getAllConcepts(world)
local query = string.format('[[Category:Concept]][[Belongs to world::%s]]', world)
local res = mw.smw.ask(query, {
mainlabel = '-',
['?Plist'] = 'plist',
['?AliChat'] = 'alichat',
['?Primary keys'] = 'primary',
['?Secondary keys'] = 'secondary',
['?Logic'] = 'logic',
['?Key mode'] = 'mode',
['?Parent concept'] = 'parent',
['?Non-recursable'] = 'nonrec',
['?Placement'] = 'placement',
limit = 999
})
if not res then return {} end
local normalized = {}
for i, row in ipairs(res) do
normalized[i] = {
page = normalizeValue(row[1]),
plist = normalizeValue(row.plist),
alichat = normalizeValue(row.alichat),
primary = normalizeValue(row.primary),
secondary = normalizeValue(row.secondary),
logic = normalizeValue(row.logic),
mode = normalizeValue(row.mode),
parent = normalizeValue(row.parent),
nonrec = normalizeValue(row.nonrec),
placement = normalizeValue(row.placement)
}
end
return normalized
end
local function indexByTitle(rows)
local idx = {}
for _, r in ipairs(rows) do
if r.page and r.page ~= '' then
idx[r.page] = r
end
end
return idx
end
local function shouldTrigger(row, haystack)
local mode = (row.mode or 'conditional'):upper()
if mode == 'DISABLED' then return false end
if mode == 'CONSTANT' then return true end
local logic = (row.logic or 'ANY'):upper()
local f = logicDispatch[logic] or anyMatch
local primary = splitCSV(row.primary)
local secondary = splitCSV(row.secondary)
return f(haystack, primary, secondary)
end
local function walkRecursion(rows, idx, startPages)
local children = {}
for _, r in ipairs(rows) do
local parent = r.parent
if parent and parent ~= '' then
children[parent] = children[parent] or {}
table.insert(children[parent], r.page)
end
end
local visited = {}
local stack = {}
for _, ptitle in ipairs(startPages) do table.insert(stack, ptitle) end
local ordered = {}
while #stack > 0 do
local cur = table.remove(stack)
if not visited[cur] then
visited[cur] = true
table.insert(ordered, cur)
local row = idx[cur]
if row and (row.nonrec or ''):lower() ~= 'yes' then
for _, c in ipairs(children[cur] or {}) do
table.insert(stack, c)
end
end
end
end
return ordered
end
local function buildInjection(rows, triggered)
local plistChunks, chatChunks = {}, {}
for _, title in ipairs(triggered) do
local r
for _, row in ipairs(rows) do if row.page == title then r = row break end end
if r then
if r.plist and r.plist ~= '' then table.insert(plistChunks, r.plist) end
if r.alichat and r.alichat ~= '' then table.insert(chatChunks, r.alichat) end
end
end
local text = ''
if #plistChunks > 0 then
text = text .. table.concat(plistChunks, '\n') .. '\n'
end
if #chatChunks > 0 then
text = text .. table.concat(chatChunks, '\n')
end
return text
end
function p.inject(frame)
local world = frame.args.world or 'Farlandia'
local haystack = frame.args.context or ''
local output = debug_log("Starting inject function")
output = output .. debug_log("World: " .. world)
output = output .. debug_log("Context: " .. haystack)
local rows = getAllConcepts(world)
output = output .. debug_log("Found " .. #rows .. " concepts")
if #rows == 0 then
output = output .. "\n'''⚠️ No concepts found for world: " .. world .. "'''\n\n"
output = output .. "Possible issues:\n"
output = output .. "* Concepts not created yet\n"
output = output .. "* Concepts not using {{Concept}} template\n"
output = output .. "* SMW properties not set correctly\n"
output = output .. "* SMW data cache needs refresh (re-save concept pages)\n\n"
output = output .. "Debug query: <code><nowiki>{{#ask: [[Category:Concept]] [[Belongs to world::" .. world .. "]] }}</nowiki></code>\n"
return output
end
-- 显示找到的概念列表(调试)
if DEBUG then
output = output .. "\n'''Found concepts:'''\n"
for i, r in ipairs(rows) do
output = output .. "* " .. (r.page or "unnamed") ..
" (mode: " .. (r.mode or "conditional") ..
", primary: " .. (r.primary or "none") .. ")\n"
end
output = output .. "\n"
end
local idx = indexByTitle(rows)
local start = {}
for _, r in ipairs(rows) do
if shouldTrigger(r, haystack) then
table.insert(start, r.page)
output = output .. debug_log("Triggered: " .. r.page)
end
end
output = output .. debug_log("Triggered " .. #start .. " concepts")
if #start == 0 then
output = output .. "\n'''ℹ️ No concepts triggered for context: " .. haystack .. "'''\n\n"
output = output .. "Available trigger keys:\n"
for _, r in ipairs(rows) do
if r.primary and r.primary ~= '' then
output = output .. "* " .. r.page .. ": <code>" .. r.primary .. "</code>\n"
end
end
return output
end
local triggered = walkRecursion(rows, idx, start)
output = output .. debug_log("After recursion: " .. #triggered .. " concepts")
local result = buildInjection(rows, triggered)
if DEBUG then
output = output .. "\n'''Injected concepts:'''\n"
for _, t in ipairs(triggered) do
output = output .. "* " .. t .. "\n"
end
output = output .. "\n'''=== Output ==='''\n<pre>\n"
end
output = output .. result
if DEBUG then
output = output .. "\n</pre>\n"
end
return output
end
function p.export(frame)
local world = frame.args.world or 'Farlandia'
local rows = getAllConcepts(world)
local entries = {}
for _, r in ipairs(rows) do
table.insert(entries, {
title = r.page,
plist = r.plist or '',
alichat = r.alichat or '',
primary = r.primary or '',
secondary = r.secondary or '',
logic = (r.logic or 'ANY'),
mode = (r.mode or 'conditional'),
parent = r.parent or '',
nonrecursable = r.nonrec or '',
placement = r.placement or ''
})
end
return mw.text.jsonEncode(entries)
end
return p