Help us shape the future of Go! Take the 2026 Go Developer Survey

compono

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 5, 2026 License: MIT Imports: 10 Imported by: 1

README

Compono

Compono is a platform-agnostic, component-based domain-specific language (DSL) that extends Markdown syntax with reusable components.

Originally developed for Umono CMS, Compono can be used in any Go project that needs a flexible templating solution.

Installation

go get github.com/umono-cms/compono

Quick Start

package main

import (
    "bytes"
    "fmt"
    "github.com/umono-cms/compono"
)

func main() {
    c := compono.New()

    source := []byte(`{{ SAY_HELLO name="World" }}

~ SAY_HELLO name="Guest"
# Hello, {{ name }}!
`)

    var buf bytes.Buffer
    if err := c.Convert(source, &buf); err != nil {
        panic(err)
    }

    fmt.Println(buf.String())
    // Output: <h1>Hello, World!</h1>
}

Syntax

Markdown Support

Compono supports common Markdown elements:

# Heading 1
## Heading 2
### Heading 3

This is a paragraph with **bold** and *italic* text.

`inline code`

[Link text](https://example.com)

Code blocks are also supported:

```go
fmt.Println("Hello")
```
Components

Components are the core feature of Compono. They allow you to create reusable content blocks.

Defining a Local Component

Local components are defined in the same scope where they're used:

{{ GREETING }}

~ GREETING
Welcome to our website!

The ~ COMPONENT_NAME syntax marks the beginning of a local component definition. Everything after it becomes the component's content. A component definition ends when another component definition starts or at EOF.

Components with Parameters

Components can accept parameters with default values:

{{ USER_CARD name="Anonymous" role="Guest" }}

~ USER_CARD name="" role=""
## {{ name }}
*{{ role }}*

Parameter types supported:

  • Strings: name="John"
  • Numbers: age=25
  • Booleans: active=true
Block vs Inline Components

Components containing multiple paragraphs or block elements are block components:

{{ ARTICLE }}

~ ARTICLE
# Title
First paragraph.

Second paragraph.

Components with single-line content can be used inline:

Welcome, {{ USERNAME }}!

~ USERNAME
John
Global Components

Global components can be registered once and used across multiple conversions:

c := compono.New()

// Register a global component
c.RegisterGlobalComponent("FOOTER", []byte(`© 2026 My Company`))

// Use it in any conversion
c.Convert([]byte(`
# Page Title
Content here...
{{ FOOTER }}
`), &buf)

Global components can also have parameters:

c.RegisterGlobalComponent("BLOG_PAGE", []byte(`title="" content=""
## {{ title }}
{{ content }}`))

Built-in Components

Creates an anchor element with optional target blank:

{{ LINK text="Visit us" url="https://example.com" new-tab=true }}

Output:

<a href="https://example.com" target="_blank" rel="noopener noreferrer">Visit us</a>

Parameters

Components can accept parameters. Each parameter must have a default value defined in the component definition.

If a parameter value is not provided during the call, the default value is used.

{{ SAY_HELLO name="Jane" }}

~ SAY_HELLO name="John"
# Hello, {{ name }}!
Supported Types

Supported parameter types:

  • String → name="John"
  • Number → age=25
  • Bool → active=true
  • Component → another component can be passed as a parameter

Passing Parameters to Other Components

A parameter can be passed directly to another component call.

{{ USER age=31 }}

~ USER age=18
{{ ANOTHER_COMP another-number-param=age }}

~ ANOTHER_COMP another-number-param=0
Number: *{{ another-number-param }}*

Here:

  • USER receives age
  • it forwards that value to ANOTHER_COMP

Passing Components as Parameters

Components themselves can also be passed as parameters.

{{ USER name="Yunus Emre" age=31 age-wrapper=AGE_WRAPPER_2 }}

~ USER name="John" age=25 age-wrapper=AGE_WRAPPER_1
# Welcome **{{ name }}**!
{{ age-wrapper age=age }}

~ AGE_WRAPPER_1 age=0
Your age: *{{ age }}*

~ AGE_WRAPPER_2 age=0
*{{ age }}*

Here:

  • age-wrapper receives a component
  • that component is executed inside USER

Global Parameter Visibility in Local Components

When a global component defines parameters, those parameters are visible to local components inside it.

c.RegisterGlobalComponent("PROFILE_PAGE", []byte(`
name="Guest"

{{ PROFILE_CARD }}

~ PROFILE_CARD
## {{ name }}
Welcome to the profile page.
`))

Usage:

{{ PROFILE_PAGE name="Yunus" }}

Output:

<h2>Yunus</h2>
<p>Welcome to the profile page.</p>

The local component PROFILE_CARD can directly access the global parameter name.


Error Handling

Compono provides error feedback by rendering placeholders where errors occur. Fatal errors during conversion stop the process and no output is produced.

API Reference

Core Methods
// Create a new Compono instance
c := compono.New()

// Convert source to HTML
err := c.Convert(source []byte, writer io.Writer)

// Register a global component
err := c.RegisterGlobalComponent(name string, source []byte)

// Unregister a global component
err := c.UnregisterGlobalComponent(name string)

// Convert and preview a global component
err := c.ConvertGlobalComponent(name string, source []byte, writer io.Writer)

Component Naming Convention

Component names must be in SCREAMING_SNAKE_CASE:

  • ✓ HEADER
  • ✓ USER_PROFILE
  • ✓ NAV_MENU_ITEM
  • ✗ header
  • ✗ userProfile

Parameter Naming Convention

Parameter names must be in kebab-case:

  • ✓ name
  • ✓ user-name
  • ✓ is-active
  • ✗ userName
  • ✗ user_name

Component Override Behavior

When multiple components share the same name, Compono follows a clear override hierarchy:

Local Component > Global Component > Built-in Component

Local always wins:

{{ LINK }}

~ LINK
I override the built-in LINK component!

This outputs <p>I override the built-in LINK component!</p> instead of an anchor tag.

Global overrides built-in:

c.RegisterGlobalComponent("LINK", []byte(`Custom link behavior`))

Now all {{ LINK }} calls will use your global definition instead of the built-in one.

This allows you to customize or extend built-in components without modifying the library.

License

MIT License - see LICENSE for details.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Compono

type Compono interface {
	Convert(source []byte, writer io.Writer) error
	ConvertGlobalComponent(string, []byte, io.Writer) error
	RegisterGlobalComponent(string, []byte) error
	UnregisterGlobalComponent(string) error
	Parser() parser.Parser
	SetParser(parser.Parser)
	Renderer() renderer.Renderer
	SetRenderer(renderer.Renderer)
	Validator() validator.Validator
	SetValidator(validator.Validator)
	ErrorWrapper() errwrap.ErrorWrapper
	SetErrorWrapper(errwrap.ErrorWrapper)
	Logger() logger.Logger
	SetLogger(logger.Logger)
}

func New

func New() Compono

type ComponoError

type ComponoError struct {
	Code    ErrorCode
	Message string
}

func NewComponoError

func NewComponoError(code ErrorCode, msg string) *ComponoError

func (*ComponoError) Error

func (e *ComponoError) Error() string

type ErrorCode

type ErrorCode int
const (
	ErrInvalidGlobalName ErrorCode = iota + 1
	ErrGlobalAlreadyRegistered
	ErrGlobalNotExist
	ErrInvalidAST
	ErrRender
)

Directories

Path Synopsis
internal

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL