generated from maragudk/template
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This adds a naive implementation of a `Command`, `CommandFunc`, and `CommandMux`.
- Loading branch information
1 parent
4ac94e7
commit 3a7ad34
Showing
9 changed files
with
198 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
// Package clir provides a definition of a runnable [Command] as well as a [CommandMux], which is a multiplexer/router for commands. | ||
package clir | ||
|
||
import ( | ||
"context" | ||
"io" | ||
"os" | ||
"os/signal" | ||
"syscall" | ||
) | ||
|
||
// Context for a [Command] that runs. | ||
type Context struct { | ||
Args []string | ||
Ctx context.Context | ||
Err io.Writer | ||
In io.Reader | ||
Out io.Writer | ||
} | ||
|
||
// Command can be run with a Context. | ||
type Command interface { | ||
Run(ctx Context) error | ||
} | ||
|
||
// CommandFunc is a function which satisfies [Command]. | ||
type CommandFunc func(ctx Context) error | ||
|
||
// Run satisfies [Command]. | ||
func (f CommandFunc) Run(ctx Context) error { | ||
return f(ctx) | ||
} | ||
|
||
// Run a [Command] with default options, which are: | ||
// - Get args from [os.Args] | ||
// - Create context which is cancelled on [syscall.SIGTERM] or [syscall.SIGINT] | ||
// - Use [os.Stdin] for input | ||
// - Use [os.Stdout] for output | ||
// - Use [os.Stderr] for errors | ||
func Run(cmd Command) error { | ||
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT) | ||
defer stop() | ||
|
||
cmdCtx := Context{ | ||
Args: os.Args, | ||
Ctx: ctx, | ||
Err: os.Stderr, | ||
In: os.Stdin, | ||
Out: os.Stdout, | ||
} | ||
|
||
return cmd.Run(cmdCtx) | ||
} | ||
|
||
var _ Command = (*CommandFunc)(nil) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package clir_test | ||
|
||
import ( | ||
"strings" | ||
"testing" | ||
|
||
"maragu.dev/is" | ||
|
||
"maragu.dev/clir" | ||
) | ||
|
||
func TestRun(t *testing.T) { | ||
t.Run("can run a command", func(t *testing.T) { | ||
err := clir.Run(clir.CommandFunc(func(ctx clir.Context) error { | ||
is.True(t, strings.Contains(ctx.Args[0], "clir.test")) | ||
return nil | ||
})) | ||
is.NotError(t, err) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package clir | ||
|
||
type Error string | ||
|
||
func (e Error) Error() string { | ||
return string(e) | ||
} | ||
|
||
const ( | ||
ErrorNotFound = Error("not found") | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
module maragu.dev/clir | ||
|
||
go 1.23 | ||
|
||
require maragu.dev/is v0.2.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
maragu.dev/is v0.2.0 h1:poeuVEA5GG3vrDpGmzo2KjWtIMZmqUyvGnOB0/pemig= | ||
maragu.dev/is v0.2.0/go.mod h1:bviaM5S0fBshCw7wuumFGTju/izopZ/Yvq4g7Klc7y8= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package clir | ||
|
||
// CommandMux is a multiplexer/router for commands which itself satisfies [Command]. | ||
type CommandMux struct { | ||
patterns []string | ||
commands map[string]Command | ||
} | ||
|
||
func NewCommandMux() *CommandMux { | ||
return &CommandMux{ | ||
commands: map[string]Command{}, | ||
} | ||
} | ||
|
||
func (c *CommandMux) Run(ctx Context) error { | ||
if len(ctx.Args) == 0 { | ||
root, ok := c.commands[""] | ||
if !ok { | ||
return ErrorNotFound | ||
} | ||
return root.Run(ctx) | ||
} | ||
|
||
for _, pattern := range c.patterns { | ||
if ctx.Args[0] == pattern { | ||
cmd := c.commands[pattern] | ||
ctx.Args = ctx.Args[1:] | ||
return cmd.Run(ctx) | ||
} | ||
} | ||
|
||
return ErrorNotFound | ||
} | ||
|
||
func (c *CommandMux) Handle(pattern string, cmd Command) { | ||
c.patterns = append(c.patterns, pattern) | ||
c.commands[pattern] = cmd | ||
} | ||
|
||
func (c *CommandMux) HandleFunc(pattern string, cmd CommandFunc) { | ||
c.Handle(pattern, cmd) | ||
} | ||
|
||
var _ Command = (*CommandMux)(nil) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
package clir_test | ||
|
||
import ( | ||
"testing" | ||
|
||
"maragu.dev/is" | ||
|
||
"maragu.dev/clir" | ||
) | ||
|
||
func TestRoute(t *testing.T) { | ||
t.Run("can handle a root command", func(t *testing.T) { | ||
mux := clir.NewCommandMux() | ||
|
||
var called bool | ||
mux.HandleFunc("", func(ctx clir.Context) error { | ||
called = true | ||
return nil | ||
}) | ||
|
||
err := mux.Run(clir.Context{ | ||
Args: []string{}, | ||
}) | ||
is.NotError(t, err) | ||
is.True(t, called) | ||
}) | ||
|
||
t.Run("errors if there is no root command", func(t *testing.T) { | ||
mux := clir.NewCommandMux() | ||
|
||
err := mux.Run(clir.Context{ | ||
Args: []string{}, | ||
}) | ||
is.Error(t, err, clir.ErrorNotFound) | ||
}) | ||
|
||
t.Run("can handle a subcommand", func(t *testing.T) { | ||
mux := clir.NewCommandMux() | ||
|
||
var called bool | ||
mux.HandleFunc("party", func(ctx clir.Context) error { | ||
called = true | ||
is.Equal(t, 0, len(ctx.Args)) | ||
return nil | ||
}) | ||
|
||
err := mux.Run(clir.Context{ | ||
Args: []string{"party"}, | ||
}) | ||
is.NotError(t, err) | ||
is.True(t, called) | ||
}) | ||
|
||
t.Run("errors if there is no subcommand", func(t *testing.T) { | ||
mux := clir.NewCommandMux() | ||
|
||
err := mux.Run(clir.Context{ | ||
Args: []string{"party"}, | ||
}) | ||
is.Error(t, err, clir.ErrorNotFound) | ||
}) | ||
} |