feat: Add generator function for WebAwesome

This commit is contained in:
2026-01-01 14:26:25 -05:00
parent 45b17f53b4
commit ec87807487
7 changed files with 7946 additions and 3 deletions

View File

@@ -15,5 +15,5 @@ package main
import (
"fmt"
"github.com/prad/nebula"
"github.com/sonr-io/nebula"
)

79
cmd/generate/main.go Normal file
View File

@@ -0,0 +1,79 @@
package main
import (
"encoding/json"
"flag"
"fmt"
"os"
"path/filepath"
"github.com/sonr-io/nebula/internal/generator"
"github.com/sonr-io/nebula/internal/parser"
)
func main() {
var (
input = flag.String("input", "web-types.json", "Path to web-types.json")
output = flag.String("output", "pkg/wa", "Output directory for generated files")
pkg = flag.String("package", "wa", "Package name for generated files")
verbose = flag.Bool("verbose", false, "Verbose output")
)
flag.Parse()
data, err := os.ReadFile(*input)
if err != nil {
fmt.Fprintf(os.Stderr, "Error reading input: %v\n", err)
os.Exit(1)
}
var wt parser.WebTypes
if err := json.Unmarshal(data, &wt); err != nil {
fmt.Fprintf(os.Stderr, "Error parsing JSON: %v\n", err)
os.Exit(1)
}
if err := os.MkdirAll(*output, 0755); err != nil {
fmt.Fprintf(os.Stderr, "Error creating output directory: %v\n", err)
os.Exit(1)
}
gen := generator.New(*pkg, *verbose)
// Generate component files
for _, el := range wt.Contributions.HTML.Elements {
filename := filepath.Join(*output, gen.ComponentFilename(el.Name))
if err := gen.GenerateComponent(filename, el); err != nil {
fmt.Fprintf(os.Stderr, "Error generating %s: %v\n", el.Name, err)
continue
}
if *verbose {
fmt.Printf("Generated: %s\n", filename)
}
}
// Generate shared types and utilities
if err := gen.GenerateTypes(filepath.Join(*output, "types.go")); err != nil {
fmt.Fprintf(os.Stderr, "Error generating types: %v\n", err)
os.Exit(1)
}
// Generate builder helpers
if err := gen.GenerateBuilders(filepath.Join(*output, "builders.go"), wt.Contributions.HTML.Elements); err != nil {
fmt.Fprintf(os.Stderr, "Error generating builders: %v\n", err)
os.Exit(1)
}
// Generate CDN loader
if err := gen.GenerateCDN(filepath.Join(*output, "cdn.templ"), wt.Version); err != nil {
fmt.Fprintf(os.Stderr, "Error generating CDN loader: %v\n", err)
os.Exit(1)
}
// Generate events helper
if err := gen.GenerateEvents(filepath.Join(*output, "events.go"), wt.Contributions.HTML.Elements); err != nil {
fmt.Fprintf(os.Stderr, "Error generating events: %v\n", err)
os.Exit(1)
}
fmt.Printf("Generated %d components in %s\n", len(wt.Contributions.HTML.Elements), *output)
}

