refactor(cli): use kong for command parsing
This commit is contained in:
1
go.mod
1
go.mod
@@ -12,6 +12,7 @@ require (
|
|||||||
dario.cat/mergo v1.0.0 // indirect
|
dario.cat/mergo v1.0.0 // indirect
|
||||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||||
github.com/ProtonMail/go-crypto v1.1.5 // indirect
|
github.com/ProtonMail/go-crypto v1.1.5 // indirect
|
||||||
|
github.com/alecthomas/kong v1.12.1 // indirect
|
||||||
github.com/cloudflare/circl v1.6.0 // indirect
|
github.com/cloudflare/circl v1.6.0 // indirect
|
||||||
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
|
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -5,6 +5,8 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo
|
|||||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||||
github.com/ProtonMail/go-crypto v1.1.5 h1:eoAQfK2dwL+tFSFpr7TbOaPNUbPiJj4fLYwwGE1FQO4=
|
github.com/ProtonMail/go-crypto v1.1.5 h1:eoAQfK2dwL+tFSFpr7TbOaPNUbPiJj4fLYwwGE1FQO4=
|
||||||
github.com/ProtonMail/go-crypto v1.1.5/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
|
github.com/ProtonMail/go-crypto v1.1.5/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
|
||||||
|
github.com/alecthomas/kong v1.12.1 h1:iq6aMJDcFYP9uFrLdsiZQ2ZMmcshduyGv4Pek0MQPW0=
|
||||||
|
github.com/alecthomas/kong v1.12.1/go.mod h1:p2vqieVMeTAnaC83txKtXe8FLke2X07aruPWXyMPQrU=
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
@@ -8,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"git.hrafn.xyz/aether/gosick/internal/homesick/core"
|
"git.hrafn.xyz/aether/gosick/internal/homesick/core"
|
||||||
"git.hrafn.xyz/aether/gosick/internal/homesick/version"
|
"git.hrafn.xyz/aether/gosick/internal/homesick/version"
|
||||||
|
"github.com/alecthomas/kong"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Run(args []string, stdout io.Writer, stderr io.Writer) int {
|
func Run(args []string, stdout io.Writer, stderr io.Writer) int {
|
||||||
@@ -17,128 +19,219 @@ func Run(args []string, stdout io.Writer, stderr io.Writer) int {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(args) == 0 {
|
parser, err := kong.New(
|
||||||
printHelp(stdout)
|
&cliModel{},
|
||||||
return 0
|
kong.Name("homesick"),
|
||||||
}
|
kong.Description("Go scaffold"),
|
||||||
|
kong.Writers(stdout, stderr),
|
||||||
command := args[0]
|
kong.Exit(func(int) {}),
|
||||||
switch command {
|
kong.ConfigureHelp(kong.HelpOptions{Compact: true}),
|
||||||
case "-v", "--version", "version":
|
)
|
||||||
if err := app.Version(version.String); err != nil {
|
if err != nil {
|
||||||
_, _ = fmt.Fprintf(stderr, "error: %v\n", err)
|
_, _ = fmt.Fprintf(stderr, "error: %v\n", err)
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
case "help", "--help", "-h":
|
|
||||||
printHelp(stdout)
|
|
||||||
return 0
|
|
||||||
case "clone":
|
|
||||||
if len(args) < 2 {
|
|
||||||
_, _ = fmt.Fprintln(stderr, "error: clone requires URI")
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
destination := ""
|
|
||||||
if len(args) > 2 {
|
|
||||||
destination = args[2]
|
|
||||||
}
|
|
||||||
if err := app.Clone(args[1], destination); err != nil {
|
|
||||||
_, _ = fmt.Fprintf(stderr, "error: %v\n", err)
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
case "list":
|
|
||||||
if err := app.List(); err != nil {
|
|
||||||
_, _ = fmt.Fprintf(stderr, "error: %v\n", err)
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
case "show_path":
|
|
||||||
castle := defaultCastle(args)
|
|
||||||
if err := app.ShowPath(castle); err != nil {
|
|
||||||
_, _ = fmt.Fprintf(stderr, "error: %v\n", err)
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
case "status":
|
|
||||||
castle := defaultCastle(args)
|
|
||||||
if err := app.Status(castle); err != nil {
|
|
||||||
_, _ = fmt.Fprintf(stderr, "error: %v\n", err)
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
case "diff":
|
|
||||||
castle := defaultCastle(args)
|
|
||||||
if err := app.Diff(castle); err != nil {
|
|
||||||
_, _ = fmt.Fprintf(stderr, "error: %v\n", err)
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
case "link":
|
|
||||||
castle := defaultCastle(args)
|
|
||||||
if err := app.LinkCastle(castle); err != nil {
|
|
||||||
_, _ = fmt.Fprintf(stderr, "error: %v\n", err)
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
case "unlink":
|
|
||||||
castle := defaultCastle(args)
|
|
||||||
if err := app.Unlink(castle); err != nil {
|
|
||||||
_, _ = fmt.Fprintf(stderr, "error: %v\n", err)
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
case "track":
|
|
||||||
if len(args) < 2 || strings.TrimSpace(args[1]) == "" {
|
|
||||||
_, _ = fmt.Fprintln(stderr, "error: track requires FILE")
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
castle := "dotfiles"
|
|
||||||
if len(args) > 2 && strings.TrimSpace(args[2]) != "" {
|
|
||||||
castle = args[2]
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := app.Track(args[1], castle); err != nil {
|
|
||||||
_, _ = fmt.Fprintf(stderr, "error: %v\n", err)
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
case "pull", "push", "commit", "destroy", "cd", "open", "exec", "exec_all", "rc", "generate":
|
|
||||||
_, _ = fmt.Fprintf(stderr, "error: %s is not implemented in Go yet\n", command)
|
|
||||||
return 2
|
|
||||||
default:
|
|
||||||
_, _ = fmt.Fprintf(stderr, "error: unknown command %q\n\n", command)
|
|
||||||
printHelp(stderr)
|
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func defaultCastle(args []string) string {
|
normalizedArgs := normalizeArgs(args)
|
||||||
if len(args) > 1 && strings.TrimSpace(args[1]) != "" {
|
ctx, err := parser.Parse(normalizedArgs)
|
||||||
return args[1]
|
if err != nil {
|
||||||
|
var parseErr *kong.ParseError
|
||||||
|
if errors.As(err, &parseErr) {
|
||||||
|
if parseErr.ExitCode() == 0 || isHelpRequest(normalizedArgs) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
_, _ = fmt.Fprintf(stderr, "error: %v\n", err)
|
||||||
|
if parseErr.Context != nil {
|
||||||
|
_ = parseErr.Context.PrintUsage(false)
|
||||||
|
}
|
||||||
|
return parseErr.ExitCode()
|
||||||
|
}
|
||||||
|
_, _ = fmt.Fprintf(stderr, "error: %v\n", err)
|
||||||
|
return 1
|
||||||
}
|
}
|
||||||
return "dotfiles"
|
|
||||||
|
if err := ctx.Run(app); err != nil {
|
||||||
|
var exitErr *cliExitError
|
||||||
|
if errors.As(err, &exitErr) {
|
||||||
|
_, _ = fmt.Fprintf(stderr, "error: %v\n", exitErr.err)
|
||||||
|
return exitErr.code
|
||||||
|
}
|
||||||
|
_, _ = fmt.Fprintf(stderr, "error: %v\n", err)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func printHelp(w io.Writer) {
|
type cliModel struct {
|
||||||
_, _ = fmt.Fprintln(w, "homesick (Go scaffold)")
|
Clone cloneCmd `cmd:"" help:"Clone a castle."`
|
||||||
_, _ = fmt.Fprintln(w, "")
|
List listCmd `cmd:"" help:"List castles."`
|
||||||
_, _ = fmt.Fprintln(w, "Implemented commands:")
|
ShowPath showPathCmd `cmd:"" name:"show_path" help:"Show the path of a castle."`
|
||||||
_, _ = fmt.Fprintln(w, " clone URI [CASTLE_NAME]")
|
Status statusCmd `cmd:"" help:"Show git status for a castle."`
|
||||||
_, _ = fmt.Fprintln(w, " list")
|
Diff diffCmd `cmd:"" help:"Show git diff for a castle."`
|
||||||
_, _ = fmt.Fprintln(w, " show_path [CASTLE]")
|
Link linkCmd `cmd:"" help:"Symlink all dotfiles from a castle."`
|
||||||
_, _ = fmt.Fprintln(w, " status [CASTLE]")
|
Unlink unlinkCmd `cmd:"" help:"Unsymlink all dotfiles from a castle."`
|
||||||
_, _ = fmt.Fprintln(w, " diff [CASTLE]")
|
Track trackCmd `cmd:"" help:"Track a file in a castle."`
|
||||||
_, _ = fmt.Fprintln(w, " link [CASTLE]")
|
Version versionCmd `cmd:"" help:"Display the current version."`
|
||||||
_, _ = fmt.Fprintln(w, " unlink [CASTLE]")
|
Pull pullCmd `cmd:"" help:"Pull the specified castle."`
|
||||||
_, _ = fmt.Fprintln(w, " track FILE [CASTLE]")
|
Push pushCmd `cmd:"" help:"Push the specified castle."`
|
||||||
_, _ = fmt.Fprintln(w, " version | -v | --version")
|
Commit commitCmd `cmd:"" help:"Commit the specified castle's changes."`
|
||||||
_, _ = fmt.Fprintln(w, "")
|
Destroy destroyCmd `cmd:"" help:"Destroy a castle."`
|
||||||
_, _ = fmt.Fprintln(w, "Not implemented yet:")
|
Cd cdCmd `cmd:"" help:"Print the path to a castle."`
|
||||||
_, _ = fmt.Fprintln(w, " pull, push, commit, destroy, cd, open, exec, exec_all, rc, generate")
|
Open openCmd `cmd:"" help:"Open a castle."`
|
||||||
_, _ = fmt.Fprintln(w, "")
|
Exec execCmd `cmd:"" help:"Execute a command in a castle."`
|
||||||
_, _ = fmt.Fprintln(w, "Build: go build -o dist/homesick-go ./cmd/homesick")
|
ExecAll execAllCmd `cmd:"" name:"exec_all" help:"Execute a command in every castle."`
|
||||||
|
Rc rcCmd `cmd:"" help:"Run rc hooks for a castle."`
|
||||||
|
Generate generateCmd `cmd:"" help:"Generate a castle."`
|
||||||
|
}
|
||||||
|
|
||||||
|
type cloneCmd struct {
|
||||||
|
URI string `arg:"" name:"URI" help:"Castle URI to clone."`
|
||||||
|
Destination string `arg:"" optional:"" name:"CASTLE_NAME" help:"Optional local castle name."`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cloneCmd) Run(app *core.App) error {
|
||||||
|
return app.Clone(c.URI, c.Destination)
|
||||||
|
}
|
||||||
|
|
||||||
|
type listCmd struct{}
|
||||||
|
|
||||||
|
func (c *listCmd) Run(app *core.App) error {
|
||||||
|
return app.List()
|
||||||
|
}
|
||||||
|
|
||||||
|
type showPathCmd struct {
|
||||||
|
Castle string `arg:"" optional:"" name:"CASTLE" help:"Castle name."`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *showPathCmd) Run(app *core.App) error {
|
||||||
|
return app.ShowPath(defaultCastle(c.Castle))
|
||||||
|
}
|
||||||
|
|
||||||
|
type statusCmd struct {
|
||||||
|
Castle string `arg:"" optional:"" name:"CASTLE" help:"Castle name."`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *statusCmd) Run(app *core.App) error {
|
||||||
|
return app.Status(defaultCastle(c.Castle))
|
||||||
|
}
|
||||||
|
|
||||||
|
type diffCmd struct {
|
||||||
|
Castle string `arg:"" optional:"" name:"CASTLE" help:"Castle name."`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *diffCmd) Run(app *core.App) error {
|
||||||
|
return app.Diff(defaultCastle(c.Castle))
|
||||||
|
}
|
||||||
|
|
||||||
|
type linkCmd struct {
|
||||||
|
Castle string `arg:"" optional:"" name:"CASTLE" help:"Castle name."`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *linkCmd) Run(app *core.App) error {
|
||||||
|
return app.LinkCastle(defaultCastle(c.Castle))
|
||||||
|
}
|
||||||
|
|
||||||
|
type unlinkCmd struct {
|
||||||
|
Castle string `arg:"" optional:"" name:"CASTLE" help:"Castle name."`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *unlinkCmd) Run(app *core.App) error {
|
||||||
|
return app.Unlink(defaultCastle(c.Castle))
|
||||||
|
}
|
||||||
|
|
||||||
|
type trackCmd struct {
|
||||||
|
File string `arg:"" name:"FILE" help:"File to track."`
|
||||||
|
Castle string `arg:"" optional:"" name:"CASTLE" help:"Castle name."`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *trackCmd) Run(app *core.App) error {
|
||||||
|
return app.Track(c.File, defaultCastle(c.Castle))
|
||||||
|
}
|
||||||
|
|
||||||
|
type versionCmd struct{}
|
||||||
|
|
||||||
|
func (c *versionCmd) Run(app *core.App) error {
|
||||||
|
return app.Version(version.String)
|
||||||
|
}
|
||||||
|
|
||||||
|
type pullCmd struct{}
|
||||||
|
|
||||||
|
type pushCmd struct{}
|
||||||
|
|
||||||
|
type commitCmd struct{}
|
||||||
|
|
||||||
|
type destroyCmd struct{}
|
||||||
|
|
||||||
|
type cdCmd struct{}
|
||||||
|
|
||||||
|
type openCmd struct{}
|
||||||
|
|
||||||
|
type execCmd struct{}
|
||||||
|
|
||||||
|
type execAllCmd struct{}
|
||||||
|
|
||||||
|
type rcCmd struct{}
|
||||||
|
|
||||||
|
type generateCmd struct{}
|
||||||
|
|
||||||
|
func (c *pullCmd) Run() error { return notImplemented("pull") }
|
||||||
|
func (c *pushCmd) Run() error { return notImplemented("push") }
|
||||||
|
func (c *commitCmd) Run() error { return notImplemented("commit") }
|
||||||
|
func (c *destroyCmd) Run() error { return notImplemented("destroy") }
|
||||||
|
func (c *cdCmd) Run() error { return notImplemented("cd") }
|
||||||
|
func (c *openCmd) Run() error { return notImplemented("open") }
|
||||||
|
func (c *execCmd) Run() error { return notImplemented("exec") }
|
||||||
|
func (c *execAllCmd) Run() error { return notImplemented("exec_all") }
|
||||||
|
func (c *rcCmd) Run() error { return notImplemented("rc") }
|
||||||
|
func (c *generateCmd) Run() error { return notImplemented("generate") }
|
||||||
|
|
||||||
|
func defaultCastle(castle string) string {
|
||||||
|
if strings.TrimSpace(castle) == "" {
|
||||||
|
return "dotfiles"
|
||||||
|
}
|
||||||
|
return castle
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeArgs(args []string) []string {
|
||||||
|
if len(args) == 0 {
|
||||||
|
return []string{"--help"}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch args[0] {
|
||||||
|
case "-h", "--help":
|
||||||
|
return []string{"--help"}
|
||||||
|
case "help":
|
||||||
|
if len(args) == 1 {
|
||||||
|
return []string{"--help"}
|
||||||
|
}
|
||||||
|
return append(args[1:], "--help")
|
||||||
|
case "-v", "--version":
|
||||||
|
return []string{"version"}
|
||||||
|
default:
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isHelpRequest(args []string) bool {
|
||||||
|
for _, arg := range args {
|
||||||
|
if arg == "-h" || arg == "--help" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type cliExitError struct {
|
||||||
|
code int
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *cliExitError) Error() string {
|
||||||
|
return e.err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func notImplemented(command string) error {
|
||||||
|
return &cliExitError{code: 2, err: fmt.Errorf("%s is not implemented in Go yet", command)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|||||||
Reference in New Issue
Block a user