diff --git a/command.go b/command.go index 7b74e9f..c774ed6 100644 --- a/command.go +++ b/command.go @@ -69,7 +69,7 @@ func (cmd *Command) I18nKey() string { // Run executes the command's action with the given options. // -// result := cmd.Run(core.Options{{Key: "target", Value: "homelab"}}) +// result := cmd.Run(core.NewOptions(core.Option{Key: "target", Value: "homelab"})) func (cmd *Command) Run(opts Options) Result { if cmd.Action == nil { return Result{E("core.Command.Run", Concat("command \"", cmd.Path, "\" is not executable"), nil), false} diff --git a/contract.go b/contract.go index 6c89053..b32e7e5 100644 --- a/contract.go +++ b/contract.go @@ -82,7 +82,7 @@ type CoreOption func(*Core) Result // IPC access and participate in the lifecycle (ServiceStartup/ServiceShutdown). // // r := core.New( -// core.WithOptions(core.Options{{Key: "name", Value: "myapp"}}), +// core.WithOptions(core.NewOptions(core.Option{Key: "name", Value: "myapp"})), // core.WithService(auth.Register), // core.WithServiceLock(), // ) @@ -123,7 +123,7 @@ func New(opts ...CoreOption) Result { // WithOptions applies key-value configuration to Core. // -// core.WithOptions(core.Options{{Key: "name", Value: "myapp"}}) +// core.WithOptions(core.NewOptions(core.Option{Key: "name", Value: "myapp"})) func WithOptions(opts Options) CoreOption { return func(c *Core) Result { c.options = &opts diff --git a/data.go b/data.go index 3fa5d7b..47f9414 100644 --- a/data.go +++ b/data.go @@ -6,11 +6,11 @@ // // Mount a package's assets: // -// c.Data().New(core.Options{ -// {Key: "name", Value: "brain"}, -// {Key: "source", Value: brainFS}, -// {Key: "path", Value: "prompts"}, -// }) +// c.Data().New(core.NewOptions( +// core.Option{Key: "name", Value: "brain"}, +// core.Option{Key: "source", Value: brainFS}, +// core.Option{Key: "path", Value: "prompts"}, +// )) // // Read from any mounted path: // @@ -36,11 +36,11 @@ type Data struct { // New registers an embedded filesystem under a named prefix. // -// c.Data().New(core.Options{ -// {Key: "name", Value: "brain"}, -// {Key: "source", Value: brainFS}, -// {Key: "path", Value: "prompts"}, -// }) +// c.Data().New(core.NewOptions( +// core.Option{Key: "name", Value: "brain"}, +// core.Option{Key: "source", Value: brainFS}, +// core.Option{Key: "path", Value: "prompts"}, +// )) func (d *Data) New(opts Options) Result { name := opts.String("name") if name == "" { diff --git a/data_test.go b/data_test.go index d269abe..4715b20 100644 --- a/data_test.go +++ b/data_test.go @@ -14,12 +14,23 @@ var testFS embed.FS // --- Data (Embedded Content Mounts) --- +func mountTestData(t *testing.T, c *Core, name string) { + t.Helper() + + r := c.Data().New(NewOptions( + Option{Key: "name", Value: name}, + Option{Key: "source", Value: testFS}, + Option{Key: "path", Value: "testdata"}, + )) + assert.True(t, r.OK) +} + func TestData_New_Good(t *testing.T) { - t.Skip("@TODO Codex: fix embed path resolution after Options struct change") c := New().Value.(*Core) r := c.Data().New(NewOptions( Option{Key: "name", Value: "test"}, Option{Key: "source", Value: testFS}, + Option{Key: "path", Value: "testdata"}, )) assert.True(t, r.OK) assert.NotNil(t, r.Value) @@ -39,10 +50,9 @@ func TestData_New_Bad(t *testing.T) { } func TestData_ReadString_Good(t *testing.T) { - t.Skip("@TODO Codex: fix embed path resolution after Options struct change") c := New().Value.(*Core) - c.Data().New(NewOptions(Option{Key: "name", Value: "app"}, Option{Key: "source", Value: testFS})) - r := c.Data().ReadString("app/testdata/test.txt") + mountTestData(t, c, "app") + r := c.Data().ReadString("app/test.txt") assert.True(t, r.OK) assert.Equal(t, "hello from testdata\n", r.Value.(string)) } @@ -54,18 +64,16 @@ func TestData_ReadString_Bad(t *testing.T) { } func TestData_ReadFile_Good(t *testing.T) { - t.Skip("@TODO Codex: fix embed path resolution after Options struct change") c := New().Value.(*Core) - c.Data().New(NewOptions(Option{Key: "name", Value: "app"}, Option{Key: "source", Value: testFS}, )) + mountTestData(t, c, "app") r := c.Data().ReadFile("app/test.txt") assert.True(t, r.OK) assert.Equal(t, "hello from testdata\n", string(r.Value.([]byte))) } func TestData_Get_Good(t *testing.T) { - t.Skip("@TODO Codex: fix embed path resolution after Options struct change") c := New().Value.(*Core) - c.Data().New(NewOptions(Option{Key: "name", Value: "brain"}, Option{Key: "source", Value: testFS}, )) + mountTestData(t, c, "brain") gr := c.Data().Get("brain") assert.True(t, gr.OK) emb := gr.Value.(*Embed) @@ -85,26 +93,23 @@ func TestData_Get_Bad(t *testing.T) { } func TestData_Mounts_Good(t *testing.T) { - t.Skip("@TODO Codex: fix embed path resolution after Options struct change") c := New().Value.(*Core) - c.Data().New(NewOptions(Option{Key: "name", Value: "a"}, Option{Key: "source", Value: testFS}, )) - c.Data().New(NewOptions(Option{Key: "name", Value: "b"}, Option{Key: "source", Value: testFS}, )) + mountTestData(t, c, "a") + mountTestData(t, c, "b") mounts := c.Data().Mounts() assert.Len(t, mounts, 2) } func TestEmbed_Legacy_Good(t *testing.T) { - t.Skip("@TODO Codex: fix embed path resolution after Options struct change") c := New().Value.(*Core) - c.Data().New(NewOptions(Option{Key: "name", Value: "app"}, Option{Key: "source", Value: testFS}, )) + mountTestData(t, c, "app") assert.NotNil(t, c.Embed()) } func TestData_List_Good(t *testing.T) { - t.Skip("@TODO Codex: fix embed path resolution after Options struct change") c := New().Value.(*Core) - c.Data().New(NewOptions(Option{Key: "name", Value: "app"}, Option{Key: "source", Value: testFS}, )) - r := c.Data().List("app/testdata") + mountTestData(t, c, "app") + r := c.Data().List("app/.") assert.True(t, r.OK) } @@ -115,19 +120,17 @@ func TestData_List_Bad(t *testing.T) { } func TestData_ListNames_Good(t *testing.T) { - t.Skip("@TODO Codex: fix embed path resolution after Options struct change") c := New().Value.(*Core) - c.Data().New(NewOptions(Option{Key: "name", Value: "app"}, Option{Key: "source", Value: testFS}, )) - r := c.Data().ListNames("app/testdata") + mountTestData(t, c, "app") + r := c.Data().ListNames("app/.") assert.True(t, r.OK) assert.Contains(t, r.Value.([]string), "test") } func TestData_Extract_Good(t *testing.T) { - t.Skip("@TODO Codex: fix embed path resolution after Options struct change") c := New().Value.(*Core) - c.Data().New(NewOptions(Option{Key: "name", Value: "app"}, Option{Key: "source", Value: testFS}, )) - r := c.Data().Extract("app/testdata", t.TempDir(), nil) + mountTestData(t, c, "app") + r := c.Data().Extract("app/.", t.TempDir(), nil) assert.True(t, r.OK) } diff --git a/drive.go b/drive.go index cbe9ac6..2d9f7e6 100644 --- a/drive.go +++ b/drive.go @@ -6,18 +6,18 @@ // // Register a transport: // -// c.Drive().New(core.Options{ -// {Key: "name", Value: "api"}, -// {Key: "transport", Value: "https://api.lthn.ai"}, -// }) -// c.Drive().New(core.Options{ -// {Key: "name", Value: "ssh"}, -// {Key: "transport", Value: "ssh://claude@10.69.69.165"}, -// }) -// c.Drive().New(core.Options{ -// {Key: "name", Value: "mcp"}, -// {Key: "transport", Value: "mcp://mcp.lthn.sh"}, -// }) +// c.Drive().New(core.NewOptions( +// core.Option{Key: "name", Value: "api"}, +// core.Option{Key: "transport", Value: "https://api.lthn.ai"}, +// )) +// c.Drive().New(core.NewOptions( +// core.Option{Key: "name", Value: "ssh"}, +// core.Option{Key: "transport", Value: "ssh://claude@10.69.69.165"}, +// )) +// c.Drive().New(core.NewOptions( +// core.Option{Key: "name", Value: "mcp"}, +// core.Option{Key: "transport", Value: "mcp://mcp.lthn.sh"}, +// )) // // Retrieve a handle: // @@ -43,10 +43,10 @@ type Drive struct { // New registers a transport handle. // -// c.Drive().New(core.Options{ -// {Key: "name", Value: "api"}, -// {Key: "transport", Value: "https://api.lthn.ai"}, -// }) +// c.Drive().New(core.NewOptions( +// core.Option{Key: "name", Value: "api"}, +// core.Option{Key: "transport", Value: "https://api.lthn.ai"}, +// )) func (d *Drive) New(opts Options) Result { name := opts.String("name") if name == "" { diff --git a/embed_test.go b/embed_test.go index 99fc7cd..e666a6e 100644 --- a/embed_test.go +++ b/embed_test.go @@ -13,6 +13,14 @@ import ( // --- Mount --- +func mustMountTestFS(t *testing.T, basedir string) *Embed { + t.Helper() + + r := Mount(testFS, basedir) + assert.True(t, r.OK) + return r.Value.(*Embed) +} + func TestMount_Good(t *testing.T) { r := Mount(testFS, "testdata") assert.True(t, r.OK) @@ -26,34 +34,34 @@ func TestMount_Bad(t *testing.T) { // --- Embed methods --- func TestEmbed_ReadFile_Good(t *testing.T) { - emb := Mount(testFS, "testdata").Value.(*Embed) + emb := mustMountTestFS(t, "testdata") r := emb.ReadFile("test.txt") assert.True(t, r.OK) assert.Equal(t, "hello from testdata\n", string(r.Value.([]byte))) } func TestEmbed_ReadString_Good(t *testing.T) { - emb := Mount(testFS, "testdata").Value.(*Embed) + emb := mustMountTestFS(t, "testdata") r := emb.ReadString("test.txt") assert.True(t, r.OK) assert.Equal(t, "hello from testdata\n", r.Value.(string)) } func TestEmbed_Open_Good(t *testing.T) { - emb := Mount(testFS, "testdata").Value.(*Embed) + emb := mustMountTestFS(t, "testdata") r := emb.Open("test.txt") assert.True(t, r.OK) } func TestEmbed_ReadDir_Good(t *testing.T) { - emb := Mount(testFS, "testdata").Value.(*Embed) + emb := mustMountTestFS(t, "testdata") r := emb.ReadDir(".") assert.True(t, r.OK) assert.NotEmpty(t, r.Value) } func TestEmbed_Sub_Good(t *testing.T) { - emb := Mount(testFS, ".").Value.(*Embed) + emb := mustMountTestFS(t, ".") r := emb.Sub("testdata") assert.True(t, r.OK) sub := r.Value.(*Embed) @@ -62,17 +70,17 @@ func TestEmbed_Sub_Good(t *testing.T) { } func TestEmbed_BaseDir_Good(t *testing.T) { - emb := Mount(testFS, "testdata").Value.(*Embed) + emb := mustMountTestFS(t, "testdata") assert.Equal(t, "testdata", emb.BaseDirectory()) } func TestEmbed_FS_Good(t *testing.T) { - emb := Mount(testFS, "testdata").Value.(*Embed) + emb := mustMountTestFS(t, "testdata") assert.NotNil(t, emb.FS()) } func TestEmbed_EmbedFS_Good(t *testing.T) { - emb := Mount(testFS, "testdata").Value.(*Embed) + emb := mustMountTestFS(t, "testdata") efs := emb.EmbedFS() _, err := efs.ReadFile("testdata/test.txt") assert.NoError(t, err) @@ -204,13 +212,13 @@ func TestExtract_BadTargetDir_Ugly(t *testing.T) { } func TestEmbed_PathTraversal_Ugly(t *testing.T) { - emb := Mount(testFS, "testdata").Value.(*Embed) + emb := mustMountTestFS(t, "testdata") r := emb.ReadFile("../../etc/passwd") assert.False(t, r.OK) } func TestEmbed_Sub_BaseDir_Good(t *testing.T) { - emb := Mount(testFS, "testdata").Value.(*Embed) + emb := mustMountTestFS(t, "testdata") r := emb.Sub("scantest") assert.True(t, r.OK) sub := r.Value.(*Embed) @@ -218,19 +226,19 @@ func TestEmbed_Sub_BaseDir_Good(t *testing.T) { } func TestEmbed_Open_Bad(t *testing.T) { - emb := Mount(testFS, "testdata").Value.(*Embed) + emb := mustMountTestFS(t, "testdata") r := emb.Open("nonexistent.txt") assert.False(t, r.OK) } func TestEmbed_ReadDir_Bad(t *testing.T) { - emb := Mount(testFS, "testdata").Value.(*Embed) + emb := mustMountTestFS(t, "testdata") r := emb.ReadDir("nonexistent") assert.False(t, r.OK) } func TestEmbed_EmbedFS_Original_Good(t *testing.T) { - emb := Mount(testFS, "testdata").Value.(*Embed) + emb := mustMountTestFS(t, "testdata") efs := emb.EmbedFS() _, err := efs.ReadFile("testdata/test.txt") assert.NoError(t, err) diff --git a/options.go b/options.go index 443515a..cf730a2 100644 --- a/options.go +++ b/options.go @@ -32,26 +32,38 @@ type Result struct { // r.Result(value) // OK = true, Value = value // r.Result() // after set — returns the value func (r Result) Result(args ...any) Result { - if args == nil { + if len(args) == 0 { return r } return r.New(args...) } func (r Result) New(args ...any) Result { - if len(args) >= 1 { - r.Value = args[0] + if len(args) == 0 { + return r } - if err, ok := r.Value.(error); ok { - if err != nil { - r.Value = err - r.OK = false - } else { + if len(args) > 1 { + if err, ok := args[len(args)-1].(error); ok { + if err != nil { + return Result{Value: err, OK: false} + } + r.Value = args[0] r.OK = true + return r } } + r.Value = args[0] + + if err, ok := r.Value.(error); ok { + if err != nil { + return Result{Value: err, OK: false} + } + return Result{OK: true} + } + + r.OK = true return r }