Skip to content

Commit 68d489a

Browse files
authored
chore: adding caser implementation (#28)
Adding caser implementation to support transformer development A port of the Go [caser](https://github.com/cloudquery/plugin-sdk/tree/main/caser) package.
1 parent 2a91c92 commit 68d489a

File tree

3 files changed

+432
-0
lines changed

3 files changed

+432
-0
lines changed
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
package io.cloudquery.caser;
2+
3+
import lombok.Builder;
4+
5+
import java.util.ArrayList;
6+
import java.util.Arrays;
7+
import java.util.HashMap;
8+
import java.util.HashSet;
9+
import java.util.List;
10+
import java.util.Map;
11+
import java.util.Set;
12+
13+
import static io.cloudquery.caser.Initialisms.*;
14+
import static io.cloudquery.caser.Initialisms.COMMON_INITIALISMS;
15+
16+
@Builder
17+
public class Caser {
18+
@Builder.Default
19+
private Set<String> initialisms = new HashSet<>(COMMON_INITIALISMS);
20+
21+
@Builder.Default
22+
private Map<String, String> snakeToCamelExceptions = new HashMap<>();
23+
24+
@Builder.Default
25+
private Map<String, String> camelToSnakeExceptions = new HashMap<>();
26+
27+
@Builder.Default
28+
private Map<String, String> customExceptions = new HashMap<>();
29+
30+
@Builder.Default
31+
private Set<String> customInitialisms = new HashSet<>();
32+
33+
public Caser(Set<String> initialisms,
34+
Map<String, String> snakeToCamelExceptions,
35+
Map<String, String> camelToSnakeExceptions,
36+
Map<String, String> customExceptions,
37+
Set<String> customInitialisms) {
38+
this.initialisms = initialisms;
39+
this.snakeToCamelExceptions = snakeToCamelExceptions;
40+
this.camelToSnakeExceptions = camelToSnakeExceptions;
41+
this.customExceptions = customExceptions;
42+
this.customInitialisms = customInitialisms;
43+
44+
45+
HashMap<String, String> combinedExceptions = new HashMap<>(COMMON_EXCEPTIONS);
46+
combinedExceptions.putAll(customExceptions);
47+
for (String key : combinedExceptions.keySet()) {
48+
snakeToCamelExceptions.put(key, combinedExceptions.get(key));
49+
camelToSnakeExceptions.put(combinedExceptions.get(key), key);
50+
}
51+
52+
initialisms.addAll(customInitialisms);
53+
}
54+
55+
public String toSnake(String s) {
56+
List<String> words = new ArrayList<>();
57+
int lastPos = 0;
58+
for (int i = 0; i < s.length(); i++) {
59+
if (i > 0 && Character.isUpperCase(s.charAt(i))) {
60+
61+
String initialism = startsWithInitialism(s.substring(lastPos));
62+
if (!initialism.isEmpty()) {
63+
words.add(initialism);
64+
i = lastPos + initialism.length();
65+
lastPos = i;
66+
continue;
67+
}
68+
69+
String capWord = getCapWord(s.substring(lastPos));
70+
if (!capWord.isEmpty()) {
71+
words.add(capWord);
72+
i = lastPos + capWord.length();
73+
lastPos = i;
74+
continue;
75+
}
76+
77+
words.add(s.substring(lastPos, i));
78+
lastPos = i;
79+
}
80+
}
81+
82+
if (!s.substring(lastPos).isEmpty()) {
83+
String w = s.substring(lastPos);
84+
if (w.equals("s")) {
85+
String lastWord = words.remove(words.size() - 1);
86+
words.add(lastWord + w);
87+
} else {
88+
words.add(s.substring(lastPos));
89+
}
90+
}
91+
92+
return String.join("_", words).toLowerCase();
93+
}
94+
95+
/**
96+
* Returns a string converted from snake case to camel case.
97+
* <p>
98+
*
99+
* @param s The input string
100+
* @return The string converted to camel case
101+
*/
102+
public String toCamel(String s) {
103+
if (s.isEmpty()) {
104+
return s;
105+
}
106+
List<String> words = Arrays.asList(s.split("_"));
107+
return String.join("", capitalize(words));
108+
}
109+
110+
/**
111+
* Returns a string converted from snake case to title case.
112+
* <p>
113+
* Title case is similar to camel case, but spaces are used in between words.
114+
*
115+
* @param s The input string
116+
* @return The string converted to title case
117+
*/
118+
public String toTitle(String s) {
119+
if (s.isEmpty()) {
120+
return s;
121+
}
122+
String[] words = s.split("_");
123+
if (!snakeToCamelExceptions.containsKey(words[0].toLowerCase())) {
124+
words[0] = words[0].substring(0, 1).toUpperCase() + words[0].substring(1).toLowerCase();
125+
}
126+
return String.join(" ", capitalize(Arrays.asList(words)));
127+
}
128+
129+
/**
130+
* Returns a string converted from snake case to pascal case
131+
*
132+
* @param s The input string
133+
* @return The string converted to pascal case
134+
*/
135+
public String toPascal(String s) {
136+
if (s.isEmpty()) {
137+
return s;
138+
}
139+
String camel = toCamel(s);
140+
return camel.substring(0, 1).toUpperCase() + camel.substring(1);
141+
}
142+
143+
/**
144+
* gets the next sequence of capitalized letters as a single word.
145+
* <p>
146+
* If there is a word after capitalized sequence it leaves one letter as beginning of the next word
147+
*
148+
* @param s The input string
149+
* @return A single word
150+
*/
151+
private String getCapWord(String s) {
152+
for (int i = 0; i < s.length(); i++) {
153+
if (!Character.isUpperCase(s.charAt(i))) {
154+
if (i == 0) {
155+
return "";
156+
}
157+
return s.substring(0, i - 1);
158+
}
159+
}
160+
return s;
161+
}
162+
163+
/**
164+
* Returns the initialism if the given string begins with it
165+
*
166+
* @param s The input string
167+
* @return The initialism if the given string begins with it, otherwise an empty string
168+
*/
169+
private String startsWithInitialism(String s) {
170+
String initialism = "";
171+
172+
// the longest initialism is 5 char, the shortest 2 we choose the longest match
173+
for (int i = 1; i <= s.length() && i <= 5; i++) {
174+
if (s.length() > i - 1 && this.initialisms.contains(s.substring(0, i)) && s.substring(0, i).length() > initialism.length()) {
175+
initialism = s.substring(0, i);
176+
}
177+
}
178+
179+
return initialism;
180+
}
181+
182+
private List<String> capitalize(List<String> words) {
183+
int n = words.stream().map(String::length).reduce(0, Integer::sum);
184+
185+
List<String> results = new ArrayList<>();
186+
for (int i = 0; i < words.size(); i++) {
187+
if (snakeToCamelExceptions.containsKey(words.get(i))) {
188+
results.add(snakeToCamelExceptions.get(words.get(i)));
189+
continue;
190+
}
191+
192+
if (i > 0) {
193+
String upper = words.get(i).toUpperCase();
194+
if (n > i - 1 && initialisms.contains(upper)) {
195+
results.add(upper);
196+
continue;
197+
}
198+
}
199+
200+
if (i > 0 && !words.get(i).isEmpty()) {
201+
results.add(words.get(i).substring(0, 1).toUpperCase()+words.get(i).substring(1));
202+
} else {
203+
results.add(words.get(i));
204+
}
205+
}
206+
return results;
207+
}
208+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package io.cloudquery.caser;
2+
3+
import java.util.Map;
4+
import java.util.Set;
5+
6+
public class Initialisms {
7+
public static final Set<String> COMMON_INITIALISMS = Set.of(
8+
"ACL",
9+
"API",
10+
"ASCII",
11+
"CIDR",
12+
"CPU",
13+
"CSS",
14+
"DNS",
15+
"EOF",
16+
"FQDN",
17+
"GUID",
18+
"HTML",
19+
"HTTP",
20+
"HTTPS",
21+
"ID",
22+
"IP",
23+
"IPC",
24+
"IPv4",
25+
"IPv6",
26+
"JSON",
27+
"LHS",
28+
"PID",
29+
"QOS",
30+
"QPS",
31+
"RAM",
32+
"RHS",
33+
"RPC",
34+
"SLA",
35+
"SMTP",
36+
"SQL",
37+
"SSH",
38+
"TCP",
39+
"TLS",
40+
"TTL",
41+
"UDP",
42+
"UI",
43+
"UID",
44+
"UUID",
45+
"URI",
46+
"URL",
47+
"UTF8",
48+
"VM",
49+
"XML",
50+
"XMPP",
51+
"XSRF",
52+
"XSS"
53+
);
54+
55+
public static final Map<String, String> COMMON_EXCEPTIONS = Map.of(
56+
"oauth", "OAuth",
57+
"ipv4", "IPv4",
58+
"ipv6", "IPv6"
59+
);
60+
61+
private Initialisms() {
62+
}
63+
}

0 commit comments

Comments
 (0)