1 module tagion.utils.envexpand; 2 3 import std.algorithm; 4 import std.range; 5 import std.typecons; 6 7 enum ignore_env_start = "!"; 8 enum bracket_pairs = [ 9 ["$$", "$", ignore_env_start], // "!" ignored as environment start 10 ["$(", ")"], 11 ["${", "}"], 12 ["$", ""], 13 14 ]; 15 16 @safe 17 string envExpand(string text, string[string] env, void delegate(string msg) error = null) pure { 18 alias BracketState = Tuple!(string[], "bracket", ptrdiff_t, "index"); 19 static long envName(string str, string end_sym) pure { 20 import std.uni; 21 22 if (end_sym.length) { 23 return str.countUntil(end_sym); 24 } 25 if ((str.length > 0) && !str[0].isAlpha) { 26 return -1; 27 } 28 return (str ~ '!').countUntil!(c => !c.isAlphaNum); 29 } 30 31 string innerExpand(string str) { 32 auto begin = bracket_pairs 33 .map!(bracket => BracketState(bracket, str.countUntil(bracket[0]))) 34 .filter!(state => state.index >= 0); 35 // .take(1); 36 if (!begin.empty) { 37 const state = begin.front; 38 if (state.bracket.length > 2) { 39 const next_index = state.index + state.bracket[0].length; 40 return str[0 .. state.index] ~ state.bracket[1] ~ innerExpand(str[next_index .. $]); 41 } 42 const env_start_index = state.index + state.bracket[0].length; 43 auto end_str = innerExpand(str[env_start_index .. $]); 44 const env_end_index = envName(end_str, state.bracket[1]); 45 string env_value; 46 if (env_end_index > 0) { 47 const env_name = end_str[0 .. env_end_index]; 48 end_str = end_str[env_end_index + state.bracket[1].length .. $]; 49 env_value = env.get(env_name, null); 50 } 51 return str[0 .. state.index] ~ 52 env_value ~ 53 innerExpand(end_str); 54 } 55 return str; 56 57 } 58 59 return innerExpand(text); 60 } 61 62 @safe 63 unittest { 64 // Simple text without env expansion 65 assert("text".envExpand(null) == "text"); 66 // Expansion with undefined env 67 assert("text$(NAME)".envExpand(null) == "text"); 68 // Expansion where the env is defined 69 assert("text$(NAME)".envExpand(["NAME": "hugo"]) == "texthugo"); 70 // Full expansion 71 assert("text${NAME}end".envExpand(["NAME": "hugo"]) == "texthugoend"); 72 // Environment without brackets 73 assert("text$NAME".envExpand(["NAME": "hugo"]) == "texthugo"); 74 // Undefined env without brackets expansion 75 assert("text$NAMEend".envExpand(["NAME": "hugo"]) == "text"); 76 // Expansion of undefined environment of environment 77 assert("text$(OF${NAME})".envExpand(["NAME": "hugo"]) == "text"); 78 // Expansion of defined environment of environment 79 assert("text$(OF${NAME})".envExpand(["NAME": "hugo", "OFhugo": "_extra_"]) == "text_extra_"); 80 // Expansion of defined environment of environment 81 assert("text$(OF$(NAME)_end)".envExpand([ 82 "NAME": "hugo", 83 "OFhugo": "_extra_", 84 "OFhugo_end": "_other_extra_" 85 ]) == "text_other_extra_"); 86 // Double dollar ignored as an environment 87 assert("text$$(NAME)".envExpand(["NAME": "not-replaced"]) == "text$(NAME)"); 88 89 }