output

package
v0.2.2 Latest Latest
Warning

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

Go to latest
Published: Apr 10, 2026 License: MIT Imports: 5 Imported by: 0

README

Output Plugins

Output plugins generate configuration files from extracted colour palettes. They use Go's text/template package with embedded template files for easy customization.

Plugin Standard

**All output plugins MUST follow the comprehensive standard defined in PLUGIN-STANDARD.md.

This standard document defines:

  • File naming conventions (two-file vs single-file patterns)
  • Required interface implementations
  • Template structure and requirements
  • Testing requirements (>80% coverage)
  • Semantic colour naming standards
  • Documentation requirements
  • Complete checklist for new plugins

**Quick Reference:

  • Use tinct-colours.{ext} + tinct.{ext} for apps supporting variables/imports
  • Use tinct.{ext} only for apps without variable support
  • Follow semantic colour names: background, foreground, accent1, danger, etc.
  • Embed templates with go:embed *.tmpl
  • Implement all required interface methods
  • Write comprehensive tests

See existing plugins for reference implementations:

  • Two-file pattern: waybar/, hyprland/
  • Single-file pattern: kitty/

File Naming Convention

All output plugins follow a consistent naming standard for generated files:

Standard Pattern

**For applications that support colour variables/includes:

  • tinct-colours.conf (or .css, .toml, etc.) - Colour variable definitions
  • tinct.conf - Example configuration that sources/includes the colour variables

**For applications that don't support variables:

  • tinct.conf - Complete configuration with colours embedded directly
Examples

Hyprland (supports variables):

~/.config/hypr/tinct-colours.conf  # Colour variables: $background = rgb(...)
~/.config/hypr/tinct.conf          # Example config using: $background

Waybar (supports CSS variables):

~/.config/waybar/tinct-colours.css  # @define-colour background #1a1b26;
~/.config/waybar/tinct.css          # Uses: @background

Kitty (no variable support):

~/.config/kitty/tinct.conf  # Complete config with colours embedded
Implementation Checklist

When creating a new plugin, determine:

  1. Does the application support colour variables/constants?
    • Check documentation for variable syntax (CSS @define-colour, shell variables, etc.)
    • Test if variables can be defined in one file and used in another
  2. Does the application support include/source directives?
    • Most do: include, source, @import, etc.

**If YES to both:

  • Generate two files: tinct-colours.{ext} and tinct.{ext}
  • Put colour definitions in tinct-colours.{ext}
  • Put example usage in tinct.{ext}

**If NO (no variables or includes):

  • Generate single file: tinct.{ext}
  • Embed all colours directly in the configuration
Benefits
  • Consistent: Users know what to expect across all plugins
  • Predictable: Easy to find generated files
  • Flexible: Split when useful, single when not
  • Clear: File purpose obvious from name

Architecture

Plugin Structure

Each plugin lives in its own directory with:

  • plugin_name.go - Plugin implementation
  • *.tmpl - Template files (embedded with go:embed)
  • plugin_name_test.go - Tests

Example structure:

internal/plugin/output/
 plugin.go              # Base plugin interface
 tailwind/
    tailwind.go        # Plugin implementation
    globals.css.tmpl   # CSS template
    tailwind.config.js.tmpl  # Config template
    tailwind_test.go   # Tests
 alacritty/
     alacritty.go
     alacritty.toml.tmpl
     alacritty_test.go
Benefits

Separation of Concerns: Templates separate from Go code
Easy to Edit: Modify templates without touching Go
Version Control: Templates tracked separately
No Runtime Dependencies: Templates embedded at compile time
User Customizable: Can load templates from disk (future feature)
Fast Development: New plugins = new template + small Go wrapper

Creating a New Plugin

Step 1: Create Plugin Directory
mkdir internal/plugin/output/myplugin
Step 2: Create Template File

internal/plugin/output/myplugin/config.tmpl:

# My App Config
# Generated by Tinct

background = "{{ .Background.Hex }}"
foreground = "{{ .Foreground.Hex }}"

{{- if .Danger }}
danger = "{{ .Danger.Hex }}"
{{- end }}

{{- if .Warning }}
warning = "{{ .Warning.Hex }}"
{{- end }}

{{- if .Success }}
success = "{{ .Success.Hex }}"
{{- end }}
Step 3: Create Plugin Implementation

internal/plugin/output/myplugin/myplugin.go:

package myplugin

import (
    "bytes"
    "embed"
    "fmt"
    "text/template"

    "github.com/jmylchreest/tinct/internal/colour"
)

//go:embed *.tmpl
var templates embed.FS

type Plugin struct{}

func New() *Plugin {
    return &Plugin{}
}

func (p *Plugin) Name() string {
    return "myplugin"
}

func (p *Plugin) Description() string {
    return "Generate MyApp configuration"
}

func (p *Plugin) FileExtension() string {
    return "conf"
}

func (p *Plugin) DefaultPath() string {
    return "~/.config/myapp/config.conf"
}

func (p *Plugin) Generate(palette *colour.CategorisedPalette) ([]byte, error) {
    if palette == nil {
        return nil, fmt.Errorf("palette cannot be nil")
    }

    // Load template
    tmplContent, err := templates.ReadFile("config.tmpl")
    if err != nil {
        return nil, fmt.Errorf("failed to read template: %w", err)
    }

    // Parse template
    tmpl, err := template.New("config").Parse(string(tmplContent))
    if err != nil {
        return nil, fmt.Errorf("failed to parse template: %w", err)
    }

    // Prepare data
    data := prepareData(palette)

    // Execute template
    var buf bytes.Buffer
    if err := tmpl.Execute(&buf, data); err != nil {
        return nil, fmt.Errorf("failed to execute template: %w", err)
    }

    return buf.Bytes(), nil
}

// TemplateData holds data for the template
type TemplateData struct {
    Background colour.CategorisedColour
    Foreground colour.CategorisedColour
    Danger     *colour.CategorisedColour
    Warning    *colour.CategorisedColour
    Success    *colour.CategorisedColour
}

func prepareData(palette *colour.CategorisedPalette) TemplateData {
    data := TemplateData{}

    if bg, ok := palette.Get(colour.RoleBackground); ok {
        data.Background = bg
    }
    if fg, ok := palette.Get(colour.RoleForeground); ok {
        data.Foreground = fg
    }
    if danger, ok := palette.Get(colour.RoleDanger); ok {
        data.Danger = &danger
    }
    if warning, ok := palette.Get(colour.RoleWarning); ok {
        data.Warning = &warning
    }
    if success, ok := palette.Get(colour.RoleSuccess); ok {
        data.Success = &success
    }

    return data
}
Step 4: Create Tests

internal/plugin/output/myplugin/myplugin_test.go:

package myplugin

import (
    "image/colour"
    "strings"
    "testing"

    "github.com/jmylchreest/tinct/internal/colour"
)

func TestPlugin_Generate(t *testing.T) {
    colours := []colour.Colour{
        colour.RGBA{R: 20, G: 20, B: 30, A: 255},
        colour.RGBA{R: 230, G: 230, B: 240, A: 255},
        colour.RGBA{R: 229, G: 76, B: 76, A: 255},
    }

    palette := &colour.Palette{Colours: colours}
    config := colour.DefaultCategorisationConfig()
    categorised := colour.Categorise(palette, config)

    plugin := New()
    output, err := plugin.Generate(categorised)
    if err != nil {
        t.Fatalf("Generate() error = %v", err)
    }

    outputStr := string(output)

    // Verify output contains expected keys
    if !strings.Contains(outputStr, "background =") {
        t.Error("Output missing background")
    }
    if !strings.Contains(outputStr, "foreground =") {
        t.Error("Output missing foreground")
    }
}
Step 5: Run Tests
go test ./internal/plugin/output/myplugin/...

Template Functions

Built-in Functions

Go templates provide several built-in functions:

  • {{ .Field }} - Access field
  • {{ if .Field }}...{{ end }} - Conditional
  • {{ range .List }}...{{ end }} - Loop
  • {{ printf "%s" .Value }} - Format string
  • {{- }} - Trim whitespace
Custom Functions

Add custom functions with template.FuncMap:

