<commit_before>var path = require('path'),
    vow = require('vow'),
    parseArgs = require('minimist'),
    EventEmitter = require('events').EventEmitter,
    MakePlatform = require('enb/lib/make'),
    MagicConfig = require('./magic-config'),
    EVENTS = {
        START: 'start',
        PREBUILD: 'prebuild',
        BUILD: 'build',
        ERROR: 'error'
    },
    MODES = {
        FULL: 'full',
        PRE: 'pre'
    };

/**
 * Хэлпер предназначен для решения задач, связанных с созданием новых нод и таргетов в процессе выполнения сборки.
 *
 * В процессе создания конфигуратора будет зарегистрирован таск c указанным именем, который предназначается для:
 * 1. Создания директорий и файлов, формирующих уровни-сеты.
 * 2. Регистрации нод и таргетов по созданным директориям и файлам.
 * 3. Запуска сборки зарегистрированных таргетов, а также всех таргетов в рамках зарегистрированных нод.
 *
 * Общение между хэлперами происходит через каналы событий. По умолчанию каждый из хэлперов создаёт следующие события:
 *
 * 1. **start** `function () { }`
 *
 * Срабатывает, когда происходит запуск таска.
 *
 * 2. **prebuild** `function (nodes, targets) { }`
 *
 * Срабатывает после создания всех уровней-сетов на файловой системе. Возвращает список всех зарегистрированных `nodes`,
 * а также список всех зарегистрированных таргетов `targets` ноды, которых не были зарегистрированны.
 *
 * 3. **build** `function (targets) { }`
 *
 * Срабатывает в случае успешного завершения сборки, возвращая список собранных таргетов.
 *
 * 4. **error** `function (err) { }`
 *
 * Срабатывает при возникновении ошибки в процессе построения или сборки уровней-сетов.
 *
 * Таск может работать в нескольких режимах:
 *
 * 1. `full` — сборка выполняется для всех целей. Если в качестве цели задана нода или директория с нодами,
 *             то цель будет раскрыта до всех таргетов в рамках заданной ноды или директории.
 * 2. `pre`  — сборка выполняется только для тех целей, которые являются конечными таргетами (файлами),
 *             для остальных выполняется только `prebuild`.
 *
 * Название режима задаётся через переменную окружения `process.env.ENB_MAGIC_MODE`.
 * По умолчанию таск работает в `full`-режиме.
 *
 * @name MagicHelper
 * @extends EventEmitter
 * @param {String} taskName
 * @param {ProjectConfig} projectConfig
 * @constructor
 */
function MagicHelper(taskName, projectConfig) {
    this._configures = [];
    this._prebuilds = [];

    this._taskName = taskName;
    this._projectConfig = projectConfig;
    this._eventChannel = new EventEmitter();

    this._configureTask();
}

/**
 * Возвращает название таска, в котором происходит создание и нод и таргетов.
 *
 * @returns {String}
 */
MagicHelper.prototype.getTaskName = function () {
    return this._taskName;
};

/**
 * Возвращает путь к корню проекта.
 *
 * @returns {String}
 */
MagicHelper.prototype.getRootPath = function () {
    return this._projectConfig.getRootPath();
};

/**
 * Возвращает канал событий данного хэлпера.
 *
 * @returns {EventEmitter}
 */
MagicHelper.prototype.getEventChannel = function () {
    return this._eventChannel;
};

/**
 * Позволяет декларировать создание нод и таргетов на файловой системе для последующей их сборки.
 * Функции `callback` первым аргументом будет передан инстанс `MagicConfig`.
 *
 * Предполагается, что в функции `callback` будут производиться некие действия по созданию нод и таргетов
 * на файловой системе, которые будут зарегистрированы с помощью `MagicConfig`.
 *
 * Если несколько раз задекларировать создание уровней-сетов, то каждая такая `callback` функция будет выполняться
 * асинхронно, т.е. независимо от остальных.
 *
 * @param {Function} callback
 */
MagicHelper.prototype.prebuild = function (callback) {
    this._prebuilds.push(callback);
};

/**
 * Позволяет декларировать сборку нод и таргетов.
 * Функции `callback` первым аргументом будет передан инстанс `ProjectConfig`.
 *
 * Можно использовать любые методы для конфигурирования нод (`node`, `nodes`, `nodeMask`), т.к. выполнение `callback`
 * функций будет происходить после этапа создания нод и таргетов на файловой системе.
 *
 * @param {Function} callback
 */
MagicHelper.prototype.configure = function (callback) {
    this._configures.push(callback);
};

MagicHelper.prototype._configureTask = function () {
    var _this = this,
        projectConfig = this._projectConfig,
        taskName = this._taskName,
        channel = this._eventChannel;

    projectConfig.task(taskName, function (task) {
        channel.emit(EVENTS.START);

        var logger = task._logger,
            args = [].slice.call(arguments, 1),
            magicConfig = new MagicConfig(args);

        // Запускаем сборку задекларированных `callback`-функций
        return vow.all(_this._prebuilds.map(function (callback) {
                return callback.apply(this, [magicConfig, logger]);
            }))
            .then(function () {
                var registeredNodes = magicConfig.getRegisteredNodes(),
                    targets = magicConfig.getRegisteredTargets(),
                    nodes = magicConfig.getNodes(),
                    toBuild = {},
                    nodesToBuild = {},
                    targetsToBuild = {},
                    mode = process.env.ENB_MAGIC_MODE || MODES.FULL;

                channel.emit(EVENTS.PREBUILD, registeredNodes, targets);

                // Ничего не делаем, если нет зарегистрированных нод и таргетов
                if (nodes.length === 0 && targets.length === 0) {
                    channel.emit(EVENTS.BUILD, []);

                    return vow.resolve([]);
                }

                // Собираем все ноды, если пользователь не указал ожидаемых целей
                if (!args.length && mode === MODES.FULL) {
                    return _this._buildTargets(nodes, nodes, []);
                }

                // Раскрываем цели в список нод и таргетов для сборки
                args.forEach(function (arg) {
                    arg = arg.replace(/\/$/, '');

                    // Добавляем цель в сборку, если она была зарегистрированна
                    // или входит в явно или косвенно зарегистрированную ноду
                    if (magicConfig.isRegisteredTarget(arg) || nodes.indexOf(path.dirname(arg)) !== -1) {
                        targetsToBuild[arg] = true;
                        toBuild[arg] = true;
                    }

                    // В `full` режиме в сборку попадают цели, даже если они не являются конечными таргетами (файлами)
                    if (mode === MODES.FULL && magicConfig.isRequiredNode(arg)) {
                        // Раскрываем цель в список нод
                        nodes.forEach(function (node) {
                            if (node.indexOf(arg) === 0) {
                                nodesToBuild[node] = true;
                                toBuild[node] = true;
                            }
                        });
                    }

                    // В `pre` режиме в сборку попадают только зарегистрированные ноды
                    if (mode === MODES.PRE && magicConfig.isRegisteredNode(arg)) {
                        nodesToBuild[arg] = true;
                        toBuild[arg] = true;
                    }
                });

                toBuild = Object.keys(toBuild);
                nodesToBuild = Object.keys(nodesToBuild);
                targetsToBuild = Object.keys(targetsToBuild);

                // Не вызываем сборку если не оказалось таргетов для сборки
                if (toBuild.length === 0) {
                    channel.emit(EVENTS.BUILD, []);

                    return vow.resolve([]);
                }

                return _this._buildTargets(toBuild, nodesToBuild, targetsToBuild);
            })
            .fail(function (err) {
                console.error(err.stack);
            });
    });

    this._taskConfig = projectConfig.getTaskConfig(taskName);
};

MagicHelper.prototype._buildTargets = function (toBuild, nodes, targets) {
    var baseMakePlatform = this._taskConfig.getMakePlatform(),
        makePlatform = new MakePlatform(),
        root = this.getRootPath(),
        configures = this._configures,
        channel = this._eventChannel,
        argv = parseArgs(process.argv.slice(2), {
            boolean: ['no-cache'],
            alias: { 'no-cache': 'n' },
            unknown: function () { return false; }
        }),
        useCache = !argv['no-cache'];

    return makePlatform.init(root)
        .then(function () {
            if (useCache) {
                // Используем кэш основной make-платформы
                makePlatform._cacheStorage = baseMakePlatform._cacheStorage;
                makePlatform.loadCache();
            }

            // Используем логгер основной make-платформы
            makePlatform.setLogger(baseMakePlatform.getLogger());

            // Донастраиваем сборку специфично для текущего таска
            configures.length && configures.forEach(function (f) {
                f.apply(null, [makePlatform._projectConfig, nodes, targets]);
            });

            return makePlatform.buildTargets(toBuild);
        })
        .then(function () {
            if (useCache) {
                makePlatform.saveCache();
                makePlatform._cacheStorage = undefined; // Не даём очистить закэшированные данные основной
                                                        // make-платформы, т.к. они могут понадобится
                                                        // для остальных тасков
            }

            makePlatform.destruct();

            channel.emit(EVENTS.BUILD, toBuild);

            return toBuild;
        })
        .fail(function (err) {
            makePlatform.destruct();
            channel.emit(EVENTS.ERROR, err);

            throw err;
        });
};

module.exports = MagicHelper;
<commit_msg>Added mode to `MagicHelper`<commit_after>var path = require('path'),
    vow = require('vow'),
    parseArgs = require('minimist'),
    EventEmitter = require('events').EventEmitter,
    MakePlatform = require('enb/lib/make'),
    MagicConfig = require('./magic-config'),
    EVENTS = {
        START: 'start',
        PREBUILD: 'prebuild',
        BUILD: 'build',
        ERROR: 'error'
    },
    MODES = {
        FULL: 'full',
        PRE: 'pre'
    };

/**
 * Хэлпер предназначен для решения задач, связанных с созданием новых нод и таргетов в процессе выполнения сборки.
 *
 * В процессе создания конфигуратора будет зарегистрирован таск c указанным именем, который предназначается для:
 * 1. Создания директорий и файлов, формирующих уровни-сеты.
 * 2. Регистрации нод и таргетов по созданным директориям и файлам.
 * 3. Запуска сборки зарегистрированных таргетов, а также всех таргетов в рамках зарегистрированных нод.
 *
 * Общение между хэлперами происходит через каналы событий. По умолчанию каждый из хэлперов создаёт следующие события:
 *
 * 1. **start** `function () { }`
 *
 * Срабатывает, когда происходит запуск таска.
 *
 * 2. **prebuild** `function (nodes, targets) { }`
 *
 * Срабатывает после создания всех уровней-сетов на файловой системе. Возвращает список всех зарегистрированных `nodes`,
 * а также список всех зарегистрированных таргетов `targets` ноды, которых не были зарегистрированны.
 *
 * 3. **build** `function (targets) { }`
 *
 * Срабатывает в случае успешного завершения сборки, возвращая список собранных таргетов.
 *
 * 4. **error** `function (err) { }`
 *
 * Срабатывает при возникновении ошибки в процессе построения или сборки уровней-сетов.
 *
 * Таск может работать в нескольких режимах:
 *
 * 1. `full` — сборка выполняется для всех целей. Если в качестве цели задана нода или директория с нодами,
 *             то цель будет раскрыта до всех таргетов в рамках заданной ноды или директории.
 * 2. `pre`  — сборка выполняется только для тех целей, которые являются конечными таргетами (файлами),
 *             для остальных выполняется только `prebuild`.
 *
 * Название режима задаётся через переменную окружения `process.env.ENB_MAGIC_MODE`.
 * По умолчанию таск работает в `full`-режиме.
 *
 * @name MagicHelper
 * @extends EventEmitter
 * @param {String} taskName
 * @param {ProjectConfig} projectConfig
 * @constructor
 */