7020
config.json Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,644 @@
// Package generator contains the code generator for the WebAwesome library.
package generator
import (
"bytes"
"fmt"
"os"
"sort"
"text/template"
"github.com/sonr-io/nebula/internal/parser"
)
// Generator handles code generation for Web Awesome components
type Generator struct {
pkg string
verbose bool
funcMap template.FuncMap
}
// New creates a new Generator instance
func New(pkg string, verbose bool) *Generator {
g := &Generator{
pkg: pkg,
verbose: verbose,
}
g.funcMap = template.FuncMap{
"toPascal": ToPascalCase,
"toCamel": ToCamelCase,
"stripWa": StripWaPrefix,
"toGoType": ToGoType,
"toBuilderType": ToBuilderType,
"isBoolean": IsBoolean,
"isNumber": IsNumber,
"cleanDesc": CleanDescription,
"extractEnums": ExtractEnumValues,
"toEventName": ToEventName,
"toSlotName": ToSlotName,
"escapeGoString": EscapeGoString,
"hasSlots": g.hasSlots,
"namedSlots": g.namedSlots,
"hasEvents": g.hasEvents,
"filterAttrs": g.filterAttributes,
}
return g
}
// ComponentFilename returns the filename for a component
func (g *Generator) ComponentFilename(name string) string {
return StripWaPrefix(name) + ".templ"
}
func (g *Generator) hasSlots(el parser.Element) bool {
return len(el.Slots) > 0
}
func (g *Generator) namedSlots(el parser.Element) []parser.Slot {
var named []parser.Slot
for _, s := range el.Slots {
if s.Name != "" {
named = append(named, s)
}
}
return named
}
func (g *Generator) hasEvents(el parser.Element) bool {
return len(el.Events) > 0
}
func (g *Generator) filterAttributes(attrs []parser.Attribute) []parser.Attribute {
var filtered []parser.Attribute
skip := map[string]bool{"title": true} // Skip problematic attributes
for _, a := range attrs {
if !skip[a.Name] {
filtered = append(filtered, a)
}
}
return filtered
}
var componentTemplate = `// Code generated by wa-generator. DO NOT EDIT.
// Source: Web Awesome {{ .Element.Name }}
package {{ .Package }}
import (
"github.com/a]h/templ"
)
{{- $name := stripWa .Element.Name | toPascal }}
{{- $el := .Element }}
{{- $attrs := filterAttrs .Element.Attributes }}
// {{ cleanDesc .Element.Description }}
//
// Web Awesome component: <{{ .Element.Name }}>
{{- if .Element.DocURL }}
// Documentation: {{ .Element.DocURL }}
{{- end }}
// {{ $name }}Props holds all properties for the {{ .Element.Name }} component
type {{ $name }}Props struct {
{{- range $attrs }}
// {{ cleanDesc .Description }}
{{- if extractEnums .Value.Type }}
// Valid values: {{ range $i, $v := extractEnums .Value.Type }}{{ if $i }}, {{ end }}"{{ $v }}"{{ end }}
{{- end }}
{{ toPascal .Name }} {{ toGoType .Value.Type }} ` + "`attr:\"{{ .Name }}\"`" + `
{{- end }}
// Events
{{- range $el.Events }}
// {{ cleanDesc .Description }}
{{ toEventName .Name }} string ` + "`attr:\"x-on:{{ .Name }}\"`" + `
{{- end }}
// Slots contains named slot content
Slots {{ $name }}Slots
// Attrs contains additional HTML attributes
Attrs templ.Attributes
}
{{- if namedSlots $el }}
// {{ $name }}Slots holds named slot content for the component
type {{ $name }}Slots struct {
{{- range namedSlots $el }}
// {{ cleanDesc .Description }}
{{ toSlotName .Name }} templ.Component
{{- end }}
}
{{- end }}
// {{ $name }}Builder provides a fluent API for constructing {{ $name }}Props
type {{ $name }}Builder struct {
props {{ $name }}Props
}
// New{{ $name }} creates a new builder for {{ .Element.Name }}
func New{{ $name }}() *{{ $name }}Builder {
return &{{ $name }}Builder{}
}
{{- range $attrs }}
// {{ toPascal .Name }} sets the {{ .Name }} attribute
{{- if cleanDesc .Description }}
// {{ cleanDesc .Description }}
{{- end }}
func (b *{{ $name }}Builder) {{ toPascal .Name }}(v {{ toBuilderType .Value.Type }}) *{{ $name }}Builder {
b.props.{{ toPascal .Name }} = v
return b
}
{{- end }}
{{- range $el.Events }}
// {{ toEventName .Name }} sets the handler for {{ .Name }} event
// {{ cleanDesc .Description }}
func (b *{{ $name }}Builder) {{ toEventName .Name }}(handler string) *{{ $name }}Builder {
b.props.{{ toEventName .Name }} = handler
return b
}
{{- end }}
{{- range namedSlots $el }}
// {{ toSlotName .Name }}Slot sets the {{ .Name }} slot content
// {{ cleanDesc .Description }}
func (b *{{ $name }}Builder) {{ toSlotName .Name }}Slot(c templ.Component) *{{ $name }}Builder {
b.props.Slots.{{ toSlotName .Name }} = c
return b
}
{{- end }}
// Attr adds a custom HTML attribute
func (b *{{ $name }}Builder) Attr(name, value string) *{{ $name }}Builder {
if b.props.Attrs == nil {
b.props.Attrs = templ.Attributes{}
}
b.props.Attrs[name] = value
return b
}
// Attrs merges multiple attributes
func (b *{{ $name }}Builder) Attrs(attrs templ.Attributes) *{{ $name }}Builder {
if b.props.Attrs == nil {
b.props.Attrs = templ.Attributes{}
}
for k, v := range attrs {
b.props.Attrs[k] = v
}
return b
}
// Props returns the built properties
func (b *{{ $name }}Builder) Props() {{ $name }}Props {
return b.props
}
// Build returns the props (alias for Props for semantic clarity)
func (b *{{ $name }}Builder) Build() {{ $name }}Props {
return b.props
}
// {{ $name }} renders the {{ .Element.Name }} component
templ {{ $name }}(props {{ $name }}Props) {
<{{ $el.Name }}
{{- range $attrs }}
{{- if isBoolean .Value.Type }}
if props.{{ toPascal .Name }} {
{{ .Name }}
}
{{- else if isNumber .Value.Type }}
if props.{{ toPascal .Name }} != 0 {
{{ .Name }}={ templ.Sprintf("%v", props.{{ toPascal .Name }}) }
}
{{- else }}
if props.{{ toPascal .Name }} != "" {
{{ .Name }}={ props.{{ toPascal .Name }} }
}
{{- end }}
{{- end }}
{{- range $el.Events }}
if props.{{ toEventName .Name }} != "" {
x-on:{{ .Name }}={ props.{{ toEventName .Name }} }
}
{{- end }}
{ props.Attrs... }
>
{{- range namedSlots $el }}
if props.Slots.{{ toSlotName .Name }} != nil {
<div slot="{{ .Name }}">
@props.Slots.{{ toSlotName .Name }}
</div>
}
{{- end }}
{ children... }
</{{ $el.Name }}>
}
// {{ $name }}Func renders with a builder function for inline configuration
templ {{ $name }}Func(fn func(*{{ $name }}Builder)) {
{{ "{{" }} b := New{{ $name }}(); fn(b) {{ "}}" }}
@{{ $name }}(b.Props()) {
{ children... }
}
}
`
// GenerateComponent generates a templ file for a component
func (g *Generator) GenerateComponent(filename string, el parser.Element) error {
tmpl, err := template.New("component").Funcs(g.funcMap).Parse(componentTemplate)
if err != nil {
return fmt.Errorf("parsing template: %w", err)
}
var buf bytes.Buffer
data := map[string]interface{}{
"Package": g.pkg,
"Element": el,
}
if err := tmpl.Execute(&buf, data); err != nil {
return fmt.Errorf("executing template: %w", err)
}
return os.WriteFile(filename, buf.Bytes(), 0644)
}
var typesTemplate = `// Code generated by wa-generator. DO NOT EDIT.
package {{ .Package }}
import "github.com/a]h/templ"
// Variant represents component theme variants
type Variant string
const (
VariantBrand Variant = "brand"
VariantNeutral Variant = "neutral"
VariantSuccess Variant = "success"
VariantWarning Variant = "warning"
VariantDanger Variant = "danger"
)
// Size represents component sizes
type Size string
const (
SizeSmall Size = "small"
SizeMedium Size = "medium"
SizeLarge Size = "large"
)
// Appearance represents visual appearance styles
type Appearance string
const (
AppearanceAccent Appearance = "accent"
AppearanceFilled Appearance = "filled"
AppearanceOutlined Appearance = "outlined"
AppearanceFilledOutlined Appearance = "filled-outlined"
AppearancePlain Appearance = "plain"
)
// Orientation represents layout orientation
type Orientation string
const (
OrientationHorizontal Orientation = "horizontal"
OrientationVertical Orientation = "vertical"
)
// Placement represents popup/tooltip placement
type Placement string
const (
PlacementTop Placement = "top"
PlacementTopStart Placement = "top-start"
PlacementTopEnd Placement = "top-end"
PlacementBottom Placement = "bottom"
PlacementBottomStart Placement = "bottom-start"
PlacementBottomEnd Placement = "bottom-end"
PlacementLeft Placement = "left"
PlacementLeftStart Placement = "left-start"
PlacementLeftEnd Placement = "left-end"
PlacementRight Placement = "right"
PlacementRightStart Placement = "right-start"
PlacementRightEnd Placement = "right-end"
)
// SlotContent is a helper for creating slot content
type SlotContent struct {
Name string
Content templ.Component
}
// NewSlot creates a new SlotContent
func NewSlot(name string, content templ.Component) SlotContent {
return SlotContent{Name: name, Content: content}
}
// EmptyComponent returns a no-op templ component
func EmptyComponent() templ.Component {
return templ.ComponentFunc(func(ctx context.Context, w io.Writer) error {
return nil
})
}
// Text creates a simple text component
func Text(s string) templ.Component {
return templ.Raw(s)
}
`
// GenerateTypes generates shared types
func (g *Generator) GenerateTypes(filename string) error {
tmpl, err := template.New("types").Parse(typesTemplate)
if err != nil {
return err
}
var buf bytes.Buffer
if err := tmpl.Execute(&buf, map[string]string{"Package": g.pkg}); err != nil {
return err
}
return os.WriteFile(filename, buf.Bytes(), 0644)
}
var buildersTemplate = `// Code generated by wa-generator. DO NOT EDIT.
package {{ .Package }}
import "github.com/a]h/templ"
// PropsBuilder is the interface all component builders implement
type PropsBuilder[T any] interface {
Attr(name, value string) PropsBuilder[T]
Attrs(attrs templ.Attributes) PropsBuilder[T]
Build() T
}
// BaseBuilder provides common builder functionality
type BaseBuilder struct {
attrs templ.Attributes
}
// SetAttr sets a single attribute
func (b *BaseBuilder) SetAttr(name, value string) {
if b.attrs == nil {
b.attrs = templ.Attributes{}
}
b.attrs[name] = value
}
// MergeAttrs merges attributes
func (b *BaseBuilder) MergeAttrs(attrs templ.Attributes) {
if b.attrs == nil {
b.attrs = templ.Attributes{}
}
for k, v := range attrs {
b.attrs[k] = v
}
}
// GetAttrs returns the attributes
func (b *BaseBuilder) GetAttrs() templ.Attributes {
if b.attrs == nil {
return templ.Attributes{}
}
return b.attrs
}
// Common builder method helpers
// WithClass is a helper to add CSS classes
func WithClass[B interface{ Attr(string, string) B }](b B, classes ...string) B {
return b.Attr("class", strings.Join(classes, " "))
}
// WithID is a helper to set element ID
func WithID[B interface{ Attr(string, string) B }](b B, id string) B {
return b.Attr("id", id)
}
// WithStyle is a helper to add inline styles
func WithStyle[B interface{ Attr(string, string) B }](b B, style string) B {
return b.Attr("style", style)
}
// WithData is a helper to add data attributes
func WithData[B interface{ Attr(string, string) B }](b B, key, value string) B {
return b.Attr("data-"+key, value)
}
// Component registry for dynamic component creation
var componentRegistry = map[string]func() interface{}{
{{- range .Elements }}
"{{ .Name }}": func() interface{} { return New{{ stripWa .Name | toPascal }}() },
{{- end }}
}
// GetBuilder returns a builder for the given component name
func GetBuilder(name string) interface{} {
if fn, ok := componentRegistry[name]; ok {
return fn()
}
return nil
}
// ComponentNames returns all available component names
func ComponentNames() []string {
names := make([]string, 0, len(componentRegistry))
for name := range componentRegistry {
names = append(names, name)
}
sort.Strings(names)
return names
}
`
// GenerateBuilders generates builder utilities
func (g *Generator) GenerateBuilders(filename string, elements []parser.Element) error {
tmpl, err := template.New("builders").Funcs(g.funcMap).Parse(buildersTemplate)
if err != nil {
return err
}
var buf bytes.Buffer
data := map[string]interface{}{
"Package": g.pkg,
"Elements": elements,
}
if err := tmpl.Execute(&buf, data); err != nil {
return err
}
return os.WriteFile(filename, buf.Bytes(), 0644)
}
var cdnTemplate = `// Code generated by wa-generator. DO NOT EDIT.
package {{ .Package }}
// CDNVersion is the Web Awesome version used for CDN assets
const CDNVersion = "{{ .Version }}"
// CDNHead renders the required CSS and JS for Web Awesome from CDN
templ CDNHead() {
<link rel="stylesheet" href={ "https://cdn.jsdelivr.net/npm/@aspect/web-awesome@" + CDNVersion + "/dist/themes/default.css" }/>
<script type="module" src={ "https://cdn.jsdelivr.net/npm/@aspect/web-awesome@" + CDNVersion + "/dist/web-awesome.loader.js" }></script>
}
// CDNHeadPro renders the required CSS and JS for Web Awesome Pro from CDN
// Requires a valid license key configured in your environment
templ CDNHeadPro() {
<link rel="stylesheet" href={ "https://cdn.jsdelivr.net/npm/@awesome.me/webawesome-pro@" + CDNVersion + "/dist/themes/default.css" }/>
<script type="module" src={ "https://cdn.jsdelivr.net/npm/@awesome.me/webawesome-pro@" + CDNVersion + "/dist/webawesome.loader.js" }></script>
}
// CDNHeadWithTheme renders CDN assets with a specific theme
templ CDNHeadWithTheme(theme string) {
<link rel="stylesheet" href={ "https://cdn.jsdelivr.net/npm/@aspect/web-awesome@" + CDNVersion + "/dist/themes/" + theme + ".css" }/>
<script type="module" src={ "https://cdn.jsdelivr.net/npm/@aspect/web-awesome@" + CDNVersion + "/dist/web-awesome.loader.js" }></script>
}
// LocalHead renders Web Awesome assets from local paths
templ LocalHead(cssPath, jsPath string) {
<link rel="stylesheet" href={ cssPath }/>
<script type="module" src={ jsPath }></script>
}
// FontAwesomeKit renders Font Awesome kit script
templ FontAwesomeKit(kitCode string) {
<script src={ "https://kit.fontawesome.com/" + kitCode + ".js" } crossorigin="anonymous"></script>
}
`
// GenerateCDN generates CDN loader templates
func (g *Generator) GenerateCDN(filename string, version string) error {
tmpl, err := template.New("cdn").Parse(cdnTemplate)
if err != nil {
return err
}
var buf bytes.Buffer
data := map[string]string{
"Package": g.pkg,
"Version": version,
}
if err := tmpl.Execute(&buf, data); err != nil {
return err
}
return os.WriteFile(filename, buf.Bytes(), 0644)
}
var eventsTemplate = `// Code generated by wa-generator. DO NOT EDIT.
package {{ .Package }}
// Event names for Web Awesome components
// Use with Alpine.js x-on: directive or vanilla JS addEventListener
const (
{{- range .Events }}
// {{ .Name }} - {{ cleanDesc .Description }}
Event{{ toPascal .Name }} = "{{ .Name }}"
{{- end }}
)
// EventHandler creates an Alpine.js event handler string
func EventHandler(jsCode string) string {
return jsCode
}
// EventHandlerPrevent creates a handler that prevents default
func EventHandlerPrevent(jsCode string) string {
return "$event.preventDefault(); " + jsCode
}
// EventHandlerStop creates a handler that stops propagation
func EventHandlerStop(jsCode string) string {
return "$event.stopPropagation(); " + jsCode
}
// EventHandlerDebounce wraps handler with debounce
func EventHandlerDebounce(jsCode string, ms int) string {
return fmt.Sprintf("$debounce(() => { %s }, %d)", jsCode, ms)
}
// Common event handler patterns
// ToggleHandler returns a handler that toggles a boolean value
func ToggleHandler(varName string) string {
return varName + " = !" + varName
}
// SetValueHandler returns a handler that sets a value
func SetValueHandler(varName, value string) string {
return fmt.Sprintf("%s = %s", varName, value)
}
// DispatchHandler returns a handler that dispatches a custom event
func DispatchHandler(eventName string, detail string) string {
if detail != "" {
return fmt.Sprintf("$dispatch('%s', %s)", eventName, detail)
}
return fmt.Sprintf("$dispatch('%s')", eventName)
}
// FetchHandler returns a handler that performs a fetch request
func FetchHandler(url, method, body string) string {
if body != "" {
return fmt.Sprintf("fetch('%s', {method: '%s', body: JSON.stringify(%s), headers: {'Content-Type': 'application/json'}})", url, method, body)
}
return fmt.Sprintf("fetch('%s', {method: '%s'})", url, method)
}
`
// GenerateEvents generates event constants and helpers
func (g *Generator) GenerateEvents(filename string, elements []parser.Element) error {
// Collect all unique events
eventMap := make(map[string]parser.Event)
for _, el := range elements {
for _, ev := range el.Events {
if _, exists := eventMap[ev.Name]; !exists {
eventMap[ev.Name] = ev
}
}
}
// Convert to slice and sort
events := make([]parser.Event, 0, len(eventMap))
for _, ev := range eventMap {
events = append(events, ev)
}
sort.Slice(events, func(i, j int) bool {
return events[i].Name < events[j].Name
})
tmpl, err := template.New("events").Funcs(g.funcMap).Parse(eventsTemplate)
if err != nil {
return err
}
var buf bytes.Buffer
data := map[string]interface{}{
"Package": g.pkg,
"Events": events,
}
if err := tmpl.Execute(&buf, data); err != nil {
return err
}
return os.WriteFile(filename, buf.Bytes(), 0644)
}

