Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Phone number add generator #664

Merged
merged 2 commits into from
May 5, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions exercises/phone-number/.meta/gen.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// +build ignore

package main

import (
"log"
"text/template"

"../../../gen"
)

func main() {
t := template.New("").Funcs(template.FuncMap{
"areacode": areacode,
"expectErr": expectErr,
"formatted": formatted,
})
t, err := t.Parse(tmpl)
if err != nil {
log.Fatal(err)
}
var j js
if err := gen.Gen("phone-number", &j, t); err != nil {
log.Fatal(err)
}
}

func expectErr(expected string) bool {
return len(expected) == 0
}

func areacode(expected string) string {
if expectErr(expected) {
return ""
}
return expected[0:3]
}

func formatted(expected string) string {
if expectErr(expected) {
return ""
}
return "(" + areacode(expected) + ") " + expected[3:6] + "-" + expected[6:10]
}

// The JSON structure we expect to be able to unmarshal into
type js struct {
Cases []struct {
Description string
Cases []struct {
Description string
Phrase string
Expected string
}
}
}

// template applied to above data structure generates the Go test cases
var tmpl = `package phonenumber

{{.Header}}

{{range .J.Cases}}// {{.Description}}
var numberTests = []struct {
description string
input string
expectErr bool
number string
areaCode string
formatted string
}{
{{range .Cases}}{
description: {{printf "%q" .Description}},
input: {{printf "%q" .Phrase}},
{{if expectErr .Expected}} expectErr: {{expectErr .Expected | printf "%v" }},
{{else}} number: {{printf "%q" .Expected}},
areaCode: {{areacode .Expected | printf "%q" }},
formatted: {{formatted .Expected | printf "%q" }},{{- end}}
},
{{end}}{{end}}
}
`
91 changes: 91 additions & 0 deletions exercises/phone-number/cases_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package phonenumber

// Source: exercism/x-common
// Commit: 51d2475 number: Rewrite description and add tests
// x-common version: 1.1.0

// Cleanup user-entered phone numbers
var numberTests = []struct {
description string
input string
expectErr bool
number string
areaCode string
formatted string
}{
{
description: "cleans the number",
input: "(223) 456-7890",
number: "2234567890",
areaCode: "223",
formatted: "(223) 456-7890",
},
{
description: "cleans numbers with dots",
input: "223.456.7890",
number: "2234567890",
areaCode: "223",
formatted: "(223) 456-7890",
},
{
description: "cleans numbers with multiple spaces",
input: "223 456 7890 ",
number: "2234567890",
areaCode: "223",
formatted: "(223) 456-7890",
},
{
description: "invalid when 9 digits",
input: "123456789",
expectErr: true,
},
{
description: "invalid when 11 digits does not start with a 1",
input: "22234567890",
expectErr: true,
},
{
description: "valid when 11 digits and starting with 1",
input: "12234567890",
number: "2234567890",
areaCode: "223",
formatted: "(223) 456-7890",
},
{
description: "valid when 11 digits and starting with 1 even with punctuation",
input: "+1 (223) 456-7890",
number: "2234567890",
areaCode: "223",
formatted: "(223) 456-7890",
},
{
description: "invalid when more than 11 digits",
input: "321234567890",
expectErr: true,
},
{
description: "invalid with letters",
input: "123-abc-7890",
expectErr: true,
},
{
description: "invalid with punctuations",
input: "123-@:!-7890",
expectErr: true,
},
{
description: "invalid with right number of digits but letters mixed in",
input: "1a2b3c4d5e6f7g8h9i0j",
expectErr: true,
},
{
description: "invalid if area code does not start with 2-9",
input: "(123) 456-7890",
expectErr: true,
},
{
description: "invalid if exchange code does not start with 2-9",
input: "(223) 056-7890",
expectErr: true,
},
}
38 changes: 20 additions & 18 deletions exercises/phone-number/example.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,46 @@ package phonenumber

import (
"fmt"
"strings"
"unicode"
)

const testVersion = 1
const testVersion = 2

// Number takes in a potential phone number string and returns the number
// without any formatting if it's valid.
//
// * If the phone number is less than 10 digits assume that it is bad number
// * If the phone number is 10 digits assume that it is good
// * If the phone number is 10 digits, it is good if 1st digit is not 1 and 3 digit exchange begins with 2..9
// * If the phone number is 11 digits and the first number is 1, trim the 1 and use the first 10 digits
// * If the phone number is 11 digits and the first number is not 1, then it is a bad number
// * If the phone number is more than 11 digits assume that it is a bad number
func Number(s string) (string, error) {
//remove any characters that aren't digits
clean := strings.Map(
func(r rune) rune {
if unicode.IsDigit(r) {
return r
}
return -1
}, s)

// Extract just the digits.
n := make([]rune, 0, len(s))
for _, c := range s {
if c == ' ' || c == '+' || c == '(' || c == ')' || c == '-' || c == '.' {
continue
} else if unicode.IsDigit(c) {
n = append(n, c)
} else {
return "", fmt.Errorf("Phone number %q has invalid characters", s)
}
}
clean := string(n)
numDigits := len(clean)
switch {
// bad number if less than 10 digits
case numDigits < 10:
return "", fmt.Errorf("Phone number %q contains too few digits", s)
// good number if exactly 10 digits
case numDigits == 10:
// good number if exactly 10 digits and area code & exchange are valid
case numDigits == 10 && clean[0] >= '2' && clean[3] >= '2':
return clean, nil
// good number if 11 digits and 1st digit is a 1. Return last 10 digits.
case numDigits == 11 && clean[0] == '1':
// good number if 11 digits and 1st digit is a 1. Return last 10 digits, if valid.
case numDigits == 11 && clean[0] == '1' && clean[1] >= '2' && clean[4] >= '2':
return clean[1:], nil
}
// all other numbers are a bad
return "", fmt.Errorf("Phone number %q contains too many digits", s)
// all other numbers are bad
return "", fmt.Errorf("Phone number %q is invalid", s)
}

