From a7ab83550a9814abeee12dfc4e7474bee85b0a1a Mon Sep 17 00:00:00 2001 From: Snider Date: Tue, 24 Mar 2026 19:48:12 +0000 Subject: [PATCH] wip: checkpoint before v0.3.3 parity rewrite Cli as service with ServiceRuntime, incomplete. Need to properly port v0.3.3 service_manager, message_bus, WithService with full name/IPC discovery. Co-Authored-By: Virgil --- cli.go | 53 ++++++++++++++++++++++++++++++----------------------- contract.go | 4 +++- core.go | 20 ++++++++++---------- 3 files changed, 43 insertions(+), 34 deletions(-) diff --git a/cli.go b/cli.go index 8a292d0..1744f80 100644 --- a/cli.go +++ b/cli.go @@ -1,7 +1,6 @@ // SPDX-License-Identifier: EUPL-1.2 // Cli is the CLI surface layer for the Core command tree. -// It reads commands from Core's registry and wires them to terminal I/O. // // c := core.New(core.WithOption("name", "myapp")).Value.(*Core) // c.Command("deploy", core.Command{Action: handler}) @@ -13,18 +12,23 @@ import ( "os" ) +// CliOptions holds configuration for the Cli service. +type CliOptions struct{} + // Cli is the CLI surface for the Core command tree. type Cli struct { - core *Core + *ServiceRuntime[CliOptions] output io.Writer banner func(*Cli) string } -// New creates a Cli bound to a Core instance. +// Register creates a Cli service factory for core.WithService. // -// cli := core.Cli{}.New(c) -func (cl Cli) New(c *Core) *Cli { - return &Cli{core: c, output: os.Stdout} +// core.New(core.WithService(core.CliRegister)) +func CliRegister(c *Core) Result { + cl := &Cli{output: os.Stdout} + cl.ServiceRuntime = NewServiceRuntime[CliOptions](c, CliOptions{}) + return c.RegisterService("cli", cl) } // Print writes to the CLI output (defaults to os.Stdout). @@ -51,17 +55,18 @@ func (cl *Cli) Run(args ...string) Result { } clean := FilterArgs(args) + c := cl.Core() - if cl.core == nil || cl.core.commands == nil { + if c == nil || c.commands == nil { if cl.banner != nil { cl.Print(cl.banner(cl)) } return Result{} } - cl.core.commands.mu.RLock() - cmdCount := len(cl.core.commands.commands) - cl.core.commands.mu.RUnlock() + c.commands.mu.RLock() + cmdCount := len(c.commands.commands) + c.commands.mu.RUnlock() if cmdCount == 0 { if cl.banner != nil { @@ -74,16 +79,16 @@ func (cl *Cli) Run(args ...string) Result { var cmd *Command var remaining []string - cl.core.commands.mu.RLock() + c.commands.mu.RLock() for i := len(clean); i > 0; i-- { path := JoinPath(clean[:i]...) - if c, ok := cl.core.commands.commands[path]; ok { - cmd = c + if found, ok := c.commands.commands[path]; ok { + cmd = found remaining = clean[i:] break } } - cl.core.commands.mu.RUnlock() + c.commands.mu.RUnlock() if cmd == nil { if cl.banner != nil { @@ -121,13 +126,14 @@ func (cl *Cli) Run(args ...string) Result { // // c.Cli().PrintHelp() func (cl *Cli) PrintHelp() { - if cl.core == nil || cl.core.commands == nil { + c := cl.Core() + if c == nil || c.commands == nil { return } name := "" - if cl.core.app != nil { - name = cl.core.app.Name + if c.app != nil { + name = c.app.Name } if name != "" { cl.Print("%s commands:", name) @@ -135,14 +141,14 @@ func (cl *Cli) PrintHelp() { cl.Print("Commands:") } - cl.core.commands.mu.RLock() - defer cl.core.commands.mu.RUnlock() + c.commands.mu.RLock() + defer c.commands.mu.RUnlock() - for path, cmd := range cl.core.commands.commands { + for path, cmd := range c.commands.commands { if cmd.Hidden || (cmd.Action == nil && cmd.Lifecycle == nil) { continue } - tr := cl.core.I18n().Translate(cmd.I18nKey()) + tr := c.I18n().Translate(cmd.I18nKey()) desc, _ := tr.Value.(string) if desc == "" || desc == cmd.I18nKey() { cl.Print(" %s", path) @@ -164,8 +170,9 @@ func (cl *Cli) Banner() string { if cl.banner != nil { return cl.banner(cl) } - if cl.core != nil && cl.core.app != nil && cl.core.app.Name != "" { - return cl.core.app.Name + c := cl.Core() + if c != nil && c.app != nil && c.app.Name != "" { + return c.app.Name } return "" } diff --git a/contract.go b/contract.go index e5a9be3..3fce199 100644 --- a/contract.go +++ b/contract.go @@ -104,7 +104,9 @@ func New(opts ...CoreOption) Result { commands: &commandRegistry{commands: make(map[string]*Command)}, } c.context, c.cancel = context.WithCancel(context.Background()) - c.cli = Cli{}.New(c) + + // Core services + CliRegister(c) for _, opt := range opts { if r := opt(c); !r.OK { diff --git a/core.go b/core.go index fb9c5d9..3b63fb8 100644 --- a/core.go +++ b/core.go @@ -15,15 +15,15 @@ import ( // Core is the central application object that manages services, assets, and communication. type Core struct { - options *Options // c.Options() — Input configuration used to create this Core - app *App // c.App() — Application identity + optional GUI runtime - data *Data // c.Data() — Embedded/stored content from packages - drive *Drive // c.Drive() — Resource handle registry (transports) - fs *Fs // c.Fs() — Local filesystem I/O (sandboxable) - config *Config // c.Config() — Configuration, settings, feature flags - error *ErrorPanic // c.Error() — Panic recovery and crash reporting - log *ErrorLog // c.Log() — Structured logging + error wrapping - cli *Cli // c.Cli() — CLI surface layer + options *Options // c.Options() — Input configuration used to create this Core + app *App // c.App() — Application identity + optional GUI runtime + data *Data // c.Data() — Embedded/stored content from packages + drive *Drive // c.Drive() — Resource handle registry (transports) + fs *Fs // c.Fs() — Local filesystem I/O (sandboxable) + config *Config // c.Config() — Configuration, settings, feature flags + error *ErrorPanic // c.Error() — Panic recovery and crash reporting + log *ErrorLog // c.Log() — Structured logging + error wrapping + // cli accessed via ServiceFor[*Cli](c, "cli") commands *commandRegistry // c.Command("path") — Command tree services *serviceRegistry // c.Service("name") — Service registry lock *Lock // c.Lock("name") — Named mutexes @@ -49,7 +49,7 @@ func (c *Core) Fs() *Fs { return c.fs } func (c *Core) Config() *Config { return c.config } func (c *Core) Error() *ErrorPanic { return c.error } func (c *Core) Log() *ErrorLog { return c.log } -func (c *Core) Cli() *Cli { return c.cli } +func (c *Core) Cli() *Cli { return cli.New() } func (c *Core) IPC() *Ipc { return c.ipc } func (c *Core) I18n() *I18n { return c.i18n } func (c *Core) Env(key string) string { return Env(key) }