func templateFuncs() template.FuncMap {
    return template.FuncMap{
        "toRGB": func(cc colour.CategorisedColour) string {
            return fmt.Sprintf("rgb(%d, %d, %d)", 
                cc.RGB.R, cc.RGB.G, cc.RGB.B)
        },
        "toHSL": func(cc colour.CategorisedColour) string {
            return fmt.Sprintf("%.1f %.1f%% %.1f%%",
                cc.Hue, cc.Saturation*100, cc.Luminance*100)
        },
        "opacity": func(hex string, alpha float64) string {
            return fmt.Sprintf("%s%02x", hex, int(alpha*255))
        },
    }
}

// Use in template:
tmpl, err := template.New("config").Funcs(templateFuncs()).Parse(tmplContent)
Available Colour Data

In templates, you have access to CategorisedColour struct:

type CategorisedColour struct {
    Role       ColourRole  // "background", "danger", etc.
    Hex        string      // "#e54c4c"
    RGB        RGB         // {R: 229, G: 76, B: 76}
    Luminance  float64     // 0.22 (WCAG luminance)
    IsLight    bool        // false
    Hue        float64     // 0.0 (0-360)
    Saturation float64     // 0.75 (0-1)
}

Template Examples

Simple Key-Value Config
background = "{{ .Background.Hex }}"
foreground = "{{ .Foreground.Hex }}"
accent = "{{ .Primary.Hex }}"
JSON Output
{
  "colours": {
    "background": "{{ .Background.Hex }}",
    "foreground": "{{ .Foreground.Hex }}",
{{- if .Danger }}
    "danger": "{{ .Danger.Hex }}",
{{- end }}
{{- if .Warning }}
    "warning": "{{ .Warning.Hex }}",
{{- end }}
    "accents": [
{{- range $i, $accent := .Accents }}
      "{{ $accent.Hex }}"{{ if ne $i $.AccentsLast }},{{ end }}
{{- end }}
    ]
  }
}
TOML Config
[colours]
background = "{{ .Background.Hex }}"
foreground = "{{ .Foreground.Hex }}"

[colours.semantic]
danger = "{{ .Danger.Hex }}"
warning = "{{ .Warning.Hex }}"
success = "{{ .Success.Hex }}"
info = "{{ .Info.Hex }}"

[colours.accents]
{{- range $i, $accent := .Accents }}
accent{{ add $i 1 }} = "{{ $accent.Hex }}"
{{- end }}
CSS Variables
:root {
  --colour-background: {{ .Background.RGB.R }}, {{ .Background.RGB.G }}, {{ .Background.RGB.B }};
  --colour-foreground: {{ .Foreground.RGB.R }}, {{ .Foreground.RGB.G }}, {{ .Foreground.RGB.B }};
  --colour-danger: {{ .Danger.RGB.R }}, {{ .Danger.RGB.G }}, {{ .Danger.RGB.B }};
}
Shell Script
#!/bin/bash
# Generated by Tinct

export COLOR_BACKGROUND="{{ .Background.Hex }}"
export COLOR_FOREGROUND="{{ .Foreground.Hex }}"
export COLOR_DANGER="{{ .Danger.Hex }}"
export COLOR_WARNING="{{ .Warning.Hex }}"
export COLOR_SUCCESS="{{ .Success.Hex }}"

Best Practices

1. Handle Missing Colours Gracefully
{{- if .Danger }}
danger = "{{ .Danger.Hex }}"
{{- else }}
danger = "#ff0000"  # fallback
{{- end }}
2. Use Semantic Colour Roles

Prefer semantic roles over generic colours:

  • {{ .Danger.Hex }} - Clear purpose
  • {{ .Red.Hex }} - Ambiguous
3. Provide Sensible Defaults

Always set default values in prepareData():

func prepareData(palette *colour.CategorisedPalette) TemplateData {
    data := TemplateData{
        Background: getOrDefault(palette, colour.RoleBackground, "#000000"),
        Foreground: getOrDefault(palette, colour.RoleForeground, "#ffffff"),
    }
    return data
}
4. Add Comments to Generated Files
# Generated by Tinct {{ .Version }}
# Source: {{ .SourceImage }}
# Theme: {{ .ThemeType }}
# Generated at: {{ .Timestamp }}
5. Test Template Output

Always verify generated output is valid:

