Add friendly type

Amolith created

Change summary

main.go | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++-------
1 file changed, 66 insertions(+), 10 deletions(-)

Detailed changes

main.go 🔗

@@ -14,14 +14,25 @@ import (
 )
 
 var (
-	length  = 20
-	version = ""
+	length         = 20
+	version        = ""
+	consonants     = "bcdfghjklmnpqrstvwxyz"
+	vowels         = "aeiou"
+	numbers        = "0123456789"
+	newBase60Chars = "0123456789ABCDEFGHJKLMNPQRSTUVWXYZ_abcdefghijkmnopqrstuvwxyz"
 )
 
 func main() {
 	if len(os.Args) > 1 && os.Args[1] == "-h" {
 		printHelp()
 		os.Exit(0)
+	} else if len(os.Args) > 1 && os.Args[1] == "-t" {
+		switch os.Args[2] {
+		case "nb60":
+			fmt.Println(generate(length))
+		case "friendly":
+			fmt.Println(generateFriendly())
+		}
 	} else if len(os.Args) > 3 {
 		fmt.Print("Error: too many arguments...\n\n")
 		printHelp()
@@ -57,8 +68,6 @@ func main() {
 }
 
 func generate(length int) string {
-	const newBase60Chars = "0123456789ABCDEFGHJKLMNPQRSTUVWXYZ_abcdefghijkmnopqrstuvwxyz"
-
 	rng := rand.New(rand.NewSource(time.Now().UnixNano()))
 
 	var sb strings.Builder
@@ -71,14 +80,61 @@ func generate(length int) string {
 	return sb.String()
 }
 
+func generateFriendly() string {
+	rng := rand.New(rand.NewSource(time.Now().UnixNano()))
+
+	sets := make([]string, 3)
+	for i := 0; i < 3; i++ {
+		sets[i] = genFriendlySet(rng)
+	}
+
+	result := strings.Join(sets, "-")
+
+	letterPos := rng.Intn(len(result))
+	// Generate a different position until a non-separator is found
+	for result[letterPos] == '-' {
+		letterPos = rng.Intn(len(result))
+	}
+	existingLetter := result[letterPos]
+	var newLetter string
+	if strings.Contains(consonants, string(existingLetter)) {
+		newLetter = string(consonants[rng.Intn(len(consonants))])
+	} else if strings.Contains(vowels, string(existingLetter)) {
+		newLetter = string(vowels[rng.Intn(len(vowels))])
+	}
+	result = result[:letterPos] + newLetter + result[letterPos+1:]
+
+	// Generate a different position until a non-separator and non-uppercase-letter
+	// is found
+	numberPos := rng.Intn(len(result))
+	for result[numberPos] == '-' || result[numberPos] == result[letterPos] {
+		numberPos = rng.Intn(len(result))
+	}
+	result = result[:numberPos] + string(numbers[rng.Intn(len(numbers))]) + result[numberPos+1:]
+
+	return result
+}
+
+func genFriendlySet(rng *rand.Rand) string {
+	set := make([]byte, 6)
+	set[0] = consonants[rng.Intn(len(consonants))]
+	set[1] = vowels[rng.Intn(len(vowels))]
+	set[2] = consonants[rng.Intn(len(consonants))]
+	set[3] = consonants[rng.Intn(len(consonants))]
+	set[4] = vowels[rng.Intn(len(vowels))]
+	set[5] = consonants[rng.Intn(len(consonants))]
+	return string(set)
+}
+
 func printHelp() {
-	fmt.Println(`Usage: eow [-h] <length> <count>
+	fmt.Println(`Usage: eow [-h|-t] <length> <count>
 
 -h prints this help message
 
-Arguments are positional, so length AND count
-may be omitted OR count may be omitted. If
-specifying count, length must also be
-specified. When ommitted, length defaults to
-20 and count defaults to 1.`)
+-t <type> prints a single 20-character NewBase60 (nb60) or friendly (friendly)
+  		  password.
+
+Arguments are positional, so length AND count may be omitted OR count may be
+omitted. If specifying count, length must also be specified. When ommitted,
+length defaults to 20 and count defaults to 1.`)
 }