function MagicHelper(taskName, projectConfig) {
    this._mode = process.env.ENB_MAGIC_MODE || MODES.FULL;
    this._configures = [];
    this._prebuilds = [];

    this._taskName = taskName;
    this._projectConfig = projectConfig;
    this._eventChannel = new EventEmitter();

    this._configureTask();
}

/**
 * Возвращает название таска, в котором происходит создание и нод и таргетов.
 *
 * @returns {String}
 */
MagicHelper.prototype.getTaskName = function () {
    return this._taskName;
};

/**
 * Возвращает название режима сборки.
 *
 * @returns {String}
 */
MagicHelper.prototype.getMode = function () {
    return this._mode;
};

/**
 * Задаёт режим сборки.
 *
 * @param {String} mode
 */
MagicHelper.prototype.setMode = function (mode) {
    this._mode = mode;
};

/**
 * Возвращает путь к корню проекта.
 *
 * @returns {String}
 */
MagicHelper.prototype.getRootPath = function () {
    return this._projectConfig.getRootPath();
};

/**
 * Возвращает канал событий данного хэлпера.
 *
 * @returns {EventEmitter}
 */
MagicHelper.prototype.getEventChannel = function () {
    return this._eventChannel;
};

/**
 * Позволяет декларировать создание нод и таргетов на файловой системе для последующей их сборки.
 * Функции `callback` первым аргументом будет передан инстанс `MagicConfig`.
 *
 * Предполагается, что в функции `callback` будут производиться некие действия по созданию нод и таргетов
 * на файловой системе, которые будут зарегистрированы с помощью `MagicConfig`.
 *
 * Если несколько раз задекларировать создание уровней-сетов, то каждая такая `callback` функция будет выполняться
 * асинхронно, т.е. независимо от остальных.
 *
 * @param {Function} callback
 */
MagicHelper.prototype.prebuild = function (callback) {
    this._prebuilds.push(callback);
};

/**
 * Позволяет декларировать сборку нод и таргетов.
 * Функции `callback` первым аргументом будет передан инстанс `ProjectConfig`.
 *
 * Можно использовать любые методы для конфигурирования нод (`node`, `nodes`, `nodeMask`), т.к. выполнение `callback`
 * функций будет происходить после этапа создания нод и таргетов на файловой системе.
 *
 * @param {Function} callback
 */
MagicHelper.prototype.configure = function (callback) {
    this._configures.push(callback);
};

MagicHelper.prototype._configureTask = function () {
    var _this = this,
        projectConfig = this._projectConfig,
        taskName = this._taskName,
        channel = this._eventChannel;

    projectConfig.task(taskName, function (task) {
        channel.emit(EVENTS.START);

        var logger = task._logger,
            args = [].slice.call(arguments, 1),
            magicConfig = new MagicConfig(args);

        // Запускаем сборку задекларированных `callback`-функций
        return vow.all(_this._prebuilds.map(function (callback) {
                return callback.apply(this, [magicConfig, logger]);
            }))
            .then(function () {
                var registeredNodes = magicConfig.getRegisteredNodes(),
                    targets = magicConfig.getRegisteredTargets(),
                    nodes = magicConfig.getNodes(),
                    toBuild = {},
                    nodesToBuild = {},
                    targetsToBuild = {},
                    mode = _this.mode;

                channel.emit(EVENTS.PREBUILD, registeredNodes, targets);

                // Ничего не делаем, если нет зарегистрированных нод и таргетов
                if (nodes.length === 0 && targets.length === 0) {
                    channel.emit(EVENTS.BUILD, []);

                    return vow.resolve([]);
                }

                // Собираем все ноды, если пользователь не указал ожидаемых целей
                if (!args.length && mode === MODES.FULL) {
                    return _this._buildTargets(nodes, nodes, []);
                }

                // Раскрываем цели в список нод и таргетов для сборки
                args.forEach(function (arg) {
                    arg = arg.replace(/\/$/, '');

                    // Добавляем цель в сборку, если она была зарегистрированна
                    // или входит в явно или косвенно зарегистрированную ноду
                    if (magicConfig.isRegisteredTarget(arg) || nodes.indexOf(path.dirname(arg)) !== -1) {
                        targetsToBuild[arg] = true;
                        toBuild[arg] = true;
                    }

                    // В `full` режиме в сборку попадают цели, даже если они не являются конечными таргетами (файлами)
                    if (mode === MODES.FULL && magicConfig.isRequiredNode(arg)) {
                        // Раскрываем цель в список нод
                        nodes.forEach(function (node) {
                            if (node.indexOf(arg) === 0) {
                                nodesToBuild[node] = true;
                                toBuild[node] = true;
                            }
                        });
                    }

                    // В `pre` режиме в сборку попадают только зарегистрированные ноды
                    if (mode === MODES.PRE && magicConfig.isRegisteredNode(arg)) {
                        nodesToBuild[arg] = true;
                        toBuild[arg] = true;
                    }
                });

                toBuild = Object.keys(toBuild);
                nodesToBuild = Object.keys(nodesToBuild);
                targetsToBuild = Object.keys(targetsToBuild);

                // Не вызываем сборку если не оказалось таргетов для сборки
                if (toBuild.length === 0) {
                    channel.emit(EVENTS.BUILD, []);

                    return vow.resolve([]);
                }

                return _this._buildTargets(toBuild, nodesToBuild, targetsToBuild);
            })
            .fail(function (err) {
                console.error(err.stack);
            });
    });

    this._taskConfig = projectConfig.getTaskConfig(taskName);
};

MagicHelper.prototype._buildTargets = function (toBuild, nodes, targets) {
    var baseMakePlatform = this._taskConfig.getMakePlatform(),
        makePlatform = new MakePlatform(),
        root = this.getRootPath(),
        configures = this._configures,
        channel = this._eventChannel,
        argv = parseArgs(process.argv.slice(2), {
            boolean: ['no-cache'],
            alias: { 'no-cache': 'n' },
            unknown: function () { return false; }
        }),
        useCache = !argv['no-cache'];

    return makePlatform.init(root)
        .then(function () {
            if (useCache) {
                // Используем кэш основной make-платформы
                makePlatform._cacheStorage = baseMakePlatform._cacheStorage;
                makePlatform.loadCache();
            }

            // Используем логгер основной make-платформы
            makePlatform.setLogger(baseMakePlatform.getLogger());

            // Донастраиваем сборку специфично для текущего таска
            configures.length && configures.forEach(function (f) {
                f.apply(null, [makePlatform._projectConfig, nodes, targets]);
            });

            return makePlatform.buildTargets(toBuild);
        })
        .then(function () {
            if (useCache) {
                makePlatform.saveCache();
                makePlatform._cacheStorage = undefined; // Не даём очистить закэшированные данные основной
                                                        // make-платформы, т.к. они могут понадобится
                                                        // для остальных тасков
            }

            makePlatform.destruct();

            channel.emit(EVENTS.BUILD, toBuild);

            return toBuild;
        })
        .fail(function (err) {
            makePlatform.destruct();
            channel.emit(EVENTS.ERROR, err);

            throw err;
        });
};

module.exports = MagicHelper;
<|endoftext|>


<commit_before>/*jshint camelcase:false */
/**
 * Build the source code
 *
 */

var gcTools = require('grunt-closure-tools'),
    helpers= require('./helpers'),
    grunt  = require('grunt'),
    __     = require('underscore'),
    path   = require('path'),
    Tempdir = require('temporary/lib/dir'),
    gUglify = require('grunt-contrib-uglify')().uglify.init( grunt ),
    fs     = require('fs'),
    cTools   = require('closure-tools'),
    compiler = require('superstartup-closure-compiler');
// define the namespace we'll work on
var build = {
  _tmpDir: null,
  GOOG_BASE_FILE: 'base.js'

};

/**
 * ===== THIS IS A COPY FROM lib/config/config.js =====
 *
 * The properties available via the config JSON file.
 *
 * This enum does not describe structure of the config
 * map, although it tries via intendation.
 *
 * Plainly do a static declaration of keys used.
 *
 * @enum {string}
 */
build.webConfigProps = {
  BASEURL: 'baseUrl',
  LIBS: 'libs',
  NAMESPACE: 'require',

  // the build tree
  BUILD: 'build',
    INPUT: 'input',
    DEST: 'dest'

};

// Define the path to the closure compiler jar file.
var CLOSURE_COMPILER = compiler.getPathSS(),
    CLOSURE_BUILDER  = cTools.getPath('build/closurebuilder.py'),
    VENDOR_BUNDLE    = 'libs.js';

/**
 * Run the build task.
 *
 * We require the mantri configuration file that bootstraps the web.
 *
 * We expect to find there information about how to perform the build.
 *
 * After we are satisfied with what we collect we start the build process.
 *
 * The opts object expects these keys:
 *   src {string=} The path to mantriConf.json file.
 *   dest {string=} The filename to output the result.
 *   target {string=} The target we are working on, a name to identify the task.
 *
 * @param  {Object}   opts A map with options as defined above.
 * @param  {Function} cb      [description]
 */