func TestValidOutput(t *testing.T) {
    output, _ := plugin.Generate(palette)
    
    // Parse as TOML/JSON/YAML to verify syntax
    var config Config
    if err := toml.Unmarshal(output, &config); err != nil {
        t.Errorf("Invalid TOML output: %v", err)
    }
}

Plugin Interface

All output plugins must implement:

type Plugin interface {
    Name() string           // Plugin identifier
    Description() string    // Human-readable description
    FileExtension() string  // File extension (without dot)
    DefaultPath() string    // Default output path
    Generate(*colour.CategorisedPalette) ([]byte, error)
}

Registry

Register plugins with the global registry:

import "github.com/jmylchreest/tinct/internal/plugin/output"

func init() {
    registry := output.NewRegistry()
    registry.Register(myplugin.New())
}

Future Enhancements

  • Load templates from user config directory
  • Template hot-reloading for development
  • Template validation before generation
  • Multiple format support per plugin
  • Template inheritance/composition
  • Sprig template functions integration

Examples

See existing plugins for reference:

  • waybar/ - Two-file pattern with GTK @define-colour format
  • hyprland/ - Two-file pattern with Hyprland variables
  • kitty/ - Single-file pattern (no variable support)
  • More coming soon!

Contributing

When adding a new plugin:

  1. Read the standard: Review PLUGIN-STANDARD.md thoroughly
  2. Create directory: internal/plugin/output/yourplugin/
  3. Add template file(s): tinct-colours.{ext}.tmpl and/or tinct.{ext}.tmpl
  4. Implement plugin: yourplugin.go (implement all required interfaces)
  5. Add tests: yourplugin_test.go (>80% coverage)
  6. Use checklist: Complete the checklist in PLUGIN-STANDARD.md
  7. Update README: Add your plugin to examples section
  8. Submit PR: Include example output and test results

** All plugins must comply with the standard or PRs will be rejected.


Made with Go 1.25+

Documentation

Overview

Package output provides the interface and base types for output plugins.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type DualThemePlugin added in v0.1.1

type DualThemePlugin interface {
	// GenerateDualTheme creates output files for both light and dark themes.
	// primaryTheme is the main theme (based on input source).
	// alternateTheme is the opposite variant (light if primary is dark, vice versa).
	// Returns map of filename -> content for all generated files.
	GenerateDualTheme(primaryTheme, alternateTheme *colour.ThemeData) (map[string][]byte, error)
}

DualThemePlugin is an optional interface that plugins can implement to support generating both light and dark theme variants in a single execution.

This is useful for desktop environments and applications that automatically switch themes based on system preference (e.g., KDE Plasma, Qt applications).

When this interface is implemented:

  • GenerateDualTheme() is called instead of Generate()
  • The plugin receives both the primary theme and its alternate variant
  • The plugin can generate configuration files for both themes at once

Example: KDE Plasma generates both TinctDark.colors and TinctLight.colors, allowing seamless switching via system settings.

type ExecutionContext

type ExecutionContext struct {
	DryRun        bool   // Whether this is a dry-run
	Verbose       bool   // Whether verbose output is enabled
	OutputDir     string // The output directory being used
	WallpaperPath string // Optional path to source wallpaper (from input plugin)
}

ExecutionContext provides context for hook execution.

type FlagHelp added in v0.1.10

type FlagHelp = plugin.FlagHelp

FlagHelp is re-exported from pkg/plugin for convenience. This allows output plugins to use output.FlagHelp instead of importing protocol directly.

type Plugin

type Plugin interface {
	// Name returns the plugin's name (e.g., "tailwind", "hyprland").
	Name() string

	// Description returns a human-readable description of the plugin.
	Description() string

	// Version returns the plugin version (e.g., "1.0.0").
	Version() string

	// Generate creates output file(s) from the given theme data.
	// Returns map of filename -> content to support plugins that generate multiple files.
	Generate(themeData *colour.ThemeData) (map[string][]byte, error)

	// RegisterFlags registers plugin-specific flags with cobra command.
	RegisterFlags(cmd *cobra.Command)

	// Validate checks if the plugin configuration is valid.
	Validate() error

	// DefaultOutputDir returns the default output directory for this plugin.
	DefaultOutputDir() string

	// GetFlagHelp returns help information for plugin-specific flags.
	// This allows dynamic help generation based on selected plugins.
	GetFlagHelp() []FlagHelp
}

