add an html interpreter
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/html-output/
|
||||
/masonry.exe
|
36
.idea/copilotDiffState.xml
generated
36
.idea/copilotDiffState.xml
generated
File diff suppressed because one or more lines are too long
@ -11,9 +11,15 @@ import (
|
||||
vue_gen "masonry/vue-gen"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/alecthomas/participle/v2"
|
||||
|
||||
"masonry/interpreter"
|
||||
"masonry/lang"
|
||||
)
|
||||
|
||||
//go:embed templates/proto/application.proto.tmpl
|
||||
@ -156,10 +162,51 @@ func generateCmd() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "generate",
|
||||
Aliases: []string{"g"},
|
||||
Usage: "Generate code from proto files",
|
||||
Usage: "Generate code from proto files or Masonry files",
|
||||
Category: "generator",
|
||||
Description: "This command will generate code from the proto files in the proto directory and place them in a language folder in the gen folder.",
|
||||
Description: "This command will generate code from proto files or convert Masonry files to various formats.",
|
||||
Subcommands: []*cli.Command{
|
||||
{
|
||||
Name: "proto",
|
||||
Usage: "Generate code from proto files",
|
||||
Action: func(c *cli.Context) error {
|
||||
return generateProtoCode()
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "html",
|
||||
Usage: "Generate HTML from Masonry files",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "input",
|
||||
Usage: "Input Masonry file path",
|
||||
Required: true,
|
||||
Aliases: []string{"i"},
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "output",
|
||||
Usage: "Output directory for generated HTML files",
|
||||
Value: "./output",
|
||||
Aliases: []string{"o"},
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
inputFile := c.String("input")
|
||||
outputDir := c.String("output")
|
||||
|
||||
return generateHTML(inputFile, outputDir)
|
||||
},
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
// Default action - generate proto code for backward compatibility
|
||||
return generateProtoCode()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// generateProtoCode handles the original proto code generation logic
|
||||
func generateProtoCode() error {
|
||||
fmt.Println("Generating code...")
|
||||
|
||||
protocArgs := []string{
|
||||
@ -250,8 +297,6 @@ func generateCmd() *cli.Command {
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func webappCmd() *cli.Command {
|
||||
@ -368,3 +413,54 @@ func vueGenCmd() *cli.Command {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// generateHTML parses a Masonry file and generates HTML output
|
||||
func generateHTML(inputFile, outputDir string) error {
|
||||
fmt.Printf("Generating HTML from %s to %s\n", inputFile, outputDir)
|
||||
|
||||
// Read the input file
|
||||
content, err := os.ReadFile(inputFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read input file: %w", err)
|
||||
}
|
||||
|
||||
// Create parser
|
||||
parser, err := participle.Build[lang.AST]()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create parser: %w", err)
|
||||
}
|
||||
|
||||
// Parse the Masonry file
|
||||
ast, err := parser.ParseString(inputFile, string(content))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse Masonry file: %w", err)
|
||||
}
|
||||
|
||||
// Create HTML interpreter
|
||||
htmlInterpreter := interpreter.NewHTMLInterpreter()
|
||||
|
||||
// Generate HTML
|
||||
htmlFiles, err := htmlInterpreter.GenerateHTML(ast)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate HTML: %w", err)
|
||||
}
|
||||
|
||||
// Create output directory
|
||||
err = os.MkdirAll(outputDir, 0755)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create output directory: %w", err)
|
||||
}
|
||||
|
||||
// Write HTML files
|
||||
for filename, content := range htmlFiles {
|
||||
outputPath := filepath.Join(outputDir, filename)
|
||||
err = os.WriteFile(outputPath, []byte(content), 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write file %s: %w", outputPath, err)
|
||||
}
|
||||
fmt.Printf("Generated: %s\n", outputPath)
|
||||
}
|
||||
|
||||
fmt.Printf("Successfully generated %d HTML file(s)\n", len(htmlFiles))
|
||||
return nil
|
||||
}
|
||||
|
61
examples/lang/sample.masonry
Normal file
61
examples/lang/sample.masonry
Normal file
@ -0,0 +1,61 @@
|
||||
// Sample Masonry application demonstrating the language features
|
||||
entity User {
|
||||
name: string required
|
||||
email: string required unique
|
||||
age: number
|
||||
role: string default "user"
|
||||
password: string required
|
||||
}
|
||||
|
||||
page UserDashboard at "/dashboard" layout main title "User Dashboard" {
|
||||
meta description "User management dashboard"
|
||||
meta keywords "user, dashboard, management"
|
||||
|
||||
section header type container class "header" {
|
||||
component nav {
|
||||
field logo type text value "MyApp"
|
||||
button profile label "Profile"
|
||||
button logout label "Logout"
|
||||
}
|
||||
}
|
||||
|
||||
section content type tab {
|
||||
section users label "Users" active {
|
||||
component table for User {
|
||||
field name type text label "Full Name" sortable searchable
|
||||
field email type email label "Email Address" sortable
|
||||
field role type select options ["admin", "user", "moderator"]
|
||||
button edit label "Edit"
|
||||
button delete label "Delete"
|
||||
}
|
||||
}
|
||||
|
||||
section settings label "Settings" {
|
||||
component form for User {
|
||||
field name type text label "Full Name" required placeholder "Enter your name"
|
||||
field email type email label "Email" required placeholder "Enter your email"
|
||||
field age type number label "Age" placeholder "Enter your age"
|
||||
field role type select label "Role" options ["admin", "user", "moderator"] default "user"
|
||||
field password type password label "Password" required
|
||||
|
||||
when role equals "admin" {
|
||||
field permissions type select label "Admin Permissions" options ["read", "write", "delete"]
|
||||
}
|
||||
|
||||
button save label "Save Changes"
|
||||
button cancel label "Cancel"
|
||||
}
|
||||
}
|
||||
|
||||
section profile label "Profile" {
|
||||
component form {
|
||||
field avatar type file label "Profile Picture" accept ".jpg,.png"
|
||||
field bio type textarea label "Biography" rows 4 placeholder "Tell us about yourself"
|
||||
field theme type select label "Theme" options ["light", "dark"] default "light"
|
||||
field notifications type checkbox label "Enable Notifications" default "true"
|
||||
|
||||
button update label "Update Profile"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
526
interpreter/html_interpreter.go
Normal file
526
interpreter/html_interpreter.go
Normal file
@ -0,0 +1,526 @@
|
||||
package interpreter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"masonry/lang"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// HTMLInterpreter converts Masonry AST to HTML/JavaScript
|
||||
type HTMLInterpreter struct {
|
||||
entities map[string]*lang.Entity
|
||||
pages map[string]*lang.Page
|
||||
}
|
||||
|
||||
// NewHTMLInterpreter creates a new HTML interpreter
|
||||
func NewHTMLInterpreter() *HTMLInterpreter {
|
||||
return &HTMLInterpreter{
|
||||
entities: make(map[string]*lang.Entity),
|
||||
pages: make(map[string]*lang.Page),
|
||||
}
|
||||
}
|
||||
|
||||
// cleanString removes surrounding quotes from string literals
|
||||
func (hi *HTMLInterpreter) cleanString(s string) string {
|
||||
if len(s) >= 2 && ((s[0] == '"' && s[len(s)-1] == '"') || (s[0] == '\'' && s[len(s)-1] == '\'')) {
|
||||
return s[1 : len(s)-1]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// escapeHTML escapes HTML special characters
|
||||
func (hi *HTMLInterpreter) escapeHTML(s string) string {
|
||||
s = strings.ReplaceAll(s, "&", "&")
|
||||
s = strings.ReplaceAll(s, "<", "<")
|
||||
s = strings.ReplaceAll(s, ">", ">")
|
||||
s = strings.ReplaceAll(s, "\"", """)
|
||||
s = strings.ReplaceAll(s, "'", "'")
|
||||
return s
|
||||
}
|
||||
|
||||
// GenerateHTML converts a Masonry AST to HTML output
|
||||
func (hi *HTMLInterpreter) GenerateHTML(ast *lang.AST) (map[string]string, error) {
|
||||
// First pass: collect entities and pages
|
||||
for _, def := range ast.Definitions {
|
||||
if def.Entity != nil {
|
||||
hi.entities[def.Entity.Name] = def.Entity
|
||||
}
|
||||
if def.Page != nil {
|
||||
hi.pages[def.Page.Name] = def.Page
|
||||
}
|
||||
}
|
||||
|
||||
// Second pass: generate HTML for each page
|
||||
htmlFiles := make(map[string]string)
|
||||
|
||||
for pageName, page := range hi.pages {
|
||||
html, err := hi.generatePageHTML(page)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error generating HTML for page %s: %w", pageName, err)
|
||||
}
|
||||
htmlFiles[fmt.Sprintf("%s.html", strings.ToLower(pageName))] = html
|
||||
}
|
||||
|
||||
return htmlFiles, nil
|
||||
}
|
||||
|
||||
// generatePageHTML creates HTML for a single page
|
||||
func (hi *HTMLInterpreter) generatePageHTML(page *lang.Page) (string, error) {
|
||||
var html strings.Builder
|
||||
|
||||
// HTML document structure
|
||||
html.WriteString("<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n")
|
||||
html.WriteString(" <meta charset=\"UTF-8\">\n")
|
||||
html.WriteString(" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n")
|
||||
|
||||
// Page title
|
||||
title := page.Name
|
||||
if page.Title != nil {
|
||||
title = hi.cleanString(*page.Title)
|
||||
}
|
||||
html.WriteString(fmt.Sprintf(" <title>%s</title>\n", hi.escapeHTML(title)))
|
||||
|
||||
// Meta tags
|
||||
for _, meta := range page.Meta {
|
||||
cleanName := hi.cleanString(meta.Name)
|
||||
cleanContent := hi.cleanString(meta.Content)
|
||||
html.WriteString(fmt.Sprintf(" <meta name=\"%s\" content=\"%s\">\n",
|
||||
hi.escapeHTML(cleanName), hi.escapeHTML(cleanContent)))
|
||||
}
|
||||
|
||||
// Basic CSS for styling
|
||||
html.WriteString(" <style>\n")
|
||||
html.WriteString(" body { font-family: Arial, sans-serif; margin: 0; padding: 20px; }\n")
|
||||
html.WriteString(" .container { max-width: 1200px; margin: 0 auto; }\n")
|
||||
html.WriteString(" .section { margin: 20px 0; padding: 20px; border: 1px solid #ddd; border-radius: 8px; }\n")
|
||||
html.WriteString(" .tabs { border-bottom: 2px solid #ddd; margin-bottom: 20px; }\n")
|
||||
html.WriteString(" .tab-button { padding: 10px 20px; border: none; background: none; cursor: pointer; }\n")
|
||||
html.WriteString(" .tab-button.active { background: #007bff; color: white; }\n")
|
||||
html.WriteString(" .tab-content { display: none; }\n")
|
||||
html.WriteString(" .tab-content.active { display: block; }\n")
|
||||
html.WriteString(" .form-group { margin: 15px 0; }\n")
|
||||
html.WriteString(" .form-group label { display: block; margin-bottom: 5px; font-weight: bold; }\n")
|
||||
html.WriteString(" .form-group input, .form-group textarea, .form-group select { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; }\n")
|
||||
html.WriteString(" .button { padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; margin: 5px; }\n")
|
||||
html.WriteString(" .button-primary { background: #007bff; color: white; }\n")
|
||||
html.WriteString(" .button-secondary { background: #6c757d; color: white; }\n")
|
||||
html.WriteString(" .modal { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); }\n")
|
||||
html.WriteString(" .modal-content { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; padding: 20px; border-radius: 8px; min-width: 400px; }\n")
|
||||
html.WriteString(" </style>\n")
|
||||
html.WriteString("</head>\n<body>\n")
|
||||
|
||||
// Page content
|
||||
html.WriteString(" <div class=\"container\">\n")
|
||||
html.WriteString(fmt.Sprintf(" <h1>%s</h1>\n", hi.escapeHTML(title)))
|
||||
|
||||
// Generate sections
|
||||
for _, section := range page.Sections {
|
||||
sectionHTML, err := hi.generateSectionHTML(§ion, 2)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
html.WriteString(sectionHTML)
|
||||
}
|
||||
|
||||
// Generate direct components
|
||||
for _, component := range page.Components {
|
||||
componentHTML, err := hi.generateComponentHTML(&component, 2)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
html.WriteString(componentHTML)
|
||||
}
|
||||
|
||||
html.WriteString(" </div>\n")
|
||||
|
||||
// JavaScript for interactivity
|
||||
html.WriteString(" <script>\n")
|
||||
html.WriteString(" // Tab functionality\n")
|
||||
html.WriteString(" function showTab(tabName) {\n")
|
||||
html.WriteString(" const tabs = document.querySelectorAll('.tab-content');\n")
|
||||
html.WriteString(" tabs.forEach(tab => tab.classList.remove('active'));\n")
|
||||
html.WriteString(" const buttons = document.querySelectorAll('.tab-button');\n")
|
||||
html.WriteString(" buttons.forEach(btn => btn.classList.remove('active'));\n")
|
||||
html.WriteString(" document.getElementById(tabName).classList.add('active');\n")
|
||||
html.WriteString(" event.target.classList.add('active');\n")
|
||||
html.WriteString(" }\n")
|
||||
html.WriteString(" \n")
|
||||
html.WriteString(" // Modal functionality\n")
|
||||
html.WriteString(" function showModal(modalId) {\n")
|
||||
html.WriteString(" document.getElementById(modalId).style.display = 'block';\n")
|
||||
html.WriteString(" }\n")
|
||||
html.WriteString(" function hideModal(modalId) {\n")
|
||||
html.WriteString(" document.getElementById(modalId).style.display = 'none';\n")
|
||||
html.WriteString(" }\n")
|
||||
html.WriteString(" \n")
|
||||
html.WriteString(" // Form validation and submission\n")
|
||||
html.WriteString(" function submitForm(formId) {\n")
|
||||
html.WriteString(" const form = document.getElementById(formId);\n")
|
||||
html.WriteString(" const formData = new FormData(form);\n")
|
||||
html.WriteString(" console.log('Form submitted:', Object.fromEntries(formData));\n")
|
||||
html.WriteString(" alert('Form submitted successfully!');\n")
|
||||
html.WriteString(" }\n")
|
||||
html.WriteString(" </script>\n")
|
||||
|
||||
html.WriteString("</body>\n</html>")
|
||||
|
||||
return html.String(), nil
|
||||
}
|
||||
|
||||
// generateSectionHTML creates HTML for a section
|
||||
func (hi *HTMLInterpreter) generateSectionHTML(section *lang.Section, indent int) (string, error) {
|
||||
var html strings.Builder
|
||||
indentStr := strings.Repeat(" ", indent)
|
||||
|
||||
sectionType := "container"
|
||||
if section.Type != nil {
|
||||
sectionType = *section.Type
|
||||
}
|
||||
|
||||
switch sectionType {
|
||||
case "tab":
|
||||
html.WriteString(fmt.Sprintf("%s<div class=\"tabs\" id=\"%s\">\n", indentStr, section.Name))
|
||||
|
||||
// Generate tab buttons
|
||||
for _, element := range section.Elements {
|
||||
if element.Section != nil && element.Section.Label != nil {
|
||||
activeClass := ""
|
||||
if element.Section.Active {
|
||||
activeClass = " active"
|
||||
}
|
||||
cleanLabel := hi.cleanString(*element.Section.Label)
|
||||
html.WriteString(fmt.Sprintf("%s <button class=\"tab-button%s\" onclick=\"showTab('%s')\">%s</button>\n",
|
||||
indentStr, activeClass, element.Section.Name, hi.escapeHTML(cleanLabel)))
|
||||
}
|
||||
}
|
||||
|
||||
// Generate tab content
|
||||
for _, element := range section.Elements {
|
||||
if element.Section != nil {
|
||||
activeClass := ""
|
||||
if element.Section.Active {
|
||||
activeClass = " active"
|
||||
}
|
||||
html.WriteString(fmt.Sprintf("%s <div class=\"tab-content%s\" id=\"%s\">\n",
|
||||
indentStr, activeClass, element.Section.Name))
|
||||
|
||||
sectionHTML, err := hi.generateSectionHTML(element.Section, indent+2)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
html.WriteString(sectionHTML)
|
||||
html.WriteString(fmt.Sprintf("%s </div>\n", indentStr))
|
||||
}
|
||||
}
|
||||
|
||||
html.WriteString(fmt.Sprintf("%s</div>\n", indentStr))
|
||||
|
||||
case "modal":
|
||||
html.WriteString(fmt.Sprintf("%s<div class=\"modal\" id=\"%s\">\n", indentStr, section.Name))
|
||||
html.WriteString(fmt.Sprintf("%s <div class=\"modal-content\">\n", indentStr))
|
||||
if section.Label != nil {
|
||||
cleanLabel := hi.cleanString(*section.Label)
|
||||
html.WriteString(fmt.Sprintf("%s <h3>%s</h3>\n", indentStr, hi.escapeHTML(cleanLabel)))
|
||||
}
|
||||
|
||||
// Generate modal content
|
||||
for _, element := range section.Elements {
|
||||
elementHTML, err := hi.generateSectionElementHTML(&element, indent+2)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
html.WriteString(elementHTML)
|
||||
}
|
||||
|
||||
html.WriteString(fmt.Sprintf("%s <button class=\"button button-secondary\" onclick=\"hideModal('%s')\">Close</button>\n", indentStr, section.Name))
|
||||
html.WriteString(fmt.Sprintf("%s </div>\n", indentStr))
|
||||
html.WriteString(fmt.Sprintf("%s</div>\n", indentStr))
|
||||
|
||||
default: // container, panel, master, detail
|
||||
cssClass := "section"
|
||||
if section.Class != nil {
|
||||
cssClass = hi.cleanString(*section.Class)
|
||||
}
|
||||
|
||||
html.WriteString(fmt.Sprintf("%s<div class=\"%s\" id=\"%s\">\n", indentStr, hi.escapeHTML(cssClass), section.Name))
|
||||
|
||||
if section.Label != nil {
|
||||
cleanLabel := hi.cleanString(*section.Label)
|
||||
html.WriteString(fmt.Sprintf("%s <h3>%s</h3>\n", indentStr, hi.escapeHTML(cleanLabel)))
|
||||
}
|
||||
|
||||
// Generate section content
|
||||
for _, element := range section.Elements {
|
||||
elementHTML, err := hi.generateSectionElementHTML(&element, indent+1)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
html.WriteString(elementHTML)
|
||||
}
|
||||
|
||||
html.WriteString(fmt.Sprintf("%s</div>\n", indentStr))
|
||||
}
|
||||
|
||||
return html.String(), nil
|
||||
}
|
||||
|
||||
// generateSectionElementHTML creates HTML for section elements
|
||||
func (hi *HTMLInterpreter) generateSectionElementHTML(element *lang.SectionElement, indent int) (string, error) {
|
||||
if element.Component != nil {
|
||||
return hi.generateComponentHTML(element.Component, indent)
|
||||
}
|
||||
if element.Section != nil {
|
||||
return hi.generateSectionHTML(element.Section, indent)
|
||||
}
|
||||
if element.When != nil {
|
||||
return hi.generateWhenConditionHTML(element.When, indent)
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// generateComponentHTML creates HTML for a component
|
||||
func (hi *HTMLInterpreter) generateComponentHTML(component *lang.Component, indent int) (string, error) {
|
||||
var html strings.Builder
|
||||
indentStr := strings.Repeat(" ", indent)
|
||||
|
||||
switch component.Type {
|
||||
case "form":
|
||||
formId := fmt.Sprintf("form_%s", component.Type)
|
||||
if component.Entity != nil {
|
||||
formId = fmt.Sprintf("form_%s", *component.Entity)
|
||||
}
|
||||
|
||||
html.WriteString(fmt.Sprintf("%s<form id=\"%s\" onsubmit=\"event.preventDefault(); submitForm('%s');\">\n", indentStr, formId, formId))
|
||||
|
||||
// Generate form fields
|
||||
for _, element := range component.Elements {
|
||||
if element.Field != nil {
|
||||
fieldHTML, err := hi.generateFieldHTML(element.Field, indent+1)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
html.WriteString(fieldHTML)
|
||||
}
|
||||
if element.Button != nil {
|
||||
buttonHTML, err := hi.generateButtonHTML(element.Button, indent+1)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
html.WriteString(buttonHTML)
|
||||
}
|
||||
}
|
||||
|
||||
html.WriteString(fmt.Sprintf("%s <button type=\"submit\" class=\"button button-primary\">Submit</button>\n", indentStr))
|
||||
html.WriteString(fmt.Sprintf("%s</form>\n", indentStr))
|
||||
|
||||
case "table":
|
||||
html.WriteString(fmt.Sprintf("%s<table class=\"table\">\n", indentStr))
|
||||
html.WriteString(fmt.Sprintf("%s <thead><tr>\n", indentStr))
|
||||
|
||||
// Generate table headers from fields
|
||||
for _, element := range component.Elements {
|
||||
if element.Field != nil {
|
||||
label := element.Field.Name
|
||||
for _, attr := range element.Field.Attributes {
|
||||
if attr.Label != nil {
|
||||
label = hi.cleanString(*attr.Label)
|
||||
break
|
||||
}
|
||||
}
|
||||
html.WriteString(fmt.Sprintf("%s <th>%s</th>\n", indentStr, hi.escapeHTML(label)))
|
||||
}
|
||||
}
|
||||
|
||||
html.WriteString(fmt.Sprintf("%s </tr></thead>\n", indentStr))
|
||||
html.WriteString(fmt.Sprintf("%s <tbody>\n", indentStr))
|
||||
html.WriteString(fmt.Sprintf("%s <tr><td colspan=\"100%%\">Data will be loaded here...</td></tr>\n", indentStr))
|
||||
html.WriteString(fmt.Sprintf("%s </tbody>\n", indentStr))
|
||||
html.WriteString(fmt.Sprintf("%s</table>\n", indentStr))
|
||||
|
||||
default:
|
||||
html.WriteString(fmt.Sprintf("%s<div class=\"component-%s\">\n", indentStr, component.Type))
|
||||
|
||||
// Generate component content
|
||||
for _, element := range component.Elements {
|
||||
if element.Field != nil {
|
||||
fieldHTML, err := hi.generateFieldHTML(element.Field, indent+1)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
html.WriteString(fieldHTML)
|
||||
}
|
||||
if element.Button != nil {
|
||||
buttonHTML, err := hi.generateButtonHTML(element.Button, indent+1)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
html.WriteString(buttonHTML)
|
||||
}
|
||||
}
|
||||
|
||||
html.WriteString(fmt.Sprintf("%s</div>\n", indentStr))
|
||||
}
|
||||
|
||||
return html.String(), nil
|
||||
}
|
||||
|
||||
// generateFieldHTML creates HTML for a field
|
||||
func (hi *HTMLInterpreter) generateFieldHTML(field *lang.ComponentField, indent int) (string, error) {
|
||||
var html strings.Builder
|
||||
indentStr := strings.Repeat(" ", indent)
|
||||
|
||||
// Get field attributes
|
||||
var label, placeholder, defaultValue string
|
||||
var required bool
|
||||
var options []string
|
||||
|
||||
label = field.Name
|
||||
for _, attr := range field.Attributes {
|
||||
if attr.Label != nil {
|
||||
label = hi.cleanString(*attr.Label)
|
||||
}
|
||||
if attr.Placeholder != nil {
|
||||
placeholder = hi.cleanString(*attr.Placeholder)
|
||||
}
|
||||
if attr.Default != nil {
|
||||
defaultValue = hi.cleanString(*attr.Default)
|
||||
}
|
||||
if attr.Required {
|
||||
required = true
|
||||
}
|
||||
if attr.Options != nil {
|
||||
// Clean each option in the array
|
||||
options = make([]string, len(attr.Options))
|
||||
for i, opt := range attr.Options {
|
||||
options[i] = hi.cleanString(opt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
html.WriteString(fmt.Sprintf("%s<div class=\"form-group\">\n", indentStr))
|
||||
|
||||
requiredAttr := ""
|
||||
if required {
|
||||
requiredAttr = " required"
|
||||
}
|
||||
|
||||
html.WriteString(fmt.Sprintf("%s <label for=\"%s\">%s</label>\n", indentStr, field.Name, hi.escapeHTML(label)))
|
||||
|
||||
switch field.Type {
|
||||
case "text", "email", "password", "number", "tel", "url":
|
||||
html.WriteString(fmt.Sprintf("%s <input type=\"%s\" id=\"%s\" name=\"%s\" placeholder=\"%s\" value=\"%s\"%s>\n",
|
||||
indentStr, field.Type, field.Name, field.Name, hi.escapeHTML(placeholder), hi.escapeHTML(defaultValue), requiredAttr))
|
||||
|
||||
case "textarea":
|
||||
html.WriteString(fmt.Sprintf("%s <textarea id=\"%s\" name=\"%s\" placeholder=\"%s\"%s>%s</textarea>\n",
|
||||
indentStr, field.Name, field.Name, hi.escapeHTML(placeholder), requiredAttr, hi.escapeHTML(defaultValue)))
|
||||
|
||||
case "select":
|
||||
html.WriteString(fmt.Sprintf("%s <select id=\"%s\" name=\"%s\"%s>\n", indentStr, field.Name, field.Name, requiredAttr))
|
||||
if !required {
|
||||
html.WriteString(fmt.Sprintf("%s <option value=\"\">Select an option</option>\n", indentStr))
|
||||
}
|
||||
for _, option := range options {
|
||||
selected := ""
|
||||
if option == defaultValue {
|
||||
selected = " selected"
|
||||
}
|
||||
html.WriteString(fmt.Sprintf("%s <option value=\"%s\"%s>%s</option>\n",
|
||||
indentStr, hi.escapeHTML(option), selected, hi.escapeHTML(option)))
|
||||
}
|
||||
html.WriteString(fmt.Sprintf("%s </select>\n", indentStr))
|
||||
|
||||
case "checkbox":
|
||||
checked := ""
|
||||
if defaultValue == "true" {
|
||||
checked = " checked"
|
||||
}
|
||||
html.WriteString(fmt.Sprintf("%s <input type=\"checkbox\" id=\"%s\" name=\"%s\" value=\"true\"%s%s>\n",
|
||||
indentStr, field.Name, field.Name, checked, requiredAttr))
|
||||
|
||||
case "file":
|
||||
html.WriteString(fmt.Sprintf("%s <input type=\"file\" id=\"%s\" name=\"%s\"%s>\n",
|
||||
indentStr, field.Name, field.Name, requiredAttr))
|
||||
|
||||
default:
|
||||
html.WriteString(fmt.Sprintf("%s <input type=\"text\" id=\"%s\" name=\"%s\" placeholder=\"%s\" value=\"%s\"%s>\n",
|
||||
indentStr, field.Type, field.Name, field.Name, hi.escapeHTML(placeholder), hi.escapeHTML(defaultValue), requiredAttr))
|
||||
}
|
||||
|
||||
html.WriteString(fmt.Sprintf("%s</div>\n", indentStr))
|
||||
|
||||
return html.String(), nil
|
||||
}
|
||||
|
||||
// generateButtonHTML creates HTML for a button
|
||||
func (hi *HTMLInterpreter) generateButtonHTML(button *lang.ComponentButton, indent int) (string, error) {
|
||||
var html strings.Builder
|
||||
indentStr := strings.Repeat(" ", indent)
|
||||
|
||||
buttonClass := "button button-primary"
|
||||
onclick := ""
|
||||
|
||||
// Process button attributes
|
||||
for _, attr := range button.Attributes {
|
||||
if attr.Style != nil {
|
||||
// Handle button style
|
||||
}
|
||||
if attr.Target != nil {
|
||||
// Handle button target/onclick
|
||||
}
|
||||
}
|
||||
|
||||
cleanLabel := hi.cleanString(button.Label)
|
||||
html.WriteString(fmt.Sprintf("%s<button type=\"button\" class=\"%s\" onclick=\"%s\">%s</button>\n",
|
||||
indentStr, buttonClass, onclick, hi.escapeHTML(cleanLabel)))
|
||||
|
||||
return html.String(), nil
|
||||
}
|
||||
|
||||
// generateWhenConditionHTML creates HTML for conditional content
|
||||
func (hi *HTMLInterpreter) generateWhenConditionHTML(when *lang.WhenCondition, indent int) (string, error) {
|
||||
var html strings.Builder
|
||||
indentStr := strings.Repeat(" ", indent)
|
||||
|
||||
// For now, we'll render the content as visible (real implementation would include JavaScript logic)
|
||||
html.WriteString(fmt.Sprintf("%s<div class=\"conditional-content\" data-when-field=\"%s\" data-when-operator=\"%s\" data-when-value=\"%s\">\n",
|
||||
indentStr, when.Field, when.Operator, when.Value))
|
||||
|
||||
// Generate conditional content
|
||||
for _, field := range when.Fields {
|
||||
fieldHTML, err := hi.generateFieldHTML(&field, indent+1)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
html.WriteString(fieldHTML)
|
||||
}
|
||||
|
||||
for _, section := range when.Sections {
|
||||
sectionHTML, err := hi.generateSectionHTML(§ion, indent+1)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
html.WriteString(sectionHTML)
|
||||
}
|
||||
|
||||
for _, component := range when.Components {
|
||||
componentHTML, err := hi.generateComponentHTML(&component, indent+1)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
html.WriteString(componentHTML)
|
||||
}
|
||||
|
||||
for _, button := range when.Buttons {
|
||||
buttonHTML, err := hi.generateButtonHTML(&button, indent+1)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
html.WriteString(buttonHTML)
|
||||
}
|
||||
|
||||
html.WriteString(fmt.Sprintf("%s</div>\n", indentStr))
|
||||
|
||||
return html.String(), nil
|
||||
}
|
Reference in New Issue
Block a user