Skip to content

Commit

Permalink
Merge branch 'master' into wait-error
Browse files Browse the repository at this point in the history
  • Loading branch information
mahadzaryab1 authored Aug 27, 2024
2 parents aff6d57 + 10e8421 commit 326ae13
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 1 deletion.
1 change: 1 addition & 0 deletions .github/workflows/audit.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
name: Security audit
on:
pull_request:
workflow_dispatch:
schedule:
- cron: '0 0 * * *'
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ If you're already familiar with shell scripting and the Unix toolset, here is a
| `>` | [`WriteFile`](https://pkg.go.dev/github.com/bitfield/script#Pipe.WriteFile) |
| `>>` | [`AppendFile`](https://pkg.go.dev/github.com/bitfield/script#Pipe.AppendFile) |
| `$*` | [`Args`](https://pkg.go.dev/github.com/bitfield/script#Args) |
| `base64` | [`DecodeBase64`](https://pkg.go.dev/github.com/bitfield/script#Pipe.DecodeBase64) / [`EncodeBase64`](https://pkg.go.dev/github.com/bitfield/script#Pipe.EncodeBase64) |
| `basename` | [`Basename`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Basename) |
| `cat` | [`File`](https://pkg.go.dev/github.com/bitfield/script#File) / [`Concat`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Concat) |
| `curl` | [`Do`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Do) / [`Get`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Get) / [`Post`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Post) |
Expand Down Expand Up @@ -290,9 +291,11 @@ Filters are methods on an existing pipe that also return a pipe, allowing you to
| [`Basename`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Basename) | removes leading path components from each line, leaving only the filename |
| [`Column`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Column) | Nth column of input |
| [`Concat`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Concat) | contents of multiple files |
| [`DecodeBase64`](https://pkg.go.dev/github.com/bitfield/script#Pipe.DecodeBase64) | input decoded from base64 |
| [`Dirname`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Dirname) | removes filename from each line, leaving only leading path components |
| [`Do`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Do) | response to supplied HTTP request |
| [`Echo`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Echo) | all input replaced by given string |
| [`EncodeBase64`](https://pkg.go.dev/github.com/bitfield/script#Pipe.EncodeBase64) | input encoded to base64 |
| [`Exec`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Exec) | filtered through external command |
| [`ExecForEach`](https://pkg.go.dev/github.com/bitfield/script#Pipe.ExecForEach) | execute given command template for each line of input |
| [`Filter`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Filter) | user-supplied function filtering a reader to a writer |
Expand Down Expand Up @@ -337,6 +340,7 @@ Sinks are methods that return some data from a pipe, ending the pipeline and ext

| Version | New |
| ----------- | ------- |
| _next_ | [`DecodeBase64`](https://pkg.go.dev/github.com/bitfield/script#Pipe.DecodeBase64) / [`EncodeBase64`](https://pkg.go.dev/github.com/bitfield/script#Pipe.EncodeBase64) |
| v0.22.0 | [`Tee`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Tee), [`WithStderr`](https://pkg.go.dev/github.com/bitfield/script#Pipe.WithStderr) |
| v0.21.0 | HTTP support: [`Do`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Do), [`Get`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Get), [`Post`](https://pkg.go.dev/github.com/bitfield/script#Pipe.Post) |
| v0.20.0 | [`JQ`](https://pkg.go.dev/github.com/bitfield/script#Pipe.JQ) |
Expand All @@ -347,7 +351,7 @@ See the [contributor's guide](CONTRIBUTING.md) for some helpful tips if you'd li

# Links

- [Scripting with Go](https://bitfieldconsulting.com/golang/scripting)
- [Scripting with Go](https://bitfieldconsulting.com/posts/scripting)
- [Code Club: Script](https://www.youtube.com/watch?v=6S5EqzVwpEg)
- [Bitfield Consulting](https://bitfieldconsulting.com/)
- [Go books by John Arundel](https://bitfieldconsulting.com/books)
Expand Down
26 changes: 26 additions & 0 deletions script.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bufio"
"container/ring"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
Expand Down Expand Up @@ -275,6 +276,18 @@ func (p *Pipe) CountLines() (lines int, err error) {
return lines, p.Error()
}

// DecodeBase64 produces the string represented by the base64 encoded input.
func (p *Pipe) DecodeBase64() *Pipe {
return p.Filter(func(r io.Reader, w io.Writer) error {
decoder := base64.NewDecoder(base64.StdEncoding, r)
_, err := io.Copy(w, decoder)
if err != nil {
return err
}
return nil
})
}

// Dirname reads paths from the pipe, one per line, and produces only the
// parent directories of each path. For example, /usr/local/bin/foo would
// become just /usr/local/bin. This is the complementary operation to
Expand Down Expand Up @@ -347,6 +360,19 @@ func (p *Pipe) Echo(s string) *Pipe {
return p.WithReader(strings.NewReader(s))
}

// EncodeBase64 produces the base64 encoding of the input.
func (p *Pipe) EncodeBase64() *Pipe {
return p.Filter(func(r io.Reader, w io.Writer) error {
encoder := base64.NewEncoder(base64.StdEncoding, w)
defer encoder.Close()
_, err := io.Copy(encoder, r)
if err != nil {
return err
}
return nil
})
}

// Error returns any error present on the pipe, or nil otherwise.
// Error is not a sink and does not wait until the pipe reaches
// completion. To wait for completion before returning the error,
Expand Down
116 changes: 116 additions & 0 deletions script_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1864,6 +1864,110 @@ func TestWaitForNoErrorOnPipe(t *testing.T) {
if err := p.Wait(); err != nil {
t.Fatal(err)
}

var base64Cases = []struct {
name string
decoded string
encoded string
}{
{
name: "empty string",
decoded: "",
encoded: "",
},
{
name: "single line string",
decoded: "hello world",
encoded: "aGVsbG8gd29ybGQ=",
},
{
name: "multi line string",
decoded: "hello\nthere\nworld\n",
encoded: "aGVsbG8KdGhlcmUKd29ybGQK",
},
}

func TestEncodeBase64_CorrectlyEncodes(t *testing.T) {

Check failure on line 1890 in script_test.go

View workflow job for this annotation

GitHub Actions / test (stable, ubuntu-latest)

expected '(', found TestEncodeBase64_CorrectlyEncodes

Check failure on line 1890 in script_test.go

View workflow job for this annotation

GitHub Actions / test (oldstable, ubuntu-latest)

expected '(', found TestEncodeBase64_CorrectlyEncodes

Check failure on line 1890 in script_test.go

View workflow job for this annotation

GitHub Actions / test (oldstable, macos-latest)

expected '(', found TestEncodeBase64_CorrectlyEncodes
t.Parallel()
for _, tc := range base64Cases {
t.Run(tc.name, func(t *testing.T) {
got, err := script.Echo(tc.decoded).EncodeBase64().String()
if err != nil {
t.Fatal(err)
}
if got != tc.encoded {
t.Logf("input %q incorrectly encoded:", tc.decoded)
t.Error(cmp.Diff(tc.encoded, got))
}
})
}
}

func TestDecodeBase64_CorrectlyDecodes(t *testing.T) {
t.Parallel()
for _, tc := range base64Cases {
t.Run(tc.name, func(t *testing.T) {
got, err := script.Echo(tc.encoded).DecodeBase64().String()
if err != nil {
t.Fatal(err)
}
if got != tc.decoded {
t.Logf("input %q incorrectly decoded:", tc.encoded)
t.Error(cmp.Diff(tc.decoded, got))
}
})
}
}

func TestEncodeBase64_FollowedByDecodeRecoversOriginal(t *testing.T) {
t.Parallel()
for _, tc := range base64Cases {
t.Run(tc.name, func(t *testing.T) {
decoded, err := script.Echo(tc.decoded).EncodeBase64().DecodeBase64().String()
if err != nil {
t.Fatal(err)
}
if decoded != tc.decoded {
t.Error("encode-decode round trip failed:", cmp.Diff(tc.decoded, decoded))
}
encoded, err := script.Echo(tc.encoded).DecodeBase64().EncodeBase64().String()
if err != nil {
t.Fatal(err)
}
if encoded != tc.encoded {
t.Error("decode-encode round trip failed:", cmp.Diff(tc.encoded, encoded))
}
})
}
}

func TestDecodeBase64_CorrectlyDecodesInputToBytes(t *testing.T) {
t.Parallel()
input := "CAAAEA=="
got, err := script.Echo(input).DecodeBase64().Bytes()
if err != nil {
t.Fatal(err)
}
want := []byte{8, 0, 0, 16}
if !bytes.Equal(want, got) {
t.Logf("input %#v incorrectly decoded:", input)
t.Error(cmp.Diff(want, got))
}
}

func TestEncodeBase64_CorrectlyEncodesInputBytes(t *testing.T) {
t.Parallel()
input := []byte{8, 0, 0, 16}
reader := bytes.NewReader(input)
want := "CAAAEA=="
got, err := script.NewPipe().WithReader(reader).EncodeBase64().String()
if err != nil {
t.Fatal(err)
}
if got != want {
t.Logf("input %#v incorrectly encoded:", input)
t.Error(cmp.Diff(want, got))
}
}

func ExampleArgs() {
Expand Down Expand Up @@ -1985,6 +2089,12 @@ func ExamplePipe_CountLines() {
// 3
}

func ExamplePipe_DecodeBase64() {
script.Echo("SGVsbG8sIHdvcmxkIQ==").DecodeBase64().Stdout()
// Output:
// Hello, world!
}

func ExamplePipe_Do() {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
data, err := io.ReadAll(r.Body)
Expand Down Expand Up @@ -2020,6 +2130,12 @@ func ExamplePipe_Echo() {
// Hello, world!
}

func ExamplePipe_EncodeBase64() {
script.Echo("Hello, world!").EncodeBase64().Stdout()
// Output:
// SGVsbG8sIHdvcmxkIQ==
}

func ExamplePipe_ExitStatus() {
p := script.Exec("echo")
fmt.Println(p.ExitStatus())
Expand Down

0 comments on commit 326ae13

Please sign in to comment.