import os
import re
import math
import fileUtil
import nodeParser
import juncWriter
with open("docsdata/templates/node.html", "r") as f:
template = f.read()
def generateBadge(title, tooltip, color, page = ""):
style = f"color: #{color}; background-color: #{color}16; border-color: #{color}60;"
return f'<a href="{page}" class="badge" style="{style}" title="{tooltip}">{title}</a>'
def generateBasicData(nodeData, metadata):
nodeName = metadata["name"]
node = metadata["baseNode"]
spr = metadata["spr"] if "spr" in metadata else f"s_{node.lower()}"
categories = nodeData["categories"]
parents = nodeData["inheritances"]
basicData = '<tr><th class="head first" colspan="2"><p>Node Data</p></th></tr>'
basicData += f'<tr><th colspan="2"><img {spr}></th></tr>'
badges = ""
for btitle, bdesp, bcolor, bpage in categories:
badges += generateBadge(btitle, bdesp, bcolor, bpage)
if badges != "":
basicData += '<tr style="height: 4px;"></tr>'
basicData += f'<tr><th colspan="2">{badges}</th></tr>'
basicData += '<tr style="height: 8px;"></tr>'
basicData += f'<tr><th colspan="2" class="summary-topic"><p class="subtopic">Display name</p></th></tr>'
basicData += f'<tr><th colspan="2" class="summary-content"><p>{nodeName}</p></th></tr>'
basicData += f'<tr><th colspan="2" class="summary-topic"><p class="subtopic">Internal name</p></th></tr>'
basicData += f'<tr><th colspan="2" class="summary-content"><p>{node}</p></th></tr>'
if "tags" in metadata:
basicData += f'<tr><th colspan="2" class="summary-tag"><div>'
for tag in metadata["tags"]:
href = f"/nodes/_tags/{fileUtil.pathSanitize(tag)}.html"
basicData += f'<a href="{href}" class="tag {tag.lower().replace(" ", "_")}">{tag}</a>'
basicData += '</div></th></tr>'
basicData += '<tr height="8px"></tr>'
basicData += '<tr><th class="head" colspan="2"><p>Inheritances</p></th></tr>'
for i, p in enumerate(parents):
pName = p["name"]
link = ""
if pName == "Node":
link = "/nodes/index.html"
elif pName == "Node_Processor":
link = "/nodes/array_processor.html"
else:
link = f"/nodes/_index/{pName}.html"
_class = "inheritance-block"
if i == len(parents) - 1:
_class = "inheritance-block current"
link = "#"
content = f'<a href="{link}">{pName}</a>'
if i > 0:
content = f'''<div class="vertical"></div>{content}'''
basicData += f'''<tr><th colspan="2" class="{_class}">{content}</th></tr>'''
return basicData
def applySummaryTable(basicData, junctionText, attributeText):
return basicData + '<tr height="8px"></tr>' + \
junctionText + \
attributeText
def applyTemplate(template, nodeName, tooltip, summary):
return template.replace("{{nodeName}}", nodeName) \
.replace("{{tooltip}}", tooltip) \
.replace("{{summary}}", summary)
def versionToInt(version):
v = re.sub(r"[^0-9]", "", version)
v += "0" * (10 - len(v))
return int(v)
def writeChangeTable(metadata, changeData):
nodeName = metadata["name"]
hasAnyChange = False
changeText = '''
<br><h2 class="nosidebar">Commit History</h2><br>
<table class="change-table">'''
addVersion = metadata["pxc_version"] if "pxc_version" in metadata else None
addString = ""
if addVersion:
addVersion = math.floor(addVersion)
vMaj = 1
if addVersion > 100_000:
vMin = math.floor((addVersion % 100_000) / 1_000)
vBet = math.floor((addVersion % 1_000) / 10)
addString = f"{vMaj}.{vMin}.{vBet}"
vPat = addVersion % 10
if vPat != 0:
addString = f"{addString}.{vPat}"
elif addVersion > 10_000:
vMin = math.floor((addVersion % 10_000) / 100)
vBet = math.floor((addVersion % 100) / 10)
addString = f"{vMaj}.{vMin}.{vBet}"
vPat = addVersion % 10
if vPat != 0:
addString = f"{addString}.{vPat}"
else:
vMin = math.floor((addVersion % 1_000) / 10)
vBet = addVersion % 10
addString = f"{vMaj}.{vMin}.{vBet}"
changeText += f'<tr><th>{addString}</th><td><ul><li><span>Introduced</span></li></ul></td></tr>'
hasAnyChange = True
changeData.sort(key=lambda x: versionToInt(x["version"]), reverse=True)
for change in changeData:
version = change["version"]
changes = change["changes"]
changeTextV = f'<tr><th>{version}</th>'
commitMessage = "<ul>"
hasChanges = False
for c in changes:
commit = c["commit"]
node = c["node"]
if(node != nodeName and node != "any"):
continue
if(commit.startswith("Fix")):
continue
if commit == "Introduced" and addString != "":
continue
commit = commit.split(":")[1] if ":" in commit else commit
commitMessage += f'<li><span>{commit}</span></li>'
hasChanges = True
commitMessage += "</ul>"
changeTextV += f'<td>{commitMessage}</td></tr>'
if hasChanges:
changeText += changeTextV
hasAnyChange = True
changeText += '</table>'
if not hasAnyChange:
return ""
return changeText
def writeLinks(linkContents, metadata, nodeData):
nodeBase = metadata["baseNode"]
srchub = "https://github.com/Ttanasart-pt/Pixel-Composer/blob/main"
links = []
baseLower = nodeBase.lower()
links.append(("Base Code", f'/scripts/{baseLower}/{baseLower}.gml'))
shaders = nodeData["shaders"] if "shaders" in nodeData else []
for shader in shaders:
shaderLower = shader.lower()
links.append((f"Shader [{shader}]", f'/shaders/{shaderLower}/{shaderLower}.fsh'))
linkText = '''<br><h2 class="nosidebar">Related Links</h2><br><ul class="link-table">'''
for link in linkContents:
if link[0] is not None:
linkText += f'''<li><span>{link[0]}: </span><a href="{link[1]}">{link[1]}</a></li>'''
else:
linkText += f'''<li><span><a href="{link[1]}">{link[1]}</a></span></li>'''
for link in links:
fullLink = srchub + link[1]
linkText += f'''<li><span>{link[0]}: </span><a href="{fullLink}">{link[1]}</a></li>'''
linkText += '</ul>'
return linkText
def writeNode(metadata, contentPath, changeData = None):
nodeName = metadata["name"]
nodeBase = metadata["baseNode"]
tooltip = metadata["tooltip"] if "tooltip" in metadata else ""
nodeData = nodeParser.getNodeData(nodeBase)
if not nodeData:
if nodeBase != "BaseNode":
print(f"Node data for {nodeBase} not found.")
return None
basicData = generateBasicData(nodeData, metadata)
junctionText = juncWriter.IOTable(nodeData)
attributeText = juncWriter.AttributeTable(nodeData)
summary = applySummaryTable(basicData, junctionText, attributeText)
content = applyTemplate(template, nodeName, tooltip, summary)
junctions = {}
for junc in nodeData["inputs"] + nodeData["outputs"]:
jName = junc["name"].lower()
junctions[jName] = junc
rawContent = ""
if os.path.exists(contentPath):
with open(contentPath, "r") as f:
rawContent = f.read()
linkContents = []
reLink = re.compile(r"\[link\]([\s\S]*?)\[\/link\]")
links = reLink.findall(rawContent)
for l in links:
lStrip = l.replace("<p>", "").replace("</p>", "")
ls = lStrip.split("|")
if len(ls) == 2:
linkContents.append((ls[0].strip(), ls[1].strip()))
elif len(ls) == 1:
linkContents.append((None, ls[0].strip()))
rawContent = rawContent.replace(f"[link]{l}[/link]", "")
juncTags = re.findall(r'<junc\s(.*?)>', rawContent)
for tag in juncTags:
jName = tag.strip("/").lower()
if jName == "":
continue
if jName in junctions:
jColor = juncWriter.getColor(junctions[jName]["type"])
rawContent = rawContent.replace(f'<junc {tag}>', f'<span class="junction" style="border-color: {jColor}AA">{jName.title()}</span>')
else:
rawContent = rawContent.replace(f'<junc {tag}>', f'<span class="junction">{jName.title()}</span>')
attrTags = re.findall(r'<attr\s(.*?)/>', rawContent)
for tag in attrTags:
rawContent = rawContent.replace(f'<attr {tag}/>', f'<span class="inline-code">{tag}</span>')
nodeBaseLower = nodeBase.lower()
imagePath = f"src/images/nodegen/{nodeBaseLower}/{nodeBaseLower}-graph.png"
if os.path.exists(f"docsdata/{imagePath}"):
content += f'<img class="node-graph-preview" src="/{imagePath}">'
samplePath = f"docsdata/src/images/nodegen/{nodeBaseLower}/{nodeBaseLower}-sample.txt"
if os.path.exists(samplePath):
with open(samplePath, "r") as f:
sampleContent = f.read()
sampleContent = sampleContent.replace('"', '\\"').replace("\n", "")
content += f'''<script>const sampleContent = "{sampleContent}";</script>'''
content += f'''<button class="copy-sample" onclick="copyToClipboard(sampleContent)">
Copy to Clipboard
</button>'''
content += "<br>"
content += rawContent
if changeData:
content += writeChangeTable(metadata, changeData)
content += writeLinks(linkContents, metadata, nodeData)
return content
def writeCategory(name, nodes, nodeMetadata, subFilter = None):
title = name
content = f'<h1>{title}</h1><br><br>'
sgName = ''
subGroupCurrent = None
closeGroup = False
closeNodeGroup = False
for node in nodes:
if not isinstance(node, str):
subGroup = node["label"]
sgName = subGroup.strip("/")
subsubGroup = subGroup.startswith("/")
if subsubGroup:
if subFilter and subFilter != subGroupCurrent:
continue
if closeNodeGroup:
content += '</div>'
closeNodeGroup = False
content += f'<h5 class="node-group-title">{sgName}</h5>'
else:
subGroupCurrent = sgName
if subFilter and subFilter != subGroupCurrent:
continue
if closeNodeGroup:
content += '</div>'
closeNodeGroup = False
if closeGroup:
content += '</div>'
closeGroup = False
content += '<div class="node-category">'
content += f'<h3 class="node-group-title">{sgName}</h3>'
closeGroup = True
continue
if subFilter and subFilter != subGroupCurrent:
continue
if not closeNodeGroup:
content += '<div class="node-group">'
closeNodeGroup = True
if node in nodeMetadata:
metadata = nodeMetadata[node]
name = metadata["name"]
spr = metadata["spr"] if "spr" in metadata else f"s_{node.lower()}"
else:
name = node.replace("_", " ").title()
spr = "node_generic"
content += f'<div class="node-card"><a href="/nodes/_index/{node.lower()}.html"><img {spr}>{name}</a></div>\n'
if closeGroup:
content += '</div>'
if closeNodeGroup:
content += '</div>'
return content
def writeTag(tag, nodes, nodeMetadata):
title = f"Tag: {tag}"
content = f"""<h1>{title}</h1><br><br>"""
content += '<div class="node-category">'
content += f'<h3 class="node-group-title">Nodes</h3>'
content += '<div class="node-group">'
for node in nodes:
if node not in nodeMetadata:
print(f"Node metadata for {node} not found.")
continue
metadata = nodeMetadata[node]
name = metadata["name"]
spr = metadata["spr"] if "spr" in metadata else f"s_{node.lower()}"
nodeName = fileUtil.pathSanitize(name)
content += f'''<div class="node-card"><a href="/nodes/_index/{nodeName}.html"><img {spr}>{name}</a></div>\n'''
content += '</div></div>'
return content