From 5573a2f26b5392b93af64e4728fb654a98bc7167 Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Thu, 15 Dec 2022 17:18:06 +0100 Subject: [PATCH] feat: Add AutoCLI Sane Kebab Case (#14316) --- client/v2/autocli/flag/enum.go | 3 +- client/v2/autocli/query.go | 2 +- client/v2/autocli/query_test.go | 8 +- .../autocli/testdata/help-deprecated.golden | 14 +-- client/v2/autocli/testdata/help-echo.golden | 6 +- client/v2/autocli/testdata/help.golden | 6 +- client/v2/go.mod | 1 - client/v2/go.sum | 2 - client/v2/internal/strcase/kebab.go | 101 ++++++++++++++++++ client/v2/internal/strcase/kebab_test.go | 42 ++++++++ client/v2/internal/util/util.go | 3 +- 11 files changed, 165 insertions(+), 23 deletions(-) create mode 100644 client/v2/internal/strcase/kebab.go create mode 100644 client/v2/internal/strcase/kebab_test.go diff --git a/client/v2/autocli/flag/enum.go b/client/v2/autocli/flag/enum.go index 79e1cba53a..8b555d9232 100644 --- a/client/v2/autocli/flag/enum.go +++ b/client/v2/autocli/flag/enum.go @@ -5,8 +5,9 @@ import ( "fmt" "strings" - "github.com/iancoleman/strcase" "google.golang.org/protobuf/reflect/protoreflect" + + "cosmossdk.io/client/v2/internal/strcase" ) type enumType struct { diff --git a/client/v2/autocli/query.go b/client/v2/autocli/query.go index 9f748e4df9..880ab3f0cd 100644 --- a/client/v2/autocli/query.go +++ b/client/v2/autocli/query.go @@ -4,12 +4,12 @@ import ( "fmt" autocliv1 "cosmossdk.io/api/cosmos/autocli/v1" - "github.com/iancoleman/strcase" "github.com/spf13/cobra" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/reflect/protoreflect" "google.golang.org/protobuf/reflect/protoregistry" + "cosmossdk.io/client/v2/internal/strcase" "cosmossdk.io/client/v2/internal/util" ) diff --git a/client/v2/autocli/query_test.go b/client/v2/autocli/query_test.go index 6eec93707b..e37d999cd9 100644 --- a/client/v2/autocli/query_test.go +++ b/client/v2/autocli/query_test.go @@ -143,9 +143,9 @@ func TestEverything(t *testing.T) { "--a-message", `{"bar":"abc", "baz":-3}`, "--duration", "4h3s", "--uint32", "27", - "--u-64", "3267246890", - "--i-32", "-253", - "--i-64", "-234602347", + "--u64", "3267246890", + "--i32", "-253", + "--i64", "-234602347", "--str", "def", "--timestamp", "2019-01-02T00:01:02Z", "--a-coin", `{"denom":"foo","amount":"100000"}`, @@ -181,7 +181,7 @@ func TestOptions(t *testing.T) { "echo", "1", "abc", `{"denom":"foo","amount":"1"}`, "-u", "27", // shorthand - "--u-64", // no opt default value + "--u64", // no opt default value ) lastReq := conn.lastRequest.(*testpb.EchoRequest) assert.Equal(t, uint32(27), lastReq.U32) // shorthand got set diff --git a/client/v2/autocli/testdata/help-deprecated.golden b/client/v2/autocli/testdata/help-deprecated.golden index 110b881293..e013419102 100644 --- a/client/v2/autocli/testdata/help-deprecated.golden +++ b/client/v2/autocli/testdata/help-deprecated.golden @@ -16,21 +16,21 @@ Flags: --enums Enum (unspecified | one | two | five | neg-three) (repeated) -h, --help help for echo --hidden-bool - --i-32 int32 - --i-64 int + --i32 int32 + --i64 int --page-count-total --page-key bytesBase64 --page-limit uint --page-offset uint --page-reverse - --positional-1 int32 - --positional-2 string - --positional-3-varargs cosmos.base.v1beta1.Coin (json) (repeated) + --positional1 int32 + --positional2 string + --positional3-varargs cosmos.base.v1beta1.Coin (json) (repeated) --shorthand-deprecated-field string --some-messages testpb.AMessage (json) (repeated) --str string --strings strings --timestamp timestamp (RFC 3339) - --u-32 uint32 - --u-64 uint + --u32 uint32 + --u64 uint --uints uints (default []) diff --git a/client/v2/autocli/testdata/help-echo.golden b/client/v2/autocli/testdata/help-echo.golden index a129553346..4f60825ed1 100644 --- a/client/v2/autocli/testdata/help-echo.golden +++ b/client/v2/autocli/testdata/help-echo.golden @@ -22,8 +22,8 @@ Flags: --durations duration (repeated) --enums Enum (unspecified | one | two | five | neg-three) (repeated) -h, --help help for echo - --i-32 int32 some random int32 - --i-64 int + --i32 int32 some random int32 + --i64 int --page-count-total --page-key bytesBase64 --page-limit uint @@ -34,7 +34,7 @@ Flags: --str string --strings strings --timestamp timestamp (RFC 3339) - --u-64 uint[=5] some random uint64 + --u64 uint[=5] some random uint64 -u, --uint32 uint32 some random uint32 --uints uints (default []) -v, --version version for echo diff --git a/client/v2/autocli/testdata/help.golden b/client/v2/autocli/testdata/help.golden index a129553346..197f4f9aee 100644 --- a/client/v2/autocli/testdata/help.golden +++ b/client/v2/autocli/testdata/help.golden @@ -22,8 +22,8 @@ Flags: --durations duration (repeated) --enums Enum (unspecified | one | two | five | neg-three) (repeated) -h, --help help for echo - --i-32 int32 some random int32 - --i-64 int + --i32 int32 some random int32 + --i64 int --page-count-total --page-key bytesBase64 --page-limit uint @@ -34,7 +34,7 @@ Flags: --str string --strings strings --timestamp timestamp (RFC 3339) - --u-64 uint[=5] some random uint64 + --u64 uint[=5] some random uint64 -u, --uint32 uint32 some random uint32 --uints uints (default []) -v, --version version for echo diff --git a/client/v2/go.mod b/client/v2/go.mod index a93e9bb609..11fcf3e530 100644 --- a/client/v2/go.mod +++ b/client/v2/go.mod @@ -6,7 +6,6 @@ require ( cosmossdk.io/api v0.2.6 cosmossdk.io/core v0.3.2 github.com/cosmos/cosmos-proto v1.0.0-beta.1 - github.com/iancoleman/strcase v0.2.0 github.com/spf13/cobra v1.6.1 github.com/spf13/pflag v1.0.5 google.golang.org/grpc v1.51.0 diff --git a/client/v2/go.sum b/client/v2/go.sum index 56dceb9d43..6a9142c757 100644 --- a/client/v2/go.sum +++ b/client/v2/go.sum @@ -21,8 +21,6 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0= -github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= diff --git a/client/v2/internal/strcase/kebab.go b/client/v2/internal/strcase/kebab.go new file mode 100644 index 0000000000..ac3eb1c0a9 --- /dev/null +++ b/client/v2/internal/strcase/kebab.go @@ -0,0 +1,101 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 Ian Coleman + * Copyright (c) 2018 Ma_124, + * Copyright (c) 2022 Cosmos SDK Developers, + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, Subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or Substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package strcase + +import ( + "strings" +) + +// ToKebab converts a string to kebab-case +func ToKebab(s string) string { + return ToDelimited(s, '-', false) +} + +// ToScreamingKebab converts a string to SCREAMING-KEBAB-CASE +func ToScreamingKebab(s string) string { + return ToDelimited(s, '-', true) +} + +// ToSnake converts a string to snake_case +func ToSnake(s string) string { + return ToDelimited(s, '_', false) +} + +// ToScreamingSnake converts a string to SCREAMING_SNAKE_CASE +func ToScreamingSnake(s string) string { + return ToDelimited(s, '_', true) +} + +// ToDelimited converts a string to delimited.snake.case +// (in this case `delimiter = '.'`) +func ToDelimited(s string, delimiter uint8, screaming bool) string { + s = strings.TrimSpace(s) + n := strings.Builder{} + n.Grow(len(s) + 2) // nominal 2 bytes of extra space for inserted delimiters + for i, v := range []byte(s) { + vIsCap := v >= 'A' && v <= 'Z' + vIsLow := v >= 'a' && v <= 'z' + if vIsLow && screaming { + v += 'A' + v -= 'a' + } else if vIsCap && !screaming { + v += 'a' + v -= 'A' + } + + // treat acronyms as words, eg for JSONData -> JSON is a whole word + if i+1 < len(s) { + next := s[i+1] + vIsNum := v >= '0' && v <= '9' + nextIsCap := next >= 'A' && next <= 'Z' + nextIsLow := next >= 'a' && next <= 'z' + nextIsNum := next >= '0' && next <= '9' + // add underscore if next letter case type is changed + if (vIsCap && (nextIsLow || nextIsNum)) || (vIsLow && (nextIsCap || nextIsNum)) || (vIsNum && (nextIsCap || nextIsLow)) { + if vIsCap && nextIsLow { + if prevIsCap := i > 0 && s[i-1] >= 'A' && s[i-1] <= 'Z'; prevIsCap { + n.WriteByte(delimiter) + } + } + n.WriteByte(v) + if (vIsLow && nextIsCap) || (vIsNum && nextIsCap) || (vIsNum && nextIsLow) { + n.WriteByte(delimiter) + } + continue + } + } + + if v == ' ' || v == '_' || v == '-' || v == '.' { + // replace space/underscore/hyphen/dot with delimiter + n.WriteByte(delimiter) + } else { + n.WriteByte(v) + } + } + + return n.String() +} diff --git a/client/v2/internal/strcase/kebab_test.go b/client/v2/internal/strcase/kebab_test.go new file mode 100644 index 0000000000..71e2300e31 --- /dev/null +++ b/client/v2/internal/strcase/kebab_test.go @@ -0,0 +1,42 @@ +package strcase_test + +import ( + "testing" + + "cosmossdk.io/client/v2/internal/strcase" + "gotest.tools/v3/assert" +) + +func toKebab(t testing.TB) { + cases := [][]string{ + {"testCase", "test-case"}, + {"TestCase", "test-case"}, + {"Test Case", "test-case"}, + {"TEST CASE", "test-case"}, + {"TESTCase", "test-case"}, + {"TESTCASE", "testcase"}, + {"TEST_CASE", "test-case"}, + {"Bech32", "bech32"}, + {"Bech32Address", "bech32-address"}, + {"Bech32_Address", "bech32-address"}, + {"Bech32Address10", "bech32-address10"}, + {"Bech32-Address10", "bech32-address10"}, + {"SecondBech32Address10Foo", "second-bech32-address10-foo"}, + } + for _, i := range cases { + in := i[0] + out := i[1] + result := strcase.ToKebab(in) + assert.Equal(t, out, result, "ToKebab(%s) = %s, want %s", in, result, out) + } +} + +func TestToKebab(t *testing.T) { + toKebab(t) +} + +func BenchmarkToKebab(b *testing.B) { + for n := 0; n < b.N; n++ { + toKebab(b) + } +} diff --git a/client/v2/internal/util/util.go b/client/v2/internal/util/util.go index 18b8d53006..d37c023a87 100644 --- a/client/v2/internal/util/util.go +++ b/client/v2/internal/util/util.go @@ -1,10 +1,11 @@ package util import ( - "github.com/iancoleman/strcase" "google.golang.org/protobuf/reflect/protoreflect" "google.golang.org/protobuf/reflect/protoregistry" "google.golang.org/protobuf/types/dynamicpb" + + "cosmossdk.io/client/v2/internal/strcase" ) func DescriptorKebabName(descriptor protoreflect.Descriptor) string {