Plugin Examples
This document provides practical examples for developing plugins for NoIdea. These examples demonstrate how to implement common plugin types and use the plugin interfaces effectively.
Basic Plugin Structure
Every NoIdea plugin follows this basic structure:
package main
import (
"github.com/AccursedGalaxy/noidea/internal/plugin"
)
// MyPlugin is a basic NoIdea plugin
type MyPlugin struct {
ctx plugin.PluginContext
logger plugin.Logger
config plugin.PluginConfig
}
// Info returns plugin metadata
func (p *MyPlugin) Info() plugin.PluginInfo {
return plugin.PluginInfo{
Name: "my-plugin",
Version: "1.0.0",
Description: "My first NoIdea plugin",
Author: "Your Name",
Website: "https://example.com/my-plugin",
MinNoideaVersion: "v0.4.0",
}
}
// Initialize sets up the plugin
func (p *MyPlugin) Initialize(ctx plugin.PluginContext) error {
p.ctx = ctx
p.logger = ctx.Logger()
p.config = ctx.Config()
// Register hooks
hooks := plugin.Hooks{
// Add your hooks here
}
return ctx.RegisterHooks(hooks)
}
// Shutdown performs cleanup
func (p *MyPlugin) Shutdown() error {
p.logger.Info("Plugin shutting down")
return nil
}
// Plugin entry point
func CreatePlugin() plugin.Plugin {
return &MyPlugin{}
}
Command Hook Example
This example demonstrates how to add a new command to NoIdea:
package main
import (
"fmt"
"github.com/spf13/cobra"
"github.com/AccursedGalaxy/noidea/internal/plugin"
)
// CommandPlugin adds custom commands
type CommandPlugin struct {
ctx plugin.PluginContext
}
// Info returns plugin metadata
func (p *CommandPlugin) Info() plugin.PluginInfo {
return plugin.PluginInfo{
Name: "custom-command",
Version: "1.0.0",
Description: "Adds custom commands to NoIdea",
Author: "Your Name",
MinNoideaVersion: "v0.4.0",
}
}
// Initialize sets up the plugin
func (p *CommandPlugin) Initialize(ctx plugin.PluginContext) error {
p.ctx = ctx
// Register command hooks
hooks := plugin.Hooks{
Command: &CommandHooks{},
}
return ctx.RegisterHooks(hooks)
}
// Shutdown performs cleanup
func (p *CommandPlugin) Shutdown() error {
return nil
}
// CommandHooks implements the CommandHooks interface
type CommandHooks struct{}
// AddCommands returns custom commands
func (h *CommandHooks) AddCommands() []plugin.Command {
statsCmd := &cobra.Command{
Use: "custom-stats",
Short: "Display custom Git statistics",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Custom Git statistics:")
fmt.Println("- Lines added: 1,024")
fmt.Println("- Lines removed: 512")
fmt.Println("- Commits: 48")
},
}
return []plugin.Command{statsCmd}
}
// ExtendCommand modifies existing commands
func (h *CommandHooks) ExtendCommand(name string, extender plugin.CommandExtender) error {
return nil // Not implementing command extension in this example
}
// Plugin entry point
func CreatePlugin() plugin.Plugin {
return &CommandPlugin{}
}
Feedback Hook Example
This example shows how to modify the Moai feedback system:
package main
import (
"fmt"
"strings"
"github.com/AccursedGalaxy/noidea/internal/plugin"
)
// FeedbackPlugin customizes the feedback system
type FeedbackPlugin struct {
ctx plugin.PluginContext
}
// Info returns plugin metadata
func (p *FeedbackPlugin) Info() plugin.PluginInfo {
return plugin.PluginInfo{
Name: "pirate-feedback",
Version: "1.0.0",
Description: "Adds pirate-themed feedback",
Author: "Your Name",
MinNoideaVersion: "v0.4.0",
}
}
// Initialize sets up the plugin
func (p *FeedbackPlugin) Initialize(ctx plugin.PluginContext) error {
p.ctx = ctx
// Register feedback hooks
hooks := plugin.Hooks{
Feedback: &PirateFeedbackHooks{},
}
return ctx.RegisterHooks(hooks)
}
// Shutdown performs cleanup
func (p *FeedbackPlugin) Shutdown() error {
return nil
}
// PirateFeedbackHooks implements the FeedbackHooks interface
type PirateFeedbackHooks struct{}
// ProcessFeedback modifies feedback messages
func (h *PirateFeedbackHooks) ProcessFeedback(feedback string, commit string) (string, error) {
// Convert feedback to pirate speak
pirateFeedback := feedback
// Replace common words with pirate equivalents
replacements := map[string]string{
"Hello": "Ahoy",
"yes": "aye",
"you": "ye",
"your": "yer",
"is": "be",
"are": "be",
"the": "th'",
"great": "mighty fine",
"good": "shipshape",
"bad": "scurvy",
}
for word, replacement := range replacements {
pirateFeedback = strings.ReplaceAll(pirateFeedback, word, replacement)
}
// Add pirate suffix
pirateFeedback += " Arrr! 🏴☠️"
return pirateFeedback, nil
}
// AddFeedbackType registers pirate feedback templates
func (h *PirateFeedbackHooks) AddFeedbackType(name string, templates []string) error {
if name == "pirate" {
pirateTemplates := []string{
"Shiver me timbers! What kind of code be this?",
"Blimey! This commit be worthy of me treasure chest!",
"Ye code like a landlubber! Walk the plank!",
"This commit be a fine addition to the ship's log!",
"Avast! This code be needin' more rum!",
}
templates = pirateTemplates
return nil
}
return fmt.Errorf("unknown feedback type: %s", name)
}
// Plugin entry point
func CreatePlugin() plugin.Plugin {
return &FeedbackPlugin{}
}
Data Hook Example
This example demonstrates how to access and modify Git analytics:
package main
import (
"time"
"github.com/AccursedGalaxy/noidea/internal/plugin"
)
// AnalyticsPlugin adds custom analytics
type AnalyticsPlugin struct {
ctx plugin.PluginContext
}
// Info returns plugin metadata
func (p *AnalyticsPlugin) Info() plugin.PluginInfo {
return plugin.PluginInfo{
Name: "time-analytics",
Version: "1.0.0",
Description: "Adds time-based commit analytics",
Author: "Your Name",
MinNoideaVersion: "v0.4.0",
}
}
// Initialize sets up the plugin
func (p *AnalyticsPlugin) Initialize(ctx plugin.PluginContext) error {
p.ctx = ctx
// Register data hooks
hooks := plugin.Hooks{
Data: &TimeAnalyticsHooks{},
}
return ctx.RegisterHooks(hooks)
}
// Shutdown performs cleanup
func (p *AnalyticsPlugin) Shutdown() error {
return nil
}
// TimeAnalyticsHooks implements the DataHooks interface
type TimeAnalyticsHooks struct{
morningCommits int
afternoonCommits int
eveningCommits int
nightCommits int
weekdayCommits int
weekendCommits int
}
// OnCollectStats analyzes commit times
func (h *TimeAnalyticsHooks) OnCollectStats(stats map[string]interface{}) error {
// Extract timestamps from commits
if commits, ok := stats["commits"].([]interface{}); ok {
for _, c := range commits {
if commit, ok := c.(map[string]interface{}); ok {
if timestamp, ok := commit["timestamp"].(time.Time); ok {
// Analyze commit time
hour := timestamp.Hour()
weekday := timestamp.Weekday()
// Time of day analysis
switch {
case hour >= 5 && hour < 12:
h.morningCommits++
case hour >= 12 && hour < 17:
h.afternoonCommits++
case hour >= 17 && hour < 22:
h.eveningCommits++
default:
h.nightCommits++
}
// Day of week analysis
if weekday == time.Saturday || weekday == time.Sunday {
h.weekendCommits++
} else {
h.weekdayCommits++
}
}
}
}
}
return nil
}
// ProvideAnalytics returns time-based analytics
func (h *TimeAnalyticsHooks) ProvideAnalytics() (map[string]interface{}, error) {
return map[string]interface{}{
"time_analytics": map[string]interface{}{
"morning_commits": h.morningCommits,
"afternoon_commits": h.afternoonCommits,
"evening_commits": h.eveningCommits,
"night_commits": h.nightCommits,
"weekday_commits": h.weekdayCommits,
"weekend_commits": h.weekendCommits,
},
}, nil
}
// Plugin entry point
func CreatePlugin() plugin.Plugin {
return &AnalyticsPlugin{}
}
UI Hook Example
This example shows how to customize NoIdea's UI:
package main
import (
"strings"
"github.com/fatih/color"
"github.com/AccursedGalaxy/noidea/internal/plugin"
)
// CustomUIPlugin enhances NoIdea's UI
type CustomUIPlugin struct {
ctx plugin.PluginContext
}
// Info returns plugin metadata
func (p *CustomUIPlugin) Info() plugin.PluginInfo {
return plugin.PluginInfo{
Name: "rainbow-ui",
Version: "1.0.0",
Description: "Adds colorful UI elements",
Author: "Your Name",
MinNoideaVersion: "v0.4.0",
}
}
// Initialize sets up the plugin
func (p *CustomUIPlugin) Initialize(ctx plugin.PluginContext) error {
p.ctx = ctx
// Register UI hooks
hooks := plugin.Hooks{
UI: &RainbowUIHooks{},
}
return ctx.RegisterHooks(hooks)
}
// Shutdown performs cleanup
func (p *CustomUIPlugin) Shutdown() error {
return nil
}
// RainbowUIHooks implements the UIHooks interface
type RainbowUIHooks struct{}
// BeforeOutput modifies CLI output
func (h *RainbowUIHooks) BeforeOutput(output string) (string, error) {
// Add rainbow dividers to the output
if strings.Contains(output, "-----") {
divider := color.New(color.FgRed).Sprint("❤️ ") +
color.New(color.FgYellow).Sprint("💛 ") +
color.New(color.FgGreen).Sprint("💚 ") +
color.New(color.FgBlue).Sprint("💙 ") +
color.New(color.FgMagenta).Sprint("💜 ")
// Repeat the pattern to match divider length
fullDivider := strings.Repeat(divider, 3)
// Replace all dividers with rainbow dividers
output = strings.ReplaceAll(output, "------------------------------------------------------", fullDivider)
}
return output, nil
}
// AfterOutput runs after CLI output is displayed
func (h *RainbowUIHooks) AfterOutput(output string) error {
// No post-processing needed
return nil
}
// CustomUI creates a custom UI element
func (h *RainbowUIHooks) CustomUI(ctx plugin.UIContext) error {
// Not implementing custom UI elements in this example
return nil
}
// Plugin entry point
func CreatePlugin() plugin.Plugin {
return &CustomUIPlugin{}
}
Commit Hook Example
This example shows how to integrate with the Git commit process:
package main
import (
"regexp"
"strings"
"github.com/AccursedGalaxy/noidea/internal/plugin"
)
// CommitRulesPlugin enforces commit message rules
type CommitRulesPlugin struct {
ctx plugin.PluginContext
}
// Info returns plugin metadata
func (p *CommitRulesPlugin) Info() plugin.PluginInfo {
return plugin.PluginInfo{
Name: "commit-rules",
Version: "1.0.0",
Description: "Enforces commit message rules",
Author: "Your Name",
MinNoideaVersion: "v0.4.0",
}
}
// Initialize sets up the plugin
func (p *CommitRulesPlugin) Initialize(ctx plugin.PluginContext) error {
p.ctx = ctx
// Register commit hooks
hooks := plugin.Hooks{
Commit: &CommitRulesHooks{logger: ctx.Logger()},
}
return ctx.RegisterHooks(hooks)
}
// Shutdown performs cleanup
func (p *CommitRulesPlugin) Shutdown() error {
return nil
}
// CommitRulesHooks implements the CommitHooks interface
type CommitRulesHooks struct{
logger plugin.Logger
}
// BeforeCommit validates commit messages
func (h *CommitRulesHooks) BeforeCommit(ctx plugin.CommitContext) error {
// Check if commit message follows conventional format
message := ctx.Message
// Conventional commit regex pattern
pattern := `^(feat|fix|docs|style|refactor|test|chore)(\([a-z0-9-]+\))?: .+`
match, err := regexp.MatchString(pattern, message)
if err != nil {
return err
}
if !match {
h.logger.Warn("Commit message does not follow conventional format")
// We don't block the commit, just warn
}
return nil
}
// AfterCommit runs after a commit is created
func (h *CommitRulesHooks) AfterCommit(ctx plugin.CommitContext) error {
// No post-commit processing needed
return nil
}
// ModifySuggestion enhances the commit message
func (h *CommitRulesHooks) ModifySuggestion(message string, diff string) (string, error) {
// Add issue reference if found in branch name
// Example: If branch is "feature/PROJ-123-new-feature", add "Refs: PROJ-123"
// Get current branch name
// Note: In a real plugin, you'd get this from git
branchName := "feature/PROJ-123-new-feature" // Example value
// Extract issue ID with regex
issuePattern := `(([A-Z]+)-\d+)`
re := regexp.MustCompile(issuePattern)
matches := re.FindStringSubmatch(branchName)
if len(matches) > 1 {
issueID := matches[1]
// Check if issue ID is already in message
if !strings.Contains(message, issueID) {
// Add reference to the end of the first line
lines := strings.SplitN(message, "\n", 2)
if len(lines) == 1 {
message = lines[0] + " (Refs: " + issueID + ")"
} else {
message = lines[0] + " (Refs: " + issueID + ")\n" + lines[1]
}
}
}
return message, nil
}
// Plugin entry point
func CreatePlugin() plugin.Plugin {
return &CommitRulesPlugin{}
}
Best Practices and Tips
- Error Handling: Always check errors and provide meaningful error messages
- Configuration: Make your plugin configurable through the Config interface
- Logging: Use the provided Logger rather than fmt.Println
- Testing: Write tests for your plugin functionality
- Documentation: Add usage instructions and examples in your plugin's README
- Dependencies: Minimize external dependencies to avoid conflicts
- Performance: Be mindful of performance impact, especially for commit hooks
- Versioning: Use semantic versioning for your plugin