Modul:ProjectGraph

Aus Kyffhäuser KI
Version vom 25. Februar 2026, 03:36 Uhr von Markus (Diskussion | Beiträge) (Die Seite wurde neu angelegt: „local p = {} -- Mermaid IDs: müssen stabil, "sicher" und eindeutig sein. local function mermaidId(title) local base = mw.ustring.gsub(title, "[^%w]", "_") if mw.ustring.match(base, "^%d") then base = "T_" .. base end -- Kollisionen/Längen vermeiden: 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 getPrintouts(row) -- SMW liefert je…“)
(Unterschied) ← Nächstältere Version | Aktuelle Version (Unterschied) | Nächstjüngere Version → (Unterschied)
Zur Navigation springen Zur Suche springen

Die Dokumentation für dieses Modul kann unter Modul:ProjectGraph/Doku erstellt werden

local p = {}

-- Mermaid IDs: müssen stabil, "sicher" und eindeutig sein.
local function mermaidId(title)
  local base = mw.ustring.gsub(title, "[^%w]", "_")
  if mw.ustring.match(base, "^%d") then
    base = "T_" .. base
  end
  -- Kollisionen/Längen vermeiden:
  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 getPrintouts(row)
  -- SMW liefert je nach Version/Config unterschiedliche Strukturen.
  -- Häufig: row.printouts["PropertyName"] = { ... }
  -- Oder ältere: row["PropertyName"]
  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
  -- Manche Werte sind Tabellen mit fulltext; manche sind schon Listen.
  return v
end

local function pageTitleFromValue(val)
  if type(val) == "table" and val.fulltext then
    return val.fulltext
  end
  if type(val) == "string" then
    return val
  end
  return nil
end

local function normalizeStatus(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

-- invoke: {{#invoke:ProjectGraph|mermaid|project=Project:Foo}}
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=Project:... fehlt."
  end

  local q = {
    string.format("[[Gehört zu Projekt::%s]]", project),
    "?Abhängig von",
    "?Status",
    "mainlabel=-",
    "limit=1000"
  }

  local res = mw.smw.ask(q) or {}

  -- Sammeln
  local nodes = {}   -- title -> { id=..., status=... }
  local edges = {}   -- "fromId-->toId" -> true (Dedup)

  for _, row in ipairs(res) do
    local title = row.fulltext or row[1]
    if title then
      local po = getPrintouts(row)
      local statusVal = nil

      -- Status kann als Liste kommen:
      local st = po["Status"]
      if type(st) == "table" then
        -- kann { "todo" } oder { { ... } } sein
        if st[1] ~= nil then
          statusVal = st[1]
        end
      else
        statusVal = st
      end

      local status = normalizeStatus(statusVal)

      if not nodes[title] then
        nodes[title] = { id = mermaidId(title), status = status }
      else
        nodes[title].status = nodes[title].status or status
      end

      -- Dependencies: Task X ist abhängig von D => Kante D --> X
      local deps = po["Abhängig von"]
      for _, dv in ipairs(asList(deps)) do
        local depTitle = pageTitleFromValue(dv)
        if depTitle and depTitle ~= "" then
          if not nodes[depTitle] then
            nodes[depTitle] = { id = mermaidId(depTitle), status = "todo" }
          end
          local fromId = nodes[depTitle].id
          local toId = nodes[title].id
          edges[fromId .. "-->" .. toId] = true
        end
      end
    end
  end

  -- Mermaid bauen
  local out = {}
  table.insert(out, "```mermaid")
  table.insert(out, "flowchart LR")

  -- Node-Definitions (mit Label ohne Namespace hübscher machen)
  for t, n in pairs(nodes) do
    local label = t
    label = mw.ustring.gsub(label, "^[^:]+:", "") -- Namespace entfernen (Task:)
    -- Mermaid Label escapen (einfach)
    label = mw.ustring.gsub(label, '"', '\\"')
    table.insert(out, string.format('  %s["%s"]', n.id, label))
  end

  -- Kanten
  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

  -- Status-Styles (optional; greift nur, wenn dein Mermaid-Renderer classDefs unterstützt)
  table.insert(out, "")
  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 blocked fill:#fff,stroke:#f00,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;")

  for _, n in pairs(nodes) do
    local st = normalizeStatus(n.status)
    table.insert(out, string.format("  class %s %s;", n.id, st))
  end

  table.insert(out, "```")
  return table.concat(out, "\n")
end

return p