Skip to content

Commit

Permalink
Simplify strcase #2
Browse files Browse the repository at this point in the history
  • Loading branch information
lippserd committed May 7, 2024
1 parent 2c8b5bf commit b1bc41e
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 158 deletions.
22 changes: 11 additions & 11 deletions cmd/icingadb-migrate/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ type commentRow = struct {
DeletionTimeUsec uint32
Name string
ObjecttypeId uint8
Name1 string `db:"name1"`
Name2 string `db:"name2"`
Name1 string
Name2 string
}

func convertCommentRows(
Expand Down Expand Up @@ -229,8 +229,8 @@ type downtimeRow = struct {
TriggerTime int64
Name string
ObjecttypeId uint8
Name1 string `db:"name1"`
Name2 string `db:"name2"`
Name1 string
Name2 string
TriggeredBy string
}

Expand Down Expand Up @@ -379,8 +379,8 @@ type flappingRow = struct {
LowThreshold float64
HighThreshold float64
ObjecttypeId uint8
Name1 string `db:"name1"`
Name2 string `db:"name2"`
Name1 string
Name2 string
}

func convertFlappingRows(
Expand Down Expand Up @@ -537,8 +537,8 @@ type notificationRow = struct {
LongOutput sql.NullString
ContactsNotified uint16
ObjecttypeId uint8
Name1 string `db:"name1"`
Name2 string `db:"name2"`
Name1 string
Name2 string
}

func convertNotificationRows(
Expand All @@ -565,7 +565,7 @@ func convertNotificationRows(

var contacts []struct {
NotificationId uint64
Name1 string `db:"name1"`
Name1 string
}

{
Expand Down Expand Up @@ -733,8 +733,8 @@ type stateRow = struct {
LongOutput sql.NullString
CheckSource sql.NullString
ObjecttypeId uint8
Name1 string `db:"name1"`
Name2 string `db:"name2"`
Name1 string
Name2 string
}

func convertStateRows(
Expand Down
4 changes: 2 additions & 2 deletions pkg/icingadb/v1/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ import (
type Host struct {
Checkable `json:",inline"`
Address string `json:"address"`
Address6 string `db:"address6" json:"address6"`
Address6 string `json:"address6"`
AddressBin AddressBin `json:"-"`
Address6Bin Address6Bin `db:"address6_bin" json:"-"`
Address6Bin Address6Bin `json:"-"`
}

// Init implements the contracts.Initer interface.
Expand Down
16 changes: 8 additions & 8 deletions pkg/icingadb/v1/icingadb_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ type IcingadbInstance struct {
EndpointId types.Binary `json:"endpoint_id"`
Heartbeat types.UnixMilli `json:"heartbeat"`
Responsible types.Bool `json:"responsible"`
Icinga2Version string `db:"icinga2_version" json:"icinga2_version"`
Icinga2StartTime types.UnixMilli `db:"icinga2_start_time" json:"icinga2_start_time"`
Icinga2NotificationsEnabled types.Bool `db:"icinga2_notifications_enabled" json:"icinga2_notifications_enabled"`
Icinga2ActiveServiceChecksEnabled types.Bool `db:"icinga2_active_service_checks_enabled" json:"icinga2_active_service_checks_enabled"`
Icinga2ActiveHostChecksEnabled types.Bool `db:"icinga2_active_host_checks_enabled" json:"icinga2_active_host_checks_enabled"`
Icinga2EventHandlersEnabled types.Bool `db:"icinga2_event_handlers_enabled" json:"icinga2_event_handlers_enabled"`
Icinga2FlapDetectionEnabled types.Bool `db:"icinga2_flap_detection_enabled" json:"icinga2_flap_detection_enabled"`
Icinga2PerformanceDataEnabled types.Bool `db:"icinga2_performance_data_enabled" json:"icinga2_performance_data_enabled"`
Icinga2Version string `json:"icinga2_version"`
Icinga2StartTime types.UnixMilli `json:"icinga2_start_time"`
Icinga2NotificationsEnabled types.Bool `json:"icinga2_notifications_enabled"`
Icinga2ActiveServiceChecksEnabled types.Bool `json:"icinga2_active_service_checks_enabled"`
Icinga2ActiveHostChecksEnabled types.Bool `json:"icinga2_active_host_checks_enabled"`
Icinga2EventHandlersEnabled types.Bool `json:"icinga2_event_handlers_enabled"`
Icinga2FlapDetectionEnabled types.Bool `json:"icinga2_flap_detection_enabled"`
Icinga2PerformanceDataEnabled types.Bool `json:"icinga2_performance_data_enabled"`
}
103 changes: 20 additions & 83 deletions pkg/strcase/strcase.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,8 @@
// Package strcase implements functions to convert a camelCase, MixedCAse or
// space/underscore/hyphen/dot delimited UTF-8 string into various cases.
// Package strcase implements functions to convert a camelCase UTF-8 string into various cases.
//
// New delimiters will be inserted based on the following transitions:
// - On any change from non-number to number or vice-versa.
// - On any change from non-letter to letter or vice-versa.
// - Between each non-delimiter character that is neither a number nor a letter.
// - On any change from uppercase to lowercase letters or vice-versa,
// taking into account the following considerations:
//
// The input string can contain the delimiters space, underscore, hyphen and dot (also mixed),
// all of which are replaced by the desired delimiter. All delimiters, i.e. existing and inserted ones,
// separate words to which the following case transition rules apply:
// - Words can begin with an uppercase letter,
// after which no delimiter is inserted if the following letter is lowercase. This means that a delimiter is
// only inserted after at least two consecutive uppercase letters before a lowercase letter.
// - However, a delimiter is always inserted when changing from a lowercase to an uppercase letter.
//
// The returned string will always have all leading and trailing white spaces removed.
// - On any change from lowercase to uppercase letter.
// - On any change from number to uppercase letter.
package strcase

import (
Expand Down Expand Up @@ -45,88 +31,39 @@ func ScreamingSnake(s string) string {
return ScreamingDelimited(s, '_')
}

// convert converts a camelCase, MixedCAse or space/underscore/hyphen/dot delimited string into various cases.
// convert converts a camelCase UTF-8 string into various cases.
// _case must be unicode.LowerCase or unicode.UpperCase.
func convert(s string, _case int, d rune) string {
s = strings.TrimSpace(s)

if len(s) == 0 {
return s
}

n := strings.Builder{}
n.Grow(len(s) + 2) // Allow adding at least 2 delimiters without another allocation.

WordLoop:
for pos, prevRune := 0, int32(0); pos < len(s); {
for _, r := range s[pos:] {
pos += utf8.RuneLen(r)
var pos int
var prevRune int32

switch r {
case ' ', '_', '-', '.':
n.WriteRune(d)
for _, r := range s[pos:] {
n.WriteRune(unicode.To(_case, r))

continue
default:
n.WriteRune(unicode.To(_case, r))
pos += utf8.RuneLen(r)
prevRune = r

prevRune = r
}
// This loop just extracts the first rune of the string,
// without needing to allocate a new slice of runes as
// range iterates over the runes of the string.
break
}

break
for _, r := range s[pos:] {
if unicode.IsUpper(r) && (unicode.IsNumber(prevRune) || unicode.IsLower(prevRune)) {
n.WriteRune(d)
}

var consecutiveUppercaseLetters int
for p, r := range s[pos:] {
switch r {
case ' ', '_', '-', '.':
n.WriteRune(d)

pos += p + 1
continue WordLoop
default:
if unicode.IsLetter(r) {
isUpper := unicode.IsUpper(r)

if !unicode.IsLetter(prevRune) {
// Add delimiter on any change from non-letter to letter.
n.WriteRune(d)
} else if unicode.IsLower(prevRune) {
if isUpper {
// Add delimiter when transitioning between lowercase and uppercase letters.
n.WriteRune(d)
}
} else if consecutiveUppercaseLetters >= 2 && unicode.IsLower(r) {
// Add delimiter when transitioning between uppercase and lowercase letters.
n.WriteRune(d)
}

if isUpper {
consecutiveUppercaseLetters++
} else {
consecutiveUppercaseLetters = 0
}
} else {
if unicode.IsNumber(r) {
if !unicode.IsNumber(prevRune) {
// Add delimiter on any change from non-number to number.
n.WriteRune(d)
}
} else {
// Add delimiter on any non-number and non-letter.
n.WriteRune(d)
}

consecutiveUppercaseLetters = 0
}

n.WriteRune(unicode.To(_case, r))

prevRune = r
}
}
n.WriteRune(unicode.To(_case, r))

break WordLoop
prevRune = r
}

return n.String()
Expand Down
81 changes: 27 additions & 54 deletions pkg/strcase/strcase_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,80 +5,53 @@ import (
"testing"
)

var cases = [][]string{
var tests = [][]string{
{"", ""},
{" ", ""},
{"-", "_"},
{"-.", "__"},
{"AnyKind of_string", "any_kind_of_string"},
{"Any.Kind-of_string", "any_kind_of_string"},
{" Test Case ", "test_case"},
{" Test Case", "test_case"},
{"Test Case ", "test_case"},
{"Test Case. ", "test_case_"},
{"Test", "test"},
{"test", "test"},
{"testCase", "test_case"},
{"test_case", "test_case"},
{"Test Case ", "test_case"},
{"Test Case", "test_case"},
{"TestCase", "test_case"},
{"TESTcase", "test_case"},
{"TestCaSe", "test_ca_se"},
{"Test cAse", "test_c_ase"},
{"Test", "test"},
{"test", "test"},
{"Test_Case", "test_case"},
{"ID", "id"},
{"userID", "user_id"},
{"UserID", "user_id"},
{"ManyManyWords", "many_many_words"},
{"manyManyWords", "many_many_words"},
{" some string", "some_string"},
{"some string", "some_string"},
{"userID", "user_id"},
{"icinga2", "icinga_2"},
{"icinga2", "icinga2"},
{"Icinga2Version", "icinga2_version"},
{"k8sVersion", "k8s_version"},
{"1234", "1234"},
{"a1b2c3d4", "a_1_b_2_c_3_d_4"},
{"with1234digits", "with_1234_digits"},
{"with1234dIgits", "with_1234_d_igits"},
{"with1234Digits", "with_1234_digits"},
{"with1234DIgits", "with_1234_di_gits"},
{"a1b2c3d4", "a1b2c3d4"},
{"with1234digits", "with1234digits"},
{"with1234Digits", "with1234_digits"},
{"IPv4", "ipv4"},
{"IPv4Address", "ipv4_address"},
{"caféCrème", "café_crème"},
{"0℃", "0_℃"},
{"~0", "~_0"},
{"icinga💯points", "icinga_💯_points"},
{"😃🙃😀", "😃_🙃_😀"},
{"0℃", "0℃"},
{"~0", "~0"},
{"icinga💯points", "icinga💯points"},
{"😃🙃😀", "😃🙃😀"},
{"こんにちは", "こんにちは"},
{"\xff\xfe\xfd", "�"},
}

func TestSnake(t *testing.T) { snake(t) }

func BenchmarkSnake(b *testing.B) {
for n := 0; n < b.N; n++ {
snake(b)
}
}

func TestScreamingSnake(t *testing.T) { screamingSnake(t) }

func BenchmarkScreamingSnake(b *testing.B) {
for n := 0; n < b.N; n++ {
screamingSnake(b)
}
}

func snake(tb testing.TB) {
for _, c := range cases {
s, expected := c[0], c[1]
func TestSnake(t *testing.T) {
for _, test := range tests {
s, expected := test[0], test[1]
actual := Snake(s)
if actual != expected {
tb.Errorf("%q: %q != %q", s, actual, expected)
t.Errorf("%q: %q != %q", s, actual, expected)
}
}
}

func screamingSnake(tb testing.TB) {
for _, c := range cases {
s, expected := c[0], strings.ToUpper(c[1])
func TestScreamingSnake(t *testing.T) {
for _, test := range tests {
s, expected := test[0], strings.ToUpper(test[1])
actual := ScreamingSnake(s)
if actual != expected {
tb.Errorf("%q: %q != %q", s, actual, expected)
t.Errorf("%q: %q != %q", s, actual, expected)
}
}
}

0 comments on commit b1bc41e

Please sign in to comment.