Skip to content

Commit 21833e2

Browse files
committed
passit: add generic Transform and case transforming generators
1 parent a182a2b commit 21833e2

File tree

3 files changed

+95
-7
lines changed

3 files changed

+95
-7
lines changed

README.md

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -79,13 +79,17 @@ The package also provides a number of generators that produce output based on us
7979

8080
There are also a number of 'helper' generators that interact with the output of other generators:
8181

82-
| Generator | Description |
83-
| ----------------- | ---------------------------------------------------------------------- |
84-
| `Alternate` | Select a generator at random |
85-
| `Join` | Concatenate the output of multiple generators |
86-
| `Repeat` | Invoke a generator multiple times and concatenate the output |
87-
| `RandomRepeat` | Invoke a generator a random number of times and concatenate the output |
88-
| `RejectionSample` | Continually invoke a generator until the output passes a test |
82+
| Generator | Description |
83+
| ----------------- | ------------------------------------------------------------------------------- |
84+
| `Alternate` | Select a generator at random |
85+
| `Join` | Concatenate the output of multiple generators |
86+
| `Repeat` | Invoke a generator multiple times and concatenate the output |
87+
| `RandomRepeat` | Invoke a generator a random number of times and concatenate the output |
88+
| `RejectionSample` | Continually invoke a generator until the output passes a test |
89+
| `Transform` | Invoke a generator and convert the output according to a user supplied function |
90+
| `LowerCase` | Invoke a generator and convert the output to lower case |
91+
| `UpperCase` | Invoke a generator and convert the output to upper case |
92+
| `TitleCase` | Invoke a generator and convert the output to language-specific title case |
8993

9094
Most generators only generate a single of something, be it a rune, ASCII character
9195
or word. For generating longer passwords use `Repeat` or `RandomRepeat`, possibly

transform.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package passit
2+
3+
import (
4+
"io"
5+
"strings"
6+
7+
"golang.org/x/text/cases"
8+
"golang.org/x/text/language"
9+
)
10+
11+
type transformGenerator struct {
12+
gen Generator
13+
fn func(string) string
14+
}
15+
16+
// Transform returns a Generator that converts the generated password according to
17+
// the user supplied function fn.
18+
func Transform(gen Generator, fn func(string) string) Generator {
19+
return &transformGenerator{gen, fn}
20+
}
21+
22+
func (tg *transformGenerator) Password(r io.Reader) (string, error) {
23+
pass, err := tg.gen.Password(r)
24+
return tg.fn(pass), err
25+
}
26+
27+
// LowerCase returns a Generator that uses [strings.ToLower] to map all Unicode
28+
// letters in the generated password to their lower case.
29+
func LowerCase(gen Generator) Generator {
30+
return Transform(gen, strings.ToLower)
31+
}
32+
33+
// UpperCase returns a Generator that uses [strings.ToUpper] to map all Unicode
34+
// letters in the generated password to their upper case.
35+
func UpperCase(gen Generator) Generator {
36+
return Transform(gen, strings.ToUpper)
37+
}
38+
39+
// TitleCase returns a Generator that uses [golang.org/x/text/cases.Title] to
40+
// convert the generated password to language-specific title case.
41+
func TitleCase(gen Generator, t language.Tag) Generator {
42+
return Transform(gen, cases.Title(t).String)
43+
}

transform_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package passit
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
"github.com/stretchr/testify/require"
8+
"golang.org/x/text/language"
9+
)
10+
11+
func TestLowerCase(t *testing.T) {
12+
tr := newTestRand()
13+
14+
pass, err := LowerCase(Repeat(LatinMixed, "", 25)).Password(tr)
15+
require.NoError(t, err)
16+
assert.Equal(t, "yxishgyluarukywwtcxdjirmd", pass)
17+
}
18+
19+
func TestUpperCase(t *testing.T) {
20+
tr := newTestRand()
21+
22+
pass, err := UpperCase(Repeat(LatinMixed, "", 25)).Password(tr)
23+
require.NoError(t, err)
24+
assert.Equal(t, "YXISHGYLUARUKYWWTCXDJIRMD", pass)
25+
}
26+
27+
func TestTitleCase(t *testing.T) {
28+
tr := newTestRand()
29+
30+
pass, err := TitleCase(Repeat(OrchardStreetLong, " ", 10), language.English).Password(tr)
31+
require.NoError(t, err)
32+
assert.Equal(t, "Agreed Stopping Brilliant Elongated Richness Populous Sprung Grassland Stamens Dined", pass)
33+
34+
pass, err = TitleCase(Repeat(OrchardStreetLong, "-", 10), language.English).Password(tr)
35+
require.NoError(t, err)
36+
assert.Equal(t, "Emphasizes-Weaving-Pickup-Cascades-Newborn-Provider-Noisy-Retailer-Compromise-Inventors", pass)
37+
38+
pass, err = TitleCase(Repeat(OrchardStreetLong, "_", 10), language.English).Password(tr)
39+
require.NoError(t, err)
40+
assert.Equal(t, "Biplane_kingship_ambient_altered_injustices_precedes_yearning_kitten_chop_carefully", pass)
41+
}

0 commit comments

Comments
 (0)