237 lines
4.8 KiB
Go
237 lines
4.8 KiB
Go
|
|
package bootstrap
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"errors"
|
||
|
|
"fmt"
|
||
|
|
"io"
|
||
|
|
"os"
|
||
|
|
"strings"
|
||
|
|
)
|
||
|
|
|
||
|
|
const (
|
||
|
|
CommandSetup = "setup"
|
||
|
|
CommandMCP = "mcp"
|
||
|
|
CommandConfig = "config"
|
||
|
|
CommandUpdate = "update"
|
||
|
|
CommandVersion = "version"
|
||
|
|
)
|
||
|
|
|
||
|
|
var (
|
||
|
|
ErrBinaryNameRequired = errors.New("binary name is required")
|
||
|
|
ErrUnknownCommand = errors.New("unknown command")
|
||
|
|
ErrCommandNotConfigured = errors.New("command not configured")
|
||
|
|
ErrVersionRequired = errors.New("version is required when no version hook is configured")
|
||
|
|
)
|
||
|
|
|
||
|
|
type Handler func(context.Context, Invocation) error
|
||
|
|
|
||
|
|
type Hooks struct {
|
||
|
|
Setup Handler
|
||
|
|
MCP Handler
|
||
|
|
Config Handler
|
||
|
|
Update Handler
|
||
|
|
Version Handler
|
||
|
|
}
|
||
|
|
|
||
|
|
type Options struct {
|
||
|
|
BinaryName string
|
||
|
|
Description string
|
||
|
|
Version string
|
||
|
|
Args []string
|
||
|
|
Stdin io.Reader
|
||
|
|
Stdout io.Writer
|
||
|
|
Stderr io.Writer
|
||
|
|
Hooks Hooks
|
||
|
|
}
|
||
|
|
|
||
|
|
type Invocation struct {
|
||
|
|
Command string
|
||
|
|
Args []string
|
||
|
|
Stdin io.Reader
|
||
|
|
Stdout io.Writer
|
||
|
|
Stderr io.Writer
|
||
|
|
}
|
||
|
|
|
||
|
|
type commandDef struct {
|
||
|
|
Name string
|
||
|
|
Description string
|
||
|
|
Handler func(Hooks) Handler
|
||
|
|
}
|
||
|
|
|
||
|
|
var commands = []commandDef{
|
||
|
|
{
|
||
|
|
Name: CommandSetup,
|
||
|
|
Description: "Initialiser ou mettre a jour la configuration locale.",
|
||
|
|
Handler: func(h Hooks) Handler {
|
||
|
|
return h.Setup
|
||
|
|
},
|
||
|
|
},
|
||
|
|
{
|
||
|
|
Name: CommandMCP,
|
||
|
|
Description: "Executer la logique MCP principale du binaire.",
|
||
|
|
Handler: func(h Hooks) Handler {
|
||
|
|
return h.MCP
|
||
|
|
},
|
||
|
|
},
|
||
|
|
{
|
||
|
|
Name: CommandConfig,
|
||
|
|
Description: "Inspecter ou modifier la configuration.",
|
||
|
|
Handler: func(h Hooks) Handler {
|
||
|
|
return h.Config
|
||
|
|
},
|
||
|
|
},
|
||
|
|
{
|
||
|
|
Name: CommandUpdate,
|
||
|
|
Description: "Declencher le flux d'auto-update.",
|
||
|
|
Handler: func(h Hooks) Handler {
|
||
|
|
return h.Update
|
||
|
|
},
|
||
|
|
},
|
||
|
|
{
|
||
|
|
Name: CommandVersion,
|
||
|
|
Description: "Afficher la version du binaire.",
|
||
|
|
Handler: func(h Hooks) Handler {
|
||
|
|
return h.Version
|
||
|
|
},
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
func Run(ctx context.Context, opts Options) error {
|
||
|
|
normalized := normalize(opts)
|
||
|
|
|
||
|
|
if strings.TrimSpace(normalized.BinaryName) == "" {
|
||
|
|
return ErrBinaryNameRequired
|
||
|
|
}
|
||
|
|
|
||
|
|
command, commandArgs, showHelp := parseArgs(normalized.Args)
|
||
|
|
if showHelp {
|
||
|
|
return printHelp(normalized, command)
|
||
|
|
}
|
||
|
|
|
||
|
|
if command == "" {
|
||
|
|
return printHelp(normalized, "")
|
||
|
|
}
|
||
|
|
|
||
|
|
handler, known := resolveHandler(command, normalized.Hooks)
|
||
|
|
if !known {
|
||
|
|
return fmt.Errorf("%w: %s", ErrUnknownCommand, command)
|
||
|
|
}
|
||
|
|
|
||
|
|
if handler == nil {
|
||
|
|
if command == CommandVersion {
|
||
|
|
if strings.TrimSpace(normalized.Version) == "" {
|
||
|
|
return ErrVersionRequired
|
||
|
|
}
|
||
|
|
_, err := fmt.Fprintln(normalized.Stdout, normalized.Version)
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
return fmt.Errorf("%w: %s", ErrCommandNotConfigured, command)
|
||
|
|
}
|
||
|
|
|
||
|
|
return handler(ctx, Invocation{
|
||
|
|
Command: command,
|
||
|
|
Args: commandArgs,
|
||
|
|
Stdin: normalized.Stdin,
|
||
|
|
Stdout: normalized.Stdout,
|
||
|
|
Stderr: normalized.Stderr,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
func normalize(opts Options) Options {
|
||
|
|
if opts.Stdin == nil {
|
||
|
|
opts.Stdin = os.Stdin
|
||
|
|
}
|
||
|
|
if opts.Stdout == nil {
|
||
|
|
opts.Stdout = os.Stdout
|
||
|
|
}
|
||
|
|
if opts.Stderr == nil {
|
||
|
|
opts.Stderr = os.Stderr
|
||
|
|
}
|
||
|
|
if opts.Args == nil {
|
||
|
|
opts.Args = os.Args[1:]
|
||
|
|
}
|
||
|
|
return opts
|
||
|
|
}
|
||
|
|
|
||
|
|
func parseArgs(args []string) (command string, commandArgs []string, showHelp bool) {
|
||
|
|
if len(args) == 0 {
|
||
|
|
return "", nil, false
|
||
|
|
}
|
||
|
|
|
||
|
|
first := strings.TrimSpace(args[0])
|
||
|
|
switch first {
|
||
|
|
case "help", "-h", "--help":
|
||
|
|
if len(args) > 1 {
|
||
|
|
return strings.TrimSpace(args[1]), nil, true
|
||
|
|
}
|
||
|
|
return "", nil, true
|
||
|
|
}
|
||
|
|
|
||
|
|
command = first
|
||
|
|
commandArgs = args[1:]
|
||
|
|
if len(commandArgs) == 1 {
|
||
|
|
helpArg := strings.TrimSpace(commandArgs[0])
|
||
|
|
if helpArg == "-h" || helpArg == "--help" {
|
||
|
|
return command, nil, true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return command, commandArgs, false
|
||
|
|
}
|
||
|
|
|
||
|
|
func resolveHandler(command string, hooks Hooks) (Handler, bool) {
|
||
|
|
for _, def := range commands {
|
||
|
|
if def.Name == command {
|
||
|
|
return def.Handler(hooks), true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return nil, false
|
||
|
|
}
|
||
|
|
|
||
|
|
func printHelp(opts Options, command string) error {
|
||
|
|
if command == "" {
|
||
|
|
return printGlobalHelp(opts)
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, def := range commands {
|
||
|
|
if def.Name != command {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
_, err := fmt.Fprintf(
|
||
|
|
opts.Stdout,
|
||
|
|
"Usage:\n %s %s [args]\n\n%s\n",
|
||
|
|
opts.BinaryName,
|
||
|
|
def.Name,
|
||
|
|
def.Description,
|
||
|
|
)
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
return fmt.Errorf("%w: %s", ErrUnknownCommand, command)
|
||
|
|
}
|
||
|
|
|
||
|
|
func printGlobalHelp(opts Options) error {
|
||
|
|
if strings.TrimSpace(opts.Description) != "" {
|
||
|
|
if _, err := fmt.Fprintf(opts.Stdout, "%s\n\n", opts.Description); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if _, err := fmt.Fprintf(opts.Stdout, "Usage:\n %s <command> [args]\n\n", opts.BinaryName); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if _, err := fmt.Fprintln(opts.Stdout, "Commandes communes:"); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, def := range commands {
|
||
|
|
if _, err := fmt.Fprintf(opts.Stdout, " %-7s %s\n", def.Name, def.Description); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
_, err := fmt.Fprintf(opts.Stdout, "\nAide detaillee: %s help <command>\n", opts.BinaryName)
|
||
|
|
return err
|
||
|
|
}
|