Modul:ProjectGraph: Unterschied zwischen den Versionen
Zur Navigation springen
Zur Suche springen
Markus (Diskussion | Beiträge) Keine Bearbeitungszusammenfassung |
Markus (Diskussion | Beiträge) Keine Bearbeitungszusammenfassung |
||
| Zeile 1: | Zeile 1: | ||
local p = {} | local p = {} | ||
local function mermaidId(title) | local function mermaidId(title) | ||
local base = mw.ustring.gsub(title, "[^%w]", "_") | local base = mw.ustring.gsub(title, "[^%w]", "_") | ||
if mw.ustring.match(base, "^%d") then | if mw.ustring.match(base, "^%d") then base = "T_" .. base end | ||
if #base > 40 then | if #base > 40 then | ||
local h = mw.hash.hashValue("md5", title):sub(1, 10) | local h = mw.hash.hashValue("md5", title):sub(1, 10) | ||
| Zeile 13: | Zeile 9: | ||
end | end | ||
return base | return base | ||
end | |||
local function stripNs(title) | |||
return mw.ustring.gsub(title or "", "^[^:]+:", "") | |||
end | end | ||
local function getPrintouts(row) | local function getPrintouts(row) | ||
if row.printouts then return row.printouts end | if row.printouts then return row.printouts end | ||
return row | return row | ||
| Zeile 26: | Zeile 23: | ||
if not v then return {} end | if not v then return {} end | ||
if type(v) ~= "table" then return { v } end | if type(v) ~= "table" then return { v } end | ||
return v | return v | ||
end | end | ||
local function | local function valueToString(v) | ||
if type( | if type(v) == "table" then | ||
if v.fulltext then return v.fulltext end | |||
if v.value then return tostring(v.value) end | |||
if v[1] then return valueToString(v[1]) end | |||
return | |||
end | end | ||
if type(v) == "string" then return v end | |||
if type(v) == "number" then return tostring(v) end | |||
return nil | |||
end | |||
local function rowTitle(row) | |||
if row.fulltext then return row.fulltext end | |||
if row[1] and type(row[1]) == "string" then return row[1] end | |||
if row[1] and type(row[1]) == "table" and row[1].fulltext then return row[1].fulltext end | |||
return nil | return nil | ||
end | end | ||
local function | local function norm(s) | ||
if not s then return "todo" end | if not s then return "todo" end | ||
s = mw.ustring.lower(mw.text.trim(tostring(s))) | s = mw.ustring.lower(mw.text.trim(tostring(s))) | ||
| Zeile 47: | Zeile 51: | ||
end | end | ||
local function firstText(po, key) | |||
local v = po[key] | |||
if not v then return nil end | |||
if type(v) == "table" and v[1] ~= nil then | |||
return valueToString(v[1]) | |||
end | |||
return valueToString(v) | |||
end | |||
function p.mermaid(frame) | function p.mermaid(frame) | ||
local args = frame.args | local args = frame.args | ||
| Zeile 53: | Zeile 65: | ||
local project = args.project or parentArgs.project | local project = args.project or parentArgs.project | ||
if not project or project == "" then | if not project or project == "" then | ||
return "Fehler: project= | return "Fehler: project=... fehlt." | ||
end | end | ||
| Zeile 61: | Zeile 73: | ||
"?Status", | "?Status", | ||
"mainlabel=-", | "mainlabel=-", | ||
"limit= | "limit=2000" | ||
} | } | ||
local res = mw.smw.ask(q) or {} | local res = mw.smw.ask(q) or {} | ||
local nodes = {} | |||
local edges = {} | |||
local nodes = { | |||
local edges = {} | |||
for _, row in ipairs(res) do | for _, row in ipairs(res) do | ||
local title = row | local title = rowTitle(row) | ||
if title then | if title then | ||
local po = getPrintouts(row) | local po = getPrintouts(row) | ||
local | local st = norm(firstText(po, "Status")) | ||
nodes[title] = nodes[title] or { id = mermaidId(title), status = st } | |||
local deps = po["Abhängig von"] | local deps = po["Abhängig von"] | ||
for _, dv in ipairs(asList(deps)) do | for _, dv in ipairs(asList(deps)) do | ||
local depTitle = | local depTitle = valueToString(dv) | ||
if depTitle and depTitle ~= "" then | if depTitle and depTitle ~= "" then | ||
nodes[depTitle] = nodes[depTitle] or { id = mermaidId(depTitle), status = "todo" } | |||
edges[nodes[depTitle].id .. "-->" .. nodes[title].id] = true | |||
end | end | ||
end | end | ||
| Zeile 111: | Zeile 99: | ||
end | end | ||
local out = {} | |||
table.insert(out, "{{#mermaid:") | |||
table.insert(out, "{{#mermaid:flowchart LR") | table.insert(out, "flowchart LR") | ||
-- Node | -- Node labels | ||
for t, n in pairs(nodes) do | for t, n in pairs(nodes) do | ||
local label = t | local label = stripNs(t) | ||
label = mw.ustring.gsub(label, '"', '\\"') | label = mw.ustring.gsub(label, '"', '\\"') | ||
table.insert(out, string.format(' %s["%s"]', n.id, label)) | table.insert(out, string.format(' %s["%s"]', n.id, label)) | ||
end | end | ||
-- | -- edges | ||
for k, _ in pairs(edges) do | for k, _ in pairs(edges) do | ||
local fromId, toId = k:match("^(.-)%-%-%>(.-)$") | local fromId, toId = k:match("^(.-)%-%-%>(.-)$") | ||
| Zeile 132: | Zeile 118: | ||
end | end | ||
- | -- Styles (optional) | ||
table.insert(out, " classDef todo fill:#fff,stroke:#333;") | table.insert(out, " classDef todo fill:#fff,stroke:#333;") | ||
table.insert(out, " classDef doing fill:#fff,stroke:#333,stroke-width:2px;") | table.insert(out, " classDef doing fill:#fff,stroke:#333,stroke-width:2px;") | ||
table.insert(out, " classDef done fill:#fff,stroke:#0a0,stroke-width:2px;") | table.insert(out, " classDef done fill:#fff,stroke:#0a0,stroke-width:2px;") | ||
table.insert(out, " classDef onhold fill:#fff,stroke:#999,stroke-dasharray: 5 5;") | table.insert(out, " classDef onhold fill:#fff,stroke:#999,stroke-dasharray: 5 5;") | ||
table.insert(out, " classDef blocked fill:#fff,stroke:#f00,stroke-width:2px;") | |||
for _, n in pairs(nodes) do | for _, n in pairs(nodes) do | ||
table.insert(out, string.format(" class %s %s;", n.id, norm(n.status))) | |||
table.insert(out, string.format(" class %s %s;", n.id, | |||
end | end | ||
Version vom 25. Februar 2026, 04:40 Uhr
Die Dokumentation für dieses Modul kann unter Modul:ProjectGraph/Doku erstellt werden
local p = {}
local function mermaidId(title)
local base = mw.ustring.gsub(title, "[^%w]", "_")
if mw.ustring.match(base, "^%d") then base = "T_" .. base end
if #base > 40 then
local h = mw.hash.hashValue("md5", title):sub(1, 10)
base = base:sub(1, 30) .. "_" .. h
end
return base
end
local function stripNs(title)
return mw.ustring.gsub(title or "", "^[^:]+:", "")
end
local function getPrintouts(row)
if row.printouts then return row.printouts end
return row
end
local function asList(v)
if not v then return {} end
if type(v) ~= "table" then return { v } end
return v
end
local function valueToString(v)
if type(v) == "table" then
if v.fulltext then return v.fulltext end
if v.value then return tostring(v.value) end
if v[1] then return valueToString(v[1]) end
end
if type(v) == "string" then return v end
if type(v) == "number" then return tostring(v) end
return nil
end
local function rowTitle(row)
if row.fulltext then return row.fulltext end
if row[1] and type(row[1]) == "string" then return row[1] end
if row[1] and type(row[1]) == "table" and row[1].fulltext then return row[1].fulltext end
return nil
end
local function norm(s)
if not s then return "todo" end
s = mw.ustring.lower(mw.text.trim(tostring(s)))
if s == "" then return "todo" end
return s
end
local function firstText(po, key)
local v = po[key]
if not v then return nil end
if type(v) == "table" and v[1] ~= nil then
return valueToString(v[1])
end
return valueToString(v)
end
function p.mermaid(frame)
local args = frame.args
local parentArgs = frame:getParent() and frame:getParent().args or {}
local project = args.project or parentArgs.project
if not project or project == "" then
return "Fehler: project=... fehlt."
end
local q = {
string.format("[[Gehört zu Projekt::%s]]", project),
"?Abhängig von",
"?Status",
"mainlabel=-",
"limit=2000"
}
local res = mw.smw.ask(q) or {}
local nodes = {}
local edges = {}
for _, row in ipairs(res) do
local title = rowTitle(row)
if title then
local po = getPrintouts(row)
local st = norm(firstText(po, "Status"))
nodes[title] = nodes[title] or { id = mermaidId(title), status = st }
local deps = po["Abhängig von"]
for _, dv in ipairs(asList(deps)) do
local depTitle = valueToString(dv)
if depTitle and depTitle ~= "" then
nodes[depTitle] = nodes[depTitle] or { id = mermaidId(depTitle), status = "todo" }
edges[nodes[depTitle].id .. "-->" .. nodes[title].id] = true
end
end
end
end
local out = {}
table.insert(out, "{{#mermaid:")
table.insert(out, "flowchart LR")
-- Node labels
for t, n in pairs(nodes) do
local label = stripNs(t)
label = mw.ustring.gsub(label, '"', '\\"')
table.insert(out, string.format(' %s["%s"]', n.id, label))
end
-- edges
for k, _ in pairs(edges) do
local fromId, toId = k:match("^(.-)%-%-%>(.-)$")
if fromId and toId then
table.insert(out, string.format(" %s --> %s", fromId, toId))
end
end
-- Styles (optional)
table.insert(out, " classDef todo fill:#fff,stroke:#333;")
table.insert(out, " classDef doing fill:#fff,stroke:#333,stroke-width:2px;")
table.insert(out, " classDef done fill:#fff,stroke:#0a0,stroke-width:2px;")
table.insert(out, " classDef onhold fill:#fff,stroke:#999,stroke-dasharray: 5 5;")
table.insert(out, " classDef blocked fill:#fff,stroke:#f00,stroke-width:2px;")
for _, n in pairs(nodes) do
table.insert(out, string.format(" class %s %s;", n.id, norm(n.status)))
end
table.insert(out, "}}")
return table.concat(out, "\n")
end
return p