diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index bd7cc86..b1e03e4 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -40,7 +40,11 @@ jobs: fi - name: Release Notes Capture run: | - sed -n '/^## [0-9]/{:loop n; /^## [0-9]/q; p; b loop}' Changes.md > release-notes.md + sed -n '/^## v[0-9]/{:loop n; /^## v[0-9]/q; p; b loop}' Changes.md > release-notes.md echo "Release Notes Will be..." echo "========================" cat release-notes.md + - name: Create Release + run: gh release create -t "v$RELEASE_VERSION" "v$RELEASE_VERSION" --draft --notes-file=release-notes.md + - name: Finalize Release + run: gh release edit "v$RELEASE_VERSION" --draft=false diff --git a/Changes.md b/Changes.md index d09a6f1..716cddf 100644 --- a/Changes.md +++ b/Changes.md @@ -1,3 +1,9 @@ +## WIP TBD + + * Adding `bytes.ContainsOnly`, `bytes.FromRange`, `bytes.Reverse`, `bytes.Indent`. + * Adding `strings.Indent`. + * Corrected documentation for `deferred.Error` which gave incorrect guidance on how to use it. + ## v0.7.0 2024-06-13 * :boom: Breaking Change :boom:: Now requires Go 1.20. diff --git a/README.md b/README.md index b931dd1..c8f8dc1 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,16 @@ There are a lot of common operations simply missing from the Golang builtin library. This makes up for that deficiency. Here's a summary of some of the provided tools: +## Byte Slice Operations + +There are a lot of common byte slice operations that are missing from the +built-in standard library: + +* `ContainsOnly(b, chars)` +* `FromRange(a, z)` +* `Reverse(b)` +* `Indent(b, indent)` + ## Deferred Handling Handling deferred functions is a bit of a pain. This provides a way to handle @@ -153,5 +163,6 @@ built-in `strings` package: * `Reverse(s)` * `Increment(s)` * `IncrementWithSets(s, sets)` + * `Indent(s, indent)` The latter two operations might warrant their own library, but I put them here for now. diff --git a/bytes/content.go b/bytes/content.go new file mode 100644 index 0000000..d634594 --- /dev/null +++ b/bytes/content.go @@ -0,0 +1,64 @@ +package bytes + +import "bytes" + +// ContainsOnly returns true fi the given byte slice only contains the given +// letters. +func ContainsOnly(b []byte, chars []rune) bool { + idx := make(map[rune]struct{}, len(chars)) + for _, c := range chars { + idx[c] = struct{}{} + } + return bytes.IndexFunc(b, func(c rune) bool { + _, expectedChar := idx[c] + return !expectedChar + }) == -1 +} + +// FromRange returns a byte slice containing all the characters between two given +// bytes (inclusive). +func FromRange(a, z byte) []byte { + reverse := false + if a > z { + reverse = true + a, z = z, a + } + + out := make([]byte, z-a+1) + for i := a; i <= z; i++ { + out[i-a] = i + } + + if reverse { + return Reverse(out) + } + + return out +} + +// Reverse returns the byte slice reversed. That is, passing the byte slice +// []byte("Reversing text.") will result in this function returning +// []byte(".txet gnisreveR"). +func Reverse(in []byte) []byte { + out := make([]byte, len(in)) + for i := 0; i < len(in); i++ { + out[i] = in[len(in)-i-1] + } + return out +} + +// Indent returns a new byte slice with each line in the input byte slice +// indented by the given byte slice. +func Indent(b, indent []byte) []byte { + startIndent := indent + if b[0] == '\n' { + startIndent = []byte{} + } + newB := bytes.ReplaceAll(b, []byte("\n"), []byte("\n"+string(indent))) + if len(startIndent) > 0 { + newB = append(newB, startIndent...) + copy(newB[len(startIndent):], newB) + copy(newB, startIndent) + } + return bytes.TrimRight(newB, " ") +} diff --git a/bytes/content_test.go b/bytes/content_test.go new file mode 100644 index 0000000..b45f9ab --- /dev/null +++ b/bytes/content_test.go @@ -0,0 +1,40 @@ +package bytes_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/zostay/go-std/bytes" +) + +func TestContainsOnly(t *testing.T) { + t.Parallel() + + assert.True(t, bytes.ContainsOnly([]byte("abc"), []rune("abc"))) + assert.True(t, bytes.ContainsOnly([]byte("a"), []rune("abc"))) + assert.True(t, bytes.ContainsOnly([]byte("aaaaaabbbbbbccccc"), []rune("cba"))) + assert.False(t, bytes.ContainsOnly([]byte("a b c"), []rune("abc"))) +} + +func TestFromRange(t *testing.T) { + t.Parallel() + + assert.Equal(t, []byte("abcd"), bytes.FromRange('a', 'd')) + assert.Equal(t, []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZ"), bytes.FromRange('A', 'Z')) + assert.Equal(t, []byte("0123456789"), bytes.FromRange('0', '9')) + assert.Equal(t, []byte("zyxwvutsrqponmlkjihgfedcba"), bytes.FromRange('z', 'a')) +} + +func TestReverse(t *testing.T) { + t.Parallel() + + assert.Equal(t, []byte(".txet gnisreveR"), bytes.Reverse([]byte("Reversing text."))) +} + +func TestIndent(t *testing.T) { + t.Parallel() + + assert.Equal(t, []byte("\n a\n b\n c\n"), bytes.Indent([]byte("\na\nb\nc\n"), []byte(" "))) + assert.Equal(t, []byte(" a\n b\n c"), bytes.Indent([]byte("a\nb\nc"), []byte(" "))) +} diff --git a/bytes/doc.go b/bytes/doc.go new file mode 100644 index 0000000..a1ed3f8 --- /dev/null +++ b/bytes/doc.go @@ -0,0 +1,2 @@ +// Package bytes provides additional tools for working with byte slices. +package bytes diff --git a/deferred/error.go b/deferred/error.go index be269b4..00b91f6 100644 --- a/deferred/error.go +++ b/deferred/error.go @@ -12,7 +12,7 @@ import "errors" // if err != nil { // return // } -// defer deferred.Error(&err, r.Close()) +// defer func() { deferred.Error(&err, r.Close()) }() // // // process file // return diff --git a/strings/content.go b/strings/content.go index 1a6d69e..5482fce 100644 --- a/strings/content.go +++ b/strings/content.go @@ -282,3 +282,14 @@ var defaultIncs = []IncrementSet{ func Increment(input string) string { return IncrementWithSets(input, defaultIncs...) } + +// Indent returns the string with each line indented by the given string. +func Indent(s, indent string) string { + startIndent := indent + if s[0] == '\n' { + startIndent = "" + } + return strings.TrimRight( + startIndent+strings.ReplaceAll(s, "\n", "\n"+indent), + " ") +} diff --git a/strings/content_test.go b/strings/content_test.go index a80ff98..94c2d28 100644 --- a/strings/content_test.go +++ b/strings/content_test.go @@ -89,3 +89,10 @@ func TestIncrement(t *testing.T) { assert.Equal(t, "ID:AAA000", strings.IncrementWithSets("ID:ZZ999", licPlate...)) assert.Equal(t, "ID:AAAA000", strings.IncrementWithSets("ID:ZZZ999", licPlate...)) } + +func TestIndent(t *testing.T) { + t.Parallel() + + assert.Equal(t, "\n a\n b\n c\n", strings.Indent("\na\nb\nc\n", " ")) + assert.Equal(t, " a\n b\n c", strings.Indent("a\nb\nc", " ")) +}