1 /*
2  * Copyright (c) 2018-2020 sel-project
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a copy
5  * of this software and associated documentation files (the "Software"), to deal
6  * in the Software without restriction, including without limitation the rights
7  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8  * copies of the Software, and to permit persons to whom the Software is
9  * furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included in all
12  * copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20  * SOFTWARE.
21  *
22  */
23 /**
24  * Copyright: Copyright (c) 2018-2020 sel-project
25  * License: MIT
26  * Authors: Kripth
27  * Source: $(HTTP github.com/sel-project/sel-util/chat/sel/chat.d, sel/chat.d)
28  */
29 module sel.chat;
30 
31 import std.array : Appender;
32 import std.json;
33 import std.traits : EnumMembers;
34 
35 import sel.format : Format;
36 
37 public @safe string parseChat(JSONValue json) {
38 	Appender!string ret;
39 	parseChatImpl(ret, json);
40 	return ret.data;
41 }
42 
43 private @trusted void parseChatImpl(ref Appender!string appender, JSONValue json) {
44 	if(json.type == JSON_TYPE.OBJECT) {
45 		auto translate = "translate" in json.object;
46 		if(translate && translate.type == JSON_TYPE.STRING) {
47 			// cannot translate without a translation table
48 			appender.put(translate.str);
49 		} else {
50 			auto color = "color" in json.object;
51 			string format;
52 			if(color && color.type == JSON_TYPE.STRING) {
53 				format = convertColor(color.str);
54 			}
55 			auto text = "text" in json.object;
56 			if(text && text.type == JSON_TYPE.STRING) {
57 				appender.put(format);
58 				appender.put(text.str);
59 			}
60 			auto extra = "extra" in json.object;
61 			if(extra && extra.type == JSON_TYPE.ARRAY) {
62 				foreach(i, element; extra.array) {
63 					parseChatImpl(appender, element);
64 					if(i < extra.array.length - 1) {
65 						appender.put(cast(string)Format.reset);
66 						appender.put(format);
67 					}
68 				}
69 			}
70 		}
71 	} else if(json.type == JSON_TYPE.STRING) {
72 		appender.put(json.str);
73 	}
74 }
75 
76 private @safe string convertColor(string cname) {
77 	switch(cname) {
78 		foreach(format ; __traits(allMembers, Format)) {
79 			static if(isColor!format) {
80 				case snakeCase!format: return mixin("Format." ~ format);
81 			}
82 		}
83 		default: return "";
84 	}
85 }
86 
87 @safe unittest {
88 
89 	assert(convertColor("black") == Format.black);
90 	assert(convertColor("dark_blue") == Format.darkBlue);
91 	assert(convertColor("reset") == "");
92 
93 }
94 
95 private enum isColor(string format) = isColorImpl(mixin("Format." ~ format)[2]);
96 
97 private bool isColorImpl(char c) {
98 	return c >= '0' && c <= '9' || c >= 'a' && c <= 'f';
99 }
100 
101 private enum snakeCase(string str) = snakeCaseImpl(str);
102 
103 private string snakeCaseImpl(string str) {
104 	string ret;
105 	foreach(c ; str) {
106 		if(c >= 'A' && c <= 'Z') ret ~= "_" ~ cast(char)(c + 32);
107 		else ret ~= c;
108 	}
109 	return ret;
110 }