import parseTpl from './parse-es6-template';
parseTpl('${name} is now master of the ${galaxy}', {
name: 'John',
galaxy: 'Milky Way',
});
-
-
Save smeijer/6580740a0ff468960a5257108af1384e to your computer and use it in GitHub Desktop.
function get(path, obj, fb = `$\{${path}}`) { | |
return path.split('.').reduce((res, key) => res[key] || fb, obj); | |
} | |
function parseTpl(template, map, fallback) { | |
return template.replace(/\$\{.+?}/g, (match) => { | |
const path = match.substr(2, match.length - 3).trim(); | |
return get(path, map, fallback); | |
}); | |
} | |
export default parseTpl; |
@mbrowne has a good point, but you'll need to change your regex to this for it to work /\${([^{]+[^}])}/g
as you need to exclude the extra }
I needed a quick and dirty version that would tolerate missing data. I renamed the parseTpl function to interpolate
and the path resolution is baked in via a fault tolerant reducer. Thank you for helping me think through this, it is a very busy evening, but I had a lot of fun.
I am using it in the Open Source cataclysm which is a static html generator like Jekyll. I am writing it to have a generator that is more respectful of HTML, it is very early, but I will finish it.
function interpolate(t, c){return t.replace(/\${([^}]+)}/g,(m,p)=>p.split('.').reduce((a,f)=>a?a[f]:undefined,c)??m);}
interpolate('My ${a} is full of eels.',{a:'hovercraft'})
//> My hovercraft is full of eels.
interpolate('Answer to the Ultimate Question of Life, the Universe, and Everything is: ${robotic}',{robotic:parseInt(101010, 2)})
//> Answer to the Ultimate Question of Life, the Universe, and Everything is: 42
interpolate('Errors are kept to minimum: ${x.o.x.o.x.o.x.o.x.o.x.o.x.o}')
//> Errors are kept to minimum: ${x.o.x.o.x.o.x.o.x.o.x.o.x.o}
interpolate('Simple arrays are kind of ${0}', ["supported!"])
//> Simple arrays are kind of supported!
interpolate('Including complex arrays... ${A.c.h.0.0.0}! and will make you very brave, if used in production, and bless you for being so mighty! <3', {A:{c:{h:[[['Achooo']]]}}})
//> Including complex arrays... Achooo! and will make you very brave, if used in production, and bless you for being so mighty! <3
interpolate('A more complex answer to the Ultimate Question of Life, the Universe, and Everything is still: ${human.answer}',
{human:{answer:['Love', 'Wisdom', 'Funnyness', 'Aardvark', 'Hugs'].map(word=>word.charCodeAt(0)).reduce((a,i)=>a^i)/2}})
//> A more complex answer to the Ultimate Question of Life, the Universe, and Everything is still: 42
interpolate('When variable data is not present the interpolator re-prints ${data} (leaves it alone, as it should be).',{})
//> When variable data is not present the interpolator re-prints ${data} (leaves it alone, as it should be).
Note: I edited this function to replace "||" with a "??" because when the value of a variable was 0 the system thought it didn't exist and it printed the ${} notation again.
In my case I was printing ${index} where the value of index variable was 0, and the system was like, "Well, zero is nothing, so, I am just going to reprint the ${index}". I apologize for that, but now we are using the nullish coalescing operator.
The ??
operator is mindful of 0 being an actual value and my program prints 0 instead of ${index}.
I updated the code above so you will not see the offending version that used "||"
instead of "??"
.
interpolate('My ${a} is full of eels.',{a:'hovercraft'})
${true}x
-> truex
Hey there, as per:
interpolate('Errors are kept to minimum: ${x.o.x.o.x.o.x.o.x.o.x.o.x.o}')
//> Errors are kept to minimum: ${x.o.x.o.x.o.x.o.x.o.x.o.x.o}
I wanted to print out references to missing variables, as opposed to cause trouble, I just wanted this little function to work without causing any issues.
I can see that you don't like that and would probably prefer for ${true}x
to result in x
, personally I hate that, because your request for the value of .true is lost, and you may not notice that your data object does not contain .true = "Interpolate with this."
BEFORE I tell you what you want to hear, let me make a note about the Nullish Coalescing Operator (??), which you can read about here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator
OK, so what is SUPER UNCLEAR about the code, is the tail end with the ??
that is just an OR or what you are used to seeing as ||
, except ||
had a problem if the reference to .test was 0, then the function would see that as a miss, and return ${true} which is a way of signalling that interpolation failed.
SO, with ||
instead of ??
all works OK...
interpolate('${true}x ${hello}', {hello:'world', true:'I think therefore I am!'})
//> "I think therefore I am!x world"
BUT, if true was 0 (zero or "falsey") then interpolate would act as if true had no value, meaning it would return the ${true}:
interpolate('${true}x ${hello}', {hello:'world', true:0})
//> "${true}x world"
NOW, with ??
zero is treated as truthy, becasue it is not undefined, it is just 0
interpolate('${true}x ${hello}', {hello:'world', true:0})
//> "0x world"
See? that is what ??
does, it allows me to interpolate your data requests with falsey variables.
I understand your question (but I am long-winded), here is what you were looking for:
USE THIS IF YOU DON'T WANT TO SEE ${true}:
function interpolate(t, c){return t.replace(/\${([^}]+)}/g,(m,p)=>p.split('.').reduce((a,f)=>a?a[f]:undefined,c)??'');}
I replaced m with '', right at the end, m, is the match! In your case ${true}, and I was just returning it so that you see it is unmatched, and then throw in .true = 'something' but what you expect is for ${true} to be interpolated with nothing or '', and that is what the code above does, ho ho.
Here is a slightly more expanded example:
interpolate('${true}x ${hello} ${C0FFEE}', {hello:'world', C0FFEE:0, true:undefined})
//> "x world 0"
Note how ${true} was understood, but since it is undefined, and you don't want to see it again, it has been removed now.
And note how ${C0FFEE} which is 0 actually does get interpolated because of the Nullish Coalescing Operator (as opposed to the ol' ||
)
And that ${hello} works just as expected.
To use your example:
interpolate('${true}x',{a:'hovercraft'})
//> "x"
you just get x.
Ps.
And in the end I want to add that:
interpolate('Errors are kept to minimum: ${x.o.x.o.x.o.x.o.x.o.x.o.x.o}')
//> Errors are kept to minimum: ${x.o.x.o.x.o.x.o.x.o.x.o.x.o}
has changed behaviour now, it returns:
//> "Errors are kept to minimum: "
See it is no onger alerting you that ${x.o.x.o.x.o.x.o.x.o.x.o.x.o} was not resolved, or in your case it is no longer alerting you that ${true} was not resolved.
Ps. Ps.
I understand you put in true to mess with my function, but it is interpreted as as an object property, it is a name, and not a value. It happends here: p.split('.').reduce((a,f)=>a?a[f]:undefined,c) p is a string with the value "true" as matched by my regular expression /${([^}]+)}/
Thank You @makmav for your interest in my fantastic code,
I hope this message finds you well,
Now let us take a moment of serenity silence, and admire our new
Nullish Coalescing Operator because it is amazing, I am so excited to be evaluating falseys as true whoo hooo!!!
Ps. Ps. Ps.
🐈
${true}x -> x
Small note: in the regex, I think the backslash in front of the first
{
is unnecessary. Also, it will match things like${foo{bar}
. It might be better to use this:/\${([^{]+)}/g