Skip to content

Commit

Permalink
Implement a very basic Command struct (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
FollowTheProcess authored Jul 9, 2024
1 parent 94ce974 commit 20ff302
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 32 deletions.
30 changes: 22 additions & 8 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,34 +9,47 @@ tasks:
default:
desc: List all available tasks
silent: true
cmd: task --list
cmds:
- task --list

tidy:
desc: Tidy dependencies in go.mod and go.sum
cmd: go mod tidy
cmds:
- go mod tidy

fmt:
desc: Run go fmt on all source files
cmd: go fmt ./...
preconditions:
- sh: command -v golines
msg: golines not installed, see https://github.com/segmentio/golines
cmds:
- go fmt ./...
- golines . --chain-split-dots --ignore-generated --write-output

test:
desc: Run the test suite
cmd: go test -race ./... {{ .CLI_ARGS }}
cmds:
- go test -race ./... {{ .CLI_ARGS }}

bench:
desc: Run all project benchmarks
cmd: go test ./... -run None -benchmem -bench . {{ .CLI_ARGS }}
cmds:
- go test ./... -run None -benchmem -bench . {{ .CLI_ARGS }}

lint:
desc: Run the linters and auto-fix if possible
cmd: golangci-lint run --fix
deps:
- fmt
cmds:
- golangci-lint run --fix
preconditions:
- sh: command -v golangci-lint
msg: golangci-lint not installed, see https://golangci-lint.run/usage/install/#local-installation

doc:
desc: Render the pkg docs locally
cmd: pkgsite -open
cmds:
- pkgsite -open
preconditions:
- sh: command -v pkgsite
msg: pkgsite not installed, run go install golang.org/x/pkgsite/cmd/pkgsite@latest
Expand All @@ -57,7 +70,8 @@ tasks:

sloc:
desc: Print lines of code
cmd: fd . -e go | xargs wc -l | sort -nr | head
cmds:
- fd . -e go | xargs wc -l | sort -nr | head

clean:
desc: Remove build artifacts and other clutter
Expand Down
7 changes: 0 additions & 7 deletions cli.go

This file was deleted.

16 changes: 0 additions & 16 deletions cli_test.go

This file was deleted.

68 changes: 68 additions & 0 deletions command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Package cli provides a tiny, simple and minimalistic CLI framework for building Go CLI tools.
package cli

import (
"fmt"
"io"

"github.com/spf13/pflag"
)

// Command represents a CLI command.
type Command struct {
// Run is the function actually implementing the command, the command and arguments to it, are passed into the function, flags
// are parsed out before the arguments are passed to Run, so `args` here are the command line arguments minus flags.
Run func(cmd *Command, args []string) error

// flags is the set of flags for this command.
flags *pflag.FlagSet

// Stdin is an [io.Reader] from which command input is read.
//
// It defaults to [os.Stdin] but can be overridden as desired e.g. for testing.
Stdin io.Reader

// Stdout is an [io.Writer] to which normal command output is written.
//
// It defaults to [os.Stdout] but can be overridden as desired e.g. for testing.
Stdout io.Writer

// Stderr is an [io.Writer] to which error command output is written.
//
// It defaults to [os.Stderr] but can be overridden as desired e.g. for testing.
Stderr io.Writer

// Name is the name of the command.
Name string

// Short is the one line summary for the command, shown inline in the -h/--help output.
Short string

// Long is the long form description for the command, shown when -h/--help is called on the command itself.
Long string

// Example is examples of how to use the command, free form text.
Example string
}

// Execute parses the flags and arguments, and invokes the Command's Run
// function, returning any error.
//
// The arguments should not include the command name.
//
// If the flags fail to parse, an error will be returned and the Run function
// will not be called.
//
// err := cmd.Execute(os.Args[1:])
func (c *Command) Execute(args []string) error {
if c.flags == nil {
c.flags = pflag.NewFlagSet(c.Name, pflag.ExitOnError)
}
if err := c.flags.Parse(args); err != nil {
return fmt.Errorf("failed to parse command flags: %w", err)
}

argsWithoutFlags := c.flags.Args()

return c.Run(c, argsWithoutFlags)
}
33 changes: 33 additions & 0 deletions command_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package cli_test

import (
"bytes"
"fmt"
"testing"

"github.com/FollowTheProcess/cli"
"github.com/FollowTheProcess/test"
)

func TestExecute(t *testing.T) {
stderr := &bytes.Buffer{}
stdout := &bytes.Buffer{}

testCmd := &cli.Command{
Run: func(cmd *cli.Command, args []string) error {
fmt.Fprintf(cmd.Stdout, "Oooh look, it ran, here are some args: %v\n", args)
return nil
},
Stdout: stdout,
Stderr: stderr,
Name: "test",
Short: "A simple test command",
Long: "Much longer description blah blah blah",
}

err := testCmd.Execute([]string{"arg1", "arg2", "arg3"})
test.Ok(t, err)

want := "Oooh look, it ran, here are some args: [arg1 arg2 arg3]\n"
test.Equal(t, stdout.String(), want)
}
9 changes: 8 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
module github.com/FollowTheProcess/cli

go 1.22
go 1.22

require (
github.com/FollowTheProcess/test v0.9.0
github.com/spf13/pflag v1.0.5
)

require github.com/google/go-cmp v0.6.0 // indirect
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
github.com/FollowTheProcess/test v0.9.0 h1:X7/cqUi6qwbB8Z8PGHI5Hk4RJJF/wRZ+3Mko3QlL/VU=
github.com/FollowTheProcess/test v0.9.0/go.mod h1:9oxZcKkTAgz3bZMiHPtYCytdPcvICS+AAp5mzZzB2oA=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=

0 comments on commit 20ff302

Please sign in to comment.