// AreaCode takes in a phone number string and returns the area code (first three digits)
Expand Down
69 changes: 17 additions & 52 deletions exercises/phone-number/phone_number_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,7 @@ import (
"testing"
)

const targetTestVersion = 1

type testCase struct {
input string
expected string
expectErr bool
}

var numberTests = []testCase{
{"(123) 456-7890", "1234567890", false},
{"123.456.7890", "1234567890", false},
{"1234567890", "1234567890", false},
{"12345678901234567", "", true},
{"21234567890", "", true},
{"123456789", "", true},
}
const targetTestVersion = 2

func TestTestVersion(t *testing.T) {
if testVersion != targetTestVersion {
Expand All @@ -34,14 +19,14 @@ func TestNumber(t *testing.T) {
if actualErr != nil {
// if we don't expect an error and there is one
var _ error = actualErr
t.Errorf("Number(%s): expected no error, but error is: %s", test.input, actualErr)
t.Errorf("FAIL: %s\nNumber(%q): expected no error, but error is: %s", test.description, test.input, actualErr)
}
if actual != test.expected {
t.Errorf("Number(%s): expected [%s], actual: [%s]", test.input, test.expected, actual)
if actual != test.number {
t.Errorf("FAIL: %s\nNumber(%q): expected [%s], actual: [%s]", test.description, test.input, test.number, actual)
}
} else if actualErr == nil {
// if we expect an error and there isn't one
t.Errorf("Number(%s): expected an error, but error is nil", test.input)
t.Errorf("FAIL: %s\nNumber(%q): expected an error, but error is nil", test.description, test.input)
}
}
}
Expand All @@ -57,38 +42,28 @@ func BenchmarkNumber(b *testing.B) {
}
}

var areaCodeTests = []testCase{
{"1234567890", "123", false},
{"213.456.7890", "213", false},
{"213.456.7890.2345", "", true},
{"213.456", "", true},
}

func TestAreaCode(t *testing.T) {
if testVersion != targetTestVersion {
t.Errorf("Found testVersion = %v, want %v.", testVersion, targetTestVersion)
}
for _, test := range areaCodeTests {
for _, test := range numberTests {
actual, actualErr := AreaCode(test.input)
if !test.expectErr {
if actual != test.expected {
t.Errorf("AreaCode(%s): expected [%s], actual: [%s]", test.input, test.expected, actual)
if actual != test.areaCode {
t.Errorf("FAIL: %s\nAreaCode(%q): expected [%s], actual: [%s]", test.description, test.input, test.areaCode, actual)
}
if actualErr != nil {
// if we don't expect an error and there is one
var _ error = actualErr
t.Errorf("AreaCode(%s): expected no error, but error is: %s", test.input, actualErr)
t.Errorf("FAIL: %s\nAreaCode(%q): expected no error, but error is: %s", test.description, test.input, actualErr)
}
} else if actualErr == nil {
// if we expect an error and there isn't one
t.Errorf("AreaCode(%s): expected an error, but error is nil", test.input)
t.Errorf("FAIL: %s\nAreaCode(%q): expected an error, but error is nil", test.description, test.input)
}
}
}

func BenchmarkAreaCode(b *testing.B) {
b.StopTimer()
for _, test := range areaCodeTests {
for _, test := range numberTests {
b.StartTimer()
for i := 0; i < b.N; i++ {
AreaCode(test.input)
Expand All @@ -97,38 +72,28 @@ func BenchmarkAreaCode(b *testing.B) {
}
}

var formatTests = []testCase{
{"1234567890", "(123) 456-7890", false},
{"11234567890", "(123) 456-7890", false},
{"112345", "", true},
{"11234590870986", "", true},
}

func TestFormat(t *testing.T) {
if testVersion != targetTestVersion {
t.Errorf("Found testVersion = %v, want %v.", testVersion, targetTestVersion)
}
for _, test := range formatTests {
for _, test := range numberTests {
actual, actualErr := Format(test.input)
if !test.expectErr {
if actualErr != nil {
// if we don't expect an error and there is one
var _ error = actualErr
t.Errorf("Format(%s): expected no error, but error is: %s", test.input, actualErr)
t.Errorf("FAIL: %s\nFormat(%q): expected no error, but error is: %s", test.description, test.input, actualErr)
}
if actual != test.expected {
t.Errorf("Format(%s): expected [%s], actual: [%s]", test.input, test.expected, actual)
if actual != test.formatted {
t.Errorf("FAIL: %s\nFormat(%q): expected [%s], actual: [%s]", test.description, test.input, test.formatted, actual)
}
} else if actualErr == nil {
// if we expect an error and there isn't one
t.Errorf("Format(%s): expected an error, but error is nil", test.input)
t.Errorf("FAIL: %s\nFormat(%q): expected an error, but error is nil", test.description, test.input)
}
}
}

func BenchmarkFormat(b *testing.B) {
b.StopTimer()
for _, test := range areaCodeTests {
for _, test := range numberTests {
b.StartTimer()
for i := 0; i < b.N; i++ {
Format(test.input)
Expand Down