Skip to content

Commit ea4dba8

Browse files
committed
refactor: extract piping utilities to shared pkg/cmd/util/piping.go
- Consolidate duplicate isStdoutPiped() from delete, ls, start, stop - Consolidate duplicate getInstanceNames() from delete, stop - Consolidate duplicate getInstanceNamesFromStdin() from start - Create IsStdoutPiped(), IsStdinPiped(), GetInstanceNames(), GetInstanceNamesWithPipeInfo() - Remove ~108 lines of duplicated code
1 parent 3ddae0a commit ea4dba8

5 files changed

Lines changed: 72 additions & 127 deletions

File tree

pkg/cmd/delete/delete.go

Lines changed: 2 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
package delete
22

33
import (
4-
"bufio"
54
_ "embed"
65
"fmt"
7-
"os"
86
"strings"
97

108
"github.com/brevdev/brev-cli/pkg/cmd/completions"
@@ -39,8 +37,8 @@ func NewCmdDelete(t *terminal.Terminal, loginDeleteStore DeleteStore, noLoginDel
3937
Example: deleteExample,
4038
ValidArgsFunction: completions.GetAllWorkspaceNameCompletionHandler(noLoginDeleteStore, t),
4139
RunE: func(cmd *cobra.Command, args []string) error {
42-
piped := isStdoutPiped()
43-
names, err := getInstanceNames(args)
40+
piped := util.IsStdoutPiped()
41+
names, err := util.GetInstanceNames(args)
4442
if err != nil {
4543
return err
4644
}
@@ -119,36 +117,3 @@ func handleAdminUser(err error, deleteStore DeleteStore, piped bool) error {
119117

120118
return nil
121119
}
122-
123-
// isStdoutPiped returns true if stdout is being piped to another command
124-
func isStdoutPiped() bool {
125-
stat, _ := os.Stdout.Stat()
126-
return (stat.Mode() & os.ModeCharDevice) == 0
127-
}
128-
129-
// getInstanceNames gets instance names from args or stdin (supports piping)
130-
func getInstanceNames(args []string) ([]string, error) {
131-
var names []string
132-
133-
// Add names from args
134-
names = append(names, args...)
135-
136-
// Check if stdin is piped
137-
stat, _ := os.Stdin.Stat()
138-
if (stat.Mode() & os.ModeCharDevice) == 0 {
139-
// Stdin is piped, read instance names (one per line)
140-
scanner := bufio.NewScanner(os.Stdin)
141-
for scanner.Scan() {
142-
name := strings.TrimSpace(scanner.Text())
143-
if name != "" {
144-
names = append(names, name)
145-
}
146-
}
147-
}
148-
149-
if len(names) == 0 {
150-
return nil, breverrors.NewValidationError("instance name required: provide as argument or pipe from another command")
151-
}
152-
153-
return names, nil
154-
}

pkg/cmd/ls/ls.go

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010
"github.com/brevdev/brev-cli/pkg/cmd/cmderrors"
1111
"github.com/brevdev/brev-cli/pkg/cmd/completions"
1212
"github.com/brevdev/brev-cli/pkg/cmd/hello"
13-
utilities "github.com/brevdev/brev-cli/pkg/cmd/util"
13+
cmdutil "github.com/brevdev/brev-cli/pkg/cmd/util"
1414
"github.com/brevdev/brev-cli/pkg/cmdcontext"
1515
"github.com/brevdev/brev-cli/pkg/config"
1616
"github.com/brevdev/brev-cli/pkg/entity"
@@ -102,7 +102,7 @@ with other commands like stop, start, or delete.`,
102102
ValidArgs: []string{"orgs", "workspaces"},
103103
RunE: func(cmd *cobra.Command, args []string) error {
104104
// Auto-switch to names-only output when piped (for chaining with stop/start/delete)
105-
piped := isStdoutPiped()
105+
piped := cmdutil.IsStdoutPiped()
106106

107107
err := RunLs(t, loginLsStore, args, org, showAll, jsonOutput, piped)
108108
if err != nil {
@@ -129,12 +129,6 @@ with other commands like stop, start, or delete.`,
129129
return cmd
130130
}
131131

132-
// isStdoutPiped returns true if stdout is being piped to another command
133-
func isStdoutPiped() bool {
134-
stat, _ := os.Stdout.Stat()
135-
return (stat.Mode() & os.ModeCharDevice) == 0
136-
}
137-
138132
// trackLsAnalytics sends analytics event for ls command
139133
func trackLsAnalytics(store LsStore) {
140134
userID := ""
@@ -550,7 +544,7 @@ func displayWorkspacesTable(t *terminal.Terminal, workspaces []entity.Workspace)
550544
ta.AppendHeader(header)
551545
for _, w := range workspaces {
552546
status := getWorkspaceDisplayStatus(w)
553-
instanceString := utilities.GetInstanceString(w)
547+
instanceString := cmdutil.GetInstanceString(w)
554548
workspaceRow := []table.Row{{w.Name, getStatusColoredText(t, status), getStatusColoredText(t, string(w.VerbBuildStatus)), getStatusColoredText(t, getShellDisplayStatus(w)), w.ID, instanceString}}
555549
ta.AppendRows(workspaceRow)
556550
}
@@ -567,7 +561,7 @@ func displayWorkspacesTablePlain(workspaces []entity.Workspace) {
567561
ta.AppendHeader(header)
568562
for _, w := range workspaces {
569563
status := getWorkspaceDisplayStatus(w)
570-
instanceString := utilities.GetInstanceString(w)
564+
instanceString := cmdutil.GetInstanceString(w)
571565
workspaceRow := []table.Row{{w.Name, status, string(w.VerbBuildStatus), getShellDisplayStatus(w), w.ID, instanceString}}
572566
ta.AppendRows(workspaceRow)
573567
}

pkg/cmd/start/start.go

Lines changed: 11 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,24 @@
22
package start
33

44
import (
5-
"bufio"
65
"fmt"
76
"net/url"
8-
"os"
97
"path/filepath"
108
"strings"
119
"time"
1210

1311
"github.com/brevdev/brev-cli/pkg/cmd/completions"
14-
"github.com/brevdev/brev-cli/pkg/cmd/util"
12+
cmdutil "github.com/brevdev/brev-cli/pkg/cmd/util"
1513
"github.com/brevdev/brev-cli/pkg/config"
1614
"github.com/brevdev/brev-cli/pkg/entity"
15+
breverrors "github.com/brevdev/brev-cli/pkg/errors"
1716
"github.com/brevdev/brev-cli/pkg/featureflag"
1817
"github.com/brevdev/brev-cli/pkg/instancetypes"
1918
"github.com/brevdev/brev-cli/pkg/mergeshells"
2019
"github.com/brevdev/brev-cli/pkg/store"
2120
"github.com/brevdev/brev-cli/pkg/terminal"
22-
allutil "github.com/brevdev/brev-cli/pkg/util"
21+
"github.com/brevdev/brev-cli/pkg/util"
2322
"github.com/spf13/cobra"
24-
25-
breverrors "github.com/brevdev/brev-cli/pkg/errors"
2623
)
2724

2825
var (
@@ -36,7 +33,7 @@ var (
3633
)
3734

3835
type StartStore interface {
39-
util.GetWorkspaceByNameOrIDErrStore
36+
cmdutil.GetWorkspaceByNameOrIDErrStore
4037
GetWorkspaces(organizationID string, options *store.GetWorkspacesOptions) ([]entity.Workspace, error)
4138
GetActiveOrganizationOrDefault() (*entity.Organization, error)
4239
GetCurrentUser() (*entity.User, error)
@@ -68,8 +65,8 @@ func NewCmdStart(t *terminal.Terminal, startStore StartStore, noLoginStartStore
6865
Example: startExample,
6966
ValidArgsFunction: completions.GetAllWorkspaceNameCompletionHandler(noLoginStartStore, t),
7067
RunE: func(cmd *cobra.Command, args []string) error {
71-
piped := isStdoutPiped()
72-
names, stdinPiped := getInstanceNamesFromStdin(args)
68+
piped := cmdutil.IsStdoutPiped()
69+
names, stdinPiped := cmdutil.GetInstanceNamesWithPipeInfo(args)
7370

7471
if gpu != "" {
7572
isValid := instancetypes.ValidateInstanceType(gpu)
@@ -162,7 +159,7 @@ func runStartWorkspace(t *terminal.Terminal, options StartOptions, startStore St
162159
}
163160

164161
func maybeStartWithLocalPath(options StartOptions, user *entity.User, t *terminal.Terminal, startStore StartStore) (bool, error) {
165-
if allutil.DoesPathExist(options.RepoOrPathOrNameOrID) {
162+
if util.DoesPathExist(options.RepoOrPathOrNameOrID) {
166163
err := startWorkspaceFromPath(user, t, options, startStore)
167164
if err != nil {
168165
return false, breverrors.WrapAndTrace(err)
@@ -194,7 +191,7 @@ func maybeStartStoppedOrJoin(t *terminal.Terminal, user *entity.User, options St
194191
return false, breverrors.NewValidationError(fmt.Sprintf("workspace with id/name %s is a failed workspace", options.RepoOrPathOrNameOrID))
195192
}
196193
}
197-
if allutil.DoesPathExist(options.RepoOrPathOrNameOrID) {
194+
if util.DoesPathExist(options.RepoOrPathOrNameOrID) {
198195
t.Print(t.Yellow(fmt.Sprintf("Warning: local path found and instance name/id found %s. Using instance name/id. If you meant to specify a local path change directory and try again.", options.RepoOrPathOrNameOrID)))
199196
}
200197
errr := startStopppedWorkspace(&userWorkspaces[0], startStore, t, options)
@@ -215,7 +212,7 @@ func maybeStartStoppedOrJoin(t *terminal.Terminal, user *entity.User, options St
215212
}
216213

217214
func maybeStartFromGitURL(t *terminal.Terminal, user *entity.User, options StartOptions, startStore StartStore) (bool, error) {
218-
if allutil.IsGitURL(options.RepoOrPathOrNameOrID) { // todo this is function is not complete, some cloneable urls are not identified
215+
if util.IsGitURL(options.RepoOrPathOrNameOrID) { // todo this is function is not complete, some cloneable urls are not identified
219216
err := createNewWorkspaceFromGit(user, t, options.SetupScript, options, startStore)
220217
if err != nil {
221218
return true, breverrors.WrapAndTrace(err)
@@ -237,7 +234,7 @@ func maybeStartEmpty(t *terminal.Terminal, user *entity.User, options StartOptio
237234
}
238235

239236
func startWorkspaceFromPath(user *entity.User, t *terminal.Terminal, options StartOptions, startStore StartStore) error {
240-
pathExists := allutil.DoesPathExist(options.RepoOrPathOrNameOrID)
237+
pathExists := util.DoesPathExist(options.RepoOrPathOrNameOrID)
241238
if !pathExists {
242239
return fmt.Errorf("Path: %s does not exist", options.RepoOrPathOrNameOrID)
243240
}
@@ -267,7 +264,7 @@ func startWorkspaceFromPath(user *entity.User, t *terminal.Terminal, options Sta
267264
if options.RepoOrPathOrNameOrID == "." {
268265
localSetupPath = filepath.Join(".brev", "setup.sh")
269266
}
270-
if !allutil.DoesPathExist(localSetupPath) {
267+
if !util.DoesPathExist(localSetupPath) {
271268
fmt.Println(strings.Join([]string{"Generating setup script at", localSetupPath}, "\n"))
272269
mergeshells.ImportPath(t, options.RepoOrPathOrNameOrID, startStore)
273270
fmt.Println("setup script generated.")
@@ -679,35 +676,6 @@ func pollUntil(t *terminal.Terminal, wsid string, state string, startStore Start
679676
return nil
680677
}
681678

682-
// isStdoutPiped returns true if stdout is being piped to another command
683-
func isStdoutPiped() bool {
684-
stat, _ := os.Stdout.Stat()
685-
return (stat.Mode() & os.ModeCharDevice) == 0
686-
}
687-
688-
// getInstanceNamesFromStdin returns instance names from args and stdin if piped
689-
// Returns the names and whether stdin was piped
690-
func getInstanceNamesFromStdin(args []string) ([]string, bool) {
691-
var names []string
692-
names = append(names, args...)
693-
694-
// Check if stdin is piped
695-
stat, _ := os.Stdin.Stat()
696-
stdinPiped := (stat.Mode() & os.ModeCharDevice) == 0
697-
698-
if stdinPiped {
699-
scanner := bufio.NewScanner(os.Stdin)
700-
for scanner.Scan() {
701-
name := strings.TrimSpace(scanner.Text())
702-
if name != "" {
703-
names = append(names, name)
704-
}
705-
}
706-
}
707-
708-
return names, stdinPiped
709-
}
710-
711679
// runBatchStart handles starting multiple instances when stdin is piped
712680
func runBatchStart(t *terminal.Terminal, names []string, org, setupScript, setupRepo, setupPath, cpu, gpu string, piped bool, startStore StartStore) error {
713681
var startedNames []string

pkg/cmd/stop/stop.go

Lines changed: 2 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22
package stop
33

44
import (
5-
"bufio"
65
"fmt"
7-
"os"
86
"strings"
97

108
"github.com/brevdev/brev-cli/pkg/cmd/completions"
@@ -45,11 +43,11 @@ func NewCmdStop(t *terminal.Terminal, loginStopStore StopStore, noLoginStopStore
4543
// Args: cmderrors.TransformToValidationError(cobra.ExactArgs()),
4644
ValidArgsFunction: completions.GetAllWorkspaceNameCompletionHandler(noLoginStopStore, t),
4745
RunE: func(cmd *cobra.Command, args []string) error {
48-
piped := isStdoutPiped()
46+
piped := util.IsStdoutPiped()
4947
if all {
5048
return stopAllWorkspaces(t, loginStopStore, piped)
5149
} else {
52-
names, err := getInstanceNames(args)
50+
names, err := util.GetInstanceNames(args)
5351
if err != nil {
5452
return err
5553
}
@@ -174,36 +172,3 @@ func stopWorkspace(workspaceName string, t *terminal.Terminal, stopStore StopSto
174172

175173
return nil
176174
}
177-
178-
// isStdoutPiped returns true if stdout is being piped to another command
179-
func isStdoutPiped() bool {
180-
stat, _ := os.Stdout.Stat()
181-
return (stat.Mode() & os.ModeCharDevice) == 0
182-
}
183-
184-
// getInstanceNames gets instance names from args or stdin (supports piping)
185-
func getInstanceNames(args []string) ([]string, error) {
186-
var names []string
187-
188-
// Add names from args
189-
names = append(names, args...)
190-
191-
// Check if stdin is piped
192-
stat, _ := os.Stdin.Stat()
193-
if (stat.Mode() & os.ModeCharDevice) == 0 {
194-
// Stdin is piped, read instance names (one per line)
195-
scanner := bufio.NewScanner(os.Stdin)
196-
for scanner.Scan() {
197-
name := strings.TrimSpace(scanner.Text())
198-
if name != "" {
199-
names = append(names, name)
200-
}
201-
}
202-
}
203-
204-
if len(names) == 0 {
205-
return nil, breverrors.NewValidationError("instance name required: provide as argument or pipe from another command")
206-
}
207-
208-
return names, nil
209-
}

pkg/cmd/util/piping.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package util
2+
3+
import (
4+
"bufio"
5+
"os"
6+
"strings"
7+
8+
breverrors "github.com/brevdev/brev-cli/pkg/errors"
9+
)
10+
11+
// IsStdoutPiped returns true if stdout is being piped to another command
12+
// Enables command chaining like: brev ls | grep RUNNING | brev stop
13+
func IsStdoutPiped() bool {
14+
stat, _ := os.Stdout.Stat()
15+
return (stat.Mode() & os.ModeCharDevice) == 0
16+
}
17+
18+
// IsStdinPiped returns true if stdin is being piped from another command
19+
func IsStdinPiped() bool {
20+
stat, _ := os.Stdin.Stat()
21+
return (stat.Mode() & os.ModeCharDevice) == 0
22+
}
23+
24+
// GetInstanceNames gets instance names from args or stdin (supports piping)
25+
// Returns error if no names are provided
26+
func GetInstanceNames(args []string) ([]string, error) {
27+
names, _ := GetInstanceNamesWithPipeInfo(args)
28+
if len(names) == 0 {
29+
return nil, breverrors.NewValidationError("instance name required: provide as argument or pipe from another command")
30+
}
31+
return names, nil
32+
}
33+
34+
// GetInstanceNamesWithPipeInfo gets instance names from args or stdin and
35+
// returns whether stdin was piped. Useful when you need to know if input came
36+
// from a pipe vs args.
37+
func GetInstanceNamesWithPipeInfo(args []string) ([]string, bool) {
38+
var names []string
39+
names = append(names, args...)
40+
41+
stdinPiped := IsStdinPiped()
42+
if stdinPiped {
43+
scanner := bufio.NewScanner(os.Stdin)
44+
for scanner.Scan() {
45+
name := strings.TrimSpace(scanner.Text())
46+
if name != "" {
47+
names = append(names, name)
48+
}
49+
}
50+
}
51+
52+
return names, stdinPiped
53+
}

0 commit comments

Comments
 (0)