66 "strings"
77)
88
9+ const argumentFormatSeparator = ":"
10+
911// Format
1012/* Func that makes string formatting from template
1113 * It differs from above function only by generic interface that allow to use only primitive data types:
@@ -36,15 +38,16 @@ func Format(template string, args ...any) string {
3638 formattedStr .Grow (templateLen + 22 * len (args ))
3739 j := - 1 //nolint:ineffassign
3840
41+ nestedBrackets := false
3942 formattedStr .WriteString (template [:start ])
4043 for i := start ; i < templateLen ; i ++ {
4144 if template [i ] == '{' {
4245 // possibly it is a template placeholder
4346 if i == templateLen - 1 {
4447 break
4548 }
46-
47- if template [i + 1 ] == '{' { // todo: umv: this not considering {{0}}
49+ // considering in 2 phases - {{ }}
50+ if template [i + 1 ] == '{' {
4851 formattedStr .WriteByte ('{' )
4952 continue
5053 }
@@ -57,6 +60,7 @@ func Format(template string, args ...any) string {
5760
5861 if template [j ] == '{' {
5962 // multiple nested curly brackets ...
63+ nestedBrackets = true
6064 formattedStr .WriteString (template [i :j ])
6165 i = j
6266 }
@@ -74,18 +78,40 @@ func Format(template string, args ...any) string {
7478 i = j + 1
7579 } else {
7680 argNumberStr := template [i + 1 : j ]
81+ // is here we should support formatting ?
7782 var argNumber int
7883 var err error
84+ var argFormatOptions string
7985 if len (argNumberStr ) == 1 {
80- // this makes work a little faster than AtoI
86+ // this calculation makes work a little faster than AtoI
8187 argNumber = int (argNumberStr [0 ] - '0' )
8288 } else {
83- argNumber , err = strconv .Atoi (argNumberStr )
89+ argNumber = - 1
90+ // Here we are going to process argument either with additional formatting or not
91+ // i.e. 0 for arg without formatting && 0:format for an argument wit formatting
92+ // todo(UMV): we could format json or yaml here ...
93+ formatOptionIndex := strings .Index (argNumberStr , argumentFormatSeparator )
94+ // formatOptionIndex can't be == 0, because 0 is a position of arg number
95+ if formatOptionIndex > 0 {
96+ // trim formatting string to remove spaces
97+ argFormatOptions = strings .Trim (argNumberStr [formatOptionIndex + 1 :], " " )
98+ argNumberStrPart := argNumberStr [:formatOptionIndex ]
99+ argNumber , err = strconv .Atoi (strings .Trim (argNumberStrPart , " " ))
100+ if err == nil {
101+ argNumberStr = argNumberStrPart
102+ }
103+ // make formatting option str for further pass to an argument
104+ }
105+ //
106+ if argNumber < 0 {
107+ argNumber , err = strconv .Atoi (argNumberStr )
108+ }
84109 }
85- //argNumber, err := strconv.Atoi(argNumberStr)
86- if err == nil && len (args ) > argNumber {
110+
111+ if (err == nil || (argFormatOptions != "" && ! nestedBrackets )) &&
112+ len (args ) > argNumber {
87113 // get number from placeholder
88- strVal := getItemAsStr (& args [argNumber ])
114+ strVal := getItemAsStr (& args [argNumber ], & argFormatOptions )
89115 formattedStr .WriteString (strVal )
90116 } else {
91117 formattedStr .WriteByte ('{' )
@@ -124,6 +150,7 @@ func FormatComplex(template string, args map[string]any) string {
124150 formattedStr := & strings.Builder {}
125151 formattedStr .Grow (templateLen + 22 * len (args ))
126152 j := - 1 //nolint:ineffassign
153+ nestedBrackets := false
127154 formattedStr .WriteString (template [:start ])
128155 for i := start ; i < templateLen ; i ++ {
129156 if template [i ] == '{' {
@@ -145,6 +172,7 @@ func FormatComplex(template string, args map[string]any) string {
145172 }
146173 if template [j ] == '{' {
147174 // multiple nested curly brackets ...
175+ nestedBrackets = true
148176 formattedStr .WriteString (template [i :j ])
149177 i = j
150178 }
@@ -159,11 +187,21 @@ func FormatComplex(template string, args map[string]any) string {
159187 formattedStr .WriteString (template [i + 1 : j + 1 ])
160188 i = j + 1
161189 } else {
190+ var argFormatOptions string
162191 argNumberStr := template [i + 1 : j ]
163192 arg , ok := args [argNumberStr ]
164- if ok {
193+ if ! ok {
194+ formatOptionIndex := strings .Index (argNumberStr , argumentFormatSeparator )
195+ if formatOptionIndex >= 0 {
196+ argFormatOptions = strings .Trim (argNumberStr [formatOptionIndex + 1 :], " " )
197+ argNumberStr = strings .Trim (argNumberStr [:formatOptionIndex ], " " )
198+ }
199+
200+ arg , ok = args [argNumberStr ]
201+ }
202+ if ok || (argFormatOptions != "" && ! nestedBrackets ) {
165203 // get number from placeholder
166- strVal := getItemAsStr (& arg )
204+ strVal := getItemAsStr (& arg , & argFormatOptions )
167205 formattedStr .WriteString (strVal )
168206 } else {
169207 formattedStr .WriteByte ('{' )
@@ -181,38 +219,159 @@ func FormatComplex(template string, args map[string]any) string {
181219 return formattedStr .String ()
182220}
183221
184- // todo: umv: impl format passing as param
185- func getItemAsStr (item * any ) string {
222+ func getItemAsStr (item * any , itemFormat * string ) string {
223+ base := 10
224+ var floatFormat byte = 'f'
225+ precision := - 1
226+ var preparedArgFormat string
227+ var argStr string
228+ postProcessingRequired := false
229+ intNumberFormat := false
230+ floatNumberFormat := false
231+
232+ if itemFormat != nil && len (* itemFormat ) > 0 {
233+ /* for numbers there are following formats:
234+ * d(D) - decimal
235+ * b(B) - binary
236+ * f(F) - fixed point i.e {0:F}, 10.5467890 -> 10.546789 ; {0:F4}, 10.5467890 -> 10.5468
237+ * e(E) - exponential - float point with scientific format {0:E2}, 191.0784 -> 1.91e+02
238+ * x(X) - hexadecimal i.e. {0:X}, 250 -> fa ; {0:X4}, 250 -> 00fa
239+ * p(P) - percent i.e. {0:P100}, 12 -> 12%
240+ * Following formats are not supported yet:
241+ * 1. c(C) currency it requires also country code
242+ * 2. g(G),and others with locales
243+ * 3. f(F) - fixed point, {0,F4}, 123.15 -> 123.1500
244+ * OUR own addition:
245+ * 1. O(o) - octahedral number format
246+ */
247+ preparedArgFormat = * itemFormat
248+ postProcessingRequired = len (preparedArgFormat ) > 1
249+
250+ switch rune ((* itemFormat )[0 ]) {
251+ case 'd' , 'D' :
252+ base = 10
253+ intNumberFormat = true
254+ case 'x' , 'X' :
255+ base = 16
256+ intNumberFormat = true
257+ case 'o' , 'O' :
258+ base = 8
259+ intNumberFormat = true
260+ case 'b' , 'B' :
261+ base = 2
262+ intNumberFormat = true
263+ case 'e' , 'E' , 'f' , 'F' :
264+ if rune (preparedArgFormat [0 ]) == 'e' || rune (preparedArgFormat [0 ]) == 'E' {
265+ floatFormat = 'e'
266+ }
267+ // precision was passed, take [1:end], extract precision
268+ if postProcessingRequired {
269+ precisionStr := preparedArgFormat [1 :]
270+ precisionVal , err := strconv .Atoi (precisionStr )
271+ if err == nil {
272+ precision = precisionVal
273+ }
274+ }
275+ postProcessingRequired = false
276+ floatNumberFormat = floatFormat == 'f'
277+
278+ case 'p' , 'P' :
279+ // percentage processes here ...
280+ if postProcessingRequired {
281+ dividerStr := preparedArgFormat [1 :]
282+ dividerVal , err := strconv .ParseFloat (dividerStr , 32 )
283+ if err == nil {
284+ // 1. Convert arg to float
285+ val := (* item ).(interface {})
286+ var floatVal float64
287+ switch val .(type ) {
288+ case float64 :
289+ floatVal = val .(float64 )
290+ case int :
291+ floatVal = float64 (val .(int ))
292+ default :
293+ floatVal = 0
294+ }
295+ // 2. Divide arg / divider and multiply by 100
296+ percentage := (floatVal / dividerVal ) * 100
297+ return strconv .FormatFloat (percentage , floatFormat , 2 , 64 )
298+ }
299+ }
300+
301+ default :
302+ base = 10
303+ }
304+ }
305+
186306 switch v := (* item ).(type ) {
187307 case string :
188- return v
308+ argStr = v
189309 case int8 :
190- return strconv .FormatInt (int64 (v ), 10 )
310+ argStr = strconv .FormatInt (int64 (v ), base )
191311 case int16 :
192- return strconv .FormatInt (int64 (v ), 10 )
312+ argStr = strconv .FormatInt (int64 (v ), base )
193313 case int32 :
194- return strconv .FormatInt (int64 (v ), 10 )
314+ argStr = strconv .FormatInt (int64 (v ), base )
195315 case int64 :
196- return strconv .FormatInt (v , 10 )
316+ argStr = strconv .FormatInt (v , base )
197317 case int :
198- return strconv .FormatInt (int64 (v ), 10 )
318+ argStr = strconv .FormatInt (int64 (v ), base )
199319 case uint8 :
200- return strconv .FormatUint (uint64 (v ), 10 )
320+ argStr = strconv .FormatUint (uint64 (v ), base )
201321 case uint16 :
202- return strconv .FormatUint (uint64 (v ), 10 )
322+ argStr = strconv .FormatUint (uint64 (v ), base )
203323 case uint32 :
204- return strconv .FormatUint (uint64 (v ), 10 )
324+ argStr = strconv .FormatUint (uint64 (v ), base )
205325 case uint64 :
206- return strconv .FormatUint (v , 10 )
326+ argStr = strconv .FormatUint (v , base )
207327 case uint :
208- return strconv .FormatUint (uint64 (v ), 10 )
328+ argStr = strconv .FormatUint (uint64 (v ), base )
209329 case bool :
210- return strconv .FormatBool (v )
330+ argStr = strconv .FormatBool (v )
211331 case float32 :
212- return strconv .FormatFloat (float64 (v ), 'f' , - 1 , 32 )
332+ argStr = strconv .FormatFloat (float64 (v ), floatFormat , precision , 32 )
213333 case float64 :
214- return strconv .FormatFloat (v , 'f' , - 1 , 64 )
334+ argStr = strconv .FormatFloat (v , floatFormat , precision , 64 )
215335 default :
216- return fmt .Sprintf ("%v" , v )
336+ argStr = fmt .Sprintf ("%v" , v )
337+ }
338+
339+ if ! postProcessingRequired {
340+ return argStr
217341 }
342+
343+ // 1. If integer numbers add filling
344+ if intNumberFormat {
345+ symbolsStr := preparedArgFormat [1 :]
346+ symbolsStrVal , err := strconv .Atoi (symbolsStr )
347+ if err == nil {
348+ symbolsToAdd := symbolsStrVal - len (argStr )
349+ if symbolsToAdd > 0 {
350+ advArgStr := strings.Builder {}
351+ advArgStr .Grow (len (argStr ) + symbolsToAdd + 1 )
352+
353+ for i := 0 ; i < symbolsToAdd ; i ++ {
354+ advArgStr .WriteByte ('0' )
355+ }
356+ advArgStr .WriteString (argStr )
357+ return advArgStr .String ()
358+ }
359+ }
360+ }
361+
362+ if floatNumberFormat && precision > 0 {
363+ pointIndex := strings .Index (argStr , "." )
364+ if pointIndex > 0 {
365+ advArgStr := strings.Builder {}
366+ advArgStr .Grow (len (argStr ) + precision + 1 )
367+ advArgStr .WriteString (argStr )
368+ numberOfSymbolsAfterPoint := len (argStr ) - (pointIndex + 1 )
369+ for i := numberOfSymbolsAfterPoint ; i < precision ; i ++ {
370+ advArgStr .WriteByte (0 )
371+ }
372+ return advArgStr .String ()
373+ }
374+ }
375+
376+ return argStr
218377}
0 commit comments