1 module text.json.Enum; 2 3 import std.algorithm; 4 import std.ascii : isLower; 5 import std.json; 6 import std.range; 7 import std.utf; 8 9 version (unittest) import dshould; 10 11 /** 12 * Helper to encode a DStyle enum ("entryName") as JSON style ("ENTRY_NAME"). 13 * 14 * Use like so: `alias encode = encodeEnum!EnumType;` when forming your encode overload. 15 */ 16 string encodeEnum(T)(const T value) 17 if (is(T == enum)) 18 { 19 import std.conv : to; 20 import std.uni : toUpper; 21 22 const enumString = value.to!string; 23 24 return enumString.splitByPredicate!isWord.map!toUpper.join("_"); 25 } 26 27 /// ditto 28 unittest 29 { 30 import text.json.Encode : encodeJson; 31 32 enum Enum 33 { 34 testValue, 35 isHttp, 36 } 37 38 alias encode = encodeEnum!Enum; 39 40 encodeJson!(Enum, encode)(Enum.testValue).should.be(JSONValue("TEST_VALUE")); 41 encodeJson!(Enum, encode)(Enum.isHttp).should.be(JSONValue("IS_HTTP")); 42 } 43 44 /** 45 * Helper to decode a JSON style enum string (ENTRY_NAME) as a DStyle enum (entryName). 46 * 47 * Use like so: `alias decode = decodeEnum!EnumType;` when forming your encode overload. 48 * Throws: JSONException if the input text does not represent an enum member. 49 */ 50 template decodeEnum(T) 51 if (is(T == enum)) 52 { 53 U decodeEnum(U : T)(const string text) 54 { 55 import std.range : only; 56 import std.conv : ConvException, to; 57 import std.exception : enforce; 58 import std.format : format; 59 import std..string : capitalize; 60 import std.uni : toLower; 61 62 enforce!JSONException(!text.empty, "expected member of " ~ T.stringof); 63 64 auto split = text.splitter("_"); 65 const camelCase = chain(split.front.toLower.only, split.dropOne.map!capitalize).join; 66 67 try 68 { 69 return camelCase.to!T; 70 } 71 catch (ConvException convException) 72 { 73 throw new JSONException( 74 format!"expected member of %s, not %s (or in D, '%s')"(T.stringof, text, camelCase)); 75 } 76 } 77 } 78 79 /// ditto 80 unittest 81 { 82 import text.json.Decode : decodeJson; 83 84 enum Enum 85 { 86 testValue, 87 isHttp, 88 } 89 90 alias decode = decodeEnum!Enum; 91 92 decodeJson!(Enum, decode)(JSONValue("TEST_VALUE")).should.be(Enum.testValue); 93 decodeJson!(Enum, decode)(JSONValue("IS_HTTP")).should.be(Enum.isHttp); 94 decodeJson!(Enum, decode)(JSONValue("")).should.throwA!JSONException; 95 decodeJson!(Enum, decode)(JSONValue("ISNT_HTTP")).should.throwA!JSONException( 96 "expected member of Enum, not ISNT_HTTP (or in D, 'isntHttp')"); 97 } 98 99 alias isWord = text => text.length > 0 && text.drop(1).all!isLower; 100 101 private string[] splitByPredicate(alias pred)(string text) 102 { 103 string[] result; 104 while (text.length > 0) 105 { 106 size_t scan = 0; 107 108 while (scan < text.length) 109 { 110 const newscan = scan + text[scan .. $].stride; 111 112 if (pred(text[0 .. newscan])) 113 { 114 scan = newscan; 115 } 116 else 117 { 118 break; 119 } 120 } 121 122 result ~= text[0 .. scan]; 123 text = text[scan .. $]; 124 } 125 return result; 126 } 127 128 unittest 129 { 130 splitByPredicate!isWord("FooBar").should.be(["Foo", "Bar"]); 131 splitByPredicate!isWord("FooBAR").should.be(["Foo", "B", "A", "R"]); 132 splitByPredicate!isWord("").should.be([]); 133 }