feat: migrate to use cobra as cli dep
This commit is contained in:
+11
-11
@@ -1,23 +1,23 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"miro/internal/miro"
|
||||
"miro/internal/output"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func runInit(args []string) int {
|
||||
if len(args) != 0 {
|
||||
output.Printf("unknown init option: %s\n", strings.Join(args, " "))
|
||||
return 1
|
||||
}
|
||||
|
||||
func newInitCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "init",
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(_ *cobra.Command, _ []string) error {
|
||||
if err := miro.Init(); err != nil {
|
||||
output.Printf("%v\n", err)
|
||||
return 1
|
||||
return err
|
||||
}
|
||||
|
||||
output.Println("Done initialising...")
|
||||
return 0
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
+11
-9
@@ -5,21 +5,23 @@ import (
|
||||
|
||||
"miro/internal/miro"
|
||||
"miro/internal/output"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func runRecord(args []string) int {
|
||||
if len(args) != 1 {
|
||||
output.Println("record requires exactly one path")
|
||||
return 1
|
||||
}
|
||||
|
||||
func newRecordCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "record <path>",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(_ *cobra.Command, args []string) error {
|
||||
path := filepath.Clean(args[0])
|
||||
createdPath, err := miro.Record(path)
|
||||
if err != nil {
|
||||
output.Printf("%v\n", err)
|
||||
return 1
|
||||
return err
|
||||
}
|
||||
|
||||
output.Println(createdPath)
|
||||
return 0
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
+21
-13
@@ -1,21 +1,29 @@
|
||||
package cmd
|
||||
|
||||
import "miro/internal/output"
|
||||
import "github.com/spf13/cobra"
|
||||
|
||||
// Run parses top-level CLI args and returns a process exit code.
|
||||
// Run executes the miro CLI and returns a process exit code.
|
||||
func Run(args []string) int {
|
||||
if len(args) == 0 {
|
||||
output.Println("unknown command")
|
||||
rootCmd := newRootCommand()
|
||||
rootCmd.SetArgs(args)
|
||||
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
switch args[0] {
|
||||
case "init":
|
||||
return runInit(args[1:])
|
||||
case "record":
|
||||
return runRecord(args[1:])
|
||||
default:
|
||||
output.Printf("unknown command: %s\n", args[0])
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func newRootCommand() *cobra.Command {
|
||||
rootCmd := &cobra.Command{
|
||||
Use: "miro",
|
||||
Short: "A lean CLI E2E testing framework.",
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
return cmd.Help()
|
||||
},
|
||||
}
|
||||
|
||||
rootCmd.AddCommand(newInitCommand(), newRecordCommand())
|
||||
|
||||
return rootCmd
|
||||
}
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"miro/internal/output"
|
||||
)
|
||||
|
||||
func TestRunShowsHelpWhenNoArgs(t *testing.T) {
|
||||
stdout, stderr := captureOutput(t, func() {
|
||||
if got := Run(nil); got != 0 {
|
||||
t.Fatalf("Run() code = %d, want %d", got, 0)
|
||||
}
|
||||
})
|
||||
|
||||
if !strings.Contains(stdout, "A lean CLI E2E testing framework.") {
|
||||
t.Fatalf("stdout = %q, want root help", stdout)
|
||||
}
|
||||
if !strings.Contains(stdout, "init") || !strings.Contains(stdout, "record") {
|
||||
t.Fatalf("stdout = %q, want listed subcommands", stdout)
|
||||
}
|
||||
if stderr != "" {
|
||||
t.Fatalf("stderr = %q, want empty", stderr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunInit(t *testing.T) {
|
||||
stdout, stderr := captureOutput(t, func() {
|
||||
if got := Run([]string{"init"}); got != 0 {
|
||||
t.Fatalf("Run() code = %d, want %d", got, 0)
|
||||
}
|
||||
})
|
||||
|
||||
if stdout != prefixed("Done initialising...\n") {
|
||||
t.Fatalf("stdout = %q, want %q", stdout, prefixed("Done initialising...\n"))
|
||||
}
|
||||
if stderr != "" {
|
||||
t.Fatalf("stderr = %q, want empty", stderr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunRecord(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
wantDir := filepath.Join(root, "e2e")
|
||||
if err := os.MkdirAll(wantDir, 0o755); err != nil {
|
||||
t.Fatalf("MkdirAll() error = %v", err)
|
||||
}
|
||||
|
||||
withWorkingDir(t, root, func() {
|
||||
stdout, stderr := captureOutput(t, func() {
|
||||
if got := Run([]string{"record", "suite/spec"}); got != 0 {
|
||||
t.Fatalf("Run() code = %d, want %d", got, 0)
|
||||
}
|
||||
})
|
||||
|
||||
createdPath := filepath.Join(wantDir, "suite", "spec")
|
||||
if stdout != prefixed(createdPath+"\n") {
|
||||
t.Fatalf("stdout = %q, want %q", stdout, prefixed(createdPath+"\n"))
|
||||
}
|
||||
if stderr != "" {
|
||||
t.Fatalf("stderr = %q, want empty", stderr)
|
||||
}
|
||||
|
||||
info, err := os.Stat(createdPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Stat() error = %v", err)
|
||||
}
|
||||
if !info.IsDir() {
|
||||
t.Fatalf("created path %q is not a directory", createdPath)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestRunRecordMissingPath(t *testing.T) {
|
||||
stdout, stderr := captureOutput(t, func() {
|
||||
if got := Run([]string{"record"}); got != 1 {
|
||||
t.Fatalf("Run() code = %d, want %d", got, 1)
|
||||
}
|
||||
})
|
||||
|
||||
if stdout != "" {
|
||||
t.Fatalf("stdout = %q, want empty", stdout)
|
||||
}
|
||||
if !strings.Contains(stderr, "accepts 1 arg(s), received 0") {
|
||||
t.Fatalf("stderr = %q, want argument error", stderr)
|
||||
}
|
||||
if !strings.Contains(stderr, "Usage:") || !strings.Contains(stderr, "miro record <path>") {
|
||||
t.Fatalf("stderr = %q, want record usage", stderr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunInitExtraArgs(t *testing.T) {
|
||||
stdout, stderr := captureOutput(t, func() {
|
||||
if got := Run([]string{"init", "extra"}); got != 1 {
|
||||
t.Fatalf("Run() code = %d, want %d", got, 1)
|
||||
}
|
||||
})
|
||||
|
||||
if stdout != "" {
|
||||
t.Fatalf("stdout = %q, want empty", stdout)
|
||||
}
|
||||
if !strings.Contains(stderr, "unknown command \"extra\" for \"miro init\"") {
|
||||
t.Fatalf("stderr = %q, want extra-arg error", stderr)
|
||||
}
|
||||
if !strings.Contains(stderr, "Usage:") || !strings.Contains(stderr, "miro init") {
|
||||
t.Fatalf("stderr = %q, want init usage", stderr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunUnknownCommand(t *testing.T) {
|
||||
stdout, stderr := captureOutput(t, func() {
|
||||
if got := Run([]string{"wat"}); got != 1 {
|
||||
t.Fatalf("Run() code = %d, want %d", got, 1)
|
||||
}
|
||||
})
|
||||
|
||||
if stdout != "" {
|
||||
t.Fatalf("stdout = %q, want empty", stdout)
|
||||
}
|
||||
if !strings.Contains(stderr, "unknown command \"wat\" for \"miro\"") {
|
||||
t.Fatalf("stderr = %q, want unknown command error", stderr)
|
||||
}
|
||||
}
|
||||
|
||||
func captureOutput(t *testing.T, fn func()) (string, string) {
|
||||
t.Helper()
|
||||
|
||||
stdoutReader, stdoutWriter, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatalf("os.Pipe() stdout error = %v", err)
|
||||
}
|
||||
stderrReader, stderrWriter, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatalf("os.Pipe() stderr error = %v", err)
|
||||
}
|
||||
|
||||
oldStdout := os.Stdout
|
||||
oldStderr := os.Stderr
|
||||
os.Stdout = stdoutWriter
|
||||
os.Stderr = stderrWriter
|
||||
t.Cleanup(func() {
|
||||
os.Stdout = oldStdout
|
||||
os.Stderr = oldStderr
|
||||
})
|
||||
|
||||
fn()
|
||||
|
||||
if err := stdoutWriter.Close(); err != nil {
|
||||
t.Fatalf("stdout close error = %v", err)
|
||||
}
|
||||
if err := stderrWriter.Close(); err != nil {
|
||||
t.Fatalf("stderr close error = %v", err)
|
||||
}
|
||||
|
||||
var stdoutBuf bytes.Buffer
|
||||
if _, err := io.Copy(&stdoutBuf, stdoutReader); err != nil {
|
||||
t.Fatalf("stdout copy error = %v", err)
|
||||
}
|
||||
|
||||
var stderrBuf bytes.Buffer
|
||||
if _, err := io.Copy(&stderrBuf, stderrReader); err != nil {
|
||||
t.Fatalf("stderr copy error = %v", err)
|
||||
}
|
||||
|
||||
return stdoutBuf.String(), stderrBuf.String()
|
||||
}
|
||||
|
||||
func prefixed(msg string) string {
|
||||
return output.Format(msg)
|
||||
}
|
||||
|
||||
func withWorkingDir(t *testing.T, dir string, fn func()) {
|
||||
t.Helper()
|
||||
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatalf("Getwd() error = %v", err)
|
||||
}
|
||||
if err := os.Chdir(dir); err != nil {
|
||||
t.Fatalf("Chdir(%q) error = %v", dir, err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
if err := os.Chdir(wd); err != nil {
|
||||
t.Fatalf("restore working directory: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
fn()
|
||||
}
|
||||
@@ -2,4 +2,12 @@ module miro
|
||||
|
||||
go 1.25.5
|
||||
|
||||
require github.com/BurntSushi/toml v1.5.0
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.5.0
|
||||
github.com/spf13/cobra v1.10.2
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.9 // indirect
|
||||
)
|
||||
|
||||
@@ -1,2 +1,12 @@
|
||||
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
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=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
Reference in New Issue
Block a user