Extending String.prototype for easier string formatting



  • I see a lot of code snippets passing by where a lot of string concatenation is done using the + operator, like console.log("Creep " + creep.name + " is located on (" + creep.pos.x + ", " + creep.pos.y + ").");. I personally find such statements difficult to write due to the excessive amounts of " and + signs and even more difficult to read.

    So I liberated the following snippet from Microsoft's Ajax library and modified it slightly for my own purposes. It is based on the .NET implementation of String.Format(), allowing you to write your statements as console.log("Creep {0} is located near ({1}, {2})".format(creep.name, creep.pos.x, creep.pos.y);

    Thought I'd share this one.

    var _ = require("lodash");
    
    module.exports = function() {
        String.prototype.format = function(format, args) {
            /// <summary>Replaces the format items in a specified String with the text equivalents of the values of   corresponding object instances. The invariant culture will be used to format dates and numbers.</summary>
            /// <param name="format" type="String">A format string.</param>
            /// <param name="args" parameterArray="true" mayBeNull="true">The objects to format.</param>
            /// <returns type="String">A copy of format in which the format items have been replaced by the   string equivalent of the corresponding instances of object arguments.</returns>
            return String.prototype._toFormattedString(this, _.values(arguments));
        };
    
        String.prototype._toFormattedString = function(format, args) {
            var result = '';
    
            for (var i = 0; ; ) {
                // Find the next opening or closing brace
                var open = format.indexOf('{', i);
                var close = format.indexOf('}', i);
                if ((open < 0) && (close < 0)) {
                    // Not found: copy the end of the string and break
                    result += format.slice(i);
                    break;
                }
                if ((close > 0) && ((close < open) || (open < 0))) {
    
                    if (format.charAt(close + 1) !== '}') {
                        throw new Error('format stringFormatBraceMismatch');
                    }
    
                    result += format.slice(i, close + 1);
                    i = close + 2;
                    continue;
                }
    
                // Copy the string before the brace
                result += format.slice(i, open);
                i = open + 1;
    
                // Check for double braces (which display as one and are not arguments)
                if (format.charAt(i) === '{') {
                    result += '{';
                    i++;
                    continue;
                }
    
                if (close < 0) throw new Error('format stringFormatBraceMismatch');
    
                // Get the string between the braces, and split it around the ':' (if any)
                var brace = format.substring(i, close);
                var colonIndex = brace.indexOf(':');
                var argNumber = parseInt((colonIndex < 0) ? brace : brace.substring(0, colonIndex), 10);
    
                if (isNaN(argNumber)) throw new Error('format stringFormatInvalid');
    
                var argFormat = (colonIndex < 0) ? '' : brace.substring(colonIndex + 1);
    
                var arg = args[argNumber];
                if (typeof (arg) === "undefined") {
                    arg = '[undefined]';
                } else if (arg === null) {
                    arg = '[null]';
                }
    
                // If it has a toFormattedString method, call it.  Otherwise, call toString()
                if (arg.format) {
                    result += arg.format(argFormat);
                }
                else
                    result += arg.toString();
    
                i = close + 1;
            }
    
            return result;
        };
    };
    


  • There are template strings in es6, which is already available in iojs and thus very likely to pop up in nodejs later than 0.12. They syntax is very similar, except that all content must be between back ticks.

    var world = 'world';
    `Hello ${world}`;
    

    Documentation can be found here: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/template_strings



  • In a game like screeps, where every bit of CPU consumption counts, I would avoid this at all costs, since it's horrrrrrrrrribly slow compared to simple string concatenation.



  • I agree, string concats are faster than using this method. But especially when I'm debugging stuff I don't really care about the speed but more about convenience and readability. Once a module is complete I strip it of any log messages anyway.



  • And they just updated to a version of iojs that supports ec6. So I'll be using template strings from now on.