1package goose
2
3import (
4	"bytes"
5	"strings"
6	"unicode"
7	"unicode/utf8"
8)
9
10type camelSnakeStateMachine int
11
12const ( //                                           _$$_This is some text, OK?!
13	idle          camelSnakeStateMachine = iota // 0 ↑                     ↑   ↑
14	firstAlphaNum                               // 1     ↑    ↑  ↑    ↑     ↑
15	alphaNum                                    // 2      ↑↑↑  ↑  ↑↑↑  ↑↑↑   ↑
16	delimiter                                   // 3         ↑  ↑    ↑    ↑   ↑
17)
18
19func (s camelSnakeStateMachine) next(r rune) camelSnakeStateMachine {
20	switch s {
21	case idle:
22		if isAlphaNum(r) {
23			return firstAlphaNum
24		}
25	case firstAlphaNum:
26		if isAlphaNum(r) {
27			return alphaNum
28		}
29		return delimiter
30	case alphaNum:
31		if !isAlphaNum(r) {
32			return delimiter
33		}
34	case delimiter:
35		if isAlphaNum(r) {
36			return firstAlphaNum
37		}
38		return idle
39	}
40	return s
41}
42
43func camelCase(str string) string {
44	var b strings.Builder
45
46	stateMachine := idle
47	for i := 0; i < len(str); {
48		r, size := utf8.DecodeRuneInString(str[i:])
49		i += size
50		stateMachine = stateMachine.next(r)
51		switch stateMachine {
52		case firstAlphaNum:
53			b.WriteRune(unicode.ToUpper(r))
54		case alphaNum:
55			b.WriteRune(unicode.ToLower(r))
56		}
57	}
58	return b.String()
59}
60
61func snakeCase(str string) string {
62	var b bytes.Buffer
63
64	stateMachine := idle
65	for i := 0; i < len(str); {
66		r, size := utf8.DecodeRuneInString(str[i:])
67		i += size
68		stateMachine = stateMachine.next(r)
69		switch stateMachine {
70		case firstAlphaNum, alphaNum:
71			b.WriteRune(unicode.ToLower(r))
72		case delimiter:
73			b.WriteByte('_')
74		}
75	}
76	if stateMachine == idle {
77		return string(bytes.TrimSuffix(b.Bytes(), []byte{'_'}))
78	}
79	return b.String()
80}
81
82func isAlphaNum(r rune) bool {
83	return unicode.IsLetter(r) || unicode.IsNumber(r)
84}
85