View File

@@ -0,0 +1,137 @@
package generator
import (
"regexp"
"strings"
)
var (
kebabRegex = regexp.MustCompile(`[-_]`)
mdLinkRegex = regexp.MustCompile(`\[([^\]]+)\]\([^)]+\)`)
)
// ToPascalCase converts kebab-case or snake_case to PascalCase
func ToPascalCase(s string) string {
parts := kebabRegex.Split(s, -1)
for i, p := range parts {
if len(p) > 0 {
parts[i] = strings.ToUpper(p[:1]) + p[1:]
}
}
return strings.Join(parts, "")
}
// ToCamelCase converts kebab-case or snake_case to camelCase
func ToCamelCase(s string) string {
p := ToPascalCase(s)
if len(p) > 0 {
return strings.ToLower(p[:1]) + p[1:]
}
return p
}
// StripWaPrefix removes the "wa-" prefix from component names
func StripWaPrefix(s string) string {
return strings.TrimPrefix(s, "wa-")
}
// ToGoType converts TypeScript type to Go type
func ToGoType(tsType string) string {
tsType = strings.TrimSpace(tsType)
switch {
case tsType == "boolean":
return "bool"
case tsType == "number":
return "float64"
case tsType == "string":
return "string"
case tsType == "string | undefined", tsType == "string | null":
return "string"
case tsType == "number | undefined", tsType == "number | null":
return "float64"
case tsType == "boolean | undefined", tsType == "boolean | null":
return "bool"
case strings.HasPrefix(tsType, "'") && strings.Contains(tsType, "|"):
// Union of string literals -> string
return "string"
case strings.Contains(tsType, "|"):
// Complex union, default to string
return "string"
default:
return "string"
}
}
// ToBuilderType returns the type used in builder methods (always non-pointer for fluent API)
func ToBuilderType(tsType string) string {
return ToGoType(tsType)
}
// IsBoolean checks if the TypeScript type is boolean
func IsBoolean(tsType string) bool {
return strings.Contains(tsType, "boolean")
}
// IsNumber checks if the TypeScript type is numeric
func IsNumber(tsType string) bool {
return tsType == "number" || strings.HasPrefix(tsType, "number")
}
// CleanDescription cleans markdown and truncates description
func CleanDescription(s string) string {
if s == "" {
return ""
}
// Take first line
lines := strings.Split(s, "\n")
first := strings.TrimSpace(lines[0])
// Strip markdown links
first = mdLinkRegex.ReplaceAllString(first, "$1")
// Remove backticks
first = strings.ReplaceAll(first, "`", "")
// Truncate
if len(first) > 120 {
first = first[:117] + "..."
}
return first
}
// ExtractEnumValues extracts string literal values from a union type
func ExtractEnumValues(tsType string) []string {
if !strings.Contains(tsType, "'") {
return nil
}
re := regexp.MustCompile(`'([^']+)'`)
matches := re.FindAllStringSubmatch(tsType, -1)
values := make([]string, 0, len(matches))
for _, m := range matches {
if len(m) > 1 {
values = append(values, m[1])
}
}
return values
}
// ToEventName converts wa-event-name to OnEventName
func ToEventName(s string) string {
s = strings.TrimPrefix(s, "wa-")
return "On" + ToPascalCase(s)
}
// ToSlotName converts slot name to Go-friendly name
func ToSlotName(s string) string {
if s == "" {
return "Default"
}
return ToPascalCase(s)
}
// EscapeGoString escapes a string for use in Go source code
func EscapeGoString(s string) string {
s = strings.ReplaceAll(s, "\\", "\\\\")
s = strings.ReplaceAll(s, "\"", "\\\"")
s = strings.ReplaceAll(s, "\n", "\\n")
s = strings.ReplaceAll(s, "\t", "\\t")
return s
}

