chore: cli refactor
This commit is contained in:
1 parent
181642172a
commit
99264ca438
8 files changed
+27
-50
No files matched your search
+59
@@ -0,0 +1,59 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/abelith/spellbook/internal/errors"
|
||||
"github.com/abelith/spellbook/internal/models"
|
||||
"github.com/spf13/cobra"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var reservedCommands = map[string]struct{}{
|
||||
"add": {},
|
||||
"rm": {},
|
||||
"help": {},
|
||||
"completion": {},
|
||||
"search": {},
|
||||
"export": {},
|
||||
"import": {},
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
if len(args) < 1 {
|
||||
return errors.ErrInsufficientArgs
|
||||
}
|
||||
|
||||
if isReservedCommand(args[0]) {
|
||||
return fmt.Errorf("%w: %s", errors.ErrReservedCmd, args[0])
|
||||
}
|
||||
|
||||
spell.Spell = strings.Join(args, " ")
|
||||
|
||||
if spell.Name == "" {
|
||||
return fmt.Errorf("%w: --name is missing", errors.ErrInsufficientArgs)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func isReservedCommand(cmd string) bool {
|
||||
_, reserved := reservedCommands[strings.ToLower(cmd)]
|
||||
return reserved
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
_ "github.com/abelith/spellbook/internal/adapters/json"
|
||||
"github.com/spf13/cobra"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
func NewExportCmd(app Application) *cobra.Command {
|
||||
var format string
|
||||
var out string
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "export",
|
||||
Short: "Export spellbook",
|
||||
Long: `WIP`,
|
||||
RunE: func(cmd *cobra.Command, args []string) (err error) {
|
||||
ctx := cmd.Context()
|
||||
|
||||
var w io.Writer = os.Stdout
|
||||
if out != "" {
|
||||
f, err := os.OpenFile(out, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if innerErr := f.Close(); innerErr != nil {
|
||||
err = errors.Join(err, innerErr)
|
||||
}
|
||||
}()
|
||||
w = f
|
||||
}
|
||||
|
||||
buf := bufio.NewWriter(w)
|
||||
defer func() {
|
||||
if innerErr := buf.Flush(); innerErr != nil {
|
||||
err = errors.Join(err, innerErr)
|
||||
}
|
||||
}()
|
||||
|
||||
return app.ExportSpells(ctx, format, buf)
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&format, "format", "f", "jsonl", "Output format")
|
||||
cmd.Flags().StringVarP(&out, "out", "o", "", "Output")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
_ "github.com/abelith/spellbook/internal/adapters/json"
|
||||
"github.com/spf13/cobra"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
func NewImportCmd(app Application) *cobra.Command {
|
||||
var format string
|
||||
var in string
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "import",
|
||||
Short: "Import spellbook",
|
||||
Long: `WIP`,
|
||||
RunE: func(cmd *cobra.Command, args []string) (err error) {
|
||||
ctx := cmd.Context()
|
||||
|
||||
var r io.Reader = os.Stdin
|
||||
if in != "" {
|
||||
f, err := os.Open(in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if innerErr := f.Close(); innerErr != nil {
|
||||
err = errors.Join(err, innerErr)
|
||||
}
|
||||
}()
|
||||
r = f
|
||||
}
|
||||
|
||||
buf := bufio.NewReader(r)
|
||||
return app.ImportSpells(ctx, format, buf)
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&format, "format", "f", "jsonl", "Input format")
|
||||
cmd.Flags().StringVarP(&in, "input", "i", "", "Input")
|
||||
|
||||
return cmd
|
||||
}
|
||||
-52
@@ -1,52 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/abelith/spellbook/internal/adapters/caster"
|
||||
"github.com/abelith/spellbook/internal/adapters/sqlite"
|
||||
"github.com/abelith/spellbook/internal/application"
|
||||
"github.com/abelith/spellbook/internal/config"
|
||||
"github.com/abelith/spellbook/internal/controllers/cli"
|
||||
_ "modernc.org/sqlite"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// Version is set on build via -ldflags
|
||||
var Version = "dev"
|
||||
|
||||
func handleErr(err error) {
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||
defer cancel()
|
||||
|
||||
cfg, err := config.NewConfig()
|
||||
handleErr(err)
|
||||
|
||||
dir := filepath.Dir(cfg.DBPath)
|
||||
handleErr(os.MkdirAll(dir, 0755))
|
||||
|
||||
db, err := sql.Open("sqlite", cfg.DBPath)
|
||||
handleErr(err)
|
||||
|
||||
repo := sqlite.NewRepo(db)
|
||||
handleErr(repo.Init(ctx))
|
||||
|
||||
app := application.NewApplication(&caster.SpellCaster{}, repo, repo)
|
||||
|
||||
c := cli.NewCLI(Version, app)
|
||||
|
||||
if err := c.Run(ctx); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/abelith/spellbook/internal/errors"
|
||||
"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()
|
||||
|
||||
if len(args) < 1 {
|
||||
return fmt.Errorf("%w: %s", errors.ErrInsufficientArgs, "spell_name")
|
||||
}
|
||||
|
||||
return app.DeleteSpell(ctx, args[0])
|
||||
},
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/abelith/spellbook/internal/models"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
"github.com/spf13/cobra"
|
||||
"io"
|
||||
"os"
|
||||
"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)
|
||||
ExportSpells(ctx context.Context, exp string, w io.Writer) error
|
||||
ImportSpells(ctx context.Context, exp string, r io.Reader) 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 nil
|
||||
}
|
||||
|
||||
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(ver string, 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,
|
||||
Version: ver,
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/abelith/spellbook/internal/errors"
|
||||
"github.com/abelith/spellbook/internal/models"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewSearchCmd(app Application) *cobra.Command {
|
||||
query := models.SearchQuery{}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "search [query]",
|
||||
Short: "Search for spells",
|
||||
Long: "WIP",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
if query.IsEmpty() {
|
||||
return errors.ErrEmptyQuery
|
||||
}
|
||||
|
||||
if len(args) > 0 {
|
||||
query.Text = args[0]
|
||||
}
|
||||
|
||||
spells, err := app.GetSpells(ctx, query)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(spells) < 1 {
|
||||
return fmt.Errorf("%w: spells", errors.ErrNotFound)
|
||||
}
|
||||
|
||||
return printTable(cmd, spells)
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&query.Name, "name", "n", "", "Spell name")
|
||||
cmd.Flags().StringVarP(&query.Command, "cmd", "c", "", "Spell command template")
|
||||
cmd.Flags().StringVarP(&query.Description, "desc", "d", "", "Spell description")
|
||||
cmd.Flags().StringSliceVarP(&query.Tags, "tags", "t", nil, "Spell tags")
|
||||
|
||||
return cmd
|
||||
}
|
||||
Reference in new issue
Block a user