From 61107f3dffbd0ba0662311e69b21bbc3f543682d Mon Sep 17 00:00:00 2001 From: Dylan Date: Fri, 15 Feb 2019 08:25:04 -0600 Subject: [PATCH 1/6] implement fmt.Formatter --- uuid.go | 43 +++++++++++++++++++++++++++++++++++++++++++ uuid_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/uuid.go b/uuid.go index 29ef440..f874db1 100644 --- a/uuid.go +++ b/uuid.go @@ -33,6 +33,8 @@ import ( "encoding/binary" "encoding/hex" "fmt" + "io" + "strings" "time" ) @@ -156,6 +158,47 @@ func (u UUID) String() string { return string(buf) } +// Format implements fmt.Formatter for UUID values. It accepts 'h' or 'H' as a format to output only +// the hex digits of the UUID. Any other format specifier returns the canonical RFC-4122 representation. +func (u UUID) Format(f fmt.State, c rune) { + // + // TODO: dylan.bourque - 2019-02-15 + // . should we implement %v? if so, what is "a Go-syntax representation" of a UUID when using %#v? + // + switch c { + case 'h', 'H': + s := hex.EncodeToString(u.Bytes()) + if c == 'H' { + s = strings.Map(toCapitalHexDigits, s) + } + _, _ = io.WriteString(f, s) + case 'q': + _, _ = io.WriteString(f, `"`+u.String()+`"`) + default: + _, _ = io.WriteString(f, u.String()) + } +} + +func toCapitalHexDigits(ch rune) rune { + // convert a-f hex digits to A-F + switch ch { + case 'a': + return 'A' + case 'b': + return 'B' + case 'c': + return 'C' + case 'd': + return 'D' + case 'e': + return 'E' + case 'f': + return 'F' + default: + return ch + } +} + // SetVersion sets the version bits. func (u *UUID) SetVersion(v byte) { u[6] = (u[6] & 0x0f) | (v << 4) diff --git a/uuid_test.go b/uuid_test.go index 8bb13fa..84a6028 100644 --- a/uuid_test.go +++ b/uuid_test.go @@ -35,6 +35,7 @@ func TestUUID(t *testing.T) { t.Run("Variant", testUUIDVariant) t.Run("SetVersion", testUUIDSetVersion) t.Run("SetVariant", testUUIDSetVariant) + t.Run("Format", testUUIDFormat) } func testUUIDBytes(t *testing.T) { @@ -114,6 +115,30 @@ func testUUIDSetVariant(t *testing.T) { } } +func testUUIDFormat(t *testing.T) { + val := Must(FromString("12345678-90ab-cdef-1234-567890abcdef")) + tests := []struct { + u UUID + f string + want string + }{ + {u: val, f: "%s", want: "12345678-90ab-cdef-1234-567890abcdef"}, + {u: val, f: "%q", want: `"12345678-90ab-cdef-1234-567890abcdef"`}, + {u: val, f: "%h", want: "1234567890abcdef1234567890abcdef"}, + {u: val, f: "%H", want: "1234567890ABCDEF1234567890ABCDEF"}, + {u: val, f: "%v", want: "12345678-90ab-cdef-1234-567890abcdef"}, + {u: val, f: "%+v", want: "12345678-90ab-cdef-1234-567890abcdef"}, + {u: val, f: "%#v", want: "12345678-90ab-cdef-1234-567890abcdef"}, + {u: val, f: "%T", want: "uuid.UUID"}, + } + for _, tt := range tests { + got := fmt.Sprintf(tt.f, tt.u) + if tt.want != got { + t.Errorf("Format(\"%s\") got %s, want %s", tt.f, got, tt.want) + } + } +} + func TestMust(t *testing.T) { sentinel := fmt.Errorf("uuid: sentinel error") defer func() { From 8817e4651350ab980226c4cf2725185a238e9f81 Mon Sep 17 00:00:00 2001 From: Dylan Date: Mon, 18 Feb 2019 14:58:47 -0600 Subject: [PATCH 2/6] use 'x'/'X' instead of 'h'/'H' for outputting only hex digits --- uuid.go | 24 +++++++++++++++--------- uuid_test.go | 21 +++++++++++++++++++-- 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/uuid.go b/uuid.go index f874db1..2e6c58c 100644 --- a/uuid.go +++ b/uuid.go @@ -158,24 +158,30 @@ func (u UUID) String() string { return string(buf) } -// Format implements fmt.Formatter for UUID values. It accepts 'h' or 'H' as a format to output only -// the hex digits of the UUID. Any other format specifier returns the canonical RFC-4122 representation. +// Format implements fmt.Formatter for UUID values. +// +// The behavior is as follows: +// The 'x' and 'X' verbs output only the hex digits of the UUID, using a-f for 'x' and A-F for 'X'. +// The 'v', 's' and 'q' verbs returns the canonical RFC-4122 string representation. +// All other verbs not handled directly by the fmt package (like %p) return an empty string. func (u UUID) Format(f fmt.State, c rune) { - // - // TODO: dylan.bourque - 2019-02-15 - // . should we implement %v? if so, what is "a Go-syntax representation" of a UUID when using %#v? - // switch c { - case 'h', 'H': + case 'x', 'X': s := hex.EncodeToString(u.Bytes()) - if c == 'H' { + if c == 'X' { s = strings.Map(toCapitalHexDigits, s) } _, _ = io.WriteString(f, s) + case 'v', 's': + // TODO: dylan.bourque - 2019-02-18 + // . should we implement %#v? if so, what is "a Go-syntax representation" of a UUID? + _, _ = io.WriteString(f, u.String()) case 'q': _, _ = io.WriteString(f, `"`+u.String()+`"`) default: - _, _ = io.WriteString(f, u.String()) + // for all other format specifiers, generate no output + // . the fmt package doesn't provide any mechanism for indicating which format verbs we support, + // so outputting nothing is at least deterministic } } diff --git a/uuid_test.go b/uuid_test.go index 84a6028..c1644de 100644 --- a/uuid_test.go +++ b/uuid_test.go @@ -124,12 +124,29 @@ func testUUIDFormat(t *testing.T) { }{ {u: val, f: "%s", want: "12345678-90ab-cdef-1234-567890abcdef"}, {u: val, f: "%q", want: `"12345678-90ab-cdef-1234-567890abcdef"`}, - {u: val, f: "%h", want: "1234567890abcdef1234567890abcdef"}, - {u: val, f: "%H", want: "1234567890ABCDEF1234567890ABCDEF"}, + {u: val, f: "%x", want: "1234567890abcdef1234567890abcdef"}, + {u: val, f: "%X", want: "1234567890ABCDEF1234567890ABCDEF"}, {u: val, f: "%v", want: "12345678-90ab-cdef-1234-567890abcdef"}, {u: val, f: "%+v", want: "12345678-90ab-cdef-1234-567890abcdef"}, {u: val, f: "%#v", want: "12345678-90ab-cdef-1234-567890abcdef"}, {u: val, f: "%T", want: "uuid.UUID"}, + {u: val, f: "%t", want: ""}, + {u: val, f: "%b", want: ""}, + {u: val, f: "%c", want: ""}, + {u: val, f: "%d", want: ""}, + {u: val, f: "%e", want: ""}, + {u: val, f: "%E", want: ""}, + {u: val, f: "%f", want: ""}, + {u: val, f: "%F", want: ""}, + {u: val, f: "%g", want: ""}, + {u: val, f: "%G", want: ""}, + {u: val, f: "%o", want: ""}, + {u: val, f: "%U", want: ""}, + // TODO: dylan-bourque - 2019-02-18 + // uuid.Format() does not get called so it doesn't seem possible to control/verify the + // output of these verbs + //{u: val, f: "%p", want: ""}, + //{u: val, f: "%#p", want: ""}, } for _, tt := range tests { got := fmt.Sprintf(tt.f, tt.u) From 0af19983b599d7c163af0daefaa0096201e44867 Mon Sep 17 00:00:00 2001 From: Dylan Date: Mon, 18 Feb 2019 15:06:21 -0600 Subject: [PATCH 3/6] removed commented out test cases from testUUIDFormat() --- uuid_test.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/uuid_test.go b/uuid_test.go index c1644de..f762155 100644 --- a/uuid_test.go +++ b/uuid_test.go @@ -142,11 +142,6 @@ func testUUIDFormat(t *testing.T) { {u: val, f: "%G", want: ""}, {u: val, f: "%o", want: ""}, {u: val, f: "%U", want: ""}, - // TODO: dylan-bourque - 2019-02-18 - // uuid.Format() does not get called so it doesn't seem possible to control/verify the - // output of these verbs - //{u: val, f: "%p", want: ""}, - //{u: val, f: "%#p", want: ""}, } for _, tt := range tests { got := fmt.Sprintf(tt.f, tt.u) From 8b125a793bca21e2f06c6ffa54d785f0068b0ce4 Mon Sep 17 00:00:00 2001 From: Dylan Date: Thu, 14 Mar 2019 12:47:38 -0500 Subject: [PATCH 4/6] refined behavior of UUID.Format() and updated unit tests to match --- uuid.go | 27 +++++++++++++++++++++------ uuid_test.go | 27 ++++++++++++++------------- 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/uuid.go b/uuid.go index 2e6c58c..42f3ddb 100644 --- a/uuid.go +++ b/uuid.go @@ -162,8 +162,11 @@ func (u UUID) String() string { // // The behavior is as follows: // The 'x' and 'X' verbs output only the hex digits of the UUID, using a-f for 'x' and A-F for 'X'. -// The 'v', 's' and 'q' verbs returns the canonical RFC-4122 string representation. -// All other verbs not handled directly by the fmt package (like %p) return an empty string. +// The 'v', '+v', 's' and 'q' verbs return the canonical RFC-4122 string representation. +// The 'S' verb returns the RFC-4122 format, but with capital hex digits. +// The '#v' verb returns the "Go syntax" representation, which is a 16 byte array initializer. +// All other verbs not handled directly by the fmt package (like '%p') are unsupported and will return +// "%!verb(uuid.UUID=value)" as recommended by the fmt package. func (u UUID) Format(f fmt.State, c rune) { switch c { case 'x', 'X': @@ -172,13 +175,25 @@ func (u UUID) Format(f fmt.State, c rune) { s = strings.Map(toCapitalHexDigits, s) } _, _ = io.WriteString(f, s) - case 'v', 's': - // TODO: dylan.bourque - 2019-02-18 - // . should we implement %#v? if so, what is "a Go-syntax representation" of a UUID? - _, _ = io.WriteString(f, u.String()) + case 'v': + var s string + if f.Flag('#') { + s = fmt.Sprintf("%#v", [Size]byte(u)) + } else { + s = u.String() + } + _, _ = io.WriteString(f, s) + case 's', 'S': + s := u.String() + if c == 'S' { + s = strings.Map(toCapitalHexDigits, s) + } + _, _ = io.WriteString(f, s) case 'q': _, _ = io.WriteString(f, `"`+u.String()+`"`) default: + // invalid/unsupported format verb + fmt.Fprintf(f, "%%!%c(uuid.UUID=%s)", c, u.String()) // for all other format specifiers, generate no output // . the fmt package doesn't provide any mechanism for indicating which format verbs we support, // so outputting nothing is at least deterministic diff --git a/uuid_test.go b/uuid_test.go index f762155..b3e5823 100644 --- a/uuid_test.go +++ b/uuid_test.go @@ -123,25 +123,26 @@ func testUUIDFormat(t *testing.T) { want string }{ {u: val, f: "%s", want: "12345678-90ab-cdef-1234-567890abcdef"}, + {u: val, f: "%S", want: "12345678-90AB-CDEF-1234-567890ABCDEF"}, {u: val, f: "%q", want: `"12345678-90ab-cdef-1234-567890abcdef"`}, {u: val, f: "%x", want: "1234567890abcdef1234567890abcdef"}, {u: val, f: "%X", want: "1234567890ABCDEF1234567890ABCDEF"}, {u: val, f: "%v", want: "12345678-90ab-cdef-1234-567890abcdef"}, {u: val, f: "%+v", want: "12345678-90ab-cdef-1234-567890abcdef"}, - {u: val, f: "%#v", want: "12345678-90ab-cdef-1234-567890abcdef"}, + {u: val, f: "%#v", want: "[16]uint8{0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef, 0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef}"}, {u: val, f: "%T", want: "uuid.UUID"}, - {u: val, f: "%t", want: ""}, - {u: val, f: "%b", want: ""}, - {u: val, f: "%c", want: ""}, - {u: val, f: "%d", want: ""}, - {u: val, f: "%e", want: ""}, - {u: val, f: "%E", want: ""}, - {u: val, f: "%f", want: ""}, - {u: val, f: "%F", want: ""}, - {u: val, f: "%g", want: ""}, - {u: val, f: "%G", want: ""}, - {u: val, f: "%o", want: ""}, - {u: val, f: "%U", want: ""}, + {u: val, f: "%t", want: "%!t(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"}, + {u: val, f: "%b", want: "%!b(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"}, + {u: val, f: "%c", want: "%!c(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"}, + {u: val, f: "%d", want: "%!d(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"}, + {u: val, f: "%e", want: "%!e(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"}, + {u: val, f: "%E", want: "%!E(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"}, + {u: val, f: "%f", want: "%!f(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"}, + {u: val, f: "%F", want: "%!F(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"}, + {u: val, f: "%g", want: "%!g(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"}, + {u: val, f: "%G", want: "%!G(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"}, + {u: val, f: "%o", want: "%!o(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"}, + {u: val, f: "%U", want: "%!U(uuid.UUID=12345678-90ab-cdef-1234-567890abcdef)"}, } for _, tt := range tests { got := fmt.Sprintf(tt.f, tt.u) From 094e8c20495e85b2066fb9e69159b0f3fc066bca Mon Sep 17 00:00:00 2001 From: Dylan Date: Thu, 14 Mar 2019 12:54:17 -0500 Subject: [PATCH 5/6] removed old comment block from UUID.Format() --- uuid.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/uuid.go b/uuid.go index 42f3ddb..9c4547f 100644 --- a/uuid.go +++ b/uuid.go @@ -194,9 +194,6 @@ func (u UUID) Format(f fmt.State, c rune) { default: // invalid/unsupported format verb fmt.Fprintf(f, "%%!%c(uuid.UUID=%s)", c, u.String()) - // for all other format specifiers, generate no output - // . the fmt package doesn't provide any mechanism for indicating which format verbs we support, - // so outputting nothing is at least deterministic } } From 90065f9eeb018e8fa41f828d58648a3227ae1283 Mon Sep 17 00:00:00 2001 From: Dylan Date: Fri, 15 Mar 2019 11:00:09 -0500 Subject: [PATCH 6/6] change to backtick-string (and removed escaped double-quotes) --- uuid_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uuid_test.go b/uuid_test.go index b3e5823..a73ecb9 100644 --- a/uuid_test.go +++ b/uuid_test.go @@ -147,7 +147,7 @@ func testUUIDFormat(t *testing.T) { for _, tt := range tests { got := fmt.Sprintf(tt.f, tt.u) if tt.want != got { - t.Errorf("Format(\"%s\") got %s, want %s", tt.f, got, tt.want) + t.Errorf(`Format("%s") got %s, want %s`, tt.f, got, tt.want) } } }