Skip to content

Commit

Permalink
Rename Router methods
Browse files Browse the repository at this point in the history
  • Loading branch information
markuswustenberg committed Oct 8, 2024
1 parent 0925e03 commit 5e29579
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 130 deletions.
2 changes: 1 addition & 1 deletion error.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ func (e Error) Error() string {
}

const (
ErrorNotFound = Error("not found")
ErrorRouteNotFound = Error("route not found")
)
101 changes: 51 additions & 50 deletions router.go
Original file line number Diff line number Diff line change
@@ -1,94 +1,95 @@
package clir

// CommandRouter is a router for commands which itself satisfies [Command].
type CommandRouter struct {
commands map[string]Command
// Router for [Runner]-s which itself satisfies [Runner].
type Router struct {
middlewares []Middleware
patterns []string
subRouters []*CommandRouter
routers []*Router
runners map[string]Runner
}

func NewCommandRouter() *CommandRouter {
return &CommandRouter{
commands: map[string]Command{},
func NewRouter() *Router {
return &Router{
runners: map[string]Runner{},
}
}

// Run satisfies [Command].
func (c *CommandRouter) Run(ctx Context) error {
// Run satisfies [Runner].
func (r *Router) Run(ctx Context) error {
if len(ctx.Args) == 0 {
cmd, ok := c.commands[""]
runner, ok := r.runners[""]
if !ok {
return ErrorNotFound
return ErrorRouteNotFound
}

// Apply middlewares in reverse order, so that middlewares are applied in the order they were added.
for i := len(c.middlewares) - 1; i >= 0; i-- {
cmd = c.middlewares[i](cmd)
for i := len(r.middlewares) - 1; i >= 0; i-- {
runner = r.middlewares[i](runner)
}

return cmd.Run(ctx)
return runner.Run(ctx)
}

for _, pattern := range c.patterns {
for _, pattern := range r.patterns {
if ctx.Args[0] == pattern {
cmd := c.commands[pattern]
runner := r.runners[pattern]
ctx.Args = ctx.Args[1:]

for i := len(c.middlewares) - 1; i >= 0; i-- {
cmd = c.middlewares[i](cmd)
for i := len(r.middlewares) - 1; i >= 0; i-- {
runner = r.middlewares[i](runner)
}

return cmd.Run(ctx)
return runner.Run(ctx)
}
}

for _, r := range c.subRouters {
if err := r.Run(ctx); err == nil {
for _, router := range r.routers {
if err := router.Run(ctx); err == nil {
return err
}
}

return ErrorNotFound
return ErrorRouteNotFound
}

// Sub adds a subcommand to the router with the given pattern.
func (c *CommandRouter) Sub(pattern string, cmd Command) {
c.patterns = append(c.patterns, pattern)
c.commands[pattern] = cmd
// Route a [Runner] with the given pattern.
func (r *Router) Route(pattern string, runner Runner) {
r.patterns = append(r.patterns, pattern)
r.runners[pattern] = runner
}

// SubFunc is a convenience method for adding a subcommand with a [CommandFunc].
// It's the same as calling [CommandRouter.Sub] with a [CommandFunc], but you don't have to wrap the function.
func (c *CommandRouter) SubFunc(pattern string, cmd CommandFunc) {
c.Sub(pattern, cmd)
// RouteFunc is like [Router.Route], but with a [RunnerFunc].
func (r *Router) RouteFunc(pattern string, runner RunnerFunc) {
r.Route(pattern, runner)
}

// SubGroup is a convenience method for adding a subcommand with a new router.
func (c *CommandRouter) SubGroup(pattern string, cb func(r *CommandRouter)) {
r := NewCommandRouter()
cb(r)
c.Sub(pattern, r)
// Branch into a new [Router] with the given pattern.
func (r *Router) Branch(pattern string, cb func(r *Router)) {
newR := NewRouter()
cb(newR)
r.Route(pattern, newR)
}

// Group commands with a new router.
// The middleware from the parent router is copied to the new router.
func (c *CommandRouter) Group(cb func(r *CommandRouter)) {
r := NewCommandRouter()
r.middlewares = append(r.middlewares, c.middlewares...)
cb(r)
c.subRouters = append(c.subRouters, r)
// Scope into a new [Router].
// The middleware from the parent router is copied to the new router,
// but new middleware is only added to the new router, not the parent router.
func (r *Router) Scope(cb func(r *Router)) {
newR := NewRouter()
newR.middlewares = append(newR.middlewares, r.middlewares...)
cb(newR)
r.routers = append(r.routers, newR)
}

type Middleware = func(next Command) Command
// Middleware for [Router.Use].
type Middleware = func(next Runner) Runner

// Use a middleware for all commands. If called on the root router, it will apply to all commands.
// If called in a Group, it will apply to all commands in that group.
func (c *CommandRouter) Use(middlewares ...Middleware) {
if len(c.commands) > 0 {
panic("cannot add middlewares after adding commands")
// Use [Middleware] on the current branch of the [Router].
// If called in a [Scope], it will apply to all routes in that scope.
func (r *Router) Use(middlewares ...Middleware) {
if len(r.runners) > 0 {
panic("cannot use middlewares after adding routes")
}
c.middlewares = append(c.middlewares, middlewares...)
r.middlewares = append(r.middlewares, middlewares...)
}

var _ Command = (*CommandRouter)(nil)
var _ Runner = (*Router)(nil)
Loading

0 comments on commit 5e29579

Please sign in to comment.