Skip to content

Commit cd907dd

Browse files
committed
Merge branch 'release/v1.6.0'
2 parents eca5f01 + 92b0672 commit cd907dd

File tree

5 files changed

+403
-2
lines changed

5 files changed

+403
-2
lines changed

README.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
# StringFormatter
22

3-
A set of a ***high performance string tools*** that helps to build strings from templates and process text faster than with `fmt`!!!.
4-
Slice printing is **50% faster with 8 items** slice and **250% with 20 items** slice
3+
* A set of a ***high performance string tools*** that helps to build strings from templates and process text faster than with `fmt`!!!.
4+
* Allows to **format code in appropriate style** (`Snake`, `Kebab`, `Camel`) and case.
5+
* Slice printing is **50% faster with 8 items** slice and **250% with 20 items** slice
56

67
![GitHub go.mod Go version (subdirectory of monorepo)](https://img.shields.io/github/go-mod/go-version/wissance/stringFormatter?style=plastic)
78
![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/wissance/stringFormatter?style=plastic)
@@ -17,6 +18,8 @@ Slice printing is **50% faster with 8 items** slice and **250% with 20 items** s
1718
![String Formatter: a convenient string formatting tool](img/benchmarks_adv.png)
1819
2. Additional text utilities:
1920
- convert ***map to string*** using one of predefined formats (see `text_utils.go`)
21+
3. Code Style formatting utilities
22+
- convert `snake`/`kebab`/`camel` programming code to each other and vice versa (see `stringstyle_formatter.go`).
2023

2124
### 1. Text formatting from templates
2225

@@ -80,6 +83,12 @@ For more convenient lines formatting we should choose how arguments are represen
8083
6. Lists
8184
- `{0:L-}, [1,2,3] outputs -> 1-2-3`
8285
- `{0:L, }, [1,2,3] outputs -> 1, 2, 3`
86+
7. Code
87+
- `{0:c:snake}, myFunc outputs -> my_func`
88+
- `{0:c:Snake}, myFunc outputs -> My_func`
89+
- `{0:c:SNAKE}, read-timeout outputs -> READ_TIMEOUT`
90+
- `{0:c:camel}, my_variable outputs -> myVariable`
91+
- `{0:c:Camel}, my_variable outputs -> MyVariable`
8392

8493
##### 1.2.4 Benchmarks of the Format and FormatComplex functions
8594

formatter.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,21 @@ func getItemAsStr(item *any, itemFormat *string) string {
575575
} else {
576576
return convertSliceToStrWithTypeDiscover(item, &separator)
577577
}
578+
case 'c', 'C':
579+
// c means code for apply stringFormatter.SetStyle()
580+
/* code formatting could be set as follows:
581+
* 1. {0:c:Camel} - stands for camel case with Capital letter at begin
582+
* 2. {0:c:camel} - stands for camel case with Capital letter at begin
583+
* 3. {0:c:Kebab}, {0:c:kebab}, {0:c:KEBAB} - 3 ways of Kebab formatting: first lower case with start Capital letter
584+
* 4. {0:c:Snake}, {0:c:snake}, {0:c:SNAKE} - 3 ways of Snake formatting: first lower case with start Capital letter
585+
*/
586+
formatSubParts := strings.Split(*itemFormat, ":")
587+
if len(formatSubParts) > 1 {
588+
format, firstSymbolCase, textCase := GetFormattingStyleOptions(formatSubParts[1])
589+
val := (*item).([]interface{})
590+
itemStr := val[0].(string)
591+
return SetStyle(&itemStr, format, firstSymbolCase, textCase)
592+
}
578593
default:
579594
base = 10
580595
}

