>
- if cMatches := adocChoiceRegex.FindAllStringSubmatch(line, -1); len(cMatches) > 0 {
- for _, match := range cMatches {
- currentNode.Choices = append(currentNode.Choices, Choice{
- TargetID: match[1],
- Text: strings.TrimSpace(match[2]),
- })
- }
- }
-
- // Markdown Verzweigung finden: [Optionstext](#ziel_id)
- if mMatches := mdChoiceRegex.FindAllStringSubmatch(line, -1); len(mMatches) > 0 {
- for _, match := range mMatches {
- currentNode.Choices = append(currentNode.Choices, Choice{
- TargetID: match[2],
- Text: strings.TrimSpace(match[1]),
- })
- }
- }
-
- // Status-Kennzeichnungen erkennen
- lowerLine := strings.ToLower(line)
- if strings.Contains(lowerLine, "[reboot]") || strings.Contains(lowerLine, "speicherabbild") {
- currentNode.IsReboot = true
- }
- if strings.Contains(lowerLine, "[ende]") || strings.Contains(lowerLine, "[end]") {
- currentNode.IsEnd = true
- }
-
- currentBody.WriteString(line + "\n")
- }
-
- if currentNode != nil {
- currentNode.Content = renderWithEchoBox(currentBody.String())
- graph.Nodes[currentNode.ID] = currentNode
- }
-
return nil
})
-
- if err != nil {
- fmt.Printf("\033[1;31m[WARNUNG]\033[0m Fehler beim Durchsuchen der Dateien: %v\n", err)
- }
-
return graph
}
-// renderWithEchoBox entfernt Syntax-Rauschen und nutzt den Debian 13 Docker Container als HTML Echo Box
-func renderWithEchoBox(raw string) string {
- fmt.Println("render with docker")
- // Links entfernen (Diese sollen als Buttons, nicht als Text-Wirrwarr auftauchen)
- adocChoiceRegex := regexp.MustCompile(`<<[a-zA-Z0-9_\-]+\s*,\s*([^>]+)>>`)
- cleaned := adocChoiceRegex.ReplaceAllString(raw, "")
-
- mdChoiceRegex := regexp.MustCompile(`\[([^\]]+)\]\((?:#)?[a-zA-Z0-9_\-]+\)`)
- cleaned = mdChoiceRegex.ReplaceAllString(cleaned, "")
-
- // Docker Echo Box aufrufen (Debian 13 Asciidoctor)
- cmd := exec.Command("docker", "run", "--rm", "-i", "asciidoctor-echo")
- cmd.Stdin = strings.NewReader(strings.TrimSpace(cleaned))
-
- out, err := cmd.Output()
- if err != nil {
- // Fallback bei Docker-Fehler (z.B. wenn der Daemon nicht läuft)
- fmt.Printf("\033[1;31m[DOCKER FEHLER]\033[0m Konnte Echo Box nicht nutzen. Fallback auf RAW Text.\n")
- return html.EscapeString(strings.TrimSpace(cleaned))
- }
-
- return string(out)
-}
-
-// validateGraph prüft Verzweigungen auf Konsistenz
func validateGraph(g *StoryGraph) {
- fmt.Printf("Gefundene Knotenpunkte: \033[1;32m%d\033[0m | Start-Knoten: \033[1;33m%s\033[0m\n", len(g.Nodes), g.StartNodeID)
-
referenced := make(map[string]bool)
- if g.StartNodeID != "" {
- referenced[g.StartNodeID] = true
- }
-
+ if g.StartNodeID != "" { referenced[g.StartNodeID] = true }
for id, node := range g.Nodes {
- // Tote Links prüfen
for _, choice := range node.Choices {
referenced[choice.TargetID] = true
if _, exists := g.Nodes[choice.TargetID]; !exists {
g.BrokenLinks = append(g.BrokenLinks, fmt.Sprintf("%s -> %s (in %s)", id, choice.TargetID, node.FileSource))
}
}
-
- // Sackgassen ohne Choices & ohne End-Markierung
if len(node.Choices) == 0 && !node.IsReboot && !node.IsEnd {
g.DeadEndNodes = append(g.DeadEndNodes, id)
}
}
-
- // Waisen-Knoten ermitteln
for id := range g.Nodes {
- if !referenced[id] {
- g.OrphanNodes = append(g.OrphanNodes, id)
- }
- }
-
- // Konsolen-Bericht
- if len(g.BrokenLinks) > 0 {
- fmt.Println("\n\033[1;31m[!] DEFEKTE ZIEL-LINKS (Fehlende Knoten):\033[0m")
- for _, link := range g.BrokenLinks {
- fmt.Printf(" - %s\n", link)
- }
- }
-
- if len(g.OrphanNodes) > 0 {
- fmt.Println("\n\033[1;33m[?] UNERREICHBARE KNOTEN (Waisen):\033[0m")
- for _, orphan := range g.OrphanNodes {
- fmt.Printf(" - %s\n", orphan)
- }
- }
-
- if len(g.DeadEndNodes) > 0 {
- fmt.Println("\n\033[1;35m[*] SACKGASSEN OHNE OPTIONEN:\033[0m")
- for _, dead := range g.DeadEndNodes {
- fmt.Printf(" - %s\n", dead)
- }
- }
-
- if len(g.BrokenLinks) == 0 && len(g.OrphanNodes) == 0 && len(g.DeadEndNodes) == 0 {
- fmt.Println("\033[1;32m[✓] Graph-Check bestanden: Alle Verzweigungen sind konsistent!\033[0m")
+ if !referenced[id] { g.OrphanNodes = append(g.OrphanNodes, id) }
}
}
-// generateHTML erzeugt das eigenständige HTML5 CRT-Terminal Schreibdeck
-func generateHTML(g *StoryGraph, outPath string) error {
- tmpl, err := template.New("story").Parse(htmlTemplate)
- if err != nil {
- return err
+// JSON Helper für das Template
+func getTemplateFuncMap() template.FuncMap {
+ return template.FuncMap{
+ "json": func(v interface{}) template.JS {
+ a, _ := json.Marshal(v)
+ return template.JS(a)
+ },
}
+}
+func generateHTMLFile(g *StoryGraph, outPath string, tmplPath string) error {
+ tmpl, err := template.New(filepath.Base(tmplPath)).Funcs(getTemplateFuncMap()).ParseFiles(tmplPath)
+ if err != nil { return fmt.Errorf("Template-Fehler: %v", err) }
f, err := os.Create(outPath)
- if err != nil {
- return err
- }
+ if err != nil { return err }
defer f.Close()
-
return tmpl.Execute(f, g)
}
-// startServer führt den Live-Preview-Webserver aus
-func startServer(g *StoryGraph, htmlPath string, port int, watchDir string) {
+func startServer(port int, watchDir string, htmlPath string, tmplPath string) {
addr := fmt.Sprintf(":%d", port)
- fmt.Printf("\n\033[1;34m[SERVER]\033[0m Live Terminal Web-Engine läuft unter: \033[1;32mhttp://localhost:%d\033[0m\n", port)
- fmt.Println("Drücke Strg+C zum Beenden.")
+ fmt.Printf("\n\033[1;32m[CONTAINER-API LÄUFT]\033[0m Port: %d | Nutzt Template: %s\n", port, tmplPath)
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
- // Bei jedem Aufruf Graph neu laden für Hot-Reload Feeling
- freshGraph := loadStoryGraph(watchDir)
- _ = generateHTML(freshGraph, htmlPath)
- http.ServeFile(w, r, htmlPath)
+ w.Header().Set("Access-Control-Allow-Origin", "*")
+
+ if r.Method == http.MethodPost {
+ bodyBytes, err := io.ReadAll(r.Body)
+ if err != nil {
+ http.Error(w, "Daten defekt", http.StatusBadRequest)
+ return
+ }
+
+ graph := &StoryGraph{Nodes: make(map[string]*StoryNode)}
+ // Rohdaten durch den Parser und den Asciidoctor-Container schleusen
+ parseContent(string(bodyBytes), "API_POST_Stream", graph)
+ validateGraph(graph)
+
+ w.Header().Set("Content-Type", "text/html; charset=utf-8")
+ tmpl, err := template.New(filepath.Base(tmplPath)).Funcs(getTemplateFuncMap()).ParseFiles(tmplPath)
+ if err != nil {
+ http.Error(w, "Template-Fehler: "+err.Error(), http.StatusInternalServerError)
+ return
+ }
+ tmpl.Execute(w, graph)
+ fmt.Println("\033[1;34m[POST-API]\033[0m Story inkl. Asciidoctor-HTML gerendert.")
+ return
+ }
+
+ if r.Method == http.MethodGet {
+ freshGraph := loadStoryGraphFromDir(watchDir)
+ _ = generateHTMLFile(freshGraph, htmlPath, tmplPath)
+ http.ServeFile(w, r, htmlPath)
+ }
})
- err := http.ListenAndServe(addr, nil)
- if err != nil {
- fmt.Printf("Server-Fehler: %v\n", err)
- }
-}
-
-// Integrierte Retro-CRT Engine Templates (HTML, CSS, JS)
-const htmlTemplate = `
-
-
-
-
- Computer Historyz :: Interactive Terminal Engine
-
-
-
-
-
-
-
-
-
System Initialisierung...
-
Verbinde mit Datenstrom...
-
-
-
-
-
-
-
-
- === INTERAKTIVER VORHERSAGE-GRAPH ===
-
-
-
-
-
-
-
-`
\ No newline at end of file
+ http.ListenAndServe(addr, nil)
+}
\ No newline at end of file
diff --git a/00_aMeta/toolz/CYOA/templates/template.html b/00_aMeta/toolz/CYOA/templates/template.html
new file mode 100644
index 0000000..cfad7ea
--- /dev/null
+++ b/00_aMeta/toolz/CYOA/templates/template.html
@@ -0,0 +1,336 @@
+
+
+
+
+
+ Computer Historyz :: Interactive Terminal Engine
+
+
+
+
+
+
+
+
+
System Initialisierung...
+
Verbinde mit Datenstrom...
+
+
+
+
+
+
+
+
+ === INTERAKTIVER VORHERSAGE-GRAPH ===
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file