Библиотека строку интерполяции


Мне было скучно за последние пару дней и написал строку интерполяции библиотека для JavaScript.

Я очень доволен его функциональностью, он передает его 79-тесты кросс-браузер и замечания и файл README , кажется, тоже.

Моя главная забота здесь являются регулярные выражения. Я не профессионал в этой области, так что я подозреваю, там могут быть некоторые улучшения к ним.

Другое дело, читаемость кода, а также как хорошие комментарии. Я бы хотел, чтобы "непонятно" разделы кодекса указано, поэтому я могу улучшить именования / комментарии.

Библиотека приносит с собой праматерии фабрики и FormatError.

Основное использование

> format = Formatter()
> format('Good {} Sir {}.', 'morning', 'Lancelot')
'Good morning Sir Lancelot.'

> format('Good {time} Sir {name}.', 'morning', 'Lancelot')
'Good morning Sir Lancelot.'

> format('Good {0} Sir {1}.', ['morning', 'Lancelot'])
'Good morning Sir Lancelot.'

Источник

(function(undefined) {
    'use strict';

    // Formatter factory
    function Formatter(formats) {
        function f() {
            return format(f.formats, arguments);
        }
        f.formats = formats || {};
        return f;
    }

    // Default formatters
    Formatter.formats = {
        repeat: repeat,

        join: function(value, str) {
            return value.join(str || ', ');
        },

        upper: function(value) {
            return value.toUpperCase();
        },

        lower: function(value) {
            return value.toLowerCase();
        },

        lpad: function(value, length, str) {
            return pad(value, length, str, 'l');
        },

        rpad: function(value, length, str) {
            return pad(value, length, str, 'r');
        },

        pad: function(value, length, str) {
            return pad(value, length, str);
        },

        surround: function(value, left, right) {
            return left + value + (right || left);
        },

        hex: function(value, lead) {
            return (lead ? '0x' : '') + value.toString(16);
        },

        bin: function(value, lead) {
            return (lead ? '0b' : '') + value.toString(2);
        }
    };

    function repeat(value, count) {
        return new Array((count || 0) + 1).join(value || ' ');
    }

    function pad(value, length, str, mode) {
        value = '' + value;
        str = str || ' ';

        var len = length - value.length;
        if (len < 0) {
            return value;

        } else if (mode === 'l') {
            return repeat(str, len) + value;

        } else if (mode === 'r') {
            return value + repeat(str, len);

        } else {
            return repeat(str, len - ~~(len / 2))
                   + value
                   + repeat(str, ~~(len / 2));
        }
    }

    // match {} placholders like {0}, {name}, {} and the inner "{{foo}}"
    // {} can be escaped with \
    var replaceExp = /([^\\]|^)\{([^\{\}]*[^\\^\}]|)\}/g,

        // match things like: foo[0].test["test"]['test]
        accessExp = /^\.?([^\.\[]+)|\[((-?\d+)|('|")(.*?[^\\])\4)\]/,

        // match :foo and :foo(.*?)
        formatExp = /\:([a-zA-Z]+)(\((.*?)\))?(\:|$)/,

        // match arguments: "test", 12, -12, 'test', true, false
        // strings can contain escaped characters like \"
        argumentsExp = /^(,|^)\s*?((true|false|(-?\d+))|('|")(.*?([^\\]|\5))\5)/;

    // Main formatting function
    function format(formatters, args) {

        // Setup magic!
        var string = args[0],
            first = args[1],
            argsLength = args.length - 2,
            type = first != null ? {}.toString.call(first).slice(8, -1) : '',
            arrayLength = first ? first.length - 1 : 0,
            autoIndex = 0;

        function replace(value, pre, form) {

            // Extract formatters
            var formats = [], format = null, id = form;
            while (format = form.match(formatExp)) {
                if (formats.length === 0) {
                    id = form.substring(0, format.index);
                }
                form = form.substring(format[0].length - 1);
                formats.push(format);
            }

            // In case of a valid number use it for indexing
            var num = (isNaN(+id) || id === '') ? null : +id;

            // Handle objects
            if (type === 'Object' && id !== '') {

                // Handle plain keys
                if (id.indexOf('.') === -1 && id.indexOf('[') === -1) {
                    if (first[id] !== undefined) {
                        value = first[id];

                    // fall back to obj.toString()
                    } else {
                        value = args[1 + autoIndex];
                    }

                // Access properties
                } else {
                    value = getProperty(first, id);
                }

            // Handle given array indexes
            } else if (type === 'Array' && num !== null) {
                value = first[num >= 0 ? num : arrayLength + num];

            // Handle given arguments indexes
            } else if (num !== null) {
                value = args[1 + (num >= 0 ? num : argsLength + num)];

            // Handle automatic arguments indexes
            } else {
                value = args[1 + autoIndex];
            }
            autoIndex++;

            // Apply formats
            while (format = formats.shift()) {
                var method = (formatters[format[1]] ? formatters : Formatter.formats)[format[1]];
                if (method) {
                    value = method.apply(undefined,
                                         getArguments(value, format[3] || ''));

                } else {
                    throw new FormatError(
                        replace, 'Undefined formatter "{}".', format[1]
                    );
                }
            }
            return pre + value;
        }
        return string.replace(replaceExp, replace);
    }

    // Get a specific peoperty of an object based on a accessor string
    function getProperty(obj, id) {
        var m, pos = 0;
        while (m = id.substring(pos).match(accessExp)) {
            // .name  / [0] / ["test"]
            var prop = m[1] || (m[3] ? +m[3] : m[5].replace('\\' + m[4], m[4]));
            if (obj === undefined) {
                throw new FormatError(
                    getProperty,
                    'Cannot access property "{}" of undefined.', prop
                );

            } else {
                obj = obj[prop];
            }
            pos += m[0].length;
        }
        return obj;
    }

    // Convert a string like:
    //   true, false, -1, 34, 'foo', "bla\" foo"
    //
    // Into a  list of arguments:
    //   [true, false, -1, 34, 'foo', 'bla" foo']
    function getArguments(value, string) {
        var m, pos = 0, args = [value];
        while (m = string.substring(pos).match(argumentsExp)) {
            // number
            args.push(m[4] ? +m[4]
                           // boolean
                           : (m[3] ? m[3] === 'true'
                                   // string
                                   : m[6].replace('\\' + m[5], m[5])));

            pos += m[0].length;
        }
        return args;
    }

    // Formatting error type
    function FormatError(func, msg, value) {
        this.name = 'FormatError';
        this.message = format(Formatter.formats, [msg, value]);
        if (Error.captureStackTrace) {
            Error.captureStackTrace(this, func);
        }
    }
    FormatError.prototype = new Error();

    // Exports
    var exp = typeof window === 'undefined' ? exports : window;
    exp.Formatter = Formatter;
    exp.FormatError = FormatError;
})();


724
11
задан 19 февраля 2011 в 07:02 Источник Поделиться
Комментарии
1 ответ

Я не смотрел на ваши регулярные выражения в деталях. Не могли бы вы объяснить их? Остальной код можно использовать также для улучшения документации.

Я нашел две серьезные вопросы корректности кода:


  1. Ваш :присоединяйтесь() форматирования не будет работать с пустой строкой в качестве разделителя.

    return value.join(str || ', ');    // Boolean('') === false

  2. Код :коврик() форматирования, при использовании для заполнения центра, не будет правильно добавить нечетное число символов заполнения, как прокладку по бокам закруглена вниз.

    } else {
    return repeat(str, len - ~~(len / 2))
    + value
    + repeat(str, ~~(len / 2));
    }

    В общем, используя уловки, такие как ~~ приводит к сокращению кода ясности, и это является отличным примером этого. Вы должны использовать математику.пол() и математике.метод ceil() функции вместо того, чтобы если это то, что вы хотите.


Исправить эти проблемы, и конечно же добавить соответствующие тесты. Также в документе тот факт, что :коврик() и другие отступы функции предназначены только для работы с одним заполняющий символ.

2
ответ дан 22 февраля 2011 в 11:02 Источник Поделиться