- command handlers
This commit is contained in:
abelith committed 2026-08-04 01:00:00 +05:00
1 parent 3673d318a1
commit 213562956c
6 files changed
+249 -1

No files matched your search

+18 -1
View File
@@ -2,4 +2,21 @@ module spellbook
go 1.26.0 go 1.26.0
require github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/clipperhouse/displaywidth v0.10.0 // indirect
github.com/clipperhouse/uax29/v2 v2.6.0 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.19 // indirect
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect
github.com/olekukonko/errors v1.2.0 // indirect
github.com/olekukonko/ll v0.1.6 // indirect
github.com/olekukonko/tablewriter v1.1.4 // indirect
github.com/spf13/cobra v1.10.2 // indirect
github.com/spf13/pflag v1.0.9 // indirect
golang.org/x/sys v0.30.0 // indirect
)
+35
View File
@@ -1,2 +1,37 @@
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/clipperhouse/displaywidth v0.10.0 h1:GhBG8WuerxjFQQYeuZAeVTuyxuX+UraiZGD4HJQ3Y8g=
github.com/clipperhouse/displaywidth v0.10.0/go.mod h1:XqJajYsaiEwkxOj4bowCTMcT1SgvHo9flfF3jQasdbs=
github.com/clipperhouse/uax29/v2 v2.6.0 h1:z0cDbUV+aPASdFb2/ndFnS9ts/WNXgTNNGFoKXuhpos=
github.com/clipperhouse/uax29/v2 v2.6.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc=
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0=
github.com/olekukonko/errors v1.2.0 h1:10Zcn4GeV59t/EGqJc8fUjtFT/FuUh5bTMzZ1XwmCRo=
github.com/olekukonko/errors v1.2.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
github.com/olekukonko/ll v0.1.6 h1:lGVTHO+Qc4Qm+fce/2h2m5y9LvqaW+DCN7xW9hsU3uA=
github.com/olekukonko/ll v0.1.6/go.mod h1:NVUmjBb/aCtUpjKk75BhWrOlARz3dqsM+OtszpY4o88=
github.com/olekukonko/tablewriter v1.1.4 h1:ORUMI3dXbMnRlRggJX3+q7OzQFDdvgbN9nVWj1drm6I=
github.com/olekukonko/tablewriter v1.1.4/go.mod h1:+kedxuyTtgoZLwif3P1Em4hARJs+mVnzKxmsCL/C5RY=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+29
View File
@@ -0,0 +1,29 @@
package cli
import (
"github.com/spf13/cobra"
"spellbook/internal/models"
"strings"
)
func NewAddCommand(app Application) *cobra.Command {
spell := models.Spell{}
cmd := &cobra.Command{
Use: "add [flags] -- [spell]",
Short: "Add new spell",
Long: `WIP`,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
spell.Spell = strings.Join(args, " ")
return app.AddSpell(ctx, spell)
},
}
cmd.Flags().StringVarP(&spell.Name, "name", "n", "", "Spell name")
cmd.Flags().StringSliceVarP(&spell.Tags, "tags", "t", nil, "Spell tags")
cmd.Flags().StringVarP(&spell.ShortDesc, "desc", "d", "", "Short spell description")
return cmd
}
+18
View File
@@ -0,0 +1,18 @@
package cli
import "github.com/spf13/cobra"
func NewRmCommand(app Application) *cobra.Command {
cmd := &cobra.Command{
Use: "rm [spell_name]",
Short: "Delete spell",
Long: "WIP",
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
return app.DeleteSpell(ctx, args[0])
},
}
return cmd
}
+103
View File
@@ -0,0 +1,103 @@
package cli
import (
"context"
"github.com/olekukonko/tablewriter"
"github.com/spf13/cobra"
"os"
"spellbook/internal/models"
"strings"
)
type Application interface {
AddSpell(ctx context.Context, spell models.Spell) error
GetSpell(ctx context.Context, spellName string) (models.Spell, error)
GetSpells(ctx context.Context, query models.SearchQuery) ([]models.Spell, error)
DeleteSpell(ctx context.Context, spellName string) error
CastSpell(ctx context.Context, spellCast models.SpellCast) (models.CastResult, error)
}
func rootCmd(app Application) func(*cobra.Command, []string) error {
return func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
if len(args) < 1 {
return listCommands(ctx, cmd, app)
}
return runCommand(ctx, app, args)
}
}
func runCommand(ctx context.Context, app Application, args []string) error {
spell, err := app.GetSpell(ctx, args[0])
if err != nil {
return err
}
m := make(map[string]string, len(args[1:]))
for _, pair := range args[1:] {
keyVal := strings.Split(pair, "=")
m[keyVal[0]] = keyVal[1]
}
_, err = app.CastSpell(ctx, models.SpellCast{
Spell: spell,
Args: m,
Stdin: os.Stdin,
Stdout: os.Stdout,
Stderr: os.Stderr,
})
if err != nil {
return err
}
return nil
}
func listCommands(ctx context.Context, cmd *cobra.Command, app Application) error {
spells, err := app.GetSpells(ctx, models.SearchQuery{})
if err != nil {
return err
}
if len(spells) == 0 {
cmd.Println("Spell book is empty. Try `spellbook add` to add your first spell")
}
return printTable(cmd, spells)
}
func printTable(cmd *cobra.Command, spells []models.Spell) error {
table := tablewriter.NewTable(cmd.OutOrStdout())
rows := make([][]string, 0, len(spells))
for _, spell := range spells {
row := []string{
spell.Name, spell.Spell, spell.ShortDesc, strings.Join(spell.Tags, ", "),
}
rows = append(rows, row)
}
table.Header("Name", "Spell", "Short description", "Tags")
_ = table.Bulk(rows)
if err := table.Render(); err != nil {
return err
}
return nil
}
func NewRootCmd(app Application) *cobra.Command {
cmd := &cobra.Command{
Use: "spellbook",
Short: "CLI tool to store, manage and execute command templates",
Long: `Spellbook is a lightweight binary command manager written in Go.
It allows you to save complex shell commands as parametrized templates (spells)
and execute them instantly with custom arguments from anywhere in your terminal`,
RunE: rootCmd(app),
Args: cobra.ArbitraryArgs,
}
return cmd
}
+46
View File
@@ -0,0 +1,46 @@
package mock
import (
"context"
"fmt"
"spellbook/internal/adapters/caster"
"spellbook/internal/models"
)
var testSpell = models.Spell{
Id: 0,
Name: "test",
Spell: "curl {{.target}}",
ShortDesc: "test spell",
Tags: []string{"tag1", "tag2", "tag3"},
}
type Application struct{}
func (a *Application) AddSpell(_ context.Context, spell models.Spell) error {
fmt.Println("TEST - AddSpell", spell)
return nil
}
func (a *Application) GetSpell(_ context.Context, spellName string) (models.Spell, error) {
fmt.Println("TEST - GetSpell", spellName)
return testSpell, nil
}
func (a *Application) GetSpells(_ context.Context, query models.SearchQuery) ([]models.Spell, error) {
fmt.Println("TEST - GetSpells", query)
return []models.Spell{
testSpell,
}, nil
}
func (a *Application) DeleteSpell(_ context.Context, spellName string) error {
fmt.Println("TEST - DeleteSpell", spellName)
return nil
}
func (a *Application) CastSpell(ctx context.Context, spellCast models.SpellCast) (models.CastResult, error) {
fmt.Println("TEST - CastSpell")
return (&caster.SpellCaster{}).Cast(ctx, spellCast)
}