1 /**
2 	* snake_case transform
3 	*
4 	* Copyright: © 2016 David Monagle
5 	* License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
6 	* Authors: David Monagle
7 */
8 module transforms.snake;
9 
10 import std.uni;
11 import std.algorithm;
12 import std.regex;
13 
14 /// Transforms the given `input` into snake_case
15 /// This precomiled regex version does not work at compile time as regex uses malloc
16 string snakeCase(const string input) {
17 	static auto firstCapRE = ctRegex!(`(.)([A-Z][a-z]+)`);
18 	static auto allCapRE = ctRegex!(`([a-z0-9])([A-Z])`, "g");
19 	
20 	string output = input.replace(firstCapRE, `$1_$2`);
21 	output = output.replace(allCapRE, `$1_$2`);
22 	
23 	return output.toLower;
24 }
25 
26 unittest {
27 	assert("C".snakeCase == "c");
28 	assert("cA".snakeCase == "c_a");
29 	assert("Ca".snakeCase == "ca");
30 	assert("Camel".snakeCase == "camel");
31 	assert("CamelCase".snakeCase == "camel_case");
32 	assert("CamelCamelCase".snakeCase == "camel_camel_case");
33 	assert("Camel2Camel2Case".snakeCase == "camel2_camel2_case");
34 	assert("getHTTPResponseCode".snakeCase == "get_http_response_code");
35 	assert("getHttpResponseCode".snakeCase == "get_http_response_code");
36 	assert("get2HTTPResponseCode".snakeCase == "get2_http_response_code");
37 	assert("HTTPResponseCode".snakeCase == "http_response_code");
38 	assert("HTTPResponseCodeXYZ".snakeCase == "http_response_code_xyz");
39 }
40 
41 /// Transforms the given `input` into snake_case
42 /// Works at compile time
43 string snakeCaseCT(const string input) {
44 	string firstPass(const string input) {
45 		if (input.length < 3) return input;
46 		
47 		string output;
48 		for(auto index = 2; index < input.length; index++) {
49 			output ~= input[index - 2];
50 			if (input[index - 1].isUpper && input[index].isLower)
51 				output ~= "_";
52 		}
53 		
54 		return output ~ input[$-2..$];
55 	}
56 	
57 	string secondPass(const string input) {
58 		if (input.length < 2) return input;
59 		
60 		string output;
61 		for(auto index = 1; index < input.length; index++) {
62 			output ~= input[index - 1];
63 			if (input[index].isUpper && (input[index-1].isLower || input[index-1].isNumber))
64 				output ~= "_";
65 		}
66 		
67 		return output ~ input[$-1..$];
68 	}
69 	
70 	if (input.length < 2) return input.toLower;
71 	
72 	string output = firstPass(input);
73 	output = secondPass(output);
74 	
75 	return output.toLower;
76 }
77 
78 
79 unittest {
80 	assert("C".snakeCaseCT == "c");
81 	assert("cA".snakeCaseCT == "c_a");
82 	assert("Ca".snakeCaseCT == "ca");
83 	assert("Camel".snakeCaseCT == "camel");
84 	assert("CamelCase".snakeCaseCT == "camel_case");
85 	assert("CamelCamelCase".snakeCaseCT == "camel_camel_case");
86 	assert("Camel2Camel2Case".snakeCaseCT == "camel2_camel2_case");
87 	assert("getHTTPResponseCode".snakeCaseCT == "get_http_response_code");
88 	assert("get2HTTPResponseCode".snakeCaseCT == "get2_http_response_code");
89 	assert("HTTPResponseCode".snakeCaseCT == "http_response_code");
90 	assert("HTTPResponseCodeXYZ".snakeCaseCT == "http_response_code_xyz");
91 }