build.run = function( opts, cb) {

  // bundle and sanitize all options
  var options = {
    dest: '',
    confFile: (opts.src || 'mantriConf.json') + '',
    target: (opts.target || '') + '',
    cb: cb,
    webConfig: {}
  };

  //
  //
  // Read and validate the web config file
  //
  //
  // require the config file. Flow continues at start() function.
  if ( !grunt.file.isFile(options.confFile) ) {
    helpers.log.error('No mantriConf.json file found: '.red +
      options.confFile.blue);
    cb( false );
    return;
  }

  options.webConfig = grunt.file.readJSON( options.confFile );
  options.dest = (opts.dest || options.webConfig.build.dest || '') + '';
  if ( !options.dest ) {
    helpers.log.error('No output file defined! Use the "dest" option.'.red);
    cb( false );
    return;
  }

  if (! build.validate( options )) {
    // error messages sent by validate
    cb( false );
    return;
  }
  helpers.log.debug( options.debug, 'Passe Validations.');

  // Define debug mode
  options.debug = !!opts.debug;

  // create new temp dir and prep google mock.
  options._tmpDir      = new Tempdir();
  options.googMock     = options._tmpDir.path + '/' + build.GOOG_BASE_FILE;
  options.vendorFile   = options._tmpDir.path + '/' + VENDOR_BUNDLE;
  // temporary file to save the compiled app.
  options.appMin       = options._tmpDir.path + '/appCompiled.js';
  options.jsDirFrag    = path.dirname( options.webConfig.build.input );
  options.documentRoot = path.dirname( options.confFile );
  // resolve what the source and dest will be for the builder
  options.jsRoot       = path.join( options.documentRoot, options.jsDirFrag );

  //
  //
  // Create the temp google closure mock dir and file
  //
  //
  if ( !build._createGoogleMock( options ) ) {
    options._tmpDir.rmdir();
    cb( false );
    return;
  }

  helpers.log.debug( options.debug, 'Created temp dir and google mock: ' + options._tmpDir.path);

  //
  //
  // Resolve and bundle all third party dependencies.
  // Minify them.
  //
  //
  if ( !build._bundleLibs( options, options.dest ) ) {
    options._tmpDir.rmdir();
    cb ( false );
    return;
  }

  helpers.log.debug( options.debug, 'Vendor libs bundled. Constructing builder command...');

  //
  //
  // Prepare the options for the closureBuilder task.
  //
  //
  var src = [ options.jsRoot ];
  src.push( options._tmpDir.path );
  var cToolsOptions = {
    builder: helpers.getPath( CLOSURE_BUILDER ),
    inputs: path.dirname( options.confFile ) + '/' + options.webConfig.build.input,
    compile: true,
    compilerFile: helpers.getPath( CLOSURE_COMPILER ),
    compilerOpts: {
      compilation_level: 'SIMPLE_OPTIMIZATIONS',
      warning_level: (options.debug ? 'VERBOSE' : 'QUIET'),
      // go wild here, name every exception in the book to be ignored
      // WE BE NO SCARE HAXORShh
      jscomp_off: gcTools.closureOpts.compiler.jscomp_off
    }
  };
  var cToolsFileObj = {
    src: src,
    dest: options.appMin
  };

  var command = gcTools.builder.createCommand( cToolsOptions, cToolsFileObj );
  if ( !command ) {
    helpers.log.error('Create shell command failed for builder');
    options._tmpDir.rmdir();
    cb( false );
    return;
  }

  //
  //
  // Run the command.
  //
  //
  helpers.log.debug( options.debug, 'Builder command constructed. Executing...');
  var commands = [ {cmd: command, dest: options.target} ];

  helpers.runCommands( commands, __.partial( build._finishBuild, options ), !options.debug);
};

/**
 * Last step in the build process.
 * Is the callback of the command execution.
 *
 * @param  {Object} options The options object.
 * @param  {boolean} status The status of the command execution.
 * @private
 */
build._finishBuild = function( options, status ) {
  if ( !status ) {
    options._tmpDir.rmdir();
    options.cb( status );
    return;
  }

  helpers.log.debug( options.debug, 'Compilation complete. Concatenating files...');

  var compiledApp = grunt.file.read( options.appMin );
  fs.appendFileSync( options.dest, compiledApp );
  options._tmpDir.rmdir();

  // generate stats
  helpers.generateStats(options.dest, options.cb);
};

/**
 * Bundles the vendor libraries into one file and minifies them.
 *
 * Currently using uglify.
 *
 * For reasons i cannot explain certain patterns of libs make the
 * closure compiler to blow up (litteraly)
 *
 * @param  {Object} options The options object.
 * @param {string} dest The dest file of the operation.
 * @return {boolean} success or not.
 */
build._bundleLibs = function( options, dest ) {

  helpers.log.debug( options.debug, 'Will resolve and bundle all vendor libraries...');

  if ( !build._appendVendorLibs( options ) ) {
    options._tmpDir.rmdir();
    options.cb ( false );
    return;
  }

  helpers.log.debug( options.debug, 'Will now minify the vendor libs to dest: ' + dest.blue);
  // minify the libs using uglify.
  if ( !build.minify( options.vendorFile, dest ) ) {
    helpers.log.warn( 'Uglification of vendor libs failed');
    options.cb( false );
    return;
  }

  return true;
};

/**
 * [minify description]
 * @return {[type]} [description]
 */
build.minify = function(src, dest) {
  var uglifyOpts = {
    banner: '',
    compress: {
      warnings: false
    },
    mangle: {},
    beautify: false
  };

  // Minify files, warn and fail on error.
  var result;
  try {
    result = gUglify.minify([ src ], dest, uglifyOpts);
  } catch (e) {
    return false;
  }

  grunt.file.write( dest, result.min );

  return true;
};

/**
 * Create the temp google closure mock dir and file.
 *
 * @param  {Object} options The options object.
 * @return {boolean} success or fail.
 */
build._createGoogleMock = function( options ) {

  // write the mock goog base file in the temp dir.
  var contents = 'var goog = goog || {}; // Identifies this file as the Closure base.\n';
  grunt.file.write( options.googMock , contents) ;

  return true;
};

/**
 * Will resolve all third party dependencies and concatenate them at the end
 * of the goog base file mock.
 *
 * @param  {Object} options The options object
 * @return {boolean} operation success or fail.
 */
build._appendVendorLibs = function( options ) {
  var webConfig = options.webConfig;

  // shortcut assign libs namespace
  var libs = build.webConfigProps.LIBS;

  if ( !__.isObject( webConfig[libs] ) ) {
    return true;
  }

  var vendorRoot = options.documentRoot;

  // check if baseUrl is there
  if ( __.isString( webConfig.baseUrl ) ) {
    vendorRoot = path.join( vendorRoot, webConfig.baseUrl );
  }

  //
  // Go through all the vendor dependencies and create an
  // array of proper paths.
  //
  var exclude = webConfig.build.exclude || [],
      vendorFile, // string, the current vendor file.
      fileSrc = '';

  helpers.log.debug( options.debug, 'Starting itterating on vendor libs');

  // initialize the file
  grunt.file.write( options.vendorFile, '' );

  for ( var lib in webConfig[libs] ) {
    if ( 0 <= exclude.indexOf( lib ) ) {
      continue;
    }

    vendorFile = vendorRoot + webConfig[libs][lib] + '.js';

    if ( !grunt.file.isFile( vendorFile ) ) {
      helpers.log.error('Could not find third party library: ' + vendorFile.red);
      return false;
    }

    fileSrc = grunt.file.read( vendorFile );
    helpers.log.debug( options.debug, 'Appending vendor lib: ' + lib +
      ' :: ' + fileSrc.length + ' :: ' + vendorFile);

    fs.appendFileSync( options.vendorFile, fileSrc );
  }

  return true;
};

/**
 * Validate the options passed to us from all sources.
 *
 * @param  {Object} options [description]
 * @return {boolean}        [description]
 */
build.validate = function( options ) {
  if ( !__.isObject( options.webConfig.build ) ) {
    helpers.log.error('There is no \'build\' key in your mantriConf file.');
    return false;
  }

  if ( !__.isString( options.webConfig.build.input ) ) {
    helpers.log.error('There is no \'build.input\' key in your mantriConf file.');
    return false;
  }

  return true;
};

module.exports = build;
<commit_msg>added default file for build<commit_after>/*jshint camelcase:false */
/**
 * Build the source code
 *
 */

var gcTools = require('grunt-closure-tools'),
    helpers= require('./helpers'),
    grunt  = require('grunt'),
    __     = require('underscore'),
    path   = require('path'),
    Tempdir = require('temporary/lib/dir'),
    gUglify = require('grunt-contrib-uglify')().uglify.init( grunt ),
    fs     = require('fs'),
    cTools   = require('closure-tools'),
    compiler = require('superstartup-closure-compiler');
// define the namespace we'll work on
var build = {
  _tmpDir: null,
  GOOG_BASE_FILE: 'base.js'

};

/**
 * ===== THIS IS A COPY FROM lib/config/config.js =====
 *
 * The properties available via the config JSON file.
 *
 * This enum does not describe structure of the config
 * map, although it tries via intendation.
 *
 * Plainly do a static declaration of keys used.
 *
 * @enum {string}
 */
build.webConfigProps = {
  BASEURL: 'baseUrl',
  LIBS: 'libs',
  NAMESPACE: 'require',

  // the build tree
  BUILD: 'build',
    INPUT: 'input',
    DEST: 'dest'

};

// Define the path to the closure compiler jar file.
var CLOSURE_COMPILER = compiler.getPathSS(),
    CLOSURE_BUILDER  = cTools.getPath('build/closurebuilder.py'),
    VENDOR_BUNDLE    = 'libs.js';

/**
 * Run the build task.
 *
 * We require the mantri configuration file that bootstraps the web.
 *
 * We expect to find there information about how to perform the build.
 *
 * After we are satisfied with what we collect we start the build process.
 *
 * The opts object expects these keys:
 *   src {string=} The path to mantriConf.json file.
 *   dest {string=} The filename to output the result.
 *   target {string=} The target we are working on, a name to identify the task.
 *
 * @param  {Object}   opts A map with options as defined above.
 * @param  {Function} cb      [description]
 */
build.run = function( opts, cb) {

  // bundle and sanitize all options
  var options = {
    dest: '',
    confFile: (opts.src || 'mantriConf.json') + '',
    target: (opts.target || '') + '',
    cb: cb,
    webConfig: {}
  };

  //
  //
  // Read and validate the web config file
  //
  //
  // require the config file. Flow continues at start() function.
  if ( !grunt.file.isFile(options.confFile) ) {
    helpers.log.error('No mantriConf.json file found: '.red +
      options.confFile.blue);
    cb( false );
    return;
  }

  options.webConfig = grunt.file.readJSON( options.confFile );
  options.dest = (opts.dest || options.webConfig.build.dest || 'app.min.js') + '';
  if ( !options.dest ) {
    helpers.log.error('No output file defined! Use the "dest" option.'.red);
    cb( false );
    return;
  }

  if (! build.validate( options )) {
    // error messages sent by validate
    cb( false );
    return;
  }
  helpers.log.debug( options.debug, 'Passe Validations.');

  // Define debug mode
  options.debug = !!opts.debug;

  // create new temp dir and prep google mock.
  options._tmpDir      = new Tempdir();
  options.googMock     = options._tmpDir.path + '/' + build.GOOG_BASE_FILE;
  options.vendorFile   = options._tmpDir.path + '/' + VENDOR_BUNDLE;
  // temporary file to save the compiled app.
  options.appMin       = options._tmpDir.path + '/appCompiled.js';
  options.jsDirFrag    = path.dirname( options.webConfig.build.input );
  options.documentRoot = path.dirname( options.confFile );
  // resolve what the source and dest will be for the builder
  options.jsRoot       = path.join( options.documentRoot, options.jsDirFrag );

  //
  //
  // Create the temp google closure mock dir and file
  //
  //
  if ( !build._createGoogleMock( options ) ) {
    options._tmpDir.rmdir();
    cb( false );
    return;
  }

  helpers.log.debug( options.debug, 'Created temp dir and google mock: ' + options._tmpDir.path);

  //
  //
  // Resolve and bundle all third party dependencies.
  // Minify them.
  //
  //
  if ( !build._bundleLibs( options, options.dest ) ) {
    options._tmpDir.rmdir();
    cb ( false );
    return;
  }

  helpers.log.debug( options.debug, 'Vendor libs bundled. Constructing builder command...');

  //
  //
  // Prepare the options for the closureBuilder task.
  //
  //
  var src = [ options.jsRoot ];
  src.push( options._tmpDir.path );
  var cToolsOptions = {
    builder: helpers.getPath( CLOSURE_BUILDER ),
    inputs: path.dirname( options.confFile ) + '/' + options.webConfig.build.input,
    compile: true,
    compilerFile: helpers.getPath( CLOSURE_COMPILER ),
    compilerOpts: {
      compilation_level: 'SIMPLE_OPTIMIZATIONS',
      warning_level: (options.debug ? 'VERBOSE' : 'QUIET'),
      // go wild here, name every exception in the book to be ignored
      // WE BE NO SCARE HAXORShh
      jscomp_off: gcTools.closureOpts.compiler.jscomp_off
    }
  };
  var cToolsFileObj = {
    src: src,
    dest: options.appMin
  };

  var command = gcTools.builder.createCommand( cToolsOptions, cToolsFileObj );
  if ( !command ) {
    helpers.log.error('Create shell command failed for builder');
    options._tmpDir.rmdir();
    cb( false );
    return;
  }

  //
  //
  // Run the command.
  //
  //
  helpers.log.debug( options.debug, 'Builder command constructed. Executing...');
  var commands = [ {cmd: command, dest: options.target} ];

  helpers.runCommands( commands, __.partial( build._finishBuild, options ), !options.debug);
};

