mirror of
https://github.com/Ed94/Odin.git
synced 2026-07-16 16:01:26 -07:00
393 lines
10 KiB
Odin
393 lines
10 KiB
Odin
package bindgen
|
|
|
|
import "core:fmt"
|
|
import "core:os"
|
|
import "core:io"
|
|
import "core:strings"
|
|
import "core:unicode/utf8"
|
|
|
|
Case :: enum {
|
|
Unknown,
|
|
Camel,
|
|
Constant,
|
|
Kebab,
|
|
Pascal,
|
|
Snake,
|
|
}
|
|
|
|
WordCase :: enum {
|
|
Unknown,
|
|
Up,
|
|
Low,
|
|
FirstUp,
|
|
// When first upping, numbers are followed always by a capital
|
|
FirstUpNumberReset,
|
|
}
|
|
|
|
// Change a character to a capital.
|
|
to_uppercase :: proc(c : rune) -> rune {
|
|
c := c;
|
|
if c >= 'a' && c <= 'z' {
|
|
c = c - 'a' + 'A';
|
|
}
|
|
return c;
|
|
}
|
|
|
|
// Change a character to lowercase.
|
|
to_lowercase :: proc(c : rune) -> rune {
|
|
c := c;
|
|
if c >= 'A' && c <= 'Z' {
|
|
c = c - 'A' + 'a';
|
|
}
|
|
return c;
|
|
}
|
|
|
|
// @note Stolen tprint and fprint from fmt package, because it was confusing due to args: ..any and sep default parameter.
|
|
tcat :: proc(args: ..any) -> string {
|
|
return fmt.tprint(args=args, sep="");
|
|
}
|
|
|
|
fcat :: proc(fd: os.Handle, args: ..any) -> int {
|
|
return fmt.fprint(fd=fd, args=args, sep="");
|
|
}
|
|
|
|
// Change the case convention of a word.
|
|
change_word_case :: proc(str : string, targetCase : WordCase) -> string {
|
|
newStr : string;
|
|
if targetCase == WordCase.Up {
|
|
for c in str {
|
|
newStr = tcat(newStr, to_uppercase(c));
|
|
}
|
|
}
|
|
else if targetCase == WordCase.Low {
|
|
for c in str {
|
|
newStr = tcat(newStr, to_lowercase(c));
|
|
}
|
|
}
|
|
else if targetCase == WordCase.FirstUp {
|
|
for c, i in str {
|
|
if i == 0 {
|
|
newStr = tcat(newStr, to_uppercase(c));
|
|
} else {
|
|
newStr = tcat(newStr, to_lowercase(c));
|
|
}
|
|
}
|
|
}
|
|
else if targetCase == WordCase.FirstUpNumberReset {
|
|
for c, i in str {
|
|
if i == 0 || (str[i - 1] >= '0' && str[i - 1] <= '9') {
|
|
newStr = tcat(newStr, to_uppercase(c));
|
|
} else {
|
|
newStr = tcat(newStr, to_lowercase(c));
|
|
}
|
|
}
|
|
}
|
|
return newStr;
|
|
}
|
|
|
|
// Change the case convention of a string by detecting original convention,
|
|
// then splitting it into words.
|
|
change_case :: proc(str : string, targetCase : Case) -> string {
|
|
if targetCase == Case.Unknown {
|
|
return str;
|
|
}
|
|
|
|
// Split
|
|
parts := autosplit_string(str);
|
|
|
|
// Join
|
|
newStr : string;
|
|
if targetCase == Case.Pascal {
|
|
for part, i in parts {
|
|
newStr = tcat(newStr, change_word_case(part, WordCase.FirstUpNumberReset));
|
|
}
|
|
}
|
|
else if targetCase == Case.Snake {
|
|
for part, i in parts {
|
|
newStr = tcat(newStr, change_word_case(part, WordCase.Low), (i != len(parts) - 1) ? "_" : "");
|
|
}
|
|
}
|
|
else if targetCase == Case.Kebab {
|
|
for part, i in parts {
|
|
newStr = tcat(newStr, change_word_case(part, WordCase.Low), (i != len(parts) - 1) ? "-" : "");
|
|
}
|
|
}
|
|
else if targetCase == Case.Camel {
|
|
for part, i in parts {
|
|
if i == 0 {
|
|
newStr = tcat(newStr, change_word_case(part, WordCase.Low));
|
|
} else {
|
|
newStr = tcat(newStr, change_word_case(part, WordCase.FirstUpNumberReset));
|
|
}
|
|
}
|
|
}
|
|
else if targetCase == Case.Constant {
|
|
for part, i in parts {
|
|
newStr = tcat(newStr, change_word_case(part, WordCase.Up), (i != len(parts) - 1) ? "_" : "");
|
|
}
|
|
}
|
|
|
|
return newStr;
|
|
}
|
|
|
|
// Identify the case of the provided string.
|
|
// Full lowercase with no separator is identified as camelCase.
|
|
find_case :: proc(str : string) -> Case {
|
|
refuted : bool;
|
|
|
|
// CONSTANT_CASE
|
|
refuted = false;
|
|
for c in str {
|
|
if (c != '_') && (c < 'A' || c > 'Z') && (c < '0' || c > '9') {
|
|
refuted = true;
|
|
break;
|
|
}
|
|
}
|
|
if !refuted do return Case.Constant;
|
|
|
|
for c in str {
|
|
// snake_case
|
|
if c == '_' {
|
|
return Case.Snake;
|
|
} // kebab-case
|
|
else if c == '-' {
|
|
return Case.Kebab;
|
|
}
|
|
}
|
|
|
|
// PascalCase
|
|
if str[0] >= 'A' && str[0] <= 'Z' {
|
|
return Case.Pascal;
|
|
}
|
|
|
|
// camelCase
|
|
return Case.Camel;
|
|
}
|
|
|
|
// Splits the string according to detected case.
|
|
// HeyBuddy -> {"Hey", "Buddy"}
|
|
// hey-buddy -> {"hey", "buddy"}
|
|
// _hey_buddy -> {"", "hey", "buddy"}
|
|
// and such...
|
|
autosplit_string :: proc(str : string) -> [dynamic]string {
|
|
lowCount := 0;
|
|
upCount := 0;
|
|
for c in str {
|
|
// If any '_', split according to that (CONSTANT_CASE or snake_case)
|
|
if c == '_' {
|
|
return split_from_separator(str, '_');
|
|
} // If any '-', split according to that (kebab-case)
|
|
else if c == '-' {
|
|
return split_from_separator(str, '-');
|
|
}
|
|
else if c >= 'a' && c <= 'z' {
|
|
lowCount += 1;
|
|
}
|
|
else if c >= 'A' && c <= 'Z' {
|
|
upCount += 1;
|
|
}
|
|
}
|
|
|
|
// If it seems to be only one word
|
|
if lowCount == 0 || upCount == 0 {
|
|
parts : [dynamic]string;
|
|
append(&parts, str);
|
|
return parts;
|
|
}
|
|
|
|
// Split at each uppercase letter (PascalCase or camelCase)
|
|
return split_from_capital(str);
|
|
}
|
|
|
|
split_from_separator :: proc(str : string, sep : rune) -> [dynamic]string {
|
|
parts : [dynamic]string;
|
|
|
|
lastI := 0;
|
|
|
|
// Empty strings for starting separators in string
|
|
for c in str {
|
|
if c == sep {
|
|
append(&parts, "");
|
|
lastI += 1;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Ignore non letter prefix
|
|
if lastI == 0 {
|
|
for c in str {
|
|
if (c < 'a' || c > 'z') && (c < 'A' || c > 'Z') {
|
|
lastI += 1;
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
for c, i in str {
|
|
if i > lastI + 1 && c == sep {
|
|
append(&parts, str[lastI:i]);
|
|
lastI = i + 1;
|
|
}
|
|
}
|
|
|
|
append(&parts, str[lastI:]);
|
|
|
|
return parts;
|
|
}
|
|
|
|
split_from_capital :: proc(str : string) -> [dynamic]string {
|
|
parts : [dynamic]string;
|
|
|
|
// Ignore non letter prefix
|
|
lastI := 0;
|
|
for c in str {
|
|
if (c < 'a' || c > 'z') && (c < 'A' || c > 'Z') {
|
|
lastI += 1;
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// We want to handle:
|
|
// myBrainIsCRAZY -> my Brain Is Crazy
|
|
// myCRAZYBrain -> my CRAZY Brain
|
|
// SOLO -> SOLO
|
|
|
|
// Do split
|
|
for i := 1; i < len(str); i += 1 {
|
|
if str[i] >= 'A' && str[i] <= 'Z' {
|
|
// Do not split too much if it seems to be a capitalized word
|
|
if (lastI == i - 1) && (str[lastI] >= 'A' && str[lastI] <= 'Z') {
|
|
for ; i + 1 < len(str); i += 1 {
|
|
if str[i + 1] < 'A' || str[i + 1] > 'Z' {
|
|
break;
|
|
}
|
|
}
|
|
if (i + 1 == len(str)) && (str[i] >= 'A' && str[i] <= 'Z') {
|
|
i += 1;
|
|
}
|
|
}
|
|
|
|
append(&parts, str[lastI:i]);
|
|
lastI = i;
|
|
}
|
|
}
|
|
|
|
if lastI != len(str) {
|
|
append(&parts, str[lastI:]);
|
|
}
|
|
|
|
return parts;
|
|
}
|
|
|
|
// Check if str if prefixed with any of the provided strings,
|
|
// even combinaisons of those, and remove them.
|
|
remove_prefixes :: proc(str : string, prefixes : []string, transparentPrefixes : []string = nil) -> string {
|
|
str := str;
|
|
transparentStr := "";
|
|
|
|
found := true;
|
|
for found {
|
|
found = false;
|
|
|
|
// Remove effective prefixes
|
|
for prefix in prefixes {
|
|
if len(str) >= len(prefix) &&
|
|
str[:len(prefix)] == prefix {
|
|
str = str[len(prefix):];
|
|
if len(str) != 0 && (str[0] == '_' || str[0] == '-') {
|
|
str = str[1:];
|
|
}
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if found do continue;
|
|
|
|
// Remove transparent ones, only one by one,
|
|
// as we want effective ones to be fully removed.
|
|
for prefix in transparentPrefixes {
|
|
if len(str) >= len(prefix) &&
|
|
str[:len(prefix)] == prefix {
|
|
str = str[len(prefix):];
|
|
transparentStr = tcat(transparentStr, prefix);
|
|
if len(str) != 0 && (str[0] == '_' || str[0] == '-') {
|
|
str = str[1:];
|
|
transparentStr = tcat(transparentStr, '_');
|
|
}
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return tcat(transparentStr, str);
|
|
}
|
|
|
|
// Check if str if postfixes with any of the provided strings,
|
|
// even combinaisons of those, and remove them.
|
|
remove_postfixes_with_removed :: proc(
|
|
str : string,
|
|
postfixes : []string,
|
|
transparentPostfixes : []string = nil) -> (string, [dynamic]string) {
|
|
str := str;
|
|
removedPostfixes : [dynamic]string;
|
|
transparentStr := "";
|
|
|
|
found := true;
|
|
for found {
|
|
found = false;
|
|
|
|
// Remove effective postfixes
|
|
for postfix in postfixes {
|
|
if ends_with(str, postfix) {
|
|
str = str[:len(str) - len(postfix)];
|
|
if len(str) != 0 && (str[len(str)-1] == '_' || str[len(str)-1] == '-') {
|
|
str = str[:len(str)-1];
|
|
}
|
|
append(&removedPostfixes, postfix);
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if found do continue;
|
|
|
|
// Remove transparent ones, only one by one,
|
|
// as we want effective ones to be fully removed.
|
|
for postfix in transparentPostfixes {
|
|
if ends_with(str, postfix) {
|
|
str = str[:len(str) - len(postfix)];
|
|
transparentStr = tcat(postfix, transparentStr);
|
|
if len(str) != 0 && (str[len(str)-1] == '_' || str[len(str)-1] == '-') {
|
|
str = str[:len(str)-1];
|
|
transparentStr = tcat('_', transparentStr);
|
|
}
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return tcat(str, transparentStr), removedPostfixes;
|
|
}
|
|
|
|
remove_postfixes :: proc(
|
|
str : string,
|
|
postfixes : []string,
|
|
transparentPostfixes : []string = nil) -> string {
|
|
str := str;
|
|
removedPostfixes : [dynamic]string;
|
|
str, removedPostfixes = remove_postfixes_with_removed(str, postfixes, transparentPostfixes);
|
|
return str;
|
|
}
|
|
|
|
ends_with :: proc(str : string, postfix : string) -> bool {
|
|
return len(str) >= len(postfix) && str[len(str) - len(postfix):] == postfix;
|
|
}
|