refactor(cli): clean DX with direct function calls
- cli.Success(), cli.Error(), etc. now print directly
- String-returning versions renamed to cli.FmtSuccess(), etc.
- Removes App() from common usage path
- Usage: cli.Success("done") instead of fmt.Println(cli.Success("done"))
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
23d399407c
commit
22aa1df30a
3 changed files with 75 additions and 101 deletions
|
|
@ -95,12 +95,12 @@ func runDoctor(verbose bool) error {
|
|||
// Summary
|
||||
fmt.Println()
|
||||
if failed > 0 {
|
||||
fmt.Println(cli.Error(i18n.T("cmd.doctor.issues", map[string]interface{}{"Count": failed})))
|
||||
cli.Error(i18n.T("cmd.doctor.issues", map[string]interface{}{"Count": failed}))
|
||||
fmt.Printf("\n%s\n", i18n.T("cmd.doctor.install_missing"))
|
||||
printInstallInstructions()
|
||||
return fmt.Errorf("%s", i18n.T("cmd.doctor.issues_error", map[string]interface{}{"Count": failed}))
|
||||
}
|
||||
|
||||
fmt.Println(cli.Success(i18n.T("cmd.doctor.ready")))
|
||||
cli.Success(i18n.T("cmd.doctor.ready"))
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,16 @@
|
|||
// Package cli provides the CLI runtime and utilities.
|
||||
//
|
||||
// The CLI uses the Core framework for its own runtime, providing:
|
||||
// - Global singleton access via cli.App()
|
||||
// - Output service for styled terminal printing
|
||||
// - Signal handling for graceful shutdown
|
||||
// - Worker bundle spawning for commands
|
||||
// The CLI uses the Core framework for its own runtime. Usage is simple:
|
||||
//
|
||||
// cli.Init(cli.Options{AppName: "core"})
|
||||
// defer cli.Shutdown()
|
||||
//
|
||||
// cli.Success("Done!")
|
||||
// cli.Error("Failed")
|
||||
// if cli.Confirm("Proceed?") { ... }
|
||||
//
|
||||
// // When you need the Core instance
|
||||
// c := cli.Core()
|
||||
package cli
|
||||
|
||||
import (
|
||||
|
|
@ -19,39 +25,32 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
instance *Runtime
|
||||
instance *runtime
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
// Runtime is the CLI's Core runtime.
|
||||
type Runtime struct {
|
||||
Core *framework.Core
|
||||
// runtime is the CLI's internal Core runtime.
|
||||
type runtime struct {
|
||||
core *framework.Core
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
// RuntimeOptions configures the CLI runtime.
|
||||
type RuntimeOptions struct {
|
||||
// AppName is the CLI application name (used in output)
|
||||
// Options configures the CLI runtime.
|
||||
type Options struct {
|
||||
AppName string
|
||||
// Version is the CLI version string
|
||||
Version string
|
||||
}
|
||||
|
||||
// Init initialises the global CLI runtime.
|
||||
// Call this once at startup (typically in main.go).
|
||||
func Init(opts RuntimeOptions) error {
|
||||
func Init(opts Options) error {
|
||||
var initErr error
|
||||
once.Do(func() {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
core, err := framework.New(
|
||||
framework.WithService(NewOutputService(OutputServiceOptions{
|
||||
AppName: opts.AppName,
|
||||
})),
|
||||
framework.WithService(NewSignalService(SignalServiceOptions{
|
||||
Cancel: cancel,
|
||||
})),
|
||||
c, err := framework.New(
|
||||
framework.WithName("signal", newSignalService(cancel)),
|
||||
framework.WithServiceLock(),
|
||||
)
|
||||
if err != nil {
|
||||
|
|
@ -60,14 +59,13 @@ func Init(opts RuntimeOptions) error {
|
|||
return
|
||||
}
|
||||
|
||||
instance = &Runtime{
|
||||
Core: core,
|
||||
instance = &runtime{
|
||||
core: c,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
}
|
||||
|
||||
// Start services
|
||||
if err := core.ServiceStartup(ctx, nil); err != nil {
|
||||
if err := c.ServiceStartup(ctx, nil); err != nil {
|
||||
initErr = err
|
||||
return
|
||||
}
|
||||
|
|
@ -75,114 +73,91 @@ func Init(opts RuntimeOptions) error {
|
|||
return initErr
|
||||
}
|
||||
|
||||
// App returns the global CLI runtime.
|
||||
// Panics if Init() hasn't been called.
|
||||
func App() *Runtime {
|
||||
func mustInit() {
|
||||
if instance == nil {
|
||||
panic("cli.App() called before cli.Init()")
|
||||
panic("cli not initialised - call cli.Init() first")
|
||||
}
|
||||
return instance
|
||||
}
|
||||
|
||||
// --- Core Access ---
|
||||
|
||||
// Core returns the CLI's framework Core instance.
|
||||
func Core() *framework.Core {
|
||||
mustInit()
|
||||
return instance.core
|
||||
}
|
||||
|
||||
// Context returns the CLI's root context.
|
||||
// This context is cancelled on shutdown signals.
|
||||
func (r *Runtime) Context() context.Context {
|
||||
return r.ctx
|
||||
// Cancelled on SIGINT/SIGTERM.
|
||||
func Context() context.Context {
|
||||
mustInit()
|
||||
return instance.ctx
|
||||
}
|
||||
|
||||
// Shutdown gracefully shuts down the CLI runtime.
|
||||
func (r *Runtime) Shutdown() {
|
||||
r.cancel()
|
||||
r.Core.ServiceShutdown(r.ctx)
|
||||
// Shutdown gracefully shuts down the CLI.
|
||||
func Shutdown() {
|
||||
if instance == nil {
|
||||
return
|
||||
}
|
||||
instance.cancel()
|
||||
instance.core.ServiceShutdown(instance.ctx)
|
||||
}
|
||||
|
||||
// Output returns the output service for styled printing.
|
||||
func (r *Runtime) Output() *OutputService {
|
||||
return framework.MustServiceFor[*OutputService](r.Core, "output")
|
||||
}
|
||||
|
||||
// --- Output Service ---
|
||||
|
||||
// OutputServiceOptions configures the output service.
|
||||
type OutputServiceOptions struct {
|
||||
AppName string
|
||||
}
|
||||
|
||||
// OutputService provides styled terminal output.
|
||||
type OutputService struct {
|
||||
*framework.ServiceRuntime[OutputServiceOptions]
|
||||
}
|
||||
|
||||
// NewOutputService creates an output service factory.
|
||||
func NewOutputService(opts OutputServiceOptions) func(*framework.Core) (any, error) {
|
||||
return func(c *framework.Core) (any, error) {
|
||||
return &OutputService{
|
||||
ServiceRuntime: framework.NewServiceRuntime(c, opts),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
// --- Output Functions ---
|
||||
|
||||
// Success prints a success message with checkmark.
|
||||
func (s *OutputService) Success(msg string) {
|
||||
func Success(msg string) {
|
||||
fmt.Println(SuccessStyle.Render(SymbolCheck + " " + msg))
|
||||
}
|
||||
|
||||
// Error prints an error message with cross.
|
||||
func (s *OutputService) Error(msg string) {
|
||||
func Error(msg string) {
|
||||
fmt.Println(ErrorStyle.Render(SymbolCross + " " + msg))
|
||||
}
|
||||
|
||||
// Warning prints a warning message.
|
||||
func (s *OutputService) Warning(msg string) {
|
||||
func Warning(msg string) {
|
||||
fmt.Println(WarningStyle.Render(SymbolWarning + " " + msg))
|
||||
}
|
||||
|
||||
// Info prints an info message.
|
||||
func (s *OutputService) Info(msg string) {
|
||||
func Info(msg string) {
|
||||
fmt.Println(InfoStyle.Render(SymbolInfo + " " + msg))
|
||||
}
|
||||
|
||||
// Title prints a title/header.
|
||||
func (s *OutputService) Title(msg string) {
|
||||
func Title(msg string) {
|
||||
fmt.Println(TitleStyle.Render(msg))
|
||||
}
|
||||
|
||||
// Dim prints dimmed/subtle text.
|
||||
func (s *OutputService) Dim(msg string) {
|
||||
func Dim(msg string) {
|
||||
fmt.Println(DimStyle.Render(msg))
|
||||
}
|
||||
|
||||
// --- Signal Service ---
|
||||
// --- Signal Service (internal) ---
|
||||
|
||||
// SignalServiceOptions configures the signal service.
|
||||
type SignalServiceOptions struct {
|
||||
Cancel context.CancelFunc
|
||||
}
|
||||
|
||||
// SignalService handles OS signals for graceful shutdown.
|
||||
type SignalService struct {
|
||||
*framework.ServiceRuntime[SignalServiceOptions]
|
||||
type signalService struct {
|
||||
cancel context.CancelFunc
|
||||
sigChan chan os.Signal
|
||||
}
|
||||
|
||||
// NewSignalService creates a signal service factory.
|
||||
func NewSignalService(opts SignalServiceOptions) func(*framework.Core) (any, error) {
|
||||
func newSignalService(cancel context.CancelFunc) func(*framework.Core) (any, error) {
|
||||
return func(c *framework.Core) (any, error) {
|
||||
return &SignalService{
|
||||
ServiceRuntime: framework.NewServiceRuntime(c, opts),
|
||||
return &signalService{
|
||||
cancel: cancel,
|
||||
sigChan: make(chan os.Signal, 1),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// OnStartup starts listening for signals.
|
||||
func (s *SignalService) OnStartup(ctx context.Context) error {
|
||||
func (s *signalService) OnStartup(ctx context.Context) error {
|
||||
signal.Notify(s.sigChan, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
go func() {
|
||||
select {
|
||||
case <-s.sigChan:
|
||||
s.Opts().Cancel()
|
||||
s.cancel()
|
||||
case <-ctx.Done():
|
||||
}
|
||||
}()
|
||||
|
|
@ -190,8 +165,7 @@ func (s *SignalService) OnStartup(ctx context.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// OnShutdown stops listening for signals.
|
||||
func (s *SignalService) OnShutdown(ctx context.Context) error {
|
||||
func (s *signalService) OnShutdown(ctx context.Context) error {
|
||||
signal.Stop(s.sigChan)
|
||||
close(s.sigChan)
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -327,23 +327,23 @@ var (
|
|||
// Helper Functions
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
// Success returns a styled success message with checkmark.
|
||||
func Success(msg string) string {
|
||||
// FmtSuccess returns a styled success message with checkmark.
|
||||
func FmtSuccess(msg string) string {
|
||||
return fmt.Sprintf("%s %s", SuccessStyle.Render(SymbolCheck), msg)
|
||||
}
|
||||
|
||||
// Error returns a styled error message with cross.
|
||||
func Error(msg string) string {
|
||||
// FmtError returns a styled error message with cross.
|
||||
func FmtError(msg string) string {
|
||||
return fmt.Sprintf("%s %s", ErrorStyle.Render(SymbolCross), msg)
|
||||
}
|
||||
|
||||
// Warning returns a styled warning message with warning symbol.
|
||||
func Warning(msg string) string {
|
||||
// FmtWarning returns a styled warning message with warning symbol.
|
||||
func FmtWarning(msg string) string {
|
||||
return fmt.Sprintf("%s %s", WarningStyle.Render(SymbolWarning), msg)
|
||||
}
|
||||
|
||||
// Info returns a styled info message with info symbol.
|
||||
func Info(msg string) string {
|
||||
// FmtInfo returns a styled info message with info symbol.
|
||||
func FmtInfo(msg string) string {
|
||||
return fmt.Sprintf("%s %s", InfoStyle.Render(SymbolInfo), msg)
|
||||
}
|
||||
|
||||
|
|
@ -464,13 +464,13 @@ func Header(title string, withSeparator bool) string {
|
|||
return fmt.Sprintf("\n%s", HeaderStyle.Render(title))
|
||||
}
|
||||
|
||||
// Title returns a styled command/section title.
|
||||
func Title(text string) string {
|
||||
// FmtTitle returns a styled command/section title.
|
||||
func FmtTitle(text string) string {
|
||||
return TitleStyle.Render(text)
|
||||
}
|
||||
|
||||
// Dim returns dimmed text.
|
||||
func Dim(text string) string {
|
||||
// FmtDim returns dimmed text.
|
||||
func FmtDim(text string) string {
|
||||
return DimStyle.Render(text)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue