add html and server interpreters
these are basic and lack most features. the server seems to work the best. the html on the other hand is really rough and doesn't seem to work yet. but it does build the pages and they have all the shapes and sections we wanted. More work to come. :)
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1 @@
|
|||||||
/html-output/
|
|
||||||
/masonry.exe
|
/masonry.exe
|
||||||
|
@ -14,6 +14,7 @@ func main() {
|
|||||||
tailwindCmd(),
|
tailwindCmd(),
|
||||||
setupCmd(),
|
setupCmd(),
|
||||||
vueGenCmd(),
|
vueGenCmd(),
|
||||||
|
serveCmd(), // New command for server interpreter
|
||||||
}
|
}
|
||||||
|
|
||||||
app := &cli.App{
|
app := &cli.App{
|
||||||
|
@ -464,3 +464,111 @@ func generateHTML(inputFile, outputDir string) error {
|
|||||||
fmt.Printf("Successfully generated %d HTML file(s)\n", len(htmlFiles))
|
fmt.Printf("Successfully generated %d HTML file(s)\n", len(htmlFiles))
|
||||||
return nil
|
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
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
type HTMLInterpreter struct {
|
type HTMLInterpreter struct {
|
||||||
entities map[string]*lang.Entity
|
entities map[string]*lang.Entity
|
||||||
pages map[string]*lang.Page
|
pages map[string]*lang.Page
|
||||||
|
server *lang.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHTMLInterpreter creates a new HTML interpreter
|
// NewHTMLInterpreter creates a new HTML interpreter
|
||||||
@ -40,7 +41,7 @@ func (hi *HTMLInterpreter) escapeHTML(s string) string {
|
|||||||
|
|
||||||
// GenerateHTML converts a Masonry AST to HTML output
|
// GenerateHTML converts a Masonry AST to HTML output
|
||||||
func (hi *HTMLInterpreter) GenerateHTML(ast *lang.AST) (map[string]string, error) {
|
func (hi *HTMLInterpreter) GenerateHTML(ast *lang.AST) (map[string]string, error) {
|
||||||
// First pass: collect entities and pages
|
// First pass: collect entities, pages, and server config
|
||||||
for _, def := range ast.Definitions {
|
for _, def := range ast.Definitions {
|
||||||
if def.Entity != nil {
|
if def.Entity != nil {
|
||||||
hi.entities[def.Entity.Name] = def.Entity
|
hi.entities[def.Entity.Name] = def.Entity
|
||||||
@ -48,6 +49,9 @@ func (hi *HTMLInterpreter) GenerateHTML(ast *lang.AST) (map[string]string, error
|
|||||||
if def.Page != nil {
|
if def.Page != nil {
|
||||||
hi.pages[def.Page.Name] = def.Page
|
hi.pages[def.Page.Name] = def.Page
|
||||||
}
|
}
|
||||||
|
if def.Server != nil {
|
||||||
|
hi.server = def.Server
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Second pass: generate HTML for each page
|
// Second pass: generate HTML for each page
|
||||||
@ -135,6 +139,86 @@ func (hi *HTMLInterpreter) generatePageHTML(page *lang.Page) (string, error) {
|
|||||||
|
|
||||||
// JavaScript for interactivity
|
// JavaScript for interactivity
|
||||||
html.WriteString(" <script>\n")
|
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(" // Tab functionality\n")
|
||||||
html.WriteString(" function showTab(tabName) {\n")
|
html.WriteString(" function showTab(tabName) {\n")
|
||||||
html.WriteString(" const tabs = document.querySelectorAll('.tab-content');\n")
|
html.WriteString(" const tabs = document.querySelectorAll('.tab-content');\n")
|
||||||
@ -153,13 +237,110 @@ func (hi *HTMLInterpreter) generatePageHTML(page *lang.Page) (string, error) {
|
|||||||
html.WriteString(" document.getElementById(modalId).style.display = 'none';\n")
|
html.WriteString(" document.getElementById(modalId).style.display = 'none';\n")
|
||||||
html.WriteString(" }\n")
|
html.WriteString(" }\n")
|
||||||
html.WriteString(" \n")
|
html.WriteString(" \n")
|
||||||
html.WriteString(" // Form validation and submission\n")
|
html.WriteString(" // Enhanced form submission with API integration\n")
|
||||||
html.WriteString(" function submitForm(formId) {\n")
|
html.WriteString(" async function submitForm(formId, entityType = null) {\n")
|
||||||
html.WriteString(" const form = document.getElementById(formId);\n")
|
html.WriteString(" const form = document.getElementById(formId);\n")
|
||||||
html.WriteString(" const formData = new FormData(form);\n")
|
html.WriteString(" const formData = new FormData(form);\n")
|
||||||
html.WriteString(" console.log('Form submitted:', Object.fromEntries(formData));\n")
|
html.WriteString(" const data = Object.fromEntries(formData);\n")
|
||||||
html.WriteString(" alert('Form submitted successfully!');\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(" \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(" </script>\n")
|
||||||
|
|
||||||
html.WriteString("</body>\n</html>")
|
html.WriteString("</body>\n</html>")
|
||||||
|
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