четверг, 28 мая 2015 г.

JavaScript Object Hierarchy Visualization Tree Function

// hierarchy(obj, prefix='', opts={})
// Функция 'hierarchy' возвращает строку, представляющую из себя иерархию элементов объекта 'obj', соединенных с помощью символов труб в формате Unicode.
// 'obj' должен представлять из себя дерево, состоящее из вложенных друг в друга объектов, имеющих метки 'label' и массивы узлов 'nodes'.
// 'label' это строка с текстом, который выводится на соотвествующем уровне узла, а 'nodes' - это массив зависимостей текущего узла.
// Если узел является строкой, то эта строка будет использована в качестве метки 'label', а вместо узла 'nodes' для нее будет использован пустой массив.
// 'prefix' - это строка, которая вставляется перед основным содержимым на каждом шаге сформированного графа. Используется внутри алгоритма  рекурсивного обхода дерева.
// Если метка 'label' имеет внутри себя символы перехода на новую строку (\n), то в этом случае они будут использованы в качестве перехода на новую строку в месте вывода текста метки, учитывая текущий отступ и 'prefix' на данному уровне.
// Для отключения вывода результата выполнения функции в формате Unicode при предпочтении вывода результата в формате ANSI установите значение opts.unicode в false.

function hierarchy (obj, prefix, opts) {

    if (prefix === undefined) {prefix = '';}
    if (!opts) {opts = {};}

    function chr (s) {
        var chars = {
            '│' : '|',
            '└' : '`',
            '├' : '+',
            '─' : '-',
            '┬' : '-'
        };
        return opts.unicode === false ? chars[s] : s;
    };
 
    if (typeof obj === 'string') {
        obj = {label: obj};
    }
 
    var nodes = obj.nodes || []
        , lines = (obj.label || '').split('\n')
        , splitter = '\n' + prefix + (nodes.length ? chr('│') : ' ') + ' ';
 
    return prefix
           + lines.join(splitter) + '\n'
           + nodes.map(function (node, ix) {
                                    var last = ix === nodes.length - 1
                                       , more = node.nodes && node.nodes.length
                                       , prefix_ = prefix + (last ? ' ' : chr('│')) + ' ';

                                    return prefix
                                           + (last ? chr('└') : chr('├')) + chr('─')
                                           + (more ? chr('┬') : chr('─')) + ' '
                                           + hierarchy(node, prefix_, opts).slice(prefix.length + 2);
                               }).join('');

}

// Пример использования без префикса 'prefix' и опций 'opts'

var result = hierarchy(
    {
          label: 'beep'
        , nodes: [
              'ity'
            , {
                  label: 'boop'
                , nodes: [
                      {
                            label: 'o_O'
                          , nodes: [
                                {
                                      label: 'oh'
                                    , nodes: [
                                          'hello'
                                        , 'puny'
                                      ]
                                }
                              , 'human'
                            ]
                      }
                    , 'party\ntime!'
                  ]
              }
          ]
    }
);

console.log(result);

// beep
// ├── ity
// └─┬ boop
//   ├─┬ o_O
//   │ ├─┬ oh
//   │ │ ├── hello
//   │ │ └── puny
//   │ └── human
//   └── party
//          time!

// Пример использования с префиксом 'prefix' и опциями 'opts'

var result = hierarchy(
      {
          label: 'beep'
        , nodes: [
              'ity'
            , {
                  label: 'boop'
                , nodes: [
                      {
                            label: 'o_O'
                          , nodes: [
                                {
                                      label: 'oh'
                                    , nodes: [
                                          'hello'
                                        , 'puny'
                                      ]
                                }
                              , 'human'
                            ]
                      }
                    , 'party\ntime!'
                  ]
              }
          ]
      }
    , '...'
    , {unicode: false}
);

console.log(result);

// ...beep
// ...+-- ity
// ...`-- boop
// ...  +-- o_O
// ...  | +-- oh
// ...  | | +-- hello
// ...  | | `-- puny
// ...  | `-- human
// ...  `-- party
// ...      time!

четверг, 21 мая 2015 г.

Как определить, является ли функция нативной

;(function() {

  // Используется для разложения на составляющие внутреннего `[[Class]]` значений
  var toString = Object.prototype.toString;

  // Используется для разложения на составляющие декомпилированного
  // исходного кода функции
  var fnToString = Function.prototype.toString;

  // Используется для определения конструкторов среды (Safari > 4;
  // по сути, предназначено специально для типизированных массивов)
  var reHostCtor = /^\[object .+?Constructor\]$/;

  // Составление регулярного выражения на основе часто употребляемого
  // нативного метода в качестве шаблона.
  // Выбираем `Object#toString`, так как вполне вероятно, что он ещё не задействован.
  var reNative = RegExp('^' +
    // Применяем `Object#toString` к строке
    String(toString)
    // Избавляемся от любых специальных символов регулярных выражений
    .replace(/[.*+?^${}()|[\]\/\\]/g, '\\$&')
    // Заменяем упоминания `toString` на `.*?`, чтобы сохранить обобщённый вид шаблона.
    // Заменяем `for ...` и тому подобное для поддержки окружений вроде Rhino,
    // которые добавляют дополнительную информацию, такую как арность метода.
    .replace(/toString|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$'
  );

  function isNative(value) {
    var type = typeof value;
    return type == 'function'
      // Используем `Function#toString`, чтобы обойти собственный метод
      // `toString` самого значения и избежать ложного результата.
      ? reNative.test(fnToString.call(value))
      // На всякий случай выполняем проверку на наличие объектов среды, так
      // как некоторые окружения могут представлять компоненты вроде
      // типизированных массивов как методы DOM, что может не соответствовать
      // нормальному нативному паттерну.
      : (value && type == 'object' && reHostCtor.test(toString.call(value))) || false;
  }

  // экспортируем в удобном для вас виде
  module.exports = isNative;
}());