formatter_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,21 @@ func TestFormatWithArgFormatting(t *testing.T) {
233233
args: []any{[]any{"s1", "s2", "s3"}},
234234
expected: "This is a list(slice) test: s1 s2 s3",
235235
},
236+
"docs_with_func_to_snake": {
237+
template: "This docs contains description of a \"{0:c:snake}\" function",
238+
args: []any{[]any{"callSoapService"}},
239+
expected: "This docs contains description of a \"call_soap_service\" function",
240+
},
241+
"docs_with_func_to_upper_case_snake": {
242+
template: "This docs contains depends on a \"{0:c:SNAKE}\" constant",
243+
args: []any{[]any{"ReadTimeout"}},
244+
expected: "This docs contains depends on a \"READ_TIMEOUT\" constant",
245+
},
246+
"notes-about-kebab": {
247+
template: "Nowadays we've got a very strange style it looks in a following manner \"{0:C:kebab}\" and called \"kebab\"",
248+
args: []any{[]any{"veryVeryStrange"}},
249+
expected: "Nowadays we've got a very strange style it looks in a following manner \"very-very-strange\" and called \"kebab\"",
250+
},
236251
} {
237252
// Run test here
238253
t.Run(name, func(t *testing.T) {

stringstyle_formatter.go

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
package stringFormatter
2+
3+
import (
4+
"strings"
5+
"unicode"
6+
)
7+
8+
type FormattingStyle string
9+
type CaseSetting int
10+
11+
const (
12+
Camel FormattingStyle = "camel"
13+
Snake FormattingStyle = "snake"
14+
Kebab FormattingStyle = "kebab"
15+
)
16+
17+
const (
18+
ToUpper CaseSetting = 1
19+
ToLower CaseSetting = 2
20+
NoChanges CaseSetting = 3
21+
)
22+
23+
type styleInc struct {
24+
Index int
25+
Style FormattingStyle
26+
}
27+
28+
var styleSigns = map[rune]FormattingStyle{
29+
'_': Snake,
30+
'-': Kebab,
31+
}
32+
33+
// SetStyle is a function that converts text with code to defined code style.
34+
/* Set text like a code style to on from FormattingStyle (Camel, Snake, or Kebab)
35+
* conversion of abbreviations like JSON, USB, and so on is going like a regular text
36+
* for current version, therefore they these abbreviations could be in a different
37+
* case after conversion.
38+
* Case settings apply in the following order : 1 - textCase, 2 - firstSymbol.
39+
* If you are not applying textCase to text converting from Camel to Snake or Kebab
40+
* result is lower case styled text. textCase does not apply to Camel style.
41+
* Parameters:
42+
* - text - pointer to text
43+
* - style - new code style
44+
* - firstSymbol - case settings for first symbol
45+
* - textCase - case settings for whole text except first symbol
46+
* Returns : new string with formatted line
47+
*/
48+
func SetStyle(text *string, style FormattingStyle, firstSymbol CaseSetting, textCase CaseSetting) string {
49+
if text == nil {
50+
return ""
51+
}
52+
sb := strings.Builder{}
53+
sb.Grow(len(*text))
54+
stats := defineFormattingStyle(text)
55+
startIndex := 0
56+
endIndex := 0
57+
// todo UMV think how to process ....
58+
// we could have many stats at the same time, probably we should use some config in the future
59+
// iterate over the map
60+
for _, v := range stats {
61+
endIndex = v.Index
62+
if endIndex < startIndex {
63+
continue
64+
}
65+
sb.WriteString((*text)[startIndex:endIndex])
66+
startIndex = v.Index
67+
68+
switch style {
69+
case Kebab:
70+
sb.WriteString("-")
71+
break
72+
case Snake:
73+
sb.WriteString("_")
74+
break
75+
case Camel:
76+
// in case of convert to Camel we should skip v.Index (because it is _ or -)
77+
if v.Style == Camel {
78+
sb.WriteRune(unicode.ToUpper(rune((*text)[endIndex])))
79+
} else {
80+
sb.WriteRune(unicode.ToUpper(rune((*text)[endIndex+1])))
81+
}
82+
startIndex += 1
83+
break
84+
}
85+
if v.Style != Camel {
86+
startIndex += 1
87+
}
88+
}
89+
sb.WriteString((*text)[startIndex:])
90+
result := strings.Builder{}
91+
if style != Camel {
92+
switch textCase {
93+
case ToUpper:
94+
result.WriteString(strings.ToUpper(sb.String()[1:]))
95+
break
96+
case ToLower:
97+
result.WriteString(strings.ToLower(sb.String()[1:]))
98+
break
99+
case NoChanges:
100+
result.WriteString(sb.String()[1:])
101+
break
102+
}
103+
} else {
104+
result.WriteString(sb.String()[1:])
105+
}
106+
107+
switch firstSymbol {
108+
case ToUpper:
109+
return strings.ToUpper(sb.String()[:1]) + result.String()
110+
case ToLower:
111+
return strings.ToLower(sb.String()[:1]) + result.String()
112+
case NoChanges:
113+
return sb.String()[:1] + result.String()
114+
default:
115+
return sb.String()[:1] + result.String()
116+
}
117+
}
118+
119+
// GetFormattingStyleOptions function that defines formatting style, case of first char and result from string
120+
/*
121+
*
122+
*/
123+
func GetFormattingStyleOptions(style string) (FormattingStyle, CaseSetting, CaseSetting) {
124+
styleLower := strings.ToLower(style)
125+
var formattingStyle FormattingStyle
126+
firstSymbolCase := ToLower
127+
textCase := NoChanges
128+
switch styleLower {
129+
case string(Camel):
130+
formattingStyle = Camel
131+
break
132+
case string(Snake):
133+
formattingStyle = Snake
134+
break
135+
case string(Kebab):
136+
formattingStyle = Kebab
137+
break
138+
}
139+
140+
runes := []rune(style)
141+
firstSymbolIsUpper := isSymbolIsUpper(runes[0])
142+
if firstSymbolIsUpper {
143+
firstSymbolCase = ToUpper
144+
}
145+
146+
if formattingStyle != Camel {
147+
allSymbolsUpperCase := isStringIsUpper(runes)
148+
if allSymbolsUpperCase {
149+
textCase = ToUpper
150+
} else {
151+
textCase = ToLower
152+
}
153+
}
154+
155+
return formattingStyle, firstSymbolCase, textCase
156+
}
157+
158+
// defineFormattingStyle
159+
/* This function defines what formatting style is using in text
160+
* If there are no transitions between symbols then here we have NoFormatting style
161+
* Didn't decide yet what to do if we are having multiple signatures
162+
* i.e. multiple_signs-at-sameTime .
163+
* Parameters:
164+
* - text - a sequence of symbols to check
165+
* Returns: formatting style using in the text
166+
*/
167+
func defineFormattingStyle(text *string) []styleInc {
168+
// symbol analyze, for camel case pattern -> aA, for kebab -> a-a, for snake -> a_a
169+
inclusions := make([]styleInc, 0)
170+
runes := []rune(*text)
171+
for pos, char := range runes {
172+
// define style and add stats
173+
style, ok := styleSigns[char]
174+
if !ok {
175+
// 1. Probably current symbol is not a sign and we should continue
176+
if pos > 0 && pos < len(runes)-1 {
177+
charIsUpperCase := isSymbolIsUpper(char)
178+
prevChar := runes[pos-1]
179+
prevCharIsUpperCase := unicode.IsUpper(prevChar)
180+
if charIsUpperCase && !prevCharIsUpperCase {
181+
style = Camel
182+
}
183+
}
184+
}
185+
if style != "" {
186+
inclusions = append(inclusions, styleInc{Index: pos, Style: style})
187+
}
188+
}
189+
return inclusions
190+
}
191+
192+
func isSymbolIsUpper(symbol rune) bool {
193+
return unicode.IsUpper(symbol) && unicode.IsLetter(symbol)
194+
}
195+
196+
func isStringIsUpper(str []rune) bool {
197+
isUpper := true
198+
for _, r := range str {
199+
if unicode.IsLetter(r) {
200+
isUpper = isUpper && unicode.IsUpper(r)
201+
}
202+
}
203+
return isUpper
204+
}

0 commit comments

Comments
 (0)