65
internal/parser/types.go Normal file
View File

@@ -0,0 +1,65 @@
// Package parser contains the code parser for the WebAwesome library.
package parser
// WebTypes represents the root structure of web-types.json
type WebTypes struct {
Schema string `json:"$schema"`
Name string `json:"name"`
Version string `json:"version"`
DescriptionMarkup string `json:"description-markup"`
Contributions struct {
HTML struct {
Elements []Element `json:"elements"`
} `json:"html"`
} `json:"contributions"`
}
// Element represents a web component definition
type Element struct {
Name string `json:"name"`
Description string `json:"description"`
DocURL string `json:"doc-url"`
Attributes []Attribute `json:"attributes"`
Slots []Slot `json:"slots"`
Events []Event `json:"events"`
JS JSInfo `json:"js"`
}
// Attribute represents a component attribute/property
type Attribute struct {
Name string `json:"name"`
Description string `json:"description"`
Value AttributeValue `json:"value"`
}
// AttributeValue contains type and default value info
type AttributeValue struct {
Type string `json:"type"`
Default string `json:"default,omitempty"`
}
// Slot represents a named slot in the component
type Slot struct {
Name string `json:"name"`
Description string `json:"description"`
}
// Event represents an event emitted by the component
type Event struct {
Name string `json:"name"`
Type string `json:"type,omitempty"`
Description string `json:"description"`
}
// JSInfo contains JavaScript-specific property and event info
type JSInfo struct {
Properties []JSProperty `json:"properties"`
Events []Event `json:"events"`
}
// JSProperty represents a JavaScript property
type JSProperty struct {
Name string `json:"name"`
Type string `json:"type"`
Description string `json:"description,omitempty"`
}

View File

@@ -1,2 +0,0 @@
// Package nebula is an implementation of the WebAwesome UI Library in Golang Templ syntax.
package nebula