Compare commits
2 Commits
e71b1c3a23
...
d36e1bfd86
Author | SHA1 | Date | |
---|---|---|---|
d36e1bfd86 | |||
cf3ad736b7 |
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/masonry.exe
|
36
.idea/copilotDiffState.xml
generated
36
.idea/copilotDiffState.xml
generated
File diff suppressed because one or more lines are too long
@ -14,6 +14,7 @@ func main() {
|
|||||||
tailwindCmd(),
|
tailwindCmd(),
|
||||||
setupCmd(),
|
setupCmd(),
|
||||||
vueGenCmd(),
|
vueGenCmd(),
|
||||||
|
serveCmd(), // New command for server interpreter
|
||||||
}
|
}
|
||||||
|
|
||||||
app := &cli.App{
|
app := &cli.App{
|
||||||
|
@ -11,9 +11,15 @@ import (
|
|||||||
vue_gen "masonry/vue-gen"
|
vue_gen "masonry/vue-gen"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/alecthomas/participle/v2"
|
||||||
|
|
||||||
|
"masonry/interpreter"
|
||||||
|
"masonry/lang"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed templates/proto/application.proto.tmpl
|
//go:embed templates/proto/application.proto.tmpl
|
||||||
@ -156,10 +162,51 @@ func generateCmd() *cli.Command {
|
|||||||
return &cli.Command{
|
return &cli.Command{
|
||||||
Name: "generate",
|
Name: "generate",
|
||||||
Aliases: []string{"g"},
|
Aliases: []string{"g"},
|
||||||
Usage: "Generate code from proto files",
|
Usage: "Generate code from proto files or Masonry files",
|
||||||
Category: "generator",
|
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 {
|
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...")
|
fmt.Println("Generating code...")
|
||||||
|
|
||||||
protocArgs := []string{
|
protocArgs := []string{
|
||||||
@ -250,8 +297,6 @@ func generateCmd() *cli.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func webappCmd() *cli.Command {
|
func webappCmd() *cli.Command {
|
||||||
@ -368,3 +413,162 @@ 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
|
||||||
|
}
|
||||||
|
|
||||||
|
func serveCmd() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
|
Name: "serve",
|
||||||
|
Usage: "Generate and run a simple HTTP server from a Masonry file",
|
||||||
|
Description: "This command parses a Masonry file and generates a simple Go HTTP server with in-memory database.",
|
||||||
|
Category: "development",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "file",
|
||||||
|
Usage: "Path to the Masonry file to interpret",
|
||||||
|
Required: true,
|
||||||
|
Aliases: []string{"f"},
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "output",
|
||||||
|
Usage: "Output file for the generated server code",
|
||||||
|
Value: "server.go",
|
||||||
|
Aliases: []string{"o"},
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "run",
|
||||||
|
Usage: "Run the server after generating it",
|
||||||
|
Value: false,
|
||||||
|
Aliases: []string{"r"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
masonryFile := c.String("file")
|
||||||
|
outputFile := c.String("output")
|
||||||
|
shouldRun := c.Bool("run")
|
||||||
|
|
||||||
|
fmt.Printf("Parsing Masonry file: %s\n", masonryFile)
|
||||||
|
|
||||||
|
// Read the Masonry file
|
||||||
|
content, err := os.ReadFile(masonryFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error reading Masonry file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the Masonry file
|
||||||
|
parser, err := participle.Build[lang.AST](
|
||||||
|
participle.Unquote("String"),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error building parser: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ast, err := parser.ParseString("", string(content))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error parsing Masonry file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate server code using the server interpreter
|
||||||
|
serverInterpreter := interpreter.NewServerInterpreter()
|
||||||
|
serverCode, err := serverInterpreter.Interpret(*ast)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error interpreting Masonry file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the generated server code to the output file
|
||||||
|
err = os.WriteFile(outputFile, []byte(serverCode), 0644)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error writing server code to file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Server code generated successfully: %s\n", outputFile)
|
||||||
|
|
||||||
|
if shouldRun {
|
||||||
|
fmt.Println("Installing dependencies...")
|
||||||
|
|
||||||
|
// Initialize go module if it doesn't exist
|
||||||
|
if _, err := os.Stat("go.mod"); os.IsNotExist(err) {
|
||||||
|
cmd := exec.Command("go", "mod", "init", "masonry-server")
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return fmt.Errorf("error initializing go module: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Install required dependencies
|
||||||
|
dependencies := []string{
|
||||||
|
"github.com/google/uuid",
|
||||||
|
"github.com/gorilla/mux",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, dep := range dependencies {
|
||||||
|
fmt.Printf("Installing %s...\n", dep)
|
||||||
|
cmd := exec.Command("go", "get", dep)
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return fmt.Errorf("error installing dependency %s: %w", dep, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Running server from %s...\n", outputFile)
|
||||||
|
cmd := exec.Command("go", "run", outputFile)
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
return cmd.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
707
interpreter/html_interpreter.go
Normal file
707
interpreter/html_interpreter.go
Normal file
@ -0,0 +1,707 @@
|
|||||||
|
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
|
||||||
|
server *lang.Server
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, pages, and server config
|
||||||
|
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
|
||||||
|
}
|
||||||
|
if def.Server != nil {
|
||||||
|
hi.server = def.Server
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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")
|
||||||
|
|
||||||
|
// API Base URL configuration
|
||||||
|
apiBaseURL := "http://localhost:8080"
|
||||||
|
if hi.server != nil {
|
||||||
|
host := "localhost"
|
||||||
|
port := 8080
|
||||||
|
for _, setting := range hi.server.Settings {
|
||||||
|
if setting.Host != nil {
|
||||||
|
host = *setting.Host
|
||||||
|
}
|
||||||
|
if setting.Port != nil {
|
||||||
|
port = *setting.Port
|
||||||
|
}
|
||||||
|
}
|
||||||
|
apiBaseURL = fmt.Sprintf("http://%s:%d", host, port)
|
||||||
|
}
|
||||||
|
|
||||||
|
html.WriteString(fmt.Sprintf(" const API_BASE_URL = '%s';\n", apiBaseURL))
|
||||||
|
html.WriteString(" \n")
|
||||||
|
html.WriteString(" // API helper functions\n")
|
||||||
|
html.WriteString(" async function apiRequest(method, endpoint, data = null) {\n")
|
||||||
|
html.WriteString(" const config = {\n")
|
||||||
|
html.WriteString(" method: method,\n")
|
||||||
|
html.WriteString(" headers: {\n")
|
||||||
|
html.WriteString(" 'Content-Type': 'application/json',\n")
|
||||||
|
html.WriteString(" },\n")
|
||||||
|
html.WriteString(" };\n")
|
||||||
|
html.WriteString(" \n")
|
||||||
|
html.WriteString(" if (data) {\n")
|
||||||
|
html.WriteString(" config.body = JSON.stringify(data);\n")
|
||||||
|
html.WriteString(" }\n")
|
||||||
|
html.WriteString(" \n")
|
||||||
|
html.WriteString(" try {\n")
|
||||||
|
html.WriteString(" const response = await fetch(API_BASE_URL + endpoint, config);\n")
|
||||||
|
html.WriteString(" \n")
|
||||||
|
html.WriteString(" if (!response.ok) {\n")
|
||||||
|
html.WriteString(" throw new Error(`HTTP error! status: ${response.status}`);\n")
|
||||||
|
html.WriteString(" }\n")
|
||||||
|
html.WriteString(" \n")
|
||||||
|
html.WriteString(" if (response.status === 204) {\n")
|
||||||
|
html.WriteString(" return null; // No content\n")
|
||||||
|
html.WriteString(" }\n")
|
||||||
|
html.WriteString(" \n")
|
||||||
|
html.WriteString(" return await response.json();\n")
|
||||||
|
html.WriteString(" } catch (error) {\n")
|
||||||
|
html.WriteString(" console.error('API request failed:', error);\n")
|
||||||
|
html.WriteString(" alert('Error: ' + error.message);\n")
|
||||||
|
html.WriteString(" throw error;\n")
|
||||||
|
html.WriteString(" }\n")
|
||||||
|
html.WriteString(" }\n")
|
||||||
|
html.WriteString(" \n")
|
||||||
|
html.WriteString(" // Entity-specific API functions\n")
|
||||||
|
|
||||||
|
// Generate API functions for each entity
|
||||||
|
for entityName := range hi.entities {
|
||||||
|
entityLower := strings.ToLower(entityName)
|
||||||
|
entityPlural := entityLower + "s"
|
||||||
|
|
||||||
|
html.WriteString(fmt.Sprintf(" async function list%s() {\n", entityName))
|
||||||
|
html.WriteString(fmt.Sprintf(" return await apiRequest('GET', '/%s');\n", entityPlural))
|
||||||
|
html.WriteString(" }\n")
|
||||||
|
html.WriteString(" \n")
|
||||||
|
html.WriteString(fmt.Sprintf(" async function get%s(id) {\n", entityName))
|
||||||
|
html.WriteString(fmt.Sprintf(" return await apiRequest('GET', '/%s/' + id);\n", entityPlural))
|
||||||
|
html.WriteString(" }\n")
|
||||||
|
html.WriteString(" \n")
|
||||||
|
html.WriteString(fmt.Sprintf(" async function create%s(data) {\n", entityName))
|
||||||
|
html.WriteString(fmt.Sprintf(" return await apiRequest('POST', '/%s', data);\n", entityPlural))
|
||||||
|
html.WriteString(" }\n")
|
||||||
|
html.WriteString(" \n")
|
||||||
|
html.WriteString(fmt.Sprintf(" async function update%s(id, data) {\n", entityName))
|
||||||
|
html.WriteString(fmt.Sprintf(" return await apiRequest('PUT', '/%s/' + id, data);\n", entityPlural))
|
||||||
|
html.WriteString(" }\n")
|
||||||
|
html.WriteString(" \n")
|
||||||
|
html.WriteString(fmt.Sprintf(" async function delete%s(id) {\n", entityName))
|
||||||
|
html.WriteString(fmt.Sprintf(" return await apiRequest('DELETE', '/%s/' + id);\n", entityPlural))
|
||||||
|
html.WriteString(" }\n")
|
||||||
|
html.WriteString(" \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(" // Enhanced form submission with API integration\n")
|
||||||
|
html.WriteString(" async function submitForm(formId, entityType = null) {\n")
|
||||||
|
html.WriteString(" const form = document.getElementById(formId);\n")
|
||||||
|
html.WriteString(" const formData = new FormData(form);\n")
|
||||||
|
html.WriteString(" const data = Object.fromEntries(formData);\n")
|
||||||
|
html.WriteString(" \n")
|
||||||
|
html.WriteString(" console.log('Form data:', data);\n")
|
||||||
|
html.WriteString(" \n")
|
||||||
|
html.WriteString(" if (entityType) {\n")
|
||||||
|
html.WriteString(" try {\n")
|
||||||
|
html.WriteString(" const result = await window['create' + entityType](data);\n")
|
||||||
|
html.WriteString(" console.log('Created:', result);\n")
|
||||||
|
html.WriteString(" alert(entityType + ' created successfully!');\n")
|
||||||
|
html.WriteString(" form.reset();\n")
|
||||||
|
html.WriteString(" // Refresh any tables showing this entity type\n")
|
||||||
|
html.WriteString(" await refreshTables(entityType);\n")
|
||||||
|
html.WriteString(" } catch (error) {\n")
|
||||||
|
html.WriteString(" console.error('Form submission failed:', error);\n")
|
||||||
|
html.WriteString(" }\n")
|
||||||
|
html.WriteString(" } else {\n")
|
||||||
|
html.WriteString(" alert('Form submitted successfully!');\n")
|
||||||
|
html.WriteString(" }\n")
|
||||||
|
html.WriteString(" }\n")
|
||||||
|
html.WriteString(" \n")
|
||||||
|
html.WriteString(" // Load data into tables\n")
|
||||||
|
html.WriteString(" async function loadTableData(tableId, entityType) {\n")
|
||||||
|
html.WriteString(" try {\n")
|
||||||
|
html.WriteString(" const data = await window['list' + entityType]();\n")
|
||||||
|
html.WriteString(" const table = document.getElementById(tableId);\n")
|
||||||
|
html.WriteString(" const tbody = table.querySelector('tbody');\n")
|
||||||
|
html.WriteString(" \n")
|
||||||
|
html.WriteString(" if (!data || data.length === 0) {\n")
|
||||||
|
html.WriteString(" tbody.innerHTML = '<tr><td colspan=\"100%\">No data found</td></tr>';\n")
|
||||||
|
html.WriteString(" return;\n")
|
||||||
|
html.WriteString(" }\n")
|
||||||
|
html.WriteString(" \n")
|
||||||
|
html.WriteString(" tbody.innerHTML = '';\n")
|
||||||
|
html.WriteString(" data.forEach(item => {\n")
|
||||||
|
html.WriteString(" const row = document.createElement('tr');\n")
|
||||||
|
html.WriteString(" const headers = table.querySelectorAll('thead th');\n")
|
||||||
|
html.WriteString(" \n")
|
||||||
|
html.WriteString(" headers.forEach((header, index) => {\n")
|
||||||
|
html.WriteString(" const cell = document.createElement('td');\n")
|
||||||
|
html.WriteString(" const fieldName = header.textContent.toLowerCase().replace(/\\s+/g, '_');\n")
|
||||||
|
html.WriteString(" cell.textContent = item[fieldName] || item[header.textContent.toLowerCase()] || '';\n")
|
||||||
|
html.WriteString(" row.appendChild(cell);\n")
|
||||||
|
html.WriteString(" });\n")
|
||||||
|
html.WriteString(" \n")
|
||||||
|
html.WriteString(" // Add action buttons\n")
|
||||||
|
html.WriteString(" const actionCell = document.createElement('td');\n")
|
||||||
|
html.WriteString(" actionCell.innerHTML = `\n")
|
||||||
|
html.WriteString(" <button onclick=\"editItem('${item.id}', '${entityType}')\" class=\"button button-primary\">Edit</button>\n")
|
||||||
|
html.WriteString(" <button onclick=\"deleteItem('${item.id}', '${entityType}')\" class=\"button button-secondary\">Delete</button>\n")
|
||||||
|
html.WriteString(" `;\n")
|
||||||
|
html.WriteString(" row.appendChild(actionCell);\n")
|
||||||
|
html.WriteString(" \n")
|
||||||
|
html.WriteString(" tbody.appendChild(row);\n")
|
||||||
|
html.WriteString(" });\n")
|
||||||
|
html.WriteString(" } catch (error) {\n")
|
||||||
|
html.WriteString(" console.error('Failed to load table data:', error);\n")
|
||||||
|
html.WriteString(" }\n")
|
||||||
|
html.WriteString(" }\n")
|
||||||
|
html.WriteString(" \n")
|
||||||
|
html.WriteString(" // Refresh all tables of a specific entity type\n")
|
||||||
|
html.WriteString(" async function refreshTables(entityType) {\n")
|
||||||
|
html.WriteString(" const tables = document.querySelectorAll(`table[data-entity=\"${entityType}\"]`);\n")
|
||||||
|
html.WriteString(" tables.forEach(table => {\n")
|
||||||
|
html.WriteString(" loadTableData(table.id, entityType);\n")
|
||||||
|
html.WriteString(" });\n")
|
||||||
|
html.WriteString(" }\n")
|
||||||
|
html.WriteString(" \n")
|
||||||
|
html.WriteString(" // Edit and delete functions\n")
|
||||||
|
html.WriteString(" async function editItem(id, entityType) {\n")
|
||||||
|
html.WriteString(" try {\n")
|
||||||
|
html.WriteString(" const item = await window['get' + entityType](id);\n")
|
||||||
|
html.WriteString(" console.log('Edit item:', item);\n")
|
||||||
|
html.WriteString(" // TODO: Populate form with item data for editing\n")
|
||||||
|
html.WriteString(" alert('Edit functionality coming soon!');\n")
|
||||||
|
html.WriteString(" } catch (error) {\n")
|
||||||
|
html.WriteString(" console.error('Failed to fetch item for editing:', error);\n")
|
||||||
|
html.WriteString(" }\n")
|
||||||
|
html.WriteString(" }\n")
|
||||||
|
html.WriteString(" \n")
|
||||||
|
html.WriteString(" async function deleteItem(id, entityType) {\n")
|
||||||
|
html.WriteString(" if (confirm('Are you sure you want to delete this item?')) {\n")
|
||||||
|
html.WriteString(" try {\n")
|
||||||
|
html.WriteString(" await window['delete' + entityType](id);\n")
|
||||||
|
html.WriteString(" alert('Item deleted successfully!');\n")
|
||||||
|
html.WriteString(" await refreshTables(entityType);\n")
|
||||||
|
html.WriteString(" } catch (error) {\n")
|
||||||
|
html.WriteString(" console.error('Failed to delete item:', error);\n")
|
||||||
|
html.WriteString(" }\n")
|
||||||
|
html.WriteString(" }\n")
|
||||||
|
html.WriteString(" }\n")
|
||||||
|
html.WriteString(" \n")
|
||||||
|
html.WriteString(" // Initialize page - load data when page loads\n")
|
||||||
|
html.WriteString(" document.addEventListener('DOMContentLoaded', function() {\n")
|
||||||
|
html.WriteString(" // Auto-load data for all tables with data-entity attribute\n")
|
||||||
|
html.WriteString(" const tables = document.querySelectorAll('table[data-entity]');\n")
|
||||||
|
html.WriteString(" tables.forEach(table => {\n")
|
||||||
|
html.WriteString(" const entityType = table.getAttribute('data-entity');\n")
|
||||||
|
html.WriteString(" loadTableData(table.id, entityType);\n")
|
||||||
|
html.WriteString(" });\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
|
||||||
|
}
|
386
interpreter/server_interpreter.go
Normal file
386
interpreter/server_interpreter.go
Normal file
@ -0,0 +1,386 @@
|
|||||||
|
package interpreter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"go/format"
|
||||||
|
"masonry/lang"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/text/cases"
|
||||||
|
"golang.org/x/text/language"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ServerInterpreter converts Masonry AST to a simple Go HTTP server
|
||||||
|
type ServerInterpreter struct {
|
||||||
|
entities map[string]*lang.Entity
|
||||||
|
endpoints map[string]*lang.Endpoint
|
||||||
|
server *lang.Server
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewServerInterpreter creates a new server interpreter
|
||||||
|
func NewServerInterpreter() *ServerInterpreter {
|
||||||
|
return &ServerInterpreter{
|
||||||
|
entities: make(map[string]*lang.Entity),
|
||||||
|
endpoints: make(map[string]*lang.Endpoint),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interpret processes the AST and generates server code
|
||||||
|
func (si *ServerInterpreter) Interpret(ast lang.AST) (string, error) {
|
||||||
|
// First pass: collect all definitions
|
||||||
|
for _, def := range ast.Definitions {
|
||||||
|
if def.Server != nil {
|
||||||
|
si.server = def.Server
|
||||||
|
}
|
||||||
|
if def.Entity != nil {
|
||||||
|
si.entities[def.Entity.Name] = def.Entity
|
||||||
|
}
|
||||||
|
if def.Endpoint != nil {
|
||||||
|
key := fmt.Sprintf("%s_%s", def.Endpoint.Method, strings.ReplaceAll(def.Endpoint.Path, "/", "_"))
|
||||||
|
si.endpoints[key] = def.Endpoint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the server code
|
||||||
|
rawCode, err := si.generateServer()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format the code to remove unused imports and fix formatting
|
||||||
|
formattedCode, err := format.Source([]byte(rawCode))
|
||||||
|
if err != nil {
|
||||||
|
// If formatting fails, return the raw code with a warning comment
|
||||||
|
return "// Warning: Code formatting failed, but code should still be functional\n" + rawCode, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(formattedCode), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateServer creates the complete server code
|
||||||
|
func (si *ServerInterpreter) generateServer() (string, error) {
|
||||||
|
var code strings.Builder
|
||||||
|
|
||||||
|
// Package and imports - only include what's actually used
|
||||||
|
code.WriteString(`package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
`)
|
||||||
|
|
||||||
|
// Generate entity structs
|
||||||
|
code.WriteString("// Entity definitions\n")
|
||||||
|
for _, entity := range si.entities {
|
||||||
|
code.WriteString(si.generateEntityStruct(entity))
|
||||||
|
code.WriteString("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate in-memory database
|
||||||
|
code.WriteString(si.generateInMemoryDB())
|
||||||
|
|
||||||
|
// Generate handlers
|
||||||
|
code.WriteString("// HTTP Handlers\n")
|
||||||
|
for _, entity := range si.entities {
|
||||||
|
code.WriteString(si.generateEntityHandlers(entity))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate custom endpoint handlers
|
||||||
|
for _, endpoint := range si.endpoints {
|
||||||
|
if endpoint.Entity == nil {
|
||||||
|
code.WriteString(si.generateCustomEndpointHandler(endpoint))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate main function
|
||||||
|
code.WriteString(si.generateMainFunction())
|
||||||
|
|
||||||
|
return code.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateEntityStruct creates a Go struct for an entity
|
||||||
|
func (si *ServerInterpreter) generateEntityStruct(entity *lang.Entity) string {
|
||||||
|
var code strings.Builder
|
||||||
|
|
||||||
|
if entity.Description != nil {
|
||||||
|
code.WriteString(fmt.Sprintf("// %s - %s\n", entity.Name, *entity.Description))
|
||||||
|
}
|
||||||
|
|
||||||
|
code.WriteString(fmt.Sprintf("type %s struct {\n", entity.Name))
|
||||||
|
|
||||||
|
// Always add ID field
|
||||||
|
code.WriteString("\tID string `json:\"id\"`\n")
|
||||||
|
code.WriteString("\tCreatedAt time.Time `json:\"created_at\"`\n")
|
||||||
|
code.WriteString("\tUpdatedAt time.Time `json:\"updated_at\"`\n")
|
||||||
|
|
||||||
|
for _, field := range entity.Fields {
|
||||||
|
goType := si.convertToGoType(field.Type)
|
||||||
|
jsonTag := strings.ToLower(field.Name)
|
||||||
|
|
||||||
|
if !field.Required {
|
||||||
|
goType = "*" + goType
|
||||||
|
}
|
||||||
|
|
||||||
|
code.WriteString(fmt.Sprintf("\t%s %s `json:\"%s\"`\n",
|
||||||
|
cases.Title(language.English).String(field.Name), goType, jsonTag))
|
||||||
|
}
|
||||||
|
|
||||||
|
code.WriteString("}\n")
|
||||||
|
return code.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertToGoType maps Masonry types to Go types
|
||||||
|
func (si *ServerInterpreter) convertToGoType(masonryType string) string {
|
||||||
|
switch masonryType {
|
||||||
|
case "string", "text", "email", "url":
|
||||||
|
return "string"
|
||||||
|
case "int", "number":
|
||||||
|
return "int"
|
||||||
|
case "float":
|
||||||
|
return "float64"
|
||||||
|
case "boolean":
|
||||||
|
return "bool"
|
||||||
|
case "uuid":
|
||||||
|
return "string"
|
||||||
|
case "timestamp":
|
||||||
|
return "time.Time"
|
||||||
|
default:
|
||||||
|
return "string" // default fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateInMemoryDB creates the in-memory database structure
|
||||||
|
func (si *ServerInterpreter) generateInMemoryDB() string {
|
||||||
|
var code strings.Builder
|
||||||
|
|
||||||
|
code.WriteString("// In-memory database\n")
|
||||||
|
code.WriteString("type InMemoryDB struct {\n")
|
||||||
|
code.WriteString("\tmu sync.RWMutex\n")
|
||||||
|
|
||||||
|
for entityName := range si.entities {
|
||||||
|
code.WriteString(fmt.Sprintf("\t%s map[string]*%s\n",
|
||||||
|
strings.ToLower(entityName)+"s", entityName))
|
||||||
|
}
|
||||||
|
|
||||||
|
code.WriteString("}\n\n")
|
||||||
|
|
||||||
|
code.WriteString("var db = &InMemoryDB{\n")
|
||||||
|
for entityName := range si.entities {
|
||||||
|
code.WriteString(fmt.Sprintf("\t%s: make(map[string]*%s),\n",
|
||||||
|
strings.ToLower(entityName)+"s", entityName))
|
||||||
|
}
|
||||||
|
code.WriteString("}\n\n")
|
||||||
|
|
||||||
|
return code.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateEntityHandlers creates CRUD handlers for an entity
|
||||||
|
func (si *ServerInterpreter) generateEntityHandlers(entity *lang.Entity) string {
|
||||||
|
var code strings.Builder
|
||||||
|
entityName := entity.Name
|
||||||
|
entityLower := strings.ToLower(entityName)
|
||||||
|
entityPlural := entityLower + "s"
|
||||||
|
|
||||||
|
// List handler
|
||||||
|
code.WriteString(fmt.Sprintf(`func list%sHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
db.mu.RLock()
|
||||||
|
defer db.mu.RUnlock()
|
||||||
|
|
||||||
|
var items []*%s
|
||||||
|
for _, item := range db.%s {
|
||||||
|
items = append(items, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(items)
|
||||||
|
}
|
||||||
|
|
||||||
|
`, entityName, entityName, entityPlural))
|
||||||
|
|
||||||
|
// Get by ID handler
|
||||||
|
code.WriteString(fmt.Sprintf(`func get%sHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
id := vars["id"]
|
||||||
|
|
||||||
|
db.mu.RLock()
|
||||||
|
item, exists := db.%s[id]
|
||||||
|
db.mu.RUnlock()
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
http.Error(w, "%s not found", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
`, entityName, entityPlural, entityName))
|
||||||
|
|
||||||
|
// Create handler
|
||||||
|
code.WriteString(fmt.Sprintf(`func create%sHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var item %s
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&item); err != nil {
|
||||||
|
http.Error(w, "Invalid JSON", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
item.ID = uuid.New().String()
|
||||||
|
item.CreatedAt = time.Now()
|
||||||
|
item.UpdatedAt = time.Now()
|
||||||
|
|
||||||
|
db.mu.Lock()
|
||||||
|
db.%s[item.ID] = &item
|
||||||
|
db.mu.Unlock()
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusCreated)
|
||||||
|
json.NewEncoder(w).Encode(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
`, entityName, entityName, entityPlural))
|
||||||
|
|
||||||
|
// Update handler
|
||||||
|
code.WriteString(fmt.Sprintf(`func update%sHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
id := vars["id"]
|
||||||
|
|
||||||
|
db.mu.Lock()
|
||||||
|
defer db.mu.Unlock()
|
||||||
|
|
||||||
|
existing, exists := db.%s[id]
|
||||||
|
if !exists {
|
||||||
|
http.Error(w, "%s not found", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var updates %s
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&updates); err != nil {
|
||||||
|
http.Error(w, "Invalid JSON", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preserve ID and timestamps
|
||||||
|
updates.ID = existing.ID
|
||||||
|
updates.CreatedAt = existing.CreatedAt
|
||||||
|
updates.UpdatedAt = time.Now()
|
||||||
|
|
||||||
|
db.%s[id] = &updates
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(updates)
|
||||||
|
}
|
||||||
|
|
||||||
|
`, entityName, entityPlural, entityName, entityName, entityPlural))
|
||||||
|
|
||||||
|
// Delete handler
|
||||||
|
code.WriteString(fmt.Sprintf(`func delete%sHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
id := vars["id"]
|
||||||
|
|
||||||
|
db.mu.Lock()
|
||||||
|
defer db.mu.Unlock()
|
||||||
|
|
||||||
|
if _, exists := db.%s[id]; !exists {
|
||||||
|
http.Error(w, "%s not found", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(db.%s, id)
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
`, entityName, entityPlural, entityName, entityPlural))
|
||||||
|
|
||||||
|
return code.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateCustomEndpointHandler creates handlers for custom endpoints
|
||||||
|
func (si *ServerInterpreter) generateCustomEndpointHandler(endpoint *lang.Endpoint) string {
|
||||||
|
var code strings.Builder
|
||||||
|
|
||||||
|
handlerName := fmt.Sprintf("%s%sHandler",
|
||||||
|
strings.ToLower(endpoint.Method),
|
||||||
|
strings.ReplaceAll(cases.Title(language.English).String(endpoint.Path), "/", ""))
|
||||||
|
|
||||||
|
code.WriteString(fmt.Sprintf(`func %s(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Custom endpoint: %s %s
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(map[string]string{
|
||||||
|
"message": "Custom endpoint %s %s",
|
||||||
|
"status": "success",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
`, handlerName, endpoint.Method, endpoint.Path, endpoint.Method, endpoint.Path))
|
||||||
|
|
||||||
|
return code.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateMainFunction creates the main function with routing
|
||||||
|
func (si *ServerInterpreter) generateMainFunction() string {
|
||||||
|
var code strings.Builder
|
||||||
|
|
||||||
|
code.WriteString("func main() {\n")
|
||||||
|
code.WriteString("\tr := mux.NewRouter()\n\n")
|
||||||
|
|
||||||
|
// Add routes for each entity
|
||||||
|
for entityName := range si.entities {
|
||||||
|
entityLower := strings.ToLower(entityName)
|
||||||
|
entityPlural := entityLower + "s"
|
||||||
|
|
||||||
|
code.WriteString(fmt.Sprintf("\t// %s routes\n", entityName))
|
||||||
|
code.WriteString(fmt.Sprintf("\tr.HandleFunc(\"/%s\", list%sHandler).Methods(\"GET\")\n",
|
||||||
|
entityPlural, entityName))
|
||||||
|
code.WriteString(fmt.Sprintf("\tr.HandleFunc(\"/%s/{id}\", get%sHandler).Methods(\"GET\")\n",
|
||||||
|
entityPlural, entityName))
|
||||||
|
code.WriteString(fmt.Sprintf("\tr.HandleFunc(\"/%s\", create%sHandler).Methods(\"POST\")\n",
|
||||||
|
entityPlural, entityName))
|
||||||
|
code.WriteString(fmt.Sprintf("\tr.HandleFunc(\"/%s/{id}\", update%sHandler).Methods(\"PUT\")\n",
|
||||||
|
entityPlural, entityName))
|
||||||
|
code.WriteString(fmt.Sprintf("\tr.HandleFunc(\"/%s/{id}\", delete%sHandler).Methods(\"DELETE\")\n",
|
||||||
|
entityPlural, entityName))
|
||||||
|
code.WriteString("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add custom endpoint routes
|
||||||
|
for _, endpoint := range si.endpoints {
|
||||||
|
if endpoint.Entity == nil {
|
||||||
|
handlerName := fmt.Sprintf("%s%sHandler",
|
||||||
|
strings.ToLower(endpoint.Method),
|
||||||
|
strings.ReplaceAll(cases.Title(language.English).String(endpoint.Path), "/", ""))
|
||||||
|
code.WriteString(fmt.Sprintf("\tr.HandleFunc(\"%s\", %s).Methods(\"%s\")\n",
|
||||||
|
endpoint.Path, handlerName, endpoint.Method))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server configuration
|
||||||
|
host := "localhost"
|
||||||
|
port := 8080
|
||||||
|
|
||||||
|
if si.server != nil {
|
||||||
|
for _, setting := range si.server.Settings {
|
||||||
|
if setting.Host != nil {
|
||||||
|
host = *setting.Host
|
||||||
|
}
|
||||||
|
if setting.Port != nil {
|
||||||
|
port = *setting.Port
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
code.WriteString(fmt.Sprintf("\n\taddr := \"%s:%d\"\n", host, port))
|
||||||
|
code.WriteString("\tfmt.Printf(\"Server starting on %s\\n\", addr)\n")
|
||||||
|
code.WriteString("\tlog.Fatal(http.ListenAndServe(addr, r))\n")
|
||||||
|
code.WriteString("}\n")
|
||||||
|
|
||||||
|
return code.String()
|
||||||
|
}
|
Reference in New Issue
Block a user