Skip to content

Commit 4734c79

Browse files
committed
feat(pee): Add all flags and improve early exit behavior
1 parent 1189fc2 commit 4734c79

File tree

6 files changed

+128
-22
lines changed

6 files changed

+128
-22
lines changed

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,5 +165,4 @@ My goal is 100% compatability, but there are currently some differences compared
165165
| **isutf8** | Unlike moreutils, which prints the expected value range for non-UTF-8 files, the rewrite only logs the offending line, byte, and char. |
166166
| **lckdo** | Deprecated in moreutils and intentionally not implemented here. It is recommended to use `flock` as a replacement. |
167167
| **parallel** | The `-l` flag is not yet supported. Also note parallel is not symlinked by default since [GNU Parallel](https://www.gnu.org/software/parallel/) is typically preferred. |
168-
| **pee** | The flags `--no-ignore-sigpipe` and `--no-ignore-write-errors` are not yet supported. |
169168
| **ts** | The flags `-r`, `-i`, and `-s` are not yet supported. The `-m` flag will trigger a deprecation warning since Go always uses the system's monotonic clock. |

cmd/pee/pee.go

Lines changed: 66 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,23 @@ package pee
33
import (
44
"io"
55
"os/exec"
6+
"os/signal"
7+
"sync"
8+
"syscall"
69

710
"github.com/gabe565/moreutils/internal/cmdutil"
811
"github.com/gabe565/moreutils/internal/util"
912
"github.com/spf13/cobra"
13+
flag "github.com/spf13/pflag"
1014
)
1115

1216
const (
13-
Name = "pee"
14-
FlagIgnoreSigpipe = "ignore-sigpipe"
15-
FlagIgnoreWriteErrors = "ignore-write-errors"
17+
Name = "pee"
18+
19+
FlagIgnoreSigpipe = "ignore-sigpipe"
20+
FlagNoIgnoreSigpipe = "no-ignore-sigpipe"
21+
FlagIgnoreWriteErrors = "ignore-write-errors"
22+
FlagNoIgnoreWriteErrors = "no-ignore-write-errors"
1623
)
1724

1825
func New(opts ...cmdutil.Option) *cobra.Command {
@@ -24,8 +31,10 @@ func New(opts ...cmdutil.Option) *cobra.Command {
2431
GroupID: cmdutil.Applet,
2532
}
2633

27-
cmd.Flags().Bool(FlagIgnoreSigpipe, true, "Ignores sigpipe")
28-
cmd.Flags().Bool(FlagIgnoreWriteErrors, true, "Ignores write errors")
34+
cmd.Flags().Bool(FlagIgnoreSigpipe, true, "")
35+
cmd.Flags().Bool(FlagIgnoreWriteErrors, true, "")
36+
cmd.Flags().Bool(FlagNoIgnoreSigpipe, false, "Do not ignore write errors")
37+
cmd.Flags().Bool(FlagNoIgnoreWriteErrors, false, "Do not ignore SIGPIPE errors")
2938
if err := cmd.Flags().MarkHidden(FlagIgnoreSigpipe); err != nil {
3039
panic(err)
3140
}
@@ -42,9 +51,26 @@ func New(opts ...cmdutil.Option) *cobra.Command {
4251
func run(cmd *cobra.Command, args []string) error {
4352
cmd.SilenceUsage = true
4453

54+
ignoreSigpipe, ignoreWriteErrs := true, true
55+
cmd.Flags().Visit(func(f *flag.Flag) {
56+
switch f.Name {
57+
case FlagIgnoreSigpipe:
58+
ignoreSigpipe = true
59+
case FlagNoIgnoreSigpipe:
60+
ignoreSigpipe = false
61+
case FlagIgnoreWriteErrors:
62+
ignoreWriteErrs = true
63+
case FlagNoIgnoreWriteErrors:
64+
ignoreWriteErrs = false
65+
}
66+
})
67+
68+
if ignoreSigpipe {
69+
signal.Ignore(syscall.SIGPIPE)
70+
}
71+
4572
cmds := make([]*exec.Cmd, 0, len(args))
4673
pipes := make([]io.Writer, 0, len(args))
47-
pipeClosers := make([]io.WriteCloser, 0, len(args))
4874
var errs []error
4975
for _, arg := range args {
5076
e := exec.Command("sh", "-c", arg)
@@ -60,22 +86,47 @@ func run(cmd *cobra.Command, args []string) error {
6086
}
6187

6288
cmds = append(cmds, e)
63-
pipes = append(pipes, util.NewSuppressErrorWriter(stdin))
64-
pipeClosers = append(pipeClosers, stdin)
89+
if ignoreWriteErrs {
90+
pipes = append(pipes, util.NewSuppressErrorWriter(stdin))
91+
} else {
92+
pipes = append(pipes, stdin)
93+
}
6594
}
6695

67-
if _, err := io.Copy(io.MultiWriter(pipes...), cmd.InOrStdin()); err != nil {
68-
return err
69-
}
96+
var mu sync.Mutex
97+
var wg sync.WaitGroup
98+
wg.Add(1)
99+
go func() {
100+
defer wg.Done()
70101

71-
for i, e := range cmds {
72-
if err := pipeClosers[i].Close(); err != nil {
73-
errs = append(errs, err)
102+
// Wait for all commands to exit
103+
for _, e := range cmds {
104+
if err := e.Wait(); err != nil {
105+
mu.Lock()
106+
errs = append(errs, err)
107+
mu.Unlock()
108+
}
109+
}
110+
111+
// Close pipes
112+
for _, pipe := range pipes {
113+
_ = pipe.(io.Closer).Close()
74114
}
75-
if err := e.Wait(); err != nil {
115+
}()
116+
117+
if _, err := io.Copy(io.MultiWriter(pipes...), cmd.InOrStdin()); err != nil {
118+
mu.Lock()
119+
if ignoreWriteErrs {
120+
errs = append(errs, util.NewExitCodeError(1))
121+
} else {
76122
errs = append(errs, err)
77123
}
124+
mu.Unlock()
125+
}
126+
for _, pipe := range pipes {
127+
_ = pipe.(io.Closer).Close()
78128
}
79129

130+
wg.Wait()
80131
return util.JoinErrors(errs...)
81132
}

docs/pee.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ pee command... [flags]
99
### Options
1010

1111
```
12-
-h, --help help for pee
13-
-v, --version version for pee
12+
-h, --help help for pee
13+
--no-ignore-sigpipe Do not ignore write errors
14+
--no-ignore-write-errors Do not ignore SIGPIPE errors
15+
-v, --version version for pee
1416
```
1517

1618
### SEE ALSO

internal/util/errors.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package util
33
import (
44
"errors"
55
"slices"
6+
"strconv"
67
)
78

89
var ErrNotAPipe = errors.New("this command should be run in a pipe")
@@ -21,3 +22,19 @@ func JoinErrors(errs ...error) error {
2122
return errors.Join(errs...)
2223
}
2324
}
25+
26+
func NewExitCodeError(code int) error {
27+
return &ExitCodeError{code}
28+
}
29+
30+
type ExitCodeError struct {
31+
code int
32+
}
33+
34+
func (e ExitCodeError) Error() string {
35+
return "exit code " + strconv.Itoa(e.code)
36+
}
37+
38+
func (e ExitCodeError) ExitCode() int {
39+
return e.code
40+
}

internal/util/io.go

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package util
22

3-
import "io"
3+
import (
4+
"io"
5+
"sync"
6+
"syscall"
7+
)
48

59
func NewSuppressErrorWriter(w io.Writer) *SuppressErrorWriter {
610
return &SuppressErrorWriter{w: w}
@@ -9,17 +13,43 @@ func NewSuppressErrorWriter(w io.Writer) *SuppressErrorWriter {
913
// SuppressErrorWriter proxies writes to another writer.
1014
// If a write returns an error, the error will be suppressed and the writer will be disabled.
1115
type SuppressErrorWriter struct {
12-
w io.Writer
13-
Error error
16+
w io.Writer
17+
closed bool
18+
mu sync.Mutex
19+
Error error
1420
}
1521

1622
func (w *SuppressErrorWriter) Write(p []byte) (int, error) {
17-
if w.Error == nil {
23+
w.mu.Lock()
24+
defer w.mu.Unlock()
25+
26+
switch {
27+
case w.closed:
28+
if w.Error != nil {
29+
return 0, w.Error
30+
}
31+
return 0, syscall.EPIPE
32+
case w.Error == nil:
1833
_, w.Error = w.w.Write(p)
1934
}
2035
return len(p), nil
2136
}
2237

2338
func (w *SuppressErrorWriter) Reset() {
39+
w.mu.Lock()
40+
defer w.mu.Unlock()
41+
42+
w.closed = false
2443
w.Error = nil
2544
}
45+
46+
func (w *SuppressErrorWriter) Close() error {
47+
w.mu.Lock()
48+
defer w.mu.Unlock()
49+
50+
w.closed = true
51+
if closer, ok := w.w.(io.Closer); ok {
52+
return closer.Close()
53+
}
54+
return nil
55+
}

main.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
"github.com/gabe565/moreutils/cmd"
99
"github.com/gabe565/moreutils/internal/cmdutil"
10+
"github.com/gabe565/moreutils/internal/util"
1011
)
1112

1213
var version = "beta"
@@ -19,6 +20,12 @@ func main() {
1920
if errors.As(err, &execErr) {
2021
os.Exit(execErr.ExitCode())
2122
}
23+
24+
var exitCodeErr *util.ExitCodeError
25+
if errors.As(err, &exitCodeErr) {
26+
os.Exit(exitCodeErr.ExitCode())
27+
}
28+
2229
root.PrintErrln(root.ErrPrefix(), err.Error())
2330
os.Exit(1)
2431
}

0 commit comments

Comments
 (0)