sprintf.js
sprintf.js copied to clipboard
Expose regex or function to detect presence of placeholders
Hi guys, over at angular-logging, we heavily rely on sprintf to help us provide enhanced logging for angular's $log
.
We now run into an issue where we would like to detect whether a string contains placeholders and how many. I see you have a separate regex for placeholders, but it is not exposed.
Would you guys consider either providing a function that returns a count of placeholders or alternatively exposing the placeholders regex? We need to know the number as we dynamically assign arguments to the sprintf invocation.
At the moment I implemented a messy trick to count place holders:
var placeholderCount = 0;
var f = function() { return placeholderCount++ };
sprintf('this %s is %s a test %j', f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f);
// placeholderCount yields 3
This approach stops working, however, if sprintf ever implements a placeholder that is not compatible with numbers.
That and it is probably not the most efficient way to count placeholders.
You can see it in action with out logger in this jsFiddle.
My guess is you're looking to implement a behaviour similar to that of console.log
, where
console.log("foo")
yields foo
console.log("foo", "bar")
yields foo bar
but
console.log("%s")
yields %s
console.log("%s", "foo")
returns foo
and
console.log("%s", "foo", "bar")
results in foo bar
Now, the regular expression used for matching placeholders is utterly useless on it's own. If you look at the parser, you'll notice it consumes the string rather than simply doing a simple search and replace operation. It would be impossible otherwise to support the %%
placeholder and arguments swapping. Thus, you would need a way to figure out whether the string contains placeholders and, most important, how many, so that you could subsequently call sprintf
with a limited number of arguments and join its output with the rest of your arguments.
I'll think about it. Maybe I could make the sprintf.parse
method (albeit undocumented) return information that would be useful to 3rd parties.
Meanwhile, as far as I can tell, console.log
doesn't use a full-blown sprintf
implementation. It seems, instead, to do simple replacements. So think about your use cases. Maybe a simpler method might fit your use cases.
As the function argument doesn't work for named parameters (why not? A computed value can be an object too), I moved on to some simple regexes:
var hasNamedHolders = /\x25\([a-zA-Z0-9_]+\)[b-fijosuxX]/.test(args[0]);
var placeholderCount = args[0].match(/\x25(\d\$)?[b-fijosuxX]/g).length;
I rather rely on public API however.
Like I said in my previous comment, you are looking to count the number of arguments needed by the format string, not the number of placeholders i.e. "%1$s %1$s %1$s"
has 3 placeholders, but only requires one argument.
Ahh yes, I see. The first work around I posted actually works for that then? I would test it but I'm not behind a pc currently. And with a separate check only for named arguments would rule out multiple arguments, or doesn't it?
If that's the case, then at least I have a functional workaround.
I wrote a proof of concept for the work-around, predicated on the assumption that named placeholders limit the necessary arguments to one, otherwise even sprintf will fail saying you can't mix named and positioned placeholders:
I'm not too happy with it, but it seems to do the job for now. Am I missing anything important here?
function countSprintfHolders(pattern) {
var hasNamedHolders = /\x25\([a-zA-Z0-9_]+\)[b-fijosuxX]/.test(pattern);
if (hasNamedHolders) {
return 1;
}
var placeholderCounter = 0;
function f(index) {
return function () {
// keep track of highest arg index, needed for single -but indexed- placeholders placeholder (ie. %6$s consumes the first 6 arguments)
placeholderCounter = Math.max(placeholderCounter, index);
};
}
sprintf(pattern, f(1), f(2), f(3), f(4), f(5), f(6), f(7), f(8), f(9), f(10));
return placeholderCounter;
};