# Walkthrough: Parsley Integration with Cobra

This guide demonstrates how to integrate Parsley with Cobra to build a modular and maintainable CLI application. It leverages the cobra-extension package to implement command handlers based on typed command services, making it a natural fit for Parsley’s dependency injection capabilities.

# Project Structure

The code for this example can be found at examples/integrations/cobra it has the following structure:

cobra
├── cmd
│   └── main.go
├── go.mod
├── go.sum
└── internal
    ├── commands
    │   └── hello_command.go
    ├── modules
    │   ├── cobra_module.go
    │   └── services_module.go
    └── services
        └── greeter.go

# Main Application

The cmd/main.go file serves as the application's entry point. Here, the service dependencies are registered using a ServiceRegistry instance. Dependency registration is organized in ModuleFunc methods, keeping the main setup clean. Finally, a *charmer.CommandLineApplication service is resolved, and its Execute method is called to run the application.

package main

import (
	"context"
	"github.com/matzefriedrich/cobra-extensions/pkg/charmer"
	"github.com/matzefriedrich/parsley-docs/examples/integrations/cobra/internal/modules"
	"github.com/matzefriedrich/parsley/pkg/registration"
	"github.com/matzefriedrich/parsley/pkg/resolving"
)

func main() {

	registry := registration.NewServiceRegistry()

	_ = registry.RegisterModule(modules.CobraApplicationModule("cobra-app", "A Parsley-enabled Cobra app"))
	_ = registry.RegisterModule(modules.CobraApplicationCommandsModule)
	_ = registry.RegisterModule(modules.ServicesModule)

	resolver := resolving.NewResolver(registry)
	app, _ := resolving.ResolveRequiredService[*charmer.CommandLineApplication](resolver, resolving.NewScopedContext(context.Background()))
	err := app.Execute()
	if err != nil {
		panic(err)
	}
}

# Modules

The internal/modules package defines the service configurations required for the CLI setup.

The cobra_module.go module configures the Cobra application and registers it as a singleton service. It also enables the resolution of all *cobra.Command types as a list, which is necessary to populate the Cobra application with registered commands.

package modules

import (
	"context"
	"github.com/matzefriedrich/cobra-extensions/pkg/charmer"
	"github.com/matzefriedrich/parsley-docs/examples/integrations/cobra/internal/commands"
	"github.com/matzefriedrich/parsley/pkg/features"
	"github.com/matzefriedrich/parsley/pkg/registration"
	"github.com/matzefriedrich/parsley/pkg/resolving"
	"github.com/matzefriedrich/parsley/pkg/types"
	"github.com/spf13/cobra"
)

// CobraApplicationModule registers a Cobra-based command-line application with the given name and description in the service registry.
func CobraApplicationModule(appName string, appDescription string) func(registry types.ServiceRegistry) error {

	return func(registry types.ServiceRegistry) error {

		// Enable resolution of *cobra.Command objects at once (as a list)
		_ = features.RegisterList[*cobra.Command](registry)

		// Register a factory function for *CommandLineApplication
		_ = registration.RegisterSingleton(registry, func(resolver types.Resolver) *charmer.CommandLineApplication {

			application := charmer.NewCommandLineApplication(appName, appDescription)

			// Resolve all command handlers and add them to the app instance
			typeCommandServices, _ := resolving.ResolveRequiredServices[*cobra.Command](resolver, context.Background())
			for _, command := range typeCommandServices {
				application.AddCommand(command)
			}

			return application
		})

		return nil
	}
}

// CobraApplicationCommandsModule registers typed command handler services in the given service registry.
func CobraApplicationCommandsModule(registry types.ServiceRegistry) error {
	_ = registration.RegisterTransient(registry, commands.NewHelloCommand)
	return nil
}

The services_module.go registers application-specific services, such as the Greeter service.

package modules

import (
	"github.com/matzefriedrich/parsley-docs/examples/integrations/cobra/internal/services"
	"github.com/matzefriedrich/parsley/pkg/registration"
	"github.com/matzefriedrich/parsley/pkg/types"
)

// ServicesModule registers a transient service for creating new instances of Greeter in the provided service registry.
func ServicesModule(registry types.ServiceRegistry) error {
	_ = registration.RegisterTransient(registry, services.NewGreeter)
	return nil
}

# Services

The Greeter service in internal/services/greeter.go generates a greeting message based on the provided name and politeness level. This service is used by command handlers to produce responses.

package services

import "fmt"

type Greeter interface {
	SayHello(name string, polite bool) string
}

type greeter struct {
}

func (g *greeter) SayHello(name string, polite bool) string {
	if polite {
		return fmt.Sprintf("Good day, %s!\n", name)
	} else {
		return fmt.Sprintf("Hi, %s\n", name)
	}
}

func NewGreeter() Greeter {
	return &greeter{}
}

# Command Handlers

Command handlers in this example are services that implement the TypedCommand interface, providing structured logic and dependency injection. For instance, the helloCommand service uses Greeter to output personalized greetings. With cobra-extensions and Parsley, command declaration, service resolution, and command logic are consolidated in the same module, making the codebase easier to navigate and maintain.

package commands

import (
	"fmt"
	"github.com/matzefriedrich/cobra-extensions/pkg/commands"
	"github.com/matzefriedrich/cobra-extensions/pkg/types"
	"github.com/matzefriedrich/parsley-docs/examples/integrations/cobra/internal/services"
	"github.com/spf13/cobra"
)

type helloCommand struct {
	use     types.CommandName `flag:"hello" short:"Prints a greeting to the specified name."`
	Name    string            `flag:"name" usage:"The name to greet." required:"true"`
	Polite  bool              `flag:"polite"`
	greeter services.Greeter
}

func (h *helloCommand) Execute() {
	message := h.greeter.SayHello(h.Name, h.Polite)
	fmt.Println(message)
}

var _ types.TypedCommand = (*helloCommand)(nil)

func NewHelloCommand(greeter services.Greeter) *cobra.Command {
	command := &helloCommand{
		greeter: greeter,
	}
	return commands.CreateTypedCommand(command)
}

# Running the Application

As configured in the code, the application will start a Cobra application and call it´s Execute method. To run the application, navigate to the root directory and execute the following command:

go run main.go hello --name John --polite

This should return:

Good day, John!

For more details on Parsley, check out the Parsley GitHub repository.