/**
 * Last step in the build process.
 * Is the callback of the command execution.
 *
 * @param  {Object} options The options object.
 * @param  {boolean} status The status of the command execution.
 * @private
 */
build._finishBuild = function( options, status ) {
  if ( !status ) {
    options._tmpDir.rmdir();
    options.cb( status );
    return;
  }

  helpers.log.debug( options.debug, 'Compilation complete. Concatenating files...');

  var compiledApp = grunt.file.read( options.appMin );
  fs.appendFileSync( options.dest, compiledApp );
  options._tmpDir.rmdir();

  // generate stats
  helpers.generateStats(options.dest, options.cb);
};

/**
 * Bundles the vendor libraries into one file and minifies them.
 *
 * Currently using uglify.
 *
 * For reasons i cannot explain certain patterns of libs make the
 * closure compiler to blow up (litteraly)
 *
 * @param  {Object} options The options object.
 * @param {string} dest The dest file of the operation.
 * @return {boolean} success or not.
 */
build._bundleLibs = function( options, dest ) {

  helpers.log.debug( options.debug, 'Will resolve and bundle all vendor libraries...');

  if ( !build._appendVendorLibs( options ) ) {
    options._tmpDir.rmdir();
    options.cb ( false );
    return;
  }

  helpers.log.debug( options.debug, 'Will now minify the vendor libs to dest: ' + dest.blue);
  // minify the libs using uglify.
  if ( !build.minify( options.vendorFile, dest ) ) {
    helpers.log.warn( 'Uglification of vendor libs failed');
    options.cb( false );
    return;
  }

  return true;
};

/**
 * [minify description]
 * @return {[type]} [description]
 */
build.minify = function(src, dest) {
  var uglifyOpts = {
    banner: '',
    compress: {
      warnings: false
    },
    mangle: {},
    beautify: false
  };

  // Minify files, warn and fail on error.
  var result;
  try {
    result = gUglify.minify([ src ], dest, uglifyOpts);
  } catch (e) {
    return false;
  }

  grunt.file.write( dest, result.min );

  return true;
};

/**
 * Create the temp google closure mock dir and file.
 *
 * @param  {Object} options The options object.
 * @return {boolean} success or fail.
 */
build._createGoogleMock = function( options ) {

  // write the mock goog base file in the temp dir.
  var contents = 'var goog = goog || {}; // Identifies this file as the Closure base.\n';
  grunt.file.write( options.googMock , contents) ;

  return true;
};

/**
 * Will resolve all third party dependencies and concatenate them at the end
 * of the goog base file mock.
 *
 * @param  {Object} options The options object
 * @return {boolean} operation success or fail.
 */
build._appendVendorLibs = function( options ) {
  var webConfig = options.webConfig;

  // shortcut assign libs namespace
  var libs = build.webConfigProps.LIBS;

  if ( !__.isObject( webConfig[libs] ) ) {
    return true;
  }

  var vendorRoot = options.documentRoot;

  // check if baseUrl is there
  if ( __.isString( webConfig.baseUrl ) ) {
    vendorRoot = path.join( vendorRoot, webConfig.baseUrl );
  }

  //
  // Go through all the vendor dependencies and create an
  // array of proper paths.
  //
  var exclude = webConfig.build.exclude || [],
      vendorFile, // string, the current vendor file.
      fileSrc = '';

  helpers.log.debug( options.debug, 'Starting itterating on vendor libs');

  // initialize the file
  grunt.file.write( options.vendorFile, '' );

  for ( var lib in webConfig[libs] ) {
    if ( 0 <= exclude.indexOf( lib ) ) {
      continue;
    }

    vendorFile = vendorRoot + webConfig[libs][lib] + '.js';

    if ( !grunt.file.isFile( vendorFile ) ) {
      helpers.log.error('Could not find third party library: ' + vendorFile.red);
      return false;
    }

    fileSrc = grunt.file.read( vendorFile );
    helpers.log.debug( options.debug, 'Appending vendor lib: ' + lib +
      ' :: ' + fileSrc.length + ' :: ' + vendorFile);

    fs.appendFileSync( options.vendorFile, fileSrc );
  }

  return true;
};

/**
 * Validate the options passed to us from all sources.
 *
 * @param  {Object} options [description]
 * @return {boolean}        [description]
 */
build.validate = function( options ) {
  if ( !__.isObject( options.webConfig.build ) ) {
    helpers.log.error('There is no \'build\' key in your mantriConf file.');
    return false;
  }

  if ( !__.isString( options.webConfig.build.input ) ) {
    helpers.log.error('There is no \'build.input\' key in your mantriConf file.');
    return false;
  }

  return true;
};

module.exports = build;
<|endoftext|>


<commit_before>/*
 * MelonJS Game Engine
 * Copyright (C) 2011 - 2013, Olivier Biot
 * http://www.melonjs.org
 *
 */

(function($) {

    /**
     * A pool of Object entity

     * This object is used for object pooling - a technique that might speed up your game
     * if used properly.

     * If some of your classes will be instantiated and removed a lot at a time, it is a
     * good idea to add the class to this entity pool. A separate pool for that class
     * will be created, which will reuse objects of the class. That way they won't be instantiated
     * each time you need a new one (slowing your game), but stored into that pool and taking one
     * already instantiated when you need it.

     * This object is also used by the engine to instantiate objects defined in the map,
     * which means, that on level loading the engine will try to instantiate every object
     * found in the map, based on the user defined name in each Object Properties

     *

     * @namespace me.pool
     * @memberOf me
     */
    me.pool = (function() {
        // hold public stuff in our singleton
        var api = {};

        var entityClass = {};

        /*---------------------------------------------

            PUBLIC STUFF

        ---------------------------------------------*/

        /**
         * Constructor
         * @ignore
         */

        api.init = function() {
            // add default entity object
            api.register("me.ObjectEntity", me.ObjectEntity);
            api.register("me.CollectableEntity", me.CollectableEntity);
            api.register("me.LevelEntity", me.LevelEntity);
            api.register("me.Tween", me.Tween, true);
            api.register("me.Color", me.Color, true);
            api.register("me.Particle", me.Particle, true);
        };

        /**
         * register an object to the pool.

         * Pooling must be set to true if more than one such objects will be created.

         * (note) If pooling is enabled, you shouldn't instantiate objects with `new`.
         * See examples in {@link me.pool#pull}
         * @name register
         * @memberOf me.pool
         * @public
         * @function
         * @param {String} className as defined in the Name field of the Object Properties (in Tiled)
         * @param {Object} class corresponding Class to be instantiated
         * @param {Boolean} [objectPooling=false] enables object pooling for the specified class
         * - speeds up the game by reusing existing objects
         * @example
         * // add our users defined entities in the entity pool
         * me.pool.register("playerspawnpoint", PlayerEntity);
         * me.pool.register("cherryentity", CherryEntity, true);
         * me.pool.register("heartentity", HeartEntity, true);
         * me.pool.register("starentity", StarEntity, true);
         */
        api.register = function(className, entityObj, pooling) {
            if (!pooling) {
                entityClass[className.toLowerCase()] = {
                    "class" : entityObj,
                    "pool" : undefined
                };
                return;
            }

            entityClass[className.toLowerCase()] = {
                "class" : entityObj,
                "pool" : []
            };
        };

        /**
         * Pull a new instance of the requested object (if added into the object pool)
         * @name pull
         * @memberOf me.pool
         * @public
         * @function
         * @param {String} className as used in {@link me.pool#register}
         * @param {} [arguments...] arguments to be passed when instantiating/reinitializing the object
         * @example
         * me.pool.register("player", PlayerEntity);
         * var player = me.pool.pull("player");
         * @example
         * me.pool.register("bullet", BulletEntity, true);
         * me.pool.register("enemy", EnemyEntity, true);
         * // ...
         * // when we need to manually create a new bullet:
         * var bullet = me.pool.pull("bullet", x, y, direction);
         * // ...
         * // params aren't a fixed number
         * // when we need new enemy we can add more params, that the object construct requires:
         * var enemy = me.pool.pull("enemy", x, y, direction, speed, power, life);
         * // ...
         * // when we want to destroy existing object, the remove
         * // function will ensure the object can then be reallocated later
         * me.game.world.removeChild(enemy);
         * me.game.world.removeChild(bullet);
         */

        api.pull = function(data) {
            var name = typeof data === 'string' ? data.toLowerCase() : undefined;
            var args = Array.prototype.slice.call(arguments);
            if (name && entityClass[name]) {
                var proto;
                if (!entityClass[name]['pool']) {
                    proto = entityClass[name]["class"];
                    args[0] = proto;
                    return new (proto.bind.apply(proto, args))();
                }

                var obj, entity = entityClass[name];
                proto = entity["class"];
                if (entity["pool"].length > 0) {
                    obj = entity["pool"].pop();
                    // call the object init function if defined (JR's Inheritance)
                    if (typeof obj.init === "function") {
                        obj.init.apply(obj, args.slice(1));
                    }
                    // call the object onResetEvent function if defined
                    if (typeof obj.onResetEvent === "function") {
                        obj.onResetEvent.apply(obj, args.slice(1));
                    }
                } else {
                    args[0] = proto;
                    obj = new (proto.bind.apply(proto, args))();
                    obj.className = name;
                }
                return obj;
            }

            // Tile objects can be created with a GID attribute;
            // The TMX parser will use it to create the image property.
            var settings = arguments[3];
            if (settings && settings.gid && settings.image) {
                return new me.SpriteObject(settings.x, settings.y, settings.image);
            }

            if (name) {
                console.error("Cannot instantiate entity of type '" + data + "': Class not found!");
            }
            return null;
        };

        /**
         * purge the entity pool from any inactive object

         * Object pooling must be enabled for this function to work

         * note: this will trigger the garbage collector
         * @name purge
         * @memberOf me.pool
         * @public
         * @function
         */
        api.purge = function() {
            for (var className in entityClass) {
                entityClass[className]["pool"] = [];
            }
        };

        /**
         * Push back an object instance into the entity pool

         * Object pooling for the object class must be enabled,
         * and object must have been instantiated using {@link me.pool#pull},
         * otherwise this function won't work
         * @name push
         * @memberOf me.pool
         * @public
         * @function
         * @param {Object} instance to be recycled
         */
        api.push = function(obj) {
            var name = obj.className;
            if (typeof(name) !== 'undefined' || !entityClass[name]) {
                // object is not registered, don't do anything
                return;
            }
            // store back the object instance for later recycling
            entityClass[name]["pool"].push(obj);
        };

        // return our object
        return api;

    })();

    /*---------------------------------------------------------*/
    // END END END
    /*---------------------------------------------------------*/
})(window);
<commit_msg>Fixed a regression on the `push` function<commit_after>/*
 * MelonJS Game Engine
 * Copyright (C) 2011 - 2013, Olivier Biot
 * http://www.melonjs.org
 *
 */