Plugin represents an output plugin that can generate configuration files. from a categorised colour palette.

type PostExecuteHook

type PostExecuteHook interface {
	// PostExecute runs after successful Generate() and file writing.
	// The execCtx contains execution context including wallpaper path if available.
	// The writtenFiles contains the paths that were written.
	// Errors are logged but don't fail the overall operation.
	PostExecute(ctx context.Context, execCtx ExecutionContext, writtenFiles []string) error
}

PostExecuteHook is an optional interface that plugins can implement to perform. actions after successful file generation.

Common use cases:.

  • Reload application configuration
  • Send signals to running processes
  • Restart services
  • Notify users of changes
  • Set wallpaper (when --set-wallpaper flag is used)

type PreExecuteHook

type PreExecuteHook interface {
	// PreExecute runs before Generate(). Returns:.
	//   - skip: if true, plugin is skipped (not an error, just bypassed)
	//   - reason: human-readable explanation for skipping
	//   - error: actual error that should stop execution
	PreExecute(ctx context.Context) (skip bool, reason string, err error)
}

PreExecuteHook is an optional interface that plugins can implement to perform. checks before generation. If the hook returns an error or skip=true, the plugin will be skipped without generating output.

Common use cases:.

  • Check if required executables exist on $PATH
  • Verify configuration directories exist
  • Validate environment prerequisites

type Registry

type Registry struct {
	// contains filtered or unexported fields
}

Registry holds all registered output plugins.

func NewRegistry

func NewRegistry() *Registry

NewRegistry creates a new plugin registry.

func (*Registry) All

func (r *Registry) All() map[string]Plugin

All returns all registered plugins (including disabled ones).

func (*Registry) Get

func (r *Registry) Get(name string) (Plugin, bool)

Get retrieves a plugin by name.

func (*Registry) List

func (r *Registry) List() []string

List returns all registered plugin names.

func (*Registry) Register

func (r *Registry) Register(p Plugin)

Register adds a plugin to the registry.

type TemplateProvider

type TemplateProvider interface {
	// GetEmbeddedFS returns the embedded filesystem containing template files.
	// This should return the same embed.FS used by the plugin's generateTheme method.
	GetEmbeddedFS() any
}

TemplateProvider is an optional interface that plugins can implement to expose. their embedded template filesystem for template management commands.

type VerbosePlugin

type VerbosePlugin interface {
	// SetVerbose enables or disables verbose logging for the plugin.
	SetVerbose(verbose bool)
}

VerbosePlugin is an optional interface that plugins can implement to receive. verbose logging settings from the CLI.

type VersionedPlugin added in v0.1.20

type VersionedPlugin interface {
	// GetTargetVersion returns the version of the target application.
	// Returns an empty string if the version cannot be determined.
	// The version string should be in semantic version format (e.g., "0.53.0").
	GetTargetVersion() string
}

VersionedPlugin is an optional interface that plugins can implement to support version-specific templates. When implemented, the template loader will select the most appropriate template based on the target application's version.

This is useful for applications that change their configuration format between versions (e.g., Hyprland's windowrule syntax changed in v0.53).

When this interface is implemented:

  • GetTargetVersion() is called to get the installed application version
  • Templates can be organized in version subdirectories (e.g., templates/0.53/)
  • The loader selects the highest version that doesn't exceed the target version
  • Falls back to default templates if no suitable versioned template is found

Example directory structure:

internal/plugin/output/hyprland/
├── tinct.conf.tmpl           # Default template (for older versions)
└── templates/
    └── 0.53/
        └── tinct.conf.tmpl   # Template for v0.53+

Directories

Path Synopsis
Package alacritty provides an output plugin for Alacritty terminal colour themes.
Package alacritty provides an output plugin for Alacritty terminal colour themes.
Package awww provides an output plugin for awww (An Answer to your Wayland Wallpaper Woes) wallpaper daemon.
Package awww provides an output plugin for awww (An Answer to your Wayland Wallpaper Woes) wallpaper daemon.
Package dunst provides an output plugin for Dunst notification daemon colour themes.
Package dunst provides an output plugin for Dunst notification daemon colour themes.
Package fuzzel provides an output plugin for Fuzzel application launcher colour themes.
Package fuzzel provides an output plugin for Fuzzel application launcher colour themes.
Package ghostty provides an output plugin for Ghostty terminal emulator colour themes.
Package ghostty provides an output plugin for Ghostty terminal emulator colour themes.
Package gnomeshell provides an output plugin for GNOME Shell theming.
Package gnomeshell provides an output plugin for GNOME Shell theming.
Package gtk3 provides an output plugin for GTK3 application theming.
Package gtk3 provides an output plugin for GTK3 application theming.
Package gtk4 provides an output plugin for GTK4 application theming.
Package gtk4 provides an output plugin for GTK4 application theming.
Package histui provides an output plugin for histui notification daemon CSS themes.
Package histui provides an output plugin for histui notification daemon CSS themes.
Package hyprland provides an output plugin for Hyprland window manager colour themes.
Package hyprland provides an output plugin for Hyprland window manager colour themes.
Package hyprlock provides an output plugin for Hyprlock screen lock colour themes.
Package hyprlock provides an output plugin for Hyprlock screen lock colour themes.
Package hyprpaper provides an output plugin for Hyprpaper wallpaper manager configuration.
Package hyprpaper provides an output plugin for Hyprpaper wallpaper manager configuration.
Package kdeplasma provides an output plugin for KDE Plasma desktop environment theming.
Package kdeplasma provides an output plugin for KDE Plasma desktop environment theming.
Package kitty provides an output plugin for Kitty terminal colour themes.
Package kitty provides an output plugin for Kitty terminal colour themes.
Package konsole provides an output plugin for Konsole terminal color themes.
Package konsole provides an output plugin for Konsole terminal color themes.
Package libadwaita provides an output plugin for libadwaita/GNOME application theming.
Package libadwaita provides an output plugin for libadwaita/GNOME application theming.
Package markdown provides an output plugin for exporting themes to markdown format.
Package markdown provides an output plugin for exporting themes to markdown format.
Package mc provides an output plugin for Midnight Commander (mc) skin themes.
Package mc provides an output plugin for Midnight Commander (mc) skin themes.
Package neovim provides an output plugin for Neovim colour themes.
Package neovim provides an output plugin for Neovim colour themes.
Package ptyxis provides an output plugin for Ptyxis terminal color palettes.
Package ptyxis provides an output plugin for Ptyxis terminal color palettes.
Package qt5 provides an output plugin for Qt5 application theming via qt5ct.
Package qt5 provides an output plugin for Qt5 application theming via qt5ct.
Package qt6 provides an output plugin for Qt6 application theming via qt6ct.
Package qt6 provides an output plugin for Qt6 application theming via qt6ct.
Package rosec provides an output plugin for rosec-prompt theme configuration.
Package rosec provides an output plugin for rosec-prompt theme configuration.
shared
dbus_kde
Package dbus_kde provides D-Bus helpers for KDE Plasma configuration reload.
Package dbus_kde provides D-Bus helpers for KDE Plasma configuration reload.
testing
Package testing provides shared test utilities for output plugins.
Package testing provides shared test utilities for output plugins.
utils
Package common provides shared utilities for output plugins.
Package common provides shared utilities for output plugins.
Package swayosd provides an output plugin for SwayOSD on-screen display colour themes.
Package swayosd provides an output plugin for SwayOSD on-screen display colour themes.
Package template provides utilities for loading plugin templates with custom override support.
Package template provides utilities for loading plugin templates with custom override support.
Package walker provides an output plugin for Walker application launcher colour themes.
Package walker provides an output plugin for Walker application launcher colour themes.
Package waybar provides an output plugin for Waybar status bar colour themes.
Package waybar provides an output plugin for Waybar status bar colour themes.
Package wbg provides an output plugin for wbg, a simple Wayland wallpaper application.
Package wbg provides an output plugin for wbg, a simple Wayland wallpaper application.
Package wofi provides an output plugin for Wofi application launcher colour themes.
Package wofi provides an output plugin for Wofi application launcher colour themes.
Package zellij provides an output plugin for Zellij terminal multiplexer colour themes.
Package zellij provides an output plugin for Zellij terminal multiplexer colour themes.

Jump to

Keyboard shortcuts

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