From ec87807487834d4bc4f36a5ddd32d6a0a3e3fcb0 Mon Sep 17 00:00:00 2001 From: Prad Nukala Date: Thu, 1 Jan 2026 14:26:25 -0500 Subject: [PATCH] feat: Add generator function for WebAwesome --- README.md | 2 +- cmd/generate/main.go | 79 + config.json | 7020 +++++++++++++++++++++++++++++++ internal/generator/generator.go | 644 +++ internal/generator/strings.go | 137 + internal/parser/types.go | 65 + nebula.go | 2 - 7 files changed, 7946 insertions(+), 3 deletions(-) create mode 100644 cmd/generate/main.go create mode 100644 config.json create mode 100644 internal/generator/generator.go create mode 100644 internal/generator/strings.go create mode 100644 internal/parser/types.go delete mode 100644 nebula.go diff --git a/README.md b/README.md index efad16a..be5eab4 100644 --- a/README.md +++ b/README.md @@ -15,5 +15,5 @@ package main import ( "fmt" - "github.com/prad/nebula" + "github.com/sonr-io/nebula" ) diff --git a/cmd/generate/main.go b/cmd/generate/main.go new file mode 100644 index 0000000..5dda8d6 --- /dev/null +++ b/cmd/generate/main.go @@ -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) +} diff --git a/config.json b/config.json new file mode 100644 index 0000000..c85e9a7 --- /dev/null +++ b/config.json @@ -0,0 +1,7020 @@ +{ + "$schema": "https://raw.githubusercontent.com/JetBrains/web-types/master/schema/web-types.json", + "name": "@awesome.me/webawesome-pro", + "version": "3.1.0", + "description-markup": "markdown", + "contributions": { + "html": { + "elements": [ + { + "name": "wa-animated-image", + "description": "A component for displaying animated GIFs and WEBPs that play and pause on interaction.\n---\n\n\n### **Events:**\n - **wa-load** - Emitted when the image loads successfully.\n- **wa-error** - Emitted when the image fails to load.\n\n### **Slots:**\n - **play-icon** - Optional play icon to use instead of the default. Works best with ``.\n- **pause-icon** - Optional pause icon to use instead of the default. Works best with ``.\n\n### **CSS Properties:**\n - **--control-box-size** - The size of the icon box. _(default: undefined)_\n- **--icon-size** - The size of the play/pause icons. _(default: undefined)_\n\n### **CSS Parts:**\n - **control-box** - The container that surrounds the pause/play icons and provides their background.", + "doc-url": "", + "attributes": [ + { + "name": "src", + "description": "The path to the image to load.", + "value": { "type": "string" } + }, + { + "name": "alt", + "description": "A description of the image used by assistive devices.", + "value": { "type": "string" } + }, + { + "name": "play", + "description": "Plays the animation. When this attribute is remove, the animation will pause.", + "value": { "type": "boolean" } + } + ], + "slots": [ + { + "name": "play-icon", + "description": "Optional play icon to use instead of the default. Works best with ``." + }, + { + "name": "pause-icon", + "description": "Optional pause icon to use instead of the default. Works best with ``." + } + ], + "events": [ + { + "name": "wa-load", + "description": "Emitted when the image loads successfully." + }, + { + "name": "wa-error", + "description": "Emitted when the image fails to load." + } + ], + "js": { + "properties": [ + { "name": "animatedImage", "type": "HTMLImageElement" }, + { "name": "frozenFrame", "type": "string" }, + { "name": "isLoaded", "type": "boolean" }, + { + "name": "src", + "description": "The path to the image to load.", + "type": "string" + }, + { + "name": "alt", + "description": "A description of the image used by assistive devices.", + "type": "string" + }, + { + "name": "play", + "description": "Plays the animation. When this attribute is remove, the animation will pause.", + "type": "boolean" + } + ], + "events": [ + { + "name": "wa-load", + "description": "Emitted when the image loads successfully." + }, + { + "name": "wa-error", + "description": "Emitted when the image fails to load." + } + ] + } + }, + { + "name": "wa-animation", + "description": "Animate elements declaratively with nearly 100 baked-in presets, or roll your own with custom keyframes. Powered by the [Web Animations API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API).\n---\n\n\n### **Events:**\n - **wa-cancel** - Emitted when the animation is canceled.\n- **wa-finish** - Emitted when the animation finishes.\n- **wa-start** - Emitted when the animation starts or restarts.\n\n### **Methods:**\n - **cancel()** - Clears all keyframe effects caused by this animation and aborts its playback.\n- **finish()** - Sets the playback time to the end of the animation corresponding to the current playback direction.\n\n### **Slots:**\n - _default_ - The element to animate. Avoid slotting in more than one element, as subsequent ones will be ignored. To animate multiple elements, either wrap them in a single container or use multiple `` elements.", + "doc-url": "", + "attributes": [ + { + "name": "name", + "description": "The name of the built-in animation to use. For custom animations, use the `keyframes` prop.", + "value": { "type": "string", "default": "'none'" } + }, + { + "name": "play", + "description": "Plays the animation. When omitted, the animation will be paused. This attribute will be automatically removed when\nthe animation finishes or gets canceled.", + "value": { "type": "boolean", "default": "false" } + }, + { + "name": "delay", + "description": "The number of milliseconds to delay the start of the animation.", + "value": { "type": "number", "default": "0" } + }, + { + "name": "direction", + "description": "Determines the direction of playback as well as the behavior when reaching the end of an iteration.\n[Learn more](https://developer.mozilla.org/en-US/docs/Web/CSS/animation-direction)", + "value": { "type": "PlaybackDirection", "default": "'normal'" } + }, + { + "name": "duration", + "description": "The number of milliseconds each iteration of the animation takes to complete.", + "value": { "type": "number", "default": "1000" } + }, + { + "name": "easing", + "description": "The easing function to use for the animation. This can be a Web Awesome easing function or a custom easing function\nsuch as `cubic-bezier(0, 1, .76, 1.14)`.", + "value": { "type": "string", "default": "'linear'" } + }, + { + "name": "end-delay", + "description": "The number of milliseconds to delay after the active period of an animation sequence.", + "value": { "type": "number", "default": "0" } + }, + { + "name": "fill", + "description": "Sets how the animation applies styles to its target before and after its execution.", + "value": { "type": "FillMode", "default": "'auto'" } + }, + { + "name": "iterations", + "description": "The number of iterations to run before the animation completes. Defaults to `Infinity`, which loops.", + "value": { "type": "number", "default": "Infinity" } + }, + { + "name": "iteration-start", + "description": "The offset at which to start the animation, usually between 0 (start) and 1 (end).", + "value": { "type": "number", "default": "0" } + }, + { + "name": "playback-rate", + "description": "Sets the animation's playback rate. The default is `1`, which plays the animation at a normal speed. Setting this\nto `2`, for example, will double the animation's speed. A negative value can be used to reverse the animation. This\nvalue can be changed without causing the animation to restart.", + "value": { "type": "number", "default": "1" } + } + ], + "slots": [ + { + "name": "", + "description": "The element to animate. Avoid slotting in more than one element, as subsequent ones will be ignored. To animate multiple elements, either wrap them in a single container or use multiple `` elements." + } + ], + "events": [ + { + "name": "wa-cancel", + "description": "Emitted when the animation is canceled." + }, + { + "name": "wa-finish", + "description": "Emitted when the animation finishes." + }, + { + "name": "wa-start", + "description": "Emitted when the animation starts or restarts." + } + ], + "js": { + "properties": [ + { "name": "defaultSlot", "type": "Promise" }, + { + "name": "name", + "description": "The name of the built-in animation to use. For custom animations, use the `keyframes` prop.", + "type": "string" + }, + { + "name": "play", + "description": "Plays the animation. When omitted, the animation will be paused. This attribute will be automatically removed when\nthe animation finishes or gets canceled.", + "type": "boolean" + }, + { + "name": "delay", + "description": "The number of milliseconds to delay the start of the animation.", + "type": "number" + }, + { + "name": "direction", + "description": "Determines the direction of playback as well as the behavior when reaching the end of an iteration.\n[Learn more](https://developer.mozilla.org/en-US/docs/Web/CSS/animation-direction)", + "type": "PlaybackDirection" + }, + { + "name": "duration", + "description": "The number of milliseconds each iteration of the animation takes to complete.", + "type": "number" + }, + { + "name": "easing", + "description": "The easing function to use for the animation. This can be a Web Awesome easing function or a custom easing function\nsuch as `cubic-bezier(0, 1, .76, 1.14)`.", + "type": "string" + }, + { + "name": "endDelay", + "description": "The number of milliseconds to delay after the active period of an animation sequence.", + "type": "number" + }, + { + "name": "fill", + "description": "Sets how the animation applies styles to its target before and after its execution.", + "type": "FillMode" + }, + { + "name": "iterations", + "description": "The number of iterations to run before the animation completes. Defaults to `Infinity`, which loops.", + "type": "number" + }, + { + "name": "iterationStart", + "description": "The offset at which to start the animation, usually between 0 (start) and 1 (end).", + "type": "number" + }, + { + "name": "keyframes", + "description": "The keyframes to use for the animation. If this is set, `name` will be ignored.", + "type": "Keyframe[] | undefined" + }, + { + "name": "playbackRate", + "description": "Sets the animation's playback rate. The default is `1`, which plays the animation at a normal speed. Setting this\nto `2`, for example, will double the animation's speed. A negative value can be used to reverse the animation. This\nvalue can be changed without causing the animation to restart.", + "type": "number" + }, + { + "name": "currentTime", + "description": "Gets and sets the current animation time.", + "type": "CSSNumberish" + } + ], + "events": [ + { + "name": "wa-cancel", + "description": "Emitted when the animation is canceled." + }, + { + "name": "wa-finish", + "description": "Emitted when the animation finishes." + }, + { + "name": "wa-start", + "description": "Emitted when the animation starts or restarts." + } + ] + } + }, + { + "name": "wa-avatar", + "description": "Avatars are used to represent a person or object.\n---\n\n\n### **Events:**\n - **wa-error** - The image could not be loaded. This may because of an invalid URL, a temporary network condition, or some unknown cause.\n\n### **Slots:**\n - **icon** - The default icon to use when no image or initials are present. Works best with ``.\n\n### **CSS Properties:**\n - **--size** - The size of the avatar. _(default: undefined)_\n\n### **CSS Parts:**\n - **icon** - The container that wraps the avatar's icon.\n- **initials** - The container that wraps the avatar's initials.\n- **image** - The avatar image. Only shown when the `image` attribute is set.", + "doc-url": "", + "attributes": [ + { + "name": "image", + "description": "The image source to use for the avatar.", + "value": { "type": "string", "default": "''" } + }, + { + "name": "label", + "description": "A label to use to describe the avatar to assistive devices.", + "value": { "type": "string", "default": "''" } + }, + { + "name": "initials", + "description": "Initials to use as a fallback when no image is available (1-2 characters max recommended).", + "value": { "type": "string", "default": "''" } + }, + { + "name": "loading", + "description": "Indicates how the browser should load the image.", + "value": { "type": "'eager' | 'lazy'", "default": "'eager'" } + }, + { + "name": "shape", + "description": "The shape of the avatar.", + "value": { + "type": "'circle' | 'square' | 'rounded'", + "default": "'circle'" + } + } + ], + "slots": [ + { + "name": "icon", + "description": "The default icon to use when no image or initials are present. Works best with ``." + } + ], + "events": [ + { + "name": "wa-error", + "description": "The image could not be loaded. This may because of an invalid URL, a temporary network condition, or some unknown cause." + } + ], + "js": { + "properties": [ + { + "name": "image", + "description": "The image source to use for the avatar.", + "type": "string" + }, + { + "name": "label", + "description": "A label to use to describe the avatar to assistive devices.", + "type": "string" + }, + { + "name": "initials", + "description": "Initials to use as a fallback when no image is available (1-2 characters max recommended).", + "type": "string" + }, + { + "name": "loading", + "description": "Indicates how the browser should load the image.", + "type": "'eager' | 'lazy'" + }, + { + "name": "shape", + "description": "The shape of the avatar.", + "type": "'circle' | 'square' | 'rounded'" + } + ], + "events": [ + { + "name": "wa-error", + "description": "The image could not be loaded. This may because of an invalid URL, a temporary network condition, or some unknown cause." + } + ] + } + }, + { + "name": "wa-badge", + "description": "Badges are used to draw attention and display statuses or counts.\n---\n\n\n### **Slots:**\n - _default_ - The badge's content.\n\n### **CSS Properties:**\n - **--pulse-color** - The color of the badge's pulse effect when using `attention=\"pulse\"`. _(default: undefined)_\n\n### **CSS Parts:**\n - **base** - The component's base wrapper.", + "doc-url": "", + "attributes": [ + { + "name": "variant", + "description": "The badge's theme variant. Defaults to `brand` if not within another element with a variant.", + "value": { + "type": "'brand' | 'neutral' | 'success' | 'warning' | 'danger'", + "default": "'brand'" + } + }, + { + "name": "appearance", + "description": "The badge's visual appearance.", + "value": { + "type": "'accent' | 'filled' | 'outlined' | 'filled-outlined'", + "default": "'accent'" + } + }, + { + "name": "pill", + "description": "Draws a pill-style badge with rounded edges.", + "value": { "type": "boolean", "default": "false" } + }, + { + "name": "attention", + "description": "Adds an animation to draw attention to the badge.", + "value": { + "type": "'none' | 'pulse' | 'bounce'", + "default": "'none'" + } + } + ], + "slots": [{ "name": "", "description": "The badge's content." }], + "events": [], + "js": { + "properties": [ + { + "name": "variant", + "description": "The badge's theme variant. Defaults to `brand` if not within another element with a variant.", + "type": "'brand' | 'neutral' | 'success' | 'warning' | 'danger'" + }, + { + "name": "appearance", + "description": "The badge's visual appearance.", + "type": "'accent' | 'filled' | 'outlined' | 'filled-outlined'" + }, + { + "name": "pill", + "description": "Draws a pill-style badge with rounded edges.", + "type": "boolean" + }, + { + "name": "attention", + "description": "Adds an animation to draw attention to the badge.", + "type": "'none' | 'pulse' | 'bounce'" + } + ], + "events": [] + } + }, + { + "name": "wa-breadcrumb-item", + "description": "Breadcrumb Items are used inside breadcrumbs to represent different links.\n---\n\n\n### **Slots:**\n - _default_ - The breadcrumb item's label.\n- **start** - An element, such as ``, placed before the label.\n- **end** - An element, such as ``, placed after the label.\n- **separator** - The separator to use for the breadcrumb item. This will only change the separator for this item. If you want to change it for all items in the group, set the separator on `` instead.\n\n### **CSS Parts:**\n - **label** - The breadcrumb item's label.\n- **start** - The container that wraps the `start` slot.\n- **end** - The container that wraps the `end` slot.\n- **separator** - The container that wraps the separator.", + "doc-url": "", + "attributes": [ + { + "name": "href", + "description": "Optional URL to direct the user to when the breadcrumb item is activated. When set, a link will be rendered\ninternally. When unset, a button will be rendered instead.", + "value": { "type": "string | undefined" } + }, + { + "name": "target", + "description": "Tells the browser where to open the link. Only used when `href` is set.", + "value": { + "type": "'_blank' | '_parent' | '_self' | '_top' | undefined" + } + }, + { + "name": "rel", + "description": "The `rel` attribute to use on the link. Only used when `href` is set.", + "value": { "type": "string", "default": "'noreferrer noopener'" } + } + ], + "slots": [ + { "name": "", "description": "The breadcrumb item's label." }, + { + "name": "start", + "description": "An element, such as ``, placed before the label." + }, + { + "name": "end", + "description": "An element, such as ``, placed after the label." + }, + { + "name": "separator", + "description": "The separator to use for the breadcrumb item. This will only change the separator for this item. If you want to change it for all items in the group, set the separator on `` instead." + } + ], + "events": [], + "js": { + "properties": [ + { "name": "defaultSlot", "type": "HTMLSlotElement" }, + { + "name": "href", + "description": "Optional URL to direct the user to when the breadcrumb item is activated. When set, a link will be rendered\ninternally. When unset, a button will be rendered instead.", + "type": "string | undefined" + }, + { + "name": "target", + "description": "Tells the browser where to open the link. Only used when `href` is set.", + "type": "'_blank' | '_parent' | '_self' | '_top' | undefined" + }, + { + "name": "rel", + "description": "The `rel` attribute to use on the link. Only used when `href` is set.", + "type": "string" + } + ], + "events": [] + } + }, + { + "name": "wa-breadcrumb", + "description": "Breadcrumbs provide a group of links so users can easily navigate a website's hierarchy.\n---\n\n\n### **Slots:**\n - _default_ - One or more breadcrumb items to display.\n- **separator** - The separator to use between breadcrumb items. Works best with ``.\n\n### **CSS Parts:**\n - **base** - The component's base wrapper.", + "doc-url": "", + "attributes": [ + { + "name": "label", + "description": "The label to use for the breadcrumb control. This will not be shown on the screen, but it will be announced by\nscreen readers and other assistive devices to provide more context for users.", + "value": { "type": "string", "default": "''" } + } + ], + "slots": [ + { + "name": "", + "description": "One or more breadcrumb items to display." + }, + { + "name": "separator", + "description": "The separator to use between breadcrumb items. Works best with ``." + } + ], + "events": [], + "js": { + "properties": [ + { "name": "defaultSlot", "type": "HTMLSlotElement" }, + { "name": "separatorSlot", "type": "HTMLSlotElement" }, + { + "name": "label", + "description": "The label to use for the breadcrumb control. This will not be shown on the screen, but it will be announced by\nscreen readers and other assistive devices to provide more context for users.", + "type": "string" + } + ], + "events": [] + } + }, + { + "name": "wa-button-group", + "description": "Button groups can be used to group related buttons into sections.\n---\n\n\n### **Slots:**\n - _default_ - One or more `` elements to display in the button group.\n\n### **CSS Parts:**\n - **base** - The component's base wrapper.", + "doc-url": "", + "attributes": [ + { + "name": "label", + "description": "A label to use for the button group. This won't be displayed on the screen, but it will be announced by assistive\ndevices when interacting with the control and is strongly recommended.", + "value": { "type": "string", "default": "''" } + }, + { + "name": "orientation", + "description": "The button group's orientation.", + "value": { + "type": "'horizontal' | 'vertical'", + "default": "'horizontal'" + } + } + ], + "slots": [ + { + "name": "", + "description": "One or more `` elements to display in the button group." + } + ], + "events": [], + "js": { + "properties": [ + { "name": "defaultSlot", "type": "HTMLSlotElement" }, + { "name": "disableRole", "type": "boolean" }, + { "name": "hasOutlined", "type": "boolean" }, + { + "name": "label", + "description": "A label to use for the button group. This won't be displayed on the screen, but it will be announced by assistive\ndevices when interacting with the control and is strongly recommended.", + "type": "string" + }, + { + "name": "orientation", + "description": "The button group's orientation.", + "type": "'horizontal' | 'vertical'" + } + ], + "events": [] + } + }, + { + "name": "wa-button", + "description": "Buttons represent actions that are available to the user.\n---\n\n\n### **Events:**\n - **blur** - Emitted when the button loses focus.\n- **focus** - Emitted when the button gains focus.\n- **wa-invalid** - Emitted when the form control has been checked for validity and its constraints aren't satisfied.\n\n### **Methods:**\n - **click()** - Simulates a click on the button.\n- **focus(options: _FocusOptions_)** - Sets focus on the button.\n- **blur()** - Removes focus from the button.\n\n### **Slots:**\n - _default_ - The button's label.\n- **start** - An element, such as ``, placed before the label.\n- **end** - An element, such as ``, placed after the label.\n\n### **CSS Parts:**\n - **base** - The component's base wrapper.\n- **start** - The container that wraps the `start` slot.\n- **label** - The button's label.\n- **end** - The container that wraps the `end` slot.\n- **caret** - The button's caret icon, a `` element.\n- **spinner** - The spinner that shows when the button is in the loading state.", + "doc-url": "", + "attributes": [ + { "name": "title", "value": { "type": "string", "default": "''" } }, + { + "name": "variant", + "description": "The button's theme variant. Defaults to `neutral` if not within another element with a variant.", + "value": { + "type": "'neutral' | 'brand' | 'success' | 'warning' | 'danger'", + "default": "'neutral'" + } + }, + { + "name": "appearance", + "description": "The button's visual appearance.", + "value": { + "type": "'accent' | 'filled' | 'outlined' | 'filled-outlined' | 'plain'", + "default": "'accent'" + } + }, + { + "name": "size", + "description": "The button's size.", + "value": { + "type": "'small' | 'medium' | 'large'", + "default": "'medium'" + } + }, + { + "name": "with-caret", + "description": "Draws the button with a caret. Used to indicate that the button triggers a dropdown menu or similar behavior.", + "value": { "type": "boolean", "default": "false" } + }, + { + "name": "disabled", + "description": "Disables the button. Does not apply to link buttons.", + "value": { "type": "boolean", "default": "false" } + }, + { + "name": "loading", + "description": "Draws the button in a loading state.", + "value": { "type": "boolean", "default": "false" } + }, + { + "name": "pill", + "description": "Draws a pill-style button with rounded edges.", + "value": { "type": "boolean", "default": "false" } + }, + { + "name": "type", + "description": "The type of button. Note that the default value is `button` instead of `submit`, which is opposite of how native\n`