Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@ require (
github.com/charmbracelet/x/windows v0.2.2 // indirect
github.com/clipperhouse/displaywidth v0.11.0 // indirect
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/lucasb-eyer/go-colorful v1.4.1 // indirect
github.com/mattn/go-runewidth v0.0.27 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/spf13/cobra v1.10.2 // indirect
github.com/spf13/pflag v1.0.9 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/sync v0.22.0 // indirect
golang.org/x/sys v0.47.0 // indirect
Expand Down
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSE
github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
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/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lucasb-eyer/go-colorful v1.4.1 h1:1EO+WB73+EH8EVbzlrG3KLAfEypQWVHIBqlTf+2hNss=
Expand All @@ -34,13 +37,20 @@ github.com/pelletier/go-toml/v2 v2.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdD
github.com/pelletier/go-toml/v2 v2.4.3/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sahilm/fuzzy v0.1.3 h1:juByESSS32nVD81vr6tHmKmA/8zde7gE+x5CLxrzXPU=
github.com/sahilm/fuzzy v0.1.3/go.mod h1:au6//VbVSqu6DFrkL2CfjlJ5iURpNCPeE+1GwY3XsT8=
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=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
124 changes: 100 additions & 24 deletions internal/cli/cli.go
Original file line number Diff line number Diff line change
@@ -1,40 +1,116 @@
// Package cli is the stub command-line entry point for cdd. It handles the
// "version" subcommand only; full CLI wiring (cobra, Root, Kind, Project,
// Jump, Visit, History, Scan, Picker, Wrapper commands) lands in a later
// ticket.
// Package cli wires cdd's command surface with github.com/spf13/cobra: the
// root command (bare behaves as pick), pick, init, scan, version, plus
// cobra's own help and completion commands.
package cli

import (
"errors"
"fmt"
"io"
"os"
)
"strings"

"github.com/spf13/cobra"

// version, commit, and date are set at build time via -ldflags. They default
// to values that make an unflagged build identify itself as a dev build.
var (
version = "dev"
commit = ""
date = ""
"github.com/kryft-dev/cdd/internal/jump"
)

// Execute runs the CLI and returns the process exit code. It handles
// "version" and prints a usage line to stderr for anything else.
// Execute runs the CLI against os.Args and returns the process exit code.
func Execute() int {
args := os.Args[1:]
if len(args) == 1 && args[0] == "version" {
fmt.Println(versionString())
return Run(os.Args[1:], os.Stdout, os.Stderr)
}

// Run parses args against the cdd command tree, writing to stdout and
// stderr, and returns the process exit code: 0 on success, 130 silently on
// a cancelled Picker (jump.ErrCancelled), 1 with a stderr message on any
// other error, 2 with a stderr message on a usage error.
//
// Run is exported, rather than Execute alone, so tests can drive the CLI
// in-process with cobra's SetArgs and captured stdout/stderr instead of
// exec'ing a built binary.
func Run(args []string, stdout, stderr io.Writer) int {
root := newRootCmd()
root.SetArgs(args)
root.SetOut(stdout)
root.SetErr(stderr)
root.SilenceUsage = true
root.SilenceErrors = true

err := root.Execute()
if err == nil {
return 0
}
if errors.Is(err, jump.ErrCancelled) {
return 130
}

fmt.Fprintln(os.Stderr, "usage: cdd version")
return 2
fmt.Fprintln(stderr, err)

Check failure on line 47 in internal/cli/cli.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `fmt.Fprintln` is not checked (errcheck)
if isUsageError(err) {
return 2
}
return 1
}

// versionString formats the version, commit, and date set by ldflags. When
// none of them were set, it reports "cdd dev (dev)".
func versionString() string {
if version == "dev" && commit == "" && date == "" {
return "cdd dev (dev)"
// newRootCmd builds the cdd command tree: root (bare behaves as pick),
// pick, init, scan, version, plus cobra's help and completion commands.
func newRootCmd() *cobra.Command {
root := &cobra.Command{
Use: "cdd",
Short: "cdd finds Projects under a Root and Jumps to the one you pick",
}
root.Flags().BoolP("version", "V", false, "print the cdd version and exit")

root.RunE = func(cmd *cobra.Command, args []string) error {
if v, _ := cmd.Flags().GetBool("version"); v {
fmt.Fprintln(cmd.OutOrStdout(), versionString())

Check failure on line 65 in internal/cli/cli.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `fmt.Fprintln` is not checked (errcheck)
return nil
}
return pickRunE(cmd, nil)
}

root.AddCommand(newPickCmd(), newInitCmd(), newScanCmd(), newVersionCmd())

// Initialized here, not left to Execute, so the command tree is
// complete (including "help" and "completion") whenever init's
// passthrough list is derived from it, even outside Execute.
root.InitDefaultHelpCmd()
root.InitDefaultCompletionCmd()

return root
}

// usageError marks err as a usage error: Run reports it with exit code 2
// instead of the default 1.
type usageError struct{ error }

// newUsageError wraps err so isUsageError recognizes it as a usage error.
func newUsageError(err error) error { return usageError{err} }

// isUsageError reports whether err should exit 2: either a command's own
// usageError, or one of cobra's own argument-parsing errors (an unknown
// command or an unknown flag).
func isUsageError(err error) bool {
var ue usageError
if errors.As(err, &ue) {
return true
}
msg := err.Error()
return strings.HasPrefix(msg, "unknown command") ||
strings.HasPrefix(msg, "unknown flag") ||
strings.HasPrefix(msg, "unknown shorthand flag") ||
strings.HasPrefix(msg, "accepts ")
}

// isTerminal reports whether w is a terminal, so pickRunE knows whether to
// print the "cdd init <shell>" hint.
func isTerminal(w io.Writer) bool {
f, ok := w.(*os.File)
if !ok {
return false
}
info, err := f.Stat()
if err != nil {
return false
}
return fmt.Sprintf("cdd %s (%s %s)", version, commit, date)
return info.Mode()&os.ModeCharDevice != 0
}
Loading
Loading