(function($) {

    /**
     * A pool of Object entity

     * This object is used for object pooling - a technique that might speed up your game
     * if used properly.

     * If some of your classes will be instantiated and removed a lot at a time, it is a
     * good idea to add the class to this entity pool. A separate pool for that class
     * will be created, which will reuse objects of the class. That way they won't be instantiated
     * each time you need a new one (slowing your game), but stored into that pool and taking one
     * already instantiated when you need it.

     * This object is also used by the engine to instantiate objects defined in the map,
     * which means, that on level loading the engine will try to instantiate every object
     * found in the map, based on the user defined name in each Object Properties

     *

     * @namespace me.pool
     * @memberOf me
     */
    me.pool = (function() {
        // hold public stuff in our singleton
        var api = {};

        var entityClass = {};

        /*---------------------------------------------

            PUBLIC STUFF

        ---------------------------------------------*/

        /**
         * Constructor
         * @ignore
         */

        api.init = function() {
            // add default entity object
            api.register("me.ObjectEntity", me.ObjectEntity);
            api.register("me.CollectableEntity", me.CollectableEntity);
            api.register("me.LevelEntity", me.LevelEntity);
            api.register("me.Tween", me.Tween, true);
            api.register("me.Color", me.Color, true);
            api.register("me.Particle", me.Particle, true);
        };

        /**
         * register an object to the pool.

         * Pooling must be set to true if more than one such objects will be created.

         * (note) If pooling is enabled, you shouldn't instantiate objects with `new`.
         * See examples in {@link me.pool#pull}
         * @name register
         * @memberOf me.pool
         * @public
         * @function
         * @param {String} className as defined in the Name field of the Object Properties (in Tiled)
         * @param {Object} class corresponding Class to be instantiated
         * @param {Boolean} [objectPooling=false] enables object pooling for the specified class
         * - speeds up the game by reusing existing objects
         * @example
         * // add our users defined entities in the entity pool
         * me.pool.register("playerspawnpoint", PlayerEntity);
         * me.pool.register("cherryentity", CherryEntity, true);
         * me.pool.register("heartentity", HeartEntity, true);
         * me.pool.register("starentity", StarEntity, true);
         */
        api.register = function(className, entityObj, pooling) {
            if (!pooling) {
                entityClass[className.toLowerCase()] = {
                    "class" : entityObj,
                    "pool" : undefined
                };
                return;
            }

            entityClass[className.toLowerCase()] = {
                "class" : entityObj,
                "pool" : []
            };
        };

        /**
         * Pull a new instance of the requested object (if added into the object pool)
         * @name pull
         * @memberOf me.pool
         * @public
         * @function
         * @param {String} className as used in {@link me.pool#register}
         * @param {} [arguments...] arguments to be passed when instantiating/reinitializing the object
         * @example
         * me.pool.register("player", PlayerEntity);
         * var player = me.pool.pull("player");
         * @example
         * me.pool.register("bullet", BulletEntity, true);
         * me.pool.register("enemy", EnemyEntity, true);
         * // ...
         * // when we need to manually create a new bullet:
         * var bullet = me.pool.pull("bullet", x, y, direction);
         * // ...
         * // params aren't a fixed number
         * // when we need new enemy we can add more params, that the object construct requires:
         * var enemy = me.pool.pull("enemy", x, y, direction, speed, power, life);
         * // ...
         * // when we want to destroy existing object, the remove
         * // function will ensure the object can then be reallocated later
         * me.game.world.removeChild(enemy);
         * me.game.world.removeChild(bullet);
         */

        api.pull = function(data) {
            var name = typeof data === 'string' ? data.toLowerCase() : undefined;
            var args = Array.prototype.slice.call(arguments);
            if (name && entityClass[name]) {
                var proto;
                if (!entityClass[name]['pool']) {
                    proto = entityClass[name]["class"];
                    args[0] = proto;
                    return new (proto.bind.apply(proto, args))();
                }

                var obj, entity = entityClass[name];
                proto = entity["class"];
                if (entity["pool"].length > 0) {
                    obj = entity["pool"].pop();
                    // call the object init function if defined (JR's Inheritance)
                    if (typeof obj.init === "function") {
                        obj.init.apply(obj, args.slice(1));
                    }
                    // call the object onResetEvent function if defined
                    if (typeof obj.onResetEvent === "function") {
                        obj.onResetEvent.apply(obj, args.slice(1));
                    }
                } else {
                    args[0] = proto;
                    obj = new (proto.bind.apply(proto, args))();
                    obj.className = name;
                }
                return obj;
            }

            // Tile objects can be created with a GID attribute;
            // The TMX parser will use it to create the image property.
            var settings = arguments[3];
            if (settings && settings.gid && settings.image) {
                return new me.SpriteObject(settings.x, settings.y, settings.image);
            }

            if (name) {
                console.error("Cannot instantiate entity of type '" + data + "': Class not found!");
            }
            return null;
        };

        /**
         * purge the entity pool from any inactive object

         * Object pooling must be enabled for this function to work

         * note: this will trigger the garbage collector
         * @name purge
         * @memberOf me.pool
         * @public
         * @function
         */
        api.purge = function() {
            for (var className in entityClass) {
                entityClass[className]["pool"] = [];
            }
        };

        /**
         * Push back an object instance into the entity pool

         * Object pooling for the object class must be enabled,
         * and object must have been instantiated using {@link me.pool#pull},
         * otherwise this function won't work
         * @name push
         * @memberOf me.pool
         * @public
         * @function
         * @param {Object} instance to be recycled
         */
        api.push = function(obj) {
            var name = obj.className;
            if (typeof(name) === 'undefined' || !entityClass[name]) {
                // object is not registered, don't do anything
                return;
            }
            // store back the object instance for later recycling
            entityClass[name]["pool"].push(obj);
        };

        // return our object
        return api;

    })();

    /*---------------------------------------------------------*/
    // END END END
    /*---------------------------------------------------------*/
})(window);
<|endoftext|>


<commit_before>var fs = require("fs");
var marked = require('marked');
var pdf = require('html-pdf');
var path = require('path');
var url = require('url');
var mdpreview = atom.packages.activePackages["markdown-preview"];

module.exports = {

  config: {
    "rendering":{
      "title": "Markdown Rendering",
      "description": "Render the markdown to html with Marked or Markdown-Preview.",
      "type": "string",
      "default": "Marked",
      "enum": ["Marked", "Markdown-Preview"]
    },
    "format": {
      "title": "Page Format",
      "type": "string",
      "default": "A4",
      "enum": ["A3", "A4", "A5", "Legal", "Letter", "Tabloid"]
    },
    "orientation": {
      "title": "Page Orientation",
      "type": "string",
      "default": "portrait",
      "enum": ["portrait", "landscape"]
    },
    "timeout": {
      "title": "Timeout",
      "description": "Time (ms) before PhantomJS gives up on rendering. You can set a larger value for very big files.",
      "type": "integer",
      "default": 10000
    },
    "border": {
      "title": "Border Size",
      "type": "string",
      "default": "10mm"
    },
    "type": {
      "title": "Exported Filetype",
      "type": "string",
      "default": "pdf",
      "enum": ["pdf", "png", "jpeg"]
    },
    "quality": {
      "title": "Image Quality",
      "description": "Only used for .png and .jpeg formats.",
      "type": "integer",
      "minimum": 1,
      "maximum": 100,
      "default": 100
    }
  },

  activate: function() {
    atom.workspaceView.command('markdown-pdf:convert', this.convert);
  },

  convert: function() {
    try{
      outPath = getOutputPath();
      getHtml(function(html){
        html = convertImgSrcToURI(html);
        html = getStyledHtml(html);
        makePdf(html, outPath);
      });
    }
    catch(err){
      console.log(err);
      return;
    }
  }
}

function getHtml(callback){
  var rend = atom.config.getSettings()['markdown-pdf'].rendering;
  if(rend == "Marked"){
    htmlFromMd(callback);
  }
  else{
    htmlFromPreview(callback);
  }
}

function htmlFromPreview(callback){
  //render markdown using Atom's Markdown-Preview package
  var cb = atom.clipboard;
  if(editorSelected()){
    //save old clipboard contents
    var old = cb.read();
    //get html on clipboard
    mdpreview.mainModule.copyHtml()
    var html = cb.read();
    //put old clipboard contents back
    cb.write(old);

    callback(html);
  }
  else alert('Please select the editor containing the markdown you wish to convert.');
}

function htmlFromMd(callback){

  //explicitly setting default options below, in case we want to further configure the renderer
  marked.setOptions({
    renderer: new marked.Renderer(),
    gfm: true,
    tables: true,
    breaks: false,
    pedantic: false,
    sanitize: true,
    smartLists: true,
    smartypants: false
  });
  if(editorSelected()){
    var md = atom.workspace.getActivePaneItem().buffer.cachedText;
    var html = marked(md);
    callback(html);
  }
  else alert('Please select the editor containing the markdown you wish to convert.');
}

function getStyledHtml(input){
  return getStyles() + wrapHtml(input);
}

function wrapHtml(input){
  return '' + input + '';
}

function getStyles(){
  var user_styles = atom.styles.getStyleElements();
  user_styles = user_styles[user_styles.length - 1].innerHTML;

  var mdpreview = atom.packages.activePackages["markdown-preview"];

  var styles = {
    previewStyles: mdpreview.stylesheets[0][1],
    syntaxStyles: atom.themes.getActiveThemes()[0].stylesheets[0][1],
    userStyles: user_styles
  };
  var styleString = '';
  for(var s in styles){
    styleString += '';
  }
  return styleString;
}

function makePdf(inputHtml, outputPath){
  pdf.create(inputHtml, getConfig(), function(err, buffer) {
    if (err) {
      alert("Error converting to image format. Check console for more information.");
      console.log(err + "\nIf the error is a timeout, try running the command again.");
    }
    fs.writeFile(outputPath, buffer);
    console.log("Converted successfully. Output in " + outputPath);
  });
}

function getConfig(){
  //get current configuration settings for this package
  return atom.config.getSettings()['markdown-pdf'];
}

function convertImgSrcToURI(html){
  //input: full html document
  //output: same html document with all "img src" in URI form (i.e. "file://")
  var div = document.createElement('div');
  div.innerHTML = html;
  var imgs = div.getElementsByTagName('img');
  for(var s = 0; s < imgs.length; s++){
    imgs[s].src = processSrc(imgs[s].attributes.src.value);
  }
  return div.innerHTML;
}

function processSrc(src){
  //make a local img src path into a "file:///absolute/path/" if it isn't already

  if(url.parse(src).protocol){
    //if the src already starts with "file://", "https://", etc., it's already in the right form (URI)
    return src;
  }
  else if(path.resolve(src) !== src){
    //if path is not absolute and has no protocol, it should be relative, so make it a URI
    src = path.resolve(path.dirname(outPath), src);
    return 'file:///' + src;
  }
  else{
    //otherwise, the src is an absolute path. In this case we can just prepend "file://" to it
    return 'file:///' + src;
  }
}

function getOutputPath(){
  var markdownPath = atom.workspaceView.getActivePaneItem().getPath();
  if (!isMd(markdownPath)){
    //file is saved as something other than markdown-type file
    throw 'Please save the file as a markdown (.markdown, .md, .mkd, .mkdown or .ron)';
  }
  else if (!markdownPath){
    //file is unsaved

    //get directory selected in tree-view
    var treeDirPath = atom.packages.getActivePackage('tree-view').mainModule.treeView.selectedPath;
    if(!fs.lstatSync(treeDirPath).isDirectory()){
      treeDirPath = path.dirname(treeDirPath);
    }

    var d = new Date();
    var outName = d.getTime() + '.' + atom.config.get('markdown-pdf.type');
    var outputPath = path.join(treeDirPath, outName);
    return outputPath;
  }
  else{
    //file is saved as markdown type
    var out = markdownPath.replace(getExtension(markdownPath), atom.config.get('markdown-pdf.type'));
    return out;
  }
}

function editorSelected(){
  if(atom.workspace.getActiveTextEditor() !== undefined) return true;
  return false;
}

function isMd(path){
  //if file is unsaved, return true and convert it anyway (see issue #13 in repo)
  if(!path) return true;

  //check extension of current file if it's saved
  var accepted = ["markdown", "md", "mkd", "mkdown", "ron"];
  var current = path.split(".").pop();
  if(accepted.indexOf(current) != -1) return true;
  return false;
}

function getExtension(path){
  var ex = path.split('.').pop();
  return ex;
}
<commit_msg>Fix configs.<commit_after>var fs = require("fs");
var marked = require('marked');
var pdf = require('html-pdf');
var path = require('path');
var url = require('url');
var mdpreview = atom.packages.activePackages["markdown-preview"];

module.exports = {

  config: {
    "rendering":{
      "title": "Markdown Rendering",
      "description": "Render the markdown to html with Marked or Markdown-Preview.",
      "type": "string",
      "default": "Marked",
      "enum": ["Marked", "Markdown-Preview"]
    },
    "format": {
      "title": "Page Format",
      "type": "string",
      "default": "A4",
      "enum": ["A3", "A4", "A5", "Legal", "Letter", "Tabloid"]
    },
    "orientation": {
      "title": "Page Orientation",
      "type": "string",
      "default": "portrait",
      "enum": ["portrait", "landscape"]
    },
    "timeout": {
      "title": "Timeout",
      "description": "Time (ms) before PhantomJS gives up on rendering. You can set a larger value for very big files.",
      "type": "integer",
      "default": 10000
    },
    "border": {
      "title": "Border Size",
      "type": "string",
      "default": "10mm"
    },
    "type": {
      "title": "Exported Filetype",
      "type": "string",
      "default": "pdf",
      "enum": ["pdf", "png", "jpeg"]
    },
    "quality": {
      "title": "Image Quality",
      "description": "Only used for .png and .jpeg formats.",
      "type": "integer",
      "minimum": 1,
      "maximum": 100,
      "default": 100
    }
  },

  activate: function() {
    atom.workspaceView.command('markdown-pdf:convert', this.convert);
  },

  convert: function() {
    try{
      outPath = getOutputPath();
      getHtml(function(html){
        html = convertImgSrcToURI(html);
        html = getStyledHtml(html);
        makePdf(html, outPath);
      });
    }
    catch(err){
      console.log(err);
      return;
    }
  }
}

function getHtml(callback){
  var rend = atom.config.get('markdown-pdf.rendering');
  console.log(rend);
  if(rend == "Marked"){
    htmlFromMd(callback);
  }
  else{
    htmlFromPreview(callback);
  }
}

function htmlFromPreview(callback){
  //render markdown using Atom's Markdown-Preview package
  var cb = atom.clipboard;
  if(editorSelected()){
    //save old clipboard contents
    var old = cb.read();
    //get html on clipboard
    mdpreview.mainModule.copyHtml();

    var html = cb.read();
    //put old clipboard contents back
    cb.write(old);

    callback(html);
  }
  else alert('Please select the editor containing the markdown you wish to convert.');
}

function htmlFromMd(callback){

  //explicitly setting default options below, in case we want to further configure the renderer
  marked.setOptions({
    renderer: new marked.Renderer(),
    gfm: true,
    tables: true,
    breaks: false,
    pedantic: false,
    sanitize: true,
    smartLists: true,
    smartypants: false
  });
  if(editorSelected()){
    var md = atom.workspace.getActivePaneItem().buffer.cachedText;
    var html = marked(md);
    callback(html);
  }
  else alert('Please select the editor containing the markdown you wish to convert.');
}

function getStyledHtml(input){
  return getStyles() + wrapHtml(input);
}

function wrapHtml(input){
  return '' + input + '';
}

function getStyles(){
  var user_styles = atom.styles.getStyleElements();
  user_styles = user_styles[user_styles.length - 1].innerHTML;

  var mdpreview = atom.packages.activePackages["markdown-preview"];

  var styles = {
    previewStyles: mdpreview.stylesheets[0][1],
    syntaxStyles: atom.themes.getActiveThemes()[0].stylesheets[0][1],
    userStyles: user_styles
  };
  var styleString = '';
  for(var s in styles){
    styleString += '';
  }
  return styleString;
}

function makePdf(inputHtml, outputPath){
  pdf.create(inputHtml, getConfig(), function(err, buffer) {
    if (err) {
      alert("Error converting to image format. Check console for more information.");
      console.log(err + "\nIf the error is a timeout, try running the command again.");
    }
    fs.writeFile(outputPath, buffer);
    console.log("Converted successfully. Output in " + outputPath);
  });
}

function getConfig(){
  //get current configuration settings for this package
  return atom.config.get('markdown-pdf');
}

function convertImgSrcToURI(html){
  //input: full html document
  //output: same html document with all "img src" in URI form (i.e. "file://")
  var div = document.createElement('div');
  div.innerHTML = html;
  var imgs = div.getElementsByTagName('img');
  for(var s = 0; s < imgs.length; s++){
    imgs[s].src = processSrc(imgs[s].attributes.src.value);
  }
  return div.innerHTML;
}

function processSrc(src){
  //make a local img src path into a "file:///absolute/path/" if it isn't already

  if(url.parse(src).protocol){
    //if the src already starts with "file://", "https://", etc., it's already in the right form (URI)
    return src;
  }
  else if(path.resolve(src) !== src){
    //if path is not absolute and has no protocol, it should be relative, so make it a URI
    src = path.resolve(path.dirname(outPath), src);
    return 'file:///' + src;
  }
  else{
    //otherwise, the src is an absolute path. In this case we can just prepend "file://" to it
    return 'file:///' + src;
  }
}

function getOutputPath(){
  var markdownPath = atom.workspaceView.getActivePaneItem().getPath();
  if (!isMd(markdownPath)){
    //file is saved as something other than markdown-type file
    throw 'Please save the file as a markdown (.markdown, .md, .mkd, .mkdown or .ron)';
  }
  else if (!markdownPath){
    //file is unsaved

    //get directory selected in tree-view
    var treeDirPath = atom.packages.getActivePackage('tree-view').mainModule.treeView.selectedPath;
    if(!fs.lstatSync(treeDirPath).isDirectory()){
      treeDirPath = path.dirname(treeDirPath);
    }

    var d = new Date();
    var outName = d.getTime() + '.' + atom.config.get('markdown-pdf.type');
    var outputPath = path.join(treeDirPath, outName);
    return outputPath;
  }
  else{
    //file is saved as markdown type
    var out = markdownPath.replace(getExtension(markdownPath), atom.config.get('markdown-pdf.type'));
    return out;
  }
}

function editorSelected(){
  if(atom.workspace.getActiveTextEditor() !== undefined) return true;
  return false;
}

function isMd(path){
  //if file is unsaved, return true and convert it anyway (see issue #13 in repo)
  if(!path) return true;

  //check extension of current file if it's saved
  var accepted = ["markdown", "md", "mkd", "mkdown", "ron"];
  var current = path.split(".").pop();
  if(accepted.indexOf(current) != -1) return true;
  return false;
}

function getExtension(path){
  var ex = path.split('.').pop();
  return ex;
}
<|endoftext|>


<commit_before>var fs = require("fs");
var pdf = require('html-pdf');
var path = require('path');
var url = require('url');
var less = require('less');
var mdpreview = null;

module.exports = {

  config: {
    "format": {
      "title": "Page Format",
      "type": "string",
      "default": "A4",
      "enum": ["A3", "A4", "A5", "Legal", "Letter", "Tabloid"]
    },
    "orientation": {
      "title": "Page Orientation",
      "type": "string",
      "default": "portrait",
      "enum": ["portrait", "landscape"]
    },
    "timeout": {
      "title": "Timeout",
      "description": "Time (ms) before PhantomJS gives up on rendering. You can set a larger value for very big files.",
      "type": "integer",
      "default": 10000
    },
    "border": {
      "title": "Border Size",
      "type": "string",
      "default": "10mm"
    },
    "type": {
      "title": "Exported Filetype",
      "type": "string",
      "default": "pdf",
      "enum": ["pdf", "png", "jpeg"]
    },
    "quality": {
      "title": "Image Quality",
      "description": "Only used for .png and .jpeg formats.",
      "type": "integer",
      "minimum": 1,
      "maximum": 100,
      "default": 100
    }
  },

  activate: function() {
    atom.commands.add('atom-workspace', 'markdown-pdf:convert', this.convert);
  },

  convert: function() {
    try{
      mdpreview = atom.packages.getActivePackage('markdown-preview');

      if(!mdpreview) {
        // Use markdown-preview-plus if markdown-preview has been disabled
        mdpreview = atom.packages.getActivePackage('markdown-preview-plus');
      }

      outPath = getOutputPath();
      html = converter.htmlFromPreview()
        .convertImgSrcToURI()
        .styleHtml()
        .uglyFix();
      makePdf(html, outPath);
    }
    catch(err){
      atom.notifications.addError('markdown-pdf: ' + err, {dismissable: true});
      console.log(err.stack);
      return;
    }
  }
}

var converter = {

  dataString: '',

  htmlFromPreview: function () {
    // we use the clipboard to grab HTML from a preview package (maybe there's a better way?)
    var cb = atom.clipboard;
    var old = cb.read();  //save old clipboard contents

    if(mdpreview.name === 'markdown-preview'){
      mdpreview.mainModule.copyHtml();  //get html on clipboard
    }
    else if (mdpreview.name === 'markdown-preview-plus') {
      mdpreview.mainModule.copyHtml(callback, 200); // copy parsed markdown with maths scaled 200%
    }

    this.dataString = cb.read();
    cb.write(old);  //put old clipboard contents back

    return this;
  },

  convertImgSrcToURI: function () {
    // puts all "img src" in URI form (i.e. "file://")
    var div = document.createElement('div');
    div.innerHTML = this.dataString;
    var imgs = div.getElementsByTagName('img');
    for(var s = 0; s < imgs.length; s++){
      imgs[s].src = processSrc(imgs[s].attributes.src.value);
    }
    this.dataString = div.innerHTML;
    return this;
  },

  styleHtml: function () {
    var wrappedHtml = '' + this.dataString + '';

    var styles = {};
    styles.gfmstyles = fs.readFileSync(__dirname + '/github-markdown.css', 'utf-8');
    styles.syntaxStyles = atom.themes.getActiveThemes()[0].stylesheets[0][1];

    if(fs.existsSync(atom.styles.getUserStyleSheetPath(), 'utf8')){
      //add user stylesheet to html if it exists
      var upath = atom.styles.getUserStyleSheetPath();
      var ustyle = fs.readFileSync(upath, 'utf8');

      // compiling less to css if file extension is .less
      if (upath.substr(upath.length - 5).toLowerCase() == '.less') {
        less.render(ustyle, function(e, output) {
          styles.userStyles = output;
        });
      }
      else {
        styles.userStyles = ustyle;
      }
    }

    var styleString = '';
    for(var s in styles){
      styleString += '';
    }
    this.dataString = styleString + wrappedHtml;
    return this;
  },

  uglyFix: function () {
    html = this.dataString;
    // Ugly Fix 1:
    base64hr = 'iVBORw0KGgoAAAANSUhEUgAAAAYAAAAECAYAAACtBE5DAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OENDRjNBN0E2NTZBMTFFMEI3QjRBODM4NzJDMjlGNDgiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OENDRjNBN0I2NTZBMTFFMEI3QjRBODM4NzJDMjlGNDgiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo4Q0NGM0E3ODY1NkExMUUwQjdCNEE4Mzg3MkMyOUY0OCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo4Q0NGM0E3OTY1NkExMUUwQjdCNEE4Mzg3MkMyOUY0OCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PqqezsUAAAAfSURBVHjaYmRABcYwBiM2QSA4y4hNEKYDQxAEAAIMAHNGAzhkPOlYAAAAAElFTkSuQmCC';
    hrURI = '"atom://' + mdpreview.name + '/assets/hr.png"'
    html = html.split(hrURI).join('"data:image/png;base64,' + base64hr + '"');
    // Ugly Fix 2:
    html = html.split(',\n:host {').join(' {');
    html = html.split(',\n:host').join(',');
    html = html.split(':host').join('');
    this.datastring = html;
    return this.dataString;
  }
}

function makePdf(inputHtml, outputPath){
  atom.notifications.addInfo('Converting markdown to PDF...', {icon: 'markdown'})
  var conf = atom.config.get('markdown-pdf');
  pdf.create(inputHtml, conf).toFile(outputPath, function(err, res) {
    if (err) {
      throw 'Error converting to image format. Check console for more information.';
    }else{
      atom.notifications.addSuccess('Converted successfully.', {detail: 'Output in ' + outputPath, icon: 'file-pdf'});
    }
  });
}

function processSrc(src){
  //make a local img src path into a "file:///absolute/path/" if it isn't already

  if(url.parse(src).protocol){
    //if the src already starts with "file://", "https://", etc., it's already in the right form (URI)
    return src;
  }
  else if(path.resolve(src) !== src){
    //if path is not absolute and has no protocol, it should be relative, so make it a URI
    src = path.resolve(path.dirname(outPath), src);
    return 'file:///' + src;
  }
  else{
    //otherwise, the src is an absolute path. In this case we can just prepend "file://" to it
    return 'file:///' + src;
  }
}

function getOutputPath(){
  var markdownPath = atom.workspace.getActivePaneItem().getPath();
  if (!isMd(markdownPath)){    //file is saved as something other than markdown-type file
    atom.notifications.addWarning(
      'Warning: File not saved as markdown type.',
      {
        detail: 'Attempting conversion of .{} file. \nValid extensions are `.markdown, .md, .mkd, .mkdown` and  `.ron`'.replace('{}', markdownPath.split('.').pop()),
        dismissable: true
      }
    );
  }
  else if (!markdownPath){      //file is unsaved
    atom.notifications.addWarning('Warning: File not saved!.', {detail: 'Attempting conversion anyway.'});
    var treeDirPath = atom.packages.getActivePackage('tree-view').mainModule.treeView.selectedPath;
    if(!fs.lstatSync(treeDirPath).isDirectory()){
      treeDirPath = path.dirname(treeDirPath);
    }

    var d = new Date();
    var outName = d.getTime() + '.' + atom.config.get('markdown-pdf.type');
    var outputPath = path.join(treeDirPath, outName);
    return outputPath;
  }
  else{
    var out = markdownPath.replace(markdownPath.split(path.sep).slice(-1)[0], markdownPath.split(path.sep).slice(-1)[0].replace(getExtension(markdownPath), atom.config.get('markdown-pdf.type')));
    return out;
  }
}

function isMd(path){
  if(!path) return true;
  var accepted = ["markdown", "md", "mkd", "mkdown", "ron"];
  var current = path.split(".").pop();
  if(accepted.indexOf(current) != -1) return true;
  return false;
}

function getExtension(path){
  var ex = path.split('.').pop();
  return ex;
}
<commit_msg>fixes #109<commit_after>var fs = require("fs");
var pdf = require('html-pdf');
var path = require('path');
var url = require('url');
var less = require('less');
var mdpreview = null;

module.exports = {

  config: {
    "format": {
      "title": "Page Format",
      "type": "string",
      "default": "A4",
      "enum": ["A3", "A4", "A5", "Legal", "Letter", "Tabloid"]
    },
    "orientation": {
      "title": "Page Orientation",
      "type": "string",
      "default": "portrait",
      "enum": ["portrait", "landscape"]
    },
    "timeout": {
      "title": "Timeout",
      "description": "Time (ms) before PhantomJS gives up on rendering. You can set a larger value for very big files.",
      "type": "integer",
      "default": 10000
    },
    "border": {
      "title": "Border Size",
      "type": "string",
      "default": "10mm"
    },
    "type": {
      "title": "Exported Filetype",
      "type": "string",
      "default": "pdf",
      "enum": ["pdf", "png", "jpeg"]
    },
    "quality": {
      "title": "Image Quality",
      "description": "Only used for .png and .jpeg formats.",
      "type": "integer",
      "minimum": 1,
      "maximum": 100,
      "default": 100
    }
  },

  activate: function() {
    atom.commands.add('atom-workspace', 'markdown-pdf:convert', this.convert);
  },

  convert: function() {
    try{
      mdpreview = atom.packages.getActivePackage('markdown-preview');

      if(!mdpreview) {
        // Use markdown-preview-plus if markdown-preview has been disabled
        mdpreview = atom.packages.getActivePackage('markdown-preview-plus');
      }

      outPath = getOutputPath();
      html = converter.htmlFromPreview()
        .convertImgSrcToURI()
        .styleHtml()
        .uglyFix();
      makePdf(html, outPath);
    }
    catch(err){
      atom.notifications.addError('markdown-pdf: ' + err, {dismissable: true});
      console.log(err.stack);
      return;
    }
  }
}

var converter = {

  dataString: '',

  htmlFromPreview: function () {
    // we use the clipboard to grab HTML from a preview package (maybe there's a better way?)
    var cb = atom.clipboard;
    var old = cb.read();  //save old clipboard contents

    if(mdpreview.name === 'markdown-preview'){
      mdpreview.mainModule.copyHtml();  //get html on clipboard
    }
    else if (mdpreview.name === 'markdown-preview-plus') {
      mdpreview.mainModule.copyHtml(callback, 200); // copy parsed markdown with maths scaled 200%
    }

    this.dataString = cb.read();
    cb.write(old);  //put old clipboard contents back

    return this;
  },

  convertImgSrcToURI: function () {
    // puts all "img src" in URI form (i.e. "file://")
    var div = document.createElement('div');
    div.innerHTML = this.dataString;
    var imgs = div.getElementsByTagName('img');
    for(var s = 0; s < imgs.length; s++){
      imgs[s].src = processSrc(imgs[s].attributes.src.value);
    }
    this.dataString = div.innerHTML;
    return this;
  },

  styleHtml: function () {
    var wrappedHtml = '' + this.dataString + '';

    var styles = {};
    styles.gfmstyles = fs.readFileSync(__dirname + '/github-markdown.css', 'utf-8');
    styles.syntaxStyles = atom.themes.getActiveThemes()[0].stylesheets[0][1];

    if(fs.existsSync(atom.styles.getUserStyleSheetPath(), 'utf8')){
      //add user stylesheet to html if it exists
      var upath = atom.styles.getUserStyleSheetPath();
      var ustyle = fs.readFileSync(upath, 'utf8');

      // compiling less to css if file extension is .less
      if (upath.substr(upath.length - 5).toLowerCase() == '.less') {
        less.render(ustyle, function(e, output) {
          styles.userStyles = output;
        });
      }
      else {
        styles.userStyles = ustyle;
      }
    }

    var styleString = '';
    for(var s in styles){
      styleString += '';
    }
    this.dataString = styleString + wrappedHtml;
    return this;
  },

  uglyFix: function () {
    html = this.dataString;
    // Ugly Fix 1:
    base64hr = 'iVBORw0KGgoAAAANSUhEUgAAAAYAAAAECAYAAACtBE5DAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OENDRjNBN0E2NTZBMTFFMEI3QjRBODM4NzJDMjlGNDgiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OENDRjNBN0I2NTZBMTFFMEI3QjRBODM4NzJDMjlGNDgiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo4Q0NGM0E3ODY1NkExMUUwQjdCNEE4Mzg3MkMyOUY0OCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo4Q0NGM0E3OTY1NkExMUUwQjdCNEE4Mzg3MkMyOUY0OCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PqqezsUAAAAfSURBVHjaYmRABcYwBiM2QSA4y4hNEKYDQxAEAAIMAHNGAzhkPOlYAAAAAElFTkSuQmCC';
    hrURI = '"atom://' + mdpreview.name + '/assets/hr.png"'
    html = html.split(hrURI).join('"data:image/png;base64,' + base64hr + '"');
    // Ugly Fix 2:
    html = html.split(',\n:host {').join(' {');
    html = html.split(',\n:host').join(',');
    html = html.split(':host').join('');
    this.datastring = html;
    return this.dataString;
  }
}

function makePdf(inputHtml, outputPath){
  atom.notifications.addInfo('Converting markdown to PDF...', {icon: 'markdown'})
  var conf = atom.config.get('markdown-pdf');
  pdf.create(inputHtml, conf).toFile(outputPath, function(err, res) {
    if (err) {
      throw 'Error converting to image format. Check console for more information.';
    }else{
      atom.notifications.addSuccess('Converted successfully.', {detail: 'Output in ' + outputPath, icon: 'file-pdf'});
    }
  });
}

function processSrc(src){
  //make a local img src path into a "file:///absolute/path/" if it isn't already
  var protocol = url.parse(src).protocol;
  if(protocol == 'http:' || protocol == 'https:' || protocol == 'file:'){
    //if the src already starts with "file://", "https://", etc., it's already in the right form (URI)
    return src;
  }
  else if(path.resolve(src) !== src){
    //if path is not absolute and has no protocol, it should be relative, so make it a URI
    //NOTE: previewer already took care of relative paths
    src = path.resolve(path.dirname(outPath), src);
    return 'file:///' + src;
  }
  else{
    //otherwise, the src is an absolute path. In this case we can just prepend "file://" to it
    return 'file:///' + src;
  }
}

function getOutputPath(){
  var markdownPath = atom.workspace.getActivePaneItem().getPath();
  if (!isMd(markdownPath)){    //file is saved as something other than markdown-type file
    atom.notifications.addWarning(
      'Warning: File not saved as markdown type.',
      {
        detail: 'Attempting conversion of .{} file. \nValid extensions are `.markdown, .md, .mkd, .mkdown` and  `.ron`'.replace('{}', markdownPath.split('.').pop()),
        dismissable: true
      }
    );
  }
  else if (!markdownPath){      //file is unsaved
    atom.notifications.addWarning('Warning: File not saved!.', {detail: 'Attempting conversion anyway.'});
    var treeDirPath = atom.packages.getActivePackage('tree-view').mainModule.treeView.selectedPath;
    if(!fs.lstatSync(treeDirPath).isDirectory()){
      treeDirPath = path.dirname(treeDirPath);
    }

    var d = new Date();
    var outName = d.getTime() + '.' + atom.config.get('markdown-pdf.type');
    var outputPath = path.join(treeDirPath, outName);
    return outputPath;
  }
  else{
    var out = markdownPath.replace(markdownPath.split(path.sep).slice(-1)[0], markdownPath.split(path.sep).slice(-1)[0].replace(getExtension(markdownPath), atom.config.get('markdown-pdf.type')));
    return out;
  }
}

function isMd(path){
  if(!path) return true;
  var accepted = ["markdown", "md", "mkd", "mkdown", "ron"];
  var current = path.split(".").pop();
  if(accepted.indexOf(current) != -1) return true;
  return false;
}

function getExtension(path){
  var ex = path.split('.').pop();
  return ex;
}
<|endoftext|>


<commit_before>/* jshint node:true */

'use strict';
var fs = require('fs');
var path = require('path');
var JSZip = require('jszip');
var walk = require('fs-walk');
var exec = require('child_process').exec;
var Promise = require('rsvp').Promise;
var isBinaryFile = require('isbinaryfile');
var extend = require('util-extend');

var defaults = {
  artifactId: '{{name}}',
  buildDir: 'dist',
  fileName: '{{name}}',
  type: 'zip',
  fileEncoding: 'utf-8'
};

function getProjectFile() {
  return JSON.parse(fs.readFileSync('./package.json', { encoding: defaults.fileEncoding }));;
}

function filterOptions(options) {
  var opts = options;
  var project = getProjectFile();
  var test = /{{([^}]+)}}/g;

  function replaceOption(match, key) {
    if(project[key] === undefined) {
      return match;
    }

    return project[key];
  }

  Object.keys(opts).forEach(function(key) {
    var val = opts[key];
    if(typeof val !== 'string') {
      return;
    }

    opts[key] = val.replace(test, replaceOption);
  });

  return opts;
}

function configure(options) {
  var config = extend(defaults, options);
  var filteredOptions = filterOptions(config);
  return filteredOptions;
}

function execCommand(command) {
  console.log('Executing command:', command);

  return new Promise(function(resolve, reject) {
    exec(command, function(err, stdout) {
      if(err) {
        reject(err);
      }
      resolve(stdout);
    });
  });
}

function buildMavenArgs(options, repositoryId, isSnapshot) {
  var mavenArgs = {
    packaging: options.type,
    groupId: options.groupId,
    artifactId: options.artifactId,
    version: options.version
  };

  if(repositoryId) {
    var repositories = options.repositories;
    var repoCount = repositories.length;

    for(var i = 0; i < repoCount; i++) {
      var currentRepo = repositories[i];

      if(currentRepo.id !== repositoryId) {
        continue;
      }

      mavenArgs.repositoryId = currentRepo.id;
      mavenArgs.url = currentRepo.url;
    }
  }

  if(options.postfix.length) {
    mavenArgs.artifactId = mavenArgs.artifactId + '_' + options.postfix;
  }

  if(isSnapshot) {
    mavenArgs.version = mavenArgs.version + '-SNAPSHOT';
  }

  mavenArgs.file = destPath(options, isSnapshot);

  return Object.keys(mavenArgs).reduce(function(arr, key) {
    return arr.concat('-D' + key + '=' + mavenArgs[key]);
  }, []);
}

function mavenExec(args, options, repositoryId, isSnapshot) {
  var cmdArgs = args.concat(buildMavenArgs(options, repositoryId, isSnapshot)).join(' ');
  return execCommand('mvn -B ' + cmdArgs);
}

function destPath(options, isSnapshot) {
  var fileName = '';
  var file = [options.fileName];

  if(options.postfix.length) {
    file.push('_' + options.postfix);
  }

  file.push('-' + options.version);

  if(isSnapshot) {
    file.push('-SNAPSHOT');
  }

  file.push('.' + options.type);

  fileName = file.join('');

  return path.join(options.buildDir, fileName);
}

function buildDeploymentPackage(options, isSnapshot) {
  var config = options;
  var zip = new JSZip();

  walk.walkSync(config.buildDir, function(base, file, stat) {
    if(stat.isDirectory() || file.indexOf(config.fileName) === 0) {
      return;
    }

    var filePath = path.join(base, file);
    var data;

    if(isBinaryFile(filePath)) {
      data = fs.readFileSync(filePath);
    } else {
      data = fs.readFileSync(filePath, { encoding: config.fileEncoding });
    }

    zip.file(path.relative(config.buildDir, filePath), data);
  });

  var zipBuffer = zip.generate({ type: 'nodebuffer', compression: 'DEFLATE'});
  var zipPath = destPath(config, isSnapshot);

  fs.writeFileSync(zipPath, zipBuffer);
}

function deploy(repositoryId, isSnapshot, options) {
  var config = configure(options);

  if(!config.repositories.length) {
    throw new Error('You need to have at least one maven repository configured');
  }

  buildDeploymentPackage(config, isSnapshot);

  return mavenExec(['deploy:deploy-file'], config, repositoryId, isSnapshot);
}

module.exports = {
  package: function(options, isSnapshot) {
    buildDeploymentPackage(options, isSnapshot);
  },
  deploy: function(repositoryId, isSnapshot, options) {
    deploy(repositoryId, isSnapshot, options);
  }
};
<commit_msg>Log the current file<commit_after>/* jshint node:true */

'use strict';
var fs = require('fs');
var path = require('path');
var JSZip = require('jszip');
var walk = require('fs-walk');
var exec = require('child_process').exec;
var Promise = require('rsvp').Promise;
var isBinaryFile = require('isbinaryfile');
var extend = require('util-extend');

var defaults = {
  artifactId: '{{name}}',
  buildDir: 'dist',
  fileName: '{{name}}',
  type: 'zip',
  fileEncoding: 'utf-8'
};

function getProjectFile() {
  return JSON.parse(fs.readFileSync('./package.json', { encoding: defaults.fileEncoding }));;
}

function filterOptions(options) {
  var opts = options;
  var project = getProjectFile();
  var test = /{{([^}]+)}}/g;

  function replaceOption(match, key) {
    if(project[key] === undefined) {
      return match;
    }

    return project[key];
  }

  Object.keys(opts).forEach(function(key) {
    var val = opts[key];
    if(typeof val !== 'string') {
      return;
    }

    opts[key] = val.replace(test, replaceOption);
  });

  return opts;
}

function configure(options) {
  var config = extend(defaults, options);
  var filteredOptions = filterOptions(config);
  return filteredOptions;
}

function execCommand(command) {
  console.log('Executing command:', command);

  return new Promise(function(resolve, reject) {
    exec(command, function(err, stdout) {
      if(err) {
        reject(err);
      }
      resolve(stdout);
    });
  });
}

function buildMavenArgs(options, repositoryId, isSnapshot) {
  var mavenArgs = {
    packaging: options.type,
    groupId: options.groupId,
    artifactId: options.artifactId,
    version: options.version
  };

  if(repositoryId) {
    var repositories = options.repositories;
    var repoCount = repositories.length;

    for(var i = 0; i < repoCount; i++) {
      var currentRepo = repositories[i];

      if(currentRepo.id !== repositoryId) {
        continue;
      }

      mavenArgs.repositoryId = currentRepo.id;
      mavenArgs.url = currentRepo.url;
    }
  }

  if(options.postfix.length) {
    mavenArgs.artifactId = mavenArgs.artifactId + '_' + options.postfix;
  }

  if(isSnapshot) {
    mavenArgs.version = mavenArgs.version + '-SNAPSHOT';
  }

  mavenArgs.file = destPath(options, isSnapshot);

  return Object.keys(mavenArgs).reduce(function(arr, key) {
    return arr.concat('-D' + key + '=' + mavenArgs[key]);
  }, []);
}

function mavenExec(args, options, repositoryId, isSnapshot) {
  var cmdArgs = args.concat(buildMavenArgs(options, repositoryId, isSnapshot)).join(' ');
  return execCommand('mvn -B ' + cmdArgs);
}

function destPath(options, isSnapshot) {
  var fileName = '';
  var file = [options.fileName];

  if(options.postfix.length) {
    file.push('_' + options.postfix);
  }

  file.push('-' + options.version);

  if(isSnapshot) {
    file.push('-SNAPSHOT');
  }

  file.push('.' + options.type);

  fileName = file.join('');

  return path.join(options.buildDir, fileName);
}

function buildDeploymentPackage(options, isSnapshot) {
  var config = options;
  var zip = new JSZip();

  walk.walkSync(config.buildDir, function(base, file, stat) {
    console.log(file);
    if(stat.isDirectory()) {
      return;
    }

    var filePath = path.join(base, file);
    var data;

    if(isBinaryFile(filePath)) {
      data = fs.readFileSync(filePath);
    } else {
      data = fs.readFileSync(filePath, { encoding: config.fileEncoding });
    }

    zip.file(path.relative(config.buildDir, filePath), data);
  });

  var zipBuffer = zip.generate({ type: 'nodebuffer', compression: 'DEFLATE'});
  var zipPath = destPath(config, isSnapshot);

  fs.writeFileSync(zipPath, zipBuffer);
}

function deploy(repositoryId, isSnapshot, options) {
  var config = configure(options);

  if(!config.repositories.length) {
    throw new Error('You need to have at least one maven repository configured');
  }

  buildDeploymentPackage(config, isSnapshot);

  return mavenExec(['deploy:deploy-file'], config, repositoryId, isSnapshot);
}

module.exports = {
  package: function(options, isSnapshot) {
    buildDeploymentPackage(options, isSnapshot);
  },
  deploy: function(repositoryId, isSnapshot, options) {
    deploy(repositoryId, isSnapshot, options);
  }
};
<|endoftext|>