<commit_before>'use strict';

const http = require('http');
const path = require('path');
const fse = require('fs-extra');
const Koa = require('koa');
const Router = require('koa-router');
const _ = require('lodash');
const chalk = require('chalk');
const CLITable = require('cli-table3');
const { logger, models, getAbsoluteAdminUrl, getAbsoluteServerUrl } = require('strapi-utils');
const { createDatabaseManager } = require('strapi-database');
const loadConfiguration = require('./core/app-configuration');

const utils = require('./utils');
const loadModules = require('./core/load-modules');
const bootstrap = require('./core/bootstrap');
const initializeMiddlewares = require('./middlewares');
const initializeHooks = require('./hooks');
const createStrapiFs = require('./core/fs');
const createEventHub = require('./services/event-hub');
const createWebhookRunner = require('./services/webhook-runner');
const { webhookModel, createWebhookStore } = require('./services/webhook-store');
const { createCoreStore, coreStoreModel } = require('./services/core-store');
const createEntityService = require('./services/entity-service');
const entityValidator = require('./services/entity-validator');
const createTelemetry = require('./services/metrics');
const createUpdateNotifier = require('./utils/update-notifier');
const ee = require('./utils/ee');

const LIFECYCLES = {
  BOOTSTRAP: 'bootstrap',
  REGISTER: 'register',
};

/**
 * Construct an Strapi instance.
 *
 * @constructor
 */
class Strapi {
  constructor(opts = {}) {
    this.reload = this.reload();

    // Expose `koa`.
    this.app = new Koa();
    this.router = new Router();

    this.initServer();

    // Logger.
    this.log = logger;

    // Utils.
    this.utils = {
      models,
    };

    this.dir = opts.dir || process.cwd();

    this.admin = {};
    this.plugins = {};
    this.config = loadConfiguration(this.dir, opts);
    this.app.proxy = this.config.get('server.proxy');

    this.isLoaded = false;

    // internal services.
    this.fs = createStrapiFs(this);
    this.eventHub = createEventHub();

    this.requireProjectBootstrap();

    createUpdateNotifier(this).notify();
  }

  get EE() {
    return ee({ dir: this.dir, logger });
  }

  handleRequest(req, res) {
    if (!this.requestHandler) {
      this.requestHandler = this.app.callback();
    }

    return this.requestHandler(req, res);
  }

  requireProjectBootstrap() {
    const bootstrapPath = path.resolve(this.dir, 'config/functions/bootstrap.js');

    if (fse.existsSync(bootstrapPath)) {
      require(bootstrapPath);
    }
  }

  logStats() {
    const columns = Math.min(process.stderr.columns, 80) - 2;
    console.log();
    console.log(chalk.black.bgWhite(_.padEnd(' Project information', columns)));
    console.log();

    const infoTable = new CLITable({
      colWidths: [20, 50],
      chars: { mid: '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' },
    });

    const isEE = strapi.EE === true && ee.isEE === true;

    infoTable.push(
      [chalk.blue('Time'), `${new Date()}`],
      [chalk.blue('Launched in'), Date.now() - this.config.launchedAt + ' ms'],
      [chalk.blue('Environment'), this.config.environment],
      [chalk.blue('Process PID'), process.pid],
      [chalk.blue('Version'), `${this.config.info.strapi} (node ${process.version})`],
      [chalk.blue('Edition'), isEE ? 'Enterprise' : 'Community']
    );

    console.log(infoTable.toString());
    console.log();
    console.log(chalk.black.bgWhite(_.padEnd(' Actions available', columns)));
    console.log();
  }

  logFirstStartupMessage() {
    this.logStats();

    console.log(chalk.bold('One more thing...'));
    console.log(
      chalk.grey('Create your first administrator 💻 by going to the administration panel at:')
    );
    console.log();

    const addressTable = new CLITable();

    const adminUrl = getAbsoluteAdminUrl(strapi.config);
    addressTable.push([chalk.bold(adminUrl)]);

    console.log(`${addressTable.toString()}`);
    console.log();
  }

  logStartupMessage() {
    this.logStats();

    console.log(chalk.bold('Welcome back!'));

    if (this.config.serveAdminPanel === true) {
      console.log(chalk.grey('To manage your project 🚀, go to the administration panel at:'));
      const adminUrl = getAbsoluteAdminUrl(strapi.config);
      console.log(chalk.bold(adminUrl));
      console.log();
    }

    console.log(chalk.grey('To access the server ⚡, go to:'));
    const serverUrl = getAbsoluteServerUrl(strapi.config);
    console.log(chalk.bold(serverUrl));
    console.log();
  }

  initServer() {
    this.server = http.createServer(this.handleRequest.bind(this));
    // handle port in use cleanly
    this.server.on('error', err => {
      if (err.code === 'EADDRINUSE') {
        return this.stopWithError(`The port ${err.port} is already used by another application.`);
      }

      this.log.error(err);
    });

    // Close current connections to fully destroy the server
    const connections = {};

    this.server.on('connection', conn => {
      const key = conn.remoteAddress + ':' + conn.remotePort;
      connections[key] = conn;

      conn.on('close', function() {
        delete connections[key];
      });
    });

    this.server.destroy = cb => {
      this.server.close(cb);

      for (let key in connections) {
        connections[key].destroy();
      }
    };
  }

  async start(cb) {
    try {
      if (!this.isLoaded) {
        await this.load();
      }

      this.app.use(this.router.routes()).use(this.router.allowedMethods());

      // Launch server.
      this.listen(cb);
    } catch (err) {
      this.stopWithError(err);
    }
  }

  async destroy() {
    if (_.has(this, 'server.destroy')) {
      this.server.destroy();
    }

    await Promise.all(
      Object.values(this.plugins).map(plugin => {
        if (_.has(plugin, 'destroy') && typeof plugin.destroy === 'function') {
          return plugin.destroy();
        }
      })
    );

    if (_.has(this, 'admin')) {
      await this.admin.destroy();
    }

    this.eventHub.removeAllListeners();

    if (_.has(this, 'db')) {
      await this.db.destroy();
    }

    delete global.strapi;
  }

  /**
   * Add behaviors to the server
   */
  async listen(cb) {
    const onListen = async err => {
      if (err) return this.stopWithError(err);

      // Is the project initialised?
      const isInitialised = await utils.isInitialised(this);

      // Should the startup message be displayed?
      const hideStartupMessage = process.env.STRAPI_HIDE_STARTUP_MESSAGE
        ? process.env.STRAPI_HIDE_STARTUP_MESSAGE === 'true'
        : false;

      if (hideStartupMessage === false) {
        if (!isInitialised) {
          this.logFirstStartupMessage();
        } else {
          this.logStartupMessage();
        }
      }

      // Emit started event.
      const databaseClients = _.map(this.config.get('connections'), _.property('settings.client'));
      await this.telemetry.send('didStartServer', { database: databaseClients });

      if (cb && typeof cb === 'function') {
        cb();
      }

      if (
        (this.config.environment === 'development' &&
          this.config.get('server.admin.autoOpen', true) !== false) ||
        !isInitialised
      ) {
        await utils.openBrowser.call(this);
      }
    };

    const listenSocket = this.config.get('server.socket');
    const listenErrHandler = err => onListen(err).catch(err => this.stopWithError(err));

    if (listenSocket) {
      this.server.listen(listenSocket, listenErrHandler);
    } else {
      this.server.listen(
        this.config.get('server.port'),
        this.config.get('server.host'),
        listenErrHandler
      );
    }
  }

  stopWithError(err, customMessage) {
    this.log.debug(`⛔ Server wasn't able to start properly.`);
    if (customMessage) {
      this.log.error(customMessage);
    }
    this.log.error(err);
    return this.stop();
  }

  stop(exitCode = 1) {
    // Destroy server and available connections.
    if (_.has(this, 'server.destroy')) {
      this.server.destroy();
    }

    if (this.config.autoReload) {
      process.send('stop');
    }

    // Kill process.
    process.exit(exitCode);
  }

  async load() {
    this.app.use(async (ctx, next) => {
      if (ctx.request.url === '/_health' && ['HEAD', 'GET'].includes(ctx.request.method)) {
        ctx.set('strapi', 'You are so French!');
        ctx.status = 204;
      } else {
        await next();
      }
    });

    const modules = await loadModules(this);

    this.api = modules.api;
    this.admin = modules.admin;
    this.components = modules.components;
    this.plugins = modules.plugins;
    this.middleware = modules.middlewares;
    this.hook = modules.hook;

    await bootstrap(this);

    // init webhook runner
    this.webhookRunner = createWebhookRunner({
      eventHub: this.eventHub,
      logger: this.log,
      configuration: this.config.get('server.webhooks', {}),
    });

    // Init core store
    this.models['core_store'] = coreStoreModel(this.config);
    this.models['strapi_webhooks'] = webhookModel(this.config);

    this.db = createDatabaseManager(this);

    await this.runLifecyclesFunctions(LIFECYCLES.REGISTER);
    await this.db.initialize();

    this.store = createCoreStore({
      environment: this.config.environment,
      db: this.db,
    });

    this.webhookStore = createWebhookStore({ db: this.db });

    await this.startWebhooks();

    this.entityValidator = entityValidator;

    this.entityService = createEntityService({
      db: this.db,
      eventHub: this.eventHub,
      entityValidator: this.entityValidator,
    });

    this.telemetry = createTelemetry(this);

    // Initialize hooks and middlewares.
    await initializeMiddlewares.call(this);
    await initializeHooks.call(this);

    await this.runLifecyclesFunctions(LIFECYCLES.BOOTSTRAP);
    await this.freeze();

    this.isLoaded = true;

    return this;
  }

  async startWebhooks() {
    const webhooks = await this.webhookStore.findWebhooks();
    webhooks.forEach(webhook => this.webhookRunner.add(webhook));
  }

  reload() {
    const state = {
      shouldReload: 0,
    };

    const reload = function() {
      if (state.shouldReload > 0) {
        // Reset the reloading state
        state.shouldReload -= 1;
        reload.isReloading = false;
        return;
      }

      if (this.config.autoReload) {
        this.server.close();
        process.send('reload');
      }
    };

    Object.defineProperty(reload, 'isWatching', {
      configurable: true,
      enumerable: true,
      set: value => {
        // Special state when the reloader is disabled temporarly (see GraphQL plugin example).
        if (state.isWatching === false && value === true) {
          state.shouldReload += 1;
        }
        state.isWatching = value;
      },
      get: () => {
        return state.isWatching;
      },
    });

    reload.isReloading = false;
    reload.isWatching = true;

    return reload;
  }

  async runLifecyclesFunctions(lifecycleName) {
    const execLifecycle = async fn => {
      if (!fn) {
        return;
      }

      return fn();
    };

    const configPath = `functions.${lifecycleName}`;

    // plugins
    await Promise.all(
      Object.keys(this.plugins).map(plugin => {
        const pluginFunc = _.get(this.plugins[plugin], `config.${configPath}`);

        return execLifecycle(pluginFunc).catch(err => {
          strapi.log.error(`${lifecycleName} function in plugin "${plugin}" failed`);
          strapi.log.error(err);
          strapi.stop();
        });
      })
    );

    // user
    await execLifecycle(_.get(this.config, configPath));

    // admin
    const adminFunc = _.get(this.admin.config, configPath);
    return execLifecycle(adminFunc).catch(err => {
      strapi.log.error(`${lifecycleName} function in admin failed`);
      strapi.log.error(err);
      strapi.stop();
    });
  }

  async freeze() {
    Object.freeze(this.config);
    Object.freeze(this.dir);
    Object.freeze(this.admin);
    Object.freeze(this.plugins);
    Object.freeze(this.api);
  }

  getModel(modelKey, plugin) {
    return this.db.getModel(modelKey, plugin);
  }

  /**
   * Binds queries with a specific model
   * @param {string} entity - entity name
   * @param {string} plugin - plugin name or null
   */
  query(entity, plugin) {
    return this.db.query(entity, plugin);
  }
}

module.exports = options => {
  const strapi = new Strapi(options);
  global.strapi = strapi;
  return strapi;
};
<commit_msg>order lifecycles to be accurate with order of execution<commit_after>'use strict';

const http = require('http');
const path = require('path');
const fse = require('fs-extra');
const Koa = require('koa');
const Router = require('koa-router');
const _ = require('lodash');
const chalk = require('chalk');
const CLITable = require('cli-table3');
const { logger, models, getAbsoluteAdminUrl, getAbsoluteServerUrl } = require('strapi-utils');
const { createDatabaseManager } = require('strapi-database');
const loadConfiguration = require('./core/app-configuration');

const utils = require('./utils');
const loadModules = require('./core/load-modules');
const bootstrap = require('./core/bootstrap');
const initializeMiddlewares = require('./middlewares');
const initializeHooks = require('./hooks');
const createStrapiFs = require('./core/fs');
const createEventHub = require('./services/event-hub');
const createWebhookRunner = require('./services/webhook-runner');
const { webhookModel, createWebhookStore } = require('./services/webhook-store');
const { createCoreStore, coreStoreModel } = require('./services/core-store');
const createEntityService = require('./services/entity-service');
const entityValidator = require('./services/entity-validator');
const createTelemetry = require('./services/metrics');
const createUpdateNotifier = require('./utils/update-notifier');
const ee = require('./utils/ee');

const LIFECYCLES = {
  REGISTER: 'register',
  BOOTSTRAP: 'bootstrap',
};

/**
 * Construct an Strapi instance.
 *
 * @constructor
 */
class Strapi {
  constructor(opts = {}) {
    this.reload = this.reload();

    // Expose `koa`.
    this.app = new Koa();
    this.router = new Router();

    this.initServer();

    // Logger.
    this.log = logger;

    // Utils.
    this.utils = {
      models,
    };

    this.dir = opts.dir || process.cwd();

    this.admin = {};
    this.plugins = {};
    this.config = loadConfiguration(this.dir, opts);
    this.app.proxy = this.config.get('server.proxy');

    this.isLoaded = false;

    // internal services.
    this.fs = createStrapiFs(this);
    this.eventHub = createEventHub();

    this.requireProjectBootstrap();

    createUpdateNotifier(this).notify();
  }

  get EE() {
    return ee({ dir: this.dir, logger });
  }

  handleRequest(req, res) {
    if (!this.requestHandler) {
      this.requestHandler = this.app.callback();
    }

    return this.requestHandler(req, res);
  }

  requireProjectBootstrap() {
    const bootstrapPath = path.resolve(this.dir, 'config/functions/bootstrap.js');

    if (fse.existsSync(bootstrapPath)) {
      require(bootstrapPath);
    }
  }

  logStats() {
    const columns = Math.min(process.stderr.columns, 80) - 2;
    console.log();
    console.log(chalk.black.bgWhite(_.padEnd(' Project information', columns)));
    console.log();

    const infoTable = new CLITable({
      colWidths: [20, 50],
      chars: { mid: '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' },
    });

    const isEE = strapi.EE === true && ee.isEE === true;

    infoTable.push(
      [chalk.blue('Time'), `${new Date()}`],
      [chalk.blue('Launched in'), Date.now() - this.config.launchedAt + ' ms'],
      [chalk.blue('Environment'), this.config.environment],
      [chalk.blue('Process PID'), process.pid],
      [chalk.blue('Version'), `${this.config.info.strapi} (node ${process.version})`],
      [chalk.blue('Edition'), isEE ? 'Enterprise' : 'Community']
    );

    console.log(infoTable.toString());
    console.log();
    console.log(chalk.black.bgWhite(_.padEnd(' Actions available', columns)));
    console.log();
  }

  logFirstStartupMessage() {
    this.logStats();

    console.log(chalk.bold('One more thing...'));
    console.log(
      chalk.grey('Create your first administrator 💻 by going to the administration panel at:')
    );
    console.log();

    const addressTable = new CLITable();

    const adminUrl = getAbsoluteAdminUrl(strapi.config);
    addressTable.push([chalk.bold(adminUrl)]);

    console.log(`${addressTable.toString()}`);
    console.log();
  }

  logStartupMessage() {
    this.logStats();

    console.log(chalk.bold('Welcome back!'));

    if (this.config.serveAdminPanel === true) {
      console.log(chalk.grey('To manage your project 🚀, go to the administration panel at:'));
      const adminUrl = getAbsoluteAdminUrl(strapi.config);
      console.log(chalk.bold(adminUrl));
      console.log();
    }

    console.log(chalk.grey('To access the server ⚡, go to:'));
    const serverUrl = getAbsoluteServerUrl(strapi.config);
    console.log(chalk.bold(serverUrl));
    console.log();
  }

  initServer() {
    this.server = http.createServer(this.handleRequest.bind(this));
    // handle port in use cleanly
    this.server.on('error', err => {
      if (err.code === 'EADDRINUSE') {
        return this.stopWithError(`The port ${err.port} is already used by another application.`);
      }

      this.log.error(err);
    });

    // Close current connections to fully destroy the server
    const connections = {};

    this.server.on('connection', conn => {
      const key = conn.remoteAddress + ':' + conn.remotePort;
      connections[key] = conn;

      conn.on('close', function() {
        delete connections[key];
      });
    });

    this.server.destroy = cb => {
      this.server.close(cb);

      for (let key in connections) {
        connections[key].destroy();
      }
    };
  }

  async start(cb) {
    try {
      if (!this.isLoaded) {
        await this.load();
      }

      this.app.use(this.router.routes()).use(this.router.allowedMethods());

      // Launch server.
      this.listen(cb);
    } catch (err) {
      this.stopWithError(err);
    }
  }

  async destroy() {
    if (_.has(this, 'server.destroy')) {
      this.server.destroy();
    }

    await Promise.all(
      Object.values(this.plugins).map(plugin => {
        if (_.has(plugin, 'destroy') && typeof plugin.destroy === 'function') {
          return plugin.destroy();
        }
      })
    );

    if (_.has(this, 'admin')) {
      await this.admin.destroy();
    }

    this.eventHub.removeAllListeners();

    if (_.has(this, 'db')) {
      await this.db.destroy();
    }

    delete global.strapi;
  }

  /**
   * Add behaviors to the server
   */
  async listen(cb) {
    const onListen = async err => {
      if (err) return this.stopWithError(err);

      // Is the project initialised?
      const isInitialised = await utils.isInitialised(this);

      // Should the startup message be displayed?
      const hideStartupMessage = process.env.STRAPI_HIDE_STARTUP_MESSAGE
        ? process.env.STRAPI_HIDE_STARTUP_MESSAGE === 'true'
        : false;

      if (hideStartupMessage === false) {
        if (!isInitialised) {
          this.logFirstStartupMessage();
        } else {
          this.logStartupMessage();
        }
      }

      // Emit started event.
      const databaseClients = _.map(this.config.get('connections'), _.property('settings.client'));
      await this.telemetry.send('didStartServer', { database: databaseClients });

      if (cb && typeof cb === 'function') {
        cb();
      }

      if (
        (this.config.environment === 'development' &&
          this.config.get('server.admin.autoOpen', true) !== false) ||
        !isInitialised
      ) {
        await utils.openBrowser.call(this);
      }
    };

    const listenSocket = this.config.get('server.socket');
    const listenErrHandler = err => onListen(err).catch(err => this.stopWithError(err));

    if (listenSocket) {
      this.server.listen(listenSocket, listenErrHandler);
    } else {
      this.server.listen(
        this.config.get('server.port'),
        this.config.get('server.host'),
        listenErrHandler
      );
    }
  }

  stopWithError(err, customMessage) {
    this.log.debug(`⛔ Server wasn't able to start properly.`);
    if (customMessage) {
      this.log.error(customMessage);
    }
    this.log.error(err);
    return this.stop();
  }

  stop(exitCode = 1) {
    // Destroy server and available connections.
    if (_.has(this, 'server.destroy')) {
      this.server.destroy();
    }

    if (this.config.autoReload) {
      process.send('stop');
    }

    // Kill process.
    process.exit(exitCode);
  }

  async load() {
    this.app.use(async (ctx, next) => {
      if (ctx.request.url === '/_health' && ['HEAD', 'GET'].includes(ctx.request.method)) {
        ctx.set('strapi', 'You are so French!');
        ctx.status = 204;
      } else {
        await next();
      }
    });

    const modules = await loadModules(this);

    this.api = modules.api;
    this.admin = modules.admin;
    this.components = modules.components;
    this.plugins = modules.plugins;
    this.middleware = modules.middlewares;
    this.hook = modules.hook;

    await bootstrap(this);

    // init webhook runner
    this.webhookRunner = createWebhookRunner({
      eventHub: this.eventHub,
      logger: this.log,
      configuration: this.config.get('server.webhooks', {}),
    });

    // Init core store
    this.models['core_store'] = coreStoreModel(this.config);
    this.models['strapi_webhooks'] = webhookModel(this.config);

    this.db = createDatabaseManager(this);

    await this.runLifecyclesFunctions(LIFECYCLES.REGISTER);
    await this.db.initialize();

    this.store = createCoreStore({
      environment: this.config.environment,
      db: this.db,
    });

    this.webhookStore = createWebhookStore({ db: this.db });

    await this.startWebhooks();

    this.entityValidator = entityValidator;

    this.entityService = createEntityService({
      db: this.db,
      eventHub: this.eventHub,
      entityValidator: this.entityValidator,
    });

    this.telemetry = createTelemetry(this);

    // Initialize hooks and middlewares.
    await initializeMiddlewares.call(this);
    await initializeHooks.call(this);

    await this.runLifecyclesFunctions(LIFECYCLES.BOOTSTRAP);
    await this.freeze();

    this.isLoaded = true;

    return this;
  }

  async startWebhooks() {
    const webhooks = await this.webhookStore.findWebhooks();
    webhooks.forEach(webhook => this.webhookRunner.add(webhook));
  }

  reload() {
    const state = {
      shouldReload: 0,
    };

    const reload = function() {
      if (state.shouldReload > 0) {
        // Reset the reloading state
        state.shouldReload -= 1;
        reload.isReloading = false;
        return;
      }

      if (this.config.autoReload) {
        this.server.close();
        process.send('reload');
      }
    };

    Object.defineProperty(reload, 'isWatching', {
      configurable: true,
      enumerable: true,
      set: value => {
        // Special state when the reloader is disabled temporarly (see GraphQL plugin example).
        if (state.isWatching === false && value === true) {
          state.shouldReload += 1;
        }
        state.isWatching = value;
      },
      get: () => {
        return state.isWatching;
      },
    });

    reload.isReloading = false;
    reload.isWatching = true;

    return reload;
  }

  async runLifecyclesFunctions(lifecycleName) {
    const execLifecycle = async fn => {
      if (!fn) {
        return;
      }

      return fn();
    };

    const configPath = `functions.${lifecycleName}`;

    // plugins
    await Promise.all(
      Object.keys(this.plugins).map(plugin => {
        const pluginFunc = _.get(this.plugins[plugin], `config.${configPath}`);

        return execLifecycle(pluginFunc).catch(err => {
          strapi.log.error(`${lifecycleName} function in plugin "${plugin}" failed`);
          strapi.log.error(err);
          strapi.stop();
        });
      })
    );

    // user
    await execLifecycle(_.get(this.config, configPath));

    // admin
    const adminFunc = _.get(this.admin.config, configPath);
    return execLifecycle(adminFunc).catch(err => {
      strapi.log.error(`${lifecycleName} function in admin failed`);
      strapi.log.error(err);
      strapi.stop();
    });
  }

  async freeze() {
    Object.freeze(this.config);
    Object.freeze(this.dir);
    Object.freeze(this.admin);
    Object.freeze(this.plugins);
    Object.freeze(this.api);
  }

  getModel(modelKey, plugin) {
    return this.db.getModel(modelKey, plugin);
  }

  /**
   * Binds queries with a specific model
   * @param {string} entity - entity name
   * @param {string} plugin - plugin name or null
   */
  query(entity, plugin) {
    return this.db.query(entity, plugin);
  }
}

module.exports = options => {
  const strapi = new Strapi(options);
  global.strapi = strapi;
  return strapi;
};
<|endoftext|>


<commit_before>/*
 * grunt-django-compressor
 * https://github.com/cristianrojas/grunt-django-compressor
 *
 * Copyright (c) 2014 Cristian Rojas
 * Licensed under the MIT license.
 */

'use strict';

module.exports = function(grunt) {

  var fs = require('fs');
  var comment = require('./libs/comment');
  var UglifyJS = require('uglify-js');

  // Please see the Grunt documentation for more information regarding task
  // creation: http://gruntjs.com/creating-tasks

  grunt.registerMultiTask('django_compressor', 'A Grunt plugin to iterate over every html file and compress javascripts and stylesheets.', function() {
    // Merge task-specific and/or target-specific options with these defaults.
    var options = this.options({
      // Same as the sails linker
      startTag: '<!--SCRIPTS-->',
      endTag: '<!--SCRIPTS END-->',
      staticFilesDjangoPrefix: '{{ STATIC_URL }}',
      // The path to the static files
      // typically something like: /<django_app_name>/static/
      staticFilesPath: '',
    });

    // Don't continue if the staticFilesOption isn't set
    if( options.staticFilesPath === '' ) throw new Error('Please specify the staticFilesPath option.');

    /*
     * Get html files function
     * -----------------------
     * */
    function getHtmlFiles(dir){
      var htmlFiles = [];

      function getFiles(dir){
        var files = fs.readdirSync(dir);
        for(var i in files){
          if(!files.hasOwnProperty(i)) continue;
          var name = dir+'/'+files[i];
          if(fs.statSync(name).isDirectory()){
            // It's a directory
            getFiles(name);
          } else {
            // It's a file, but verify if is an html file
            if(name.split('.').pop() === 'html'){
              htmlFiles.push(name);
            }
          }
        }
      }
      getFiles(dir);
      return htmlFiles;
    }

    // Get html files from this folder (where gruntfile lives)
    var htmlFiles = getHtmlFiles('.');

    // Variable to store the scripts
    var scripts;

    htmlFiles.forEach(function(htmlFilePath){
      var htmlFile = grunt.file.read(htmlFilePath);

      // Verify if the file has the startTag
      var indexOfStartTag = htmlFile.indexOf(options.startTag);
      if( indexOfStartTag > -1 ){
        // send the start indexOf number to start looking at this point
        var indexOfEndTag = htmlFile.indexOf(options.endTag, indexOfStartTag);
        if( indexOfStartTag === -1 || indexOfEndTag === -1 || indexOfStartTag >= indexOfEndTag ){
          // There are not scripts
          scripts = false;
          return false;
        } else {
          // The file contains start and end tag
          // Determine where stars and finish the scripts section
          var substrStart = indexOfStartTag + options.startTag.length,
            substrEnd = (indexOfEndTag - 1) - substrStart;
          // Store the scripts section in the scripts var
          scripts = htmlFile.substr(substrStart, substrEnd);
          // Look for all src="*" parts in the scripts string
          var regexp = /src=".*?"/g;
          // match them and return as an array
          scripts = scripts.match(regexp);

          // Create a new array and remove unneeded chars in the script path
          var _scripts = [];
          scripts.forEach(function(script){
            // TODO: should throw an error if staticFilesDjangoPrefix not set?
            script = script.replace(/"/g, '')
              .replace('src=', '')
              .replace(options.staticFilesDjangoPrefix, '');

            // Construct the absolute path
            script = options.staticFilesPath + script;
            _scripts.push(script);
          });
          scripts = _scripts;
        }

        if( scripts ){
          // extract the filename of the template to create a js file with
          // the same name
          var destFileName = htmlFilePath.split('/').pop().replace('.html', '.js'),
          // destination file path (the same folder as the scripts)
            destFile = scripts[0].substring(0, scripts[0].lastIndexOf('/') + 1) + destFileName,
          // variable to store the file content
            _fileContent = '';

          scripts.forEach(function(filepath) {
            // Warn on and remove invalid source files
            if ( !grunt.file.exists(filepath) ){
              grunt.log.warn('Source file "' + filepath + '" not found.');
              return false;
            }

            // Read file source and write in the variable
            var src = grunt.file.read(filepath);
            if( src ){
              _fileContent += src;
            }
          });

          // Write the stored content in the dest file
          grunt.file.write(destFile, _fileContent);
          // Print a success message.
          grunt.log.writeln('File "' + destFile + '" created.');

//          var minVersionPath = destFile.replace('.js', '.min.js');
//          var minVersion = UglifyJS.minify(destFile);
//          grunt.file.write(minVersionPath, minVersion.code);
//          grunt.log.writeln('File "' + minVersionPath + '" created.');

          var djangoStartTag = '{# SCRIPTS #}';
          var djangoEndTag = '{# SCRIPTS END #}';

          var htmlFileAlreadyParsed = false;
          if( htmlFile.indexOf(djangoStartTag) > -1 ) htmlFileAlreadyParsed = true;

          var scriptTag = '<script type="text/javascript" src="'
            + destFile.replace(options.staticFilesPath, options.staticFilesDjangoPrefix)
            + '"></script>';

//          console.log(scriptTag);

          var newHtmlFile;

          if( htmlFileAlreadyParsed ){
            var htmlFileCopy = htmlFile;
            var indexOfDjangoStartTag = htmlFileCopy.indexOf(djangoStartTag);
            var indexOfDjangoEndTag = htmlFileCopy.indexOf(djangoEndTag);
            htmlFileCopy = htmlFileCopy.slice(0, indexOfDjangoStartTag)
              + '{# SCRIPTS #}'
              + '{% if DEBUG %}'
              + htmlFileCopy.substring(indexOfStartTag - 1, indexOfEndTag + options.endTag.length)
              + '{% else %}'
              + scriptTag
              + '{% endif %}'
              + '{# SCRIPTS END #}'
              + htmlFileCopy.slice(indexOfDjangoEndTag + djangoEndTag.length, htmlFileCopy.length);

            newHtmlFile = htmlFileCopy;
          } else {
            console.log('html file not parsed');
            newHtmlFile = htmlFile.substr(0, indexOfStartTag - 1)
              + '{# SCRIPTS #}'
              + '{% if DEBUG %}'
              + htmlFile.substr(indexOfStartTag - 1, indexOfEndTag + options.endTag.length)
              + '{% else %}'
              + scriptTag
              + '{% endif %}'
              + '{# SCRIPTS END #}'
              + htmlFile.substr(indexOfEndTag + options.endTag.length, htmlFile.length);
          }

          grunt.file.write(htmlFilePath, newHtmlFile);
        }
      }
    });
  });
};<commit_msg>Fixed 1-char errors<commit_after>/*
 * grunt-django-compressor
 * https://github.com/cristianrojas/grunt-django-compressor
 *
 * Copyright (c) 2014 Cristian Rojas
 * Licensed under the MIT license.
 */

'use strict';

module.exports = function(grunt) {

  var fs = require('fs');
  var comment = require('./libs/comment');
  var UglifyJS = require('uglify-js');

  // Please see the Grunt documentation for more information regarding task
  // creation: http://gruntjs.com/creating-tasks

  grunt.registerMultiTask('django_compressor', 'A Grunt plugin to iterate over every html file and compress javascripts and stylesheets.', function() {
    // Merge task-specific and/or target-specific options with these defaults.
    var options = this.options({
      // Same as the sails linker
      startTag: '<!--SCRIPTS-->',
      endTag: '<!--SCRIPTS END-->',
      staticFilesDjangoPrefix: '{{ STATIC_URL }}',
      // The path to the static files
      // typically something like: /<django_app_name>/static/
      staticFilesPath: '',
    });

    // Don't continue if the staticFilesOption isn't set
    if( options.staticFilesPath === '' ) throw new Error('Please specify the staticFilesPath option.');

    /*
     * Get html files function
     * -----------------------
     * */
    function getHtmlFiles(dir){
      var htmlFiles = [];

      function getFiles(dir){
        var files = fs.readdirSync(dir);
        for(var i in files){
          if(!files.hasOwnProperty(i)) continue;
          var name = dir+'/'+files[i];
          if(fs.statSync(name).isDirectory()){
            // It's a directory
            getFiles(name);
          } else {
            // It's a file, but verify if is an html file
            if(name.split('.').pop() === 'html'){
              htmlFiles.push(name);
            }
          }
        }
      }
      getFiles(dir);
      return htmlFiles;
    }

    // Get html files from this folder (where gruntfile lives)
    var htmlFiles = getHtmlFiles('.');

    // Variable to store the scripts
    var scripts;

    htmlFiles.forEach(function(htmlFilePath){
      var htmlFile = grunt.file.read(htmlFilePath);

      // Verify if the file has the startTag
      var indexOfStartTag = htmlFile.indexOf(options.startTag);
      if( indexOfStartTag > -1 ){
        // send the start indexOf number to start looking at this point
        var indexOfEndTag = htmlFile.indexOf(options.endTag, indexOfStartTag);
        if( indexOfStartTag === -1 || indexOfEndTag === -1 || indexOfStartTag >= indexOfEndTag ){
          // There are not scripts
          scripts = false;
          return false;
        } else {
          // The file contains start and end tag
          // Determine where stars and finish the scripts section
          var substrStart = indexOfStartTag + options.startTag.length,
            substrEnd = (indexOfEndTag - 1) - substrStart;
          // Store the scripts section in the scripts var
          scripts = htmlFile.substr(substrStart, substrEnd);
          // Look for all src="*" parts in the scripts string
          var regexp = /src=".*?"/g;
          // match them and return as an array
          scripts = scripts.match(regexp);

          // Create a new array and remove unneeded chars in the script path
          var _scripts = [];
          scripts.forEach(function(script){
            // TODO: should throw an error if staticFilesDjangoPrefix not set?
            script = script.replace(/"/g, '')
              .replace('src=', '')
              .replace(options.staticFilesDjangoPrefix, '');

            // Construct the absolute path
            script = options.staticFilesPath + script;
            _scripts.push(script);
          });
          scripts = _scripts;
        }

        if( scripts ){
          // extract the filename of the template to create a js file with
          // the same name
          var destFileName = htmlFilePath.split('/').pop().replace('.html', '.js'),
          // destination file path (the same folder as the scripts)
            destFile = scripts[0].substring(0, scripts[0].lastIndexOf('/') + 1) + destFileName,
          // variable to store the file content
            _fileContent = '';

          scripts.forEach(function(filepath) {
            // Warn on and remove invalid source files
            if ( !grunt.file.exists(filepath) ){
              grunt.log.warn('Source file "' + filepath + '" not found.');
              return false;
            }

            // Read file source and write in the variable
            var src = grunt.file.read(filepath);
            if( src ){
              _fileContent += src;
            }
          });

          // Write the stored content in the dest file
          grunt.file.write(destFile, _fileContent);
          // Print a success message.
          grunt.log.writeln('File "' + destFile + '" created.');

//          var minVersionPath = destFile.replace('.js', '.min.js');
//          var minVersion = UglifyJS.minify(destFile);
//          grunt.file.write(minVersionPath, minVersion.code);
//          grunt.log.writeln('File "' + minVersionPath + '" created.');

          var djangoStartTag = '{# SCRIPTS #}';
          var djangoEndTag = '{# SCRIPTS END #}';

          var htmlFileAlreadyParsed = false;
          if( htmlFile.indexOf(djangoStartTag) > -1 ) htmlFileAlreadyParsed = true;

          var scriptTag = '<script type="text/javascript" src="'
            + destFile.replace(options.staticFilesPath, options.staticFilesDjangoPrefix)
            + '"></script>';

//          console.log(scriptTag);

          var newHtmlFile;

          if( htmlFileAlreadyParsed ){
            var htmlFileCopy = htmlFile;
            var indexOfDjangoStartTag = htmlFileCopy.indexOf(djangoStartTag);
            var indexOfDjangoEndTag = htmlFileCopy.indexOf(djangoEndTag);
            htmlFileCopy = htmlFileCopy.slice(0, indexOfDjangoStartTag)
              + '{# SCRIPTS #}'
              + grunt.util.linefeed
              + '{% if DEBUG %}'
              + grunt.util.linefeed
              + htmlFileCopy.substring(indexOfStartTag, indexOfEndTag + options.endTag.length)
              + grunt.util.linefeed
              + '{% else %}'
              + grunt.util.linefeed
              + scriptTag
              + grunt.util.linefeed
              + '{% endif %}'
              + grunt.util.linefeed
              + '{# SCRIPTS END #}'
              + htmlFileCopy.slice(indexOfDjangoEndTag + djangoEndTag.length, htmlFileCopy.length);

            newHtmlFile = htmlFileCopy;
          } else {
            console.log('html file not parsed');
            newHtmlFile = htmlFile.substr(0, indexOfStartTag - 1)
              + '{# SCRIPTS #}'
              + '{% if DEBUG %}'
              + htmlFile.substr(indexOfStartTag - 1, indexOfEndTag + options.endTag.length)
              + '{% else %}'
              + scriptTag
              + '{% endif %}'
              + '{# SCRIPTS END #}'
              + htmlFile.substr(indexOfEndTag + options.endTag.length, htmlFile.length);
          }

          grunt.file.write(htmlFilePath, newHtmlFile);
        }
      }
    });
  });
};<|endoftext|>


<commit_before>'use strict';

app.controller('dangerzone', function ($scope, $rootScope, neo4j, CRUD) {

    $scope.data = {
        "neo4j": neo4j
***REMOVED***

    $scope.destroy = function() {
        CRUD.post("clear", $scope.data)
            .then(function(result) {
                $rootScope.msg = result.data;
        ***REMOVED***,
            function(err) {
                $rootScope.error = err;
        ***REMOVED***)
***REMOVED***

    $scope.removeGroup = function() {
        console.log($scope.data);

        CRUD.post("remove-group", $scope.data)
            .then(function(result) {
                $rootScope.msg = "Group removed";
        ***REMOVED***,
            function(err) {
                $rootScope.error = err;
        ***REMOVED***)
***REMOVED***

***REMOVED*** Load groups for selection
    $scope.allGroups = [];

    CRUD.post("list-groups", $scope.data)
        .then(function(result) {
            $scope.allGroups = result.data.groups || [];
    ***REMOVED***,
        function(err) {
            $rootScope.error = err;
    ***REMOVED***)

***REMOVED***);
<commit_msg>Removed console log<commit_after>'use strict';

app.controller('dangerzone', function ($scope, $rootScope, neo4j, CRUD) {

    $scope.data = {
        "neo4j": neo4j
***REMOVED***

    $scope.destroy = function() {
        CRUD.post("clear", $scope.data)
            .then(function(result) {
                $rootScope.msg = result.data;
        ***REMOVED***,
            function(err) {
                $rootScope.error = err;
        ***REMOVED***)
***REMOVED***

    $scope.removeGroup = function() {
        CRUD.post("remove-group", $scope.data)
            .then(function(result) {
                $rootScope.msg = "Group removed";
        ***REMOVED***,
            function(err) {
                $rootScope.error = err;
        ***REMOVED***)
***REMOVED***

***REMOVED*** Load groups for selection
    $scope.allGroups = [];

    CRUD.post("list-groups", $scope.data)
        .then(function(result) {
            $scope.allGroups = result.data.groups || [];
    ***REMOVED***,
        function(err) {
            $rootScope.error = err;
    ***REMOVED***)

***REMOVED***);
<|endoftext|>


<commit_before>/***
|''Name''|TiddlySpaceBackstage|
|''Version''|0.5.2|
|''Description''|Provides a TiddlySpace version of the backstage|
|''Status''|@@beta@@|
|''Source''|http://github.com/TiddlySpace/tiddlyspace/raw/master/src/plugins/TiddlySpaceBackstage.js|
|''Requires''|TiddlySpaceConfig ImageMacroPlugin|
!Code
***/
//{{{
(function($) {

var tweb = config.extensions.tiddlyweb;
var tiddlyspace = config.extensions.tiddlyspace;
var imageMacro = config.macros.image;

if(config.options.chkBackstage === undefined) {
	config.options.chkBackstage = true;
}

config.backstageTasks = [];

config.tasks.login = {
	text: "login",
	tooltip: "TiddlySpace login",
	content: "<<tiddler BackstageLogin>>"
};
config.backstageTasks.push("login");

config.tasks.user = {
	text: "user: ",
	tooltip: "user control panel",
	content: "<<tiddler BackstageUser>>"
};
config.backstageTasks.push("user");

config.tasks.space = {
	text: "space: ",
	tooltip: "space control panel",
	content: "<<tiddler BackstageSpace>>",
	className: "right"
};
config.backstageTasks.push("space");

config.messages.backstage.prompt = "";
// initialize state
var _show = backstage.show;
backstage.show = function() {
	// selectively hide backstage tasks based on user status
	var tasks = $("#backstageToolbar .backstageTask").show();
	tweb.getUserInfo(function(user) {
		if(user.anon) {
			tasks.slice(1, 2).hide();
		} else {
			tasks.eq(0).hide();
		}
	});
	// display backstage
	return _show.apply(this, arguments);
};

var _init = backstage.init;
backstage.init = function(){
	_init.apply(this, arguments);

	var backstageArea = $("#backstageArea");

	// override user button (logged in) to show username
	var userBtn = $(".backstageTask[task=user]").
		html('%0'.format([
			config.tasks.user.text]));
	config.macros.option.handler($(".txtUserName", userBtn)[0], null, ["txtUserName"]);

	// override show button with an svg image
	var showBtn = $("#backstageShow")[0];
	var altText = $(showBtn).text();
	$(showBtn).empty();
	imageMacro.renderImage(showBtn, "backstage.svg",
		{ alt: altText, width: 100, height: 100 });

	// override hide button
	var hideBtn = $("#backstageHide")[0];
	altText = $(hideBtn).text();
	$(hideBtn).empty();
	imageMacro.renderImage(hideBtn, "close.svg",
		{ alt: altText, width: 24, height: 24 });

	var backstageToolbar = $("#backstageToolbar")[0];
	var backstageLogo = $('
').
		prependTo(backstageToolbar)[0];
	var iconName = readOnly ? "publicIcon" : "privateAndPublicIcon";
	imageMacro.renderImage(backstageLogo, iconName, { width: 24, height: 24 });
	// construct the tiddlyspace logo
	$('tiddly' +
			'space').
		appendTo(backstageLogo);

	// override space button to show SiteIcon
	var siteIcon = store.getTiddler("SiteIcon");
	if(siteIcon) {
		var spaceName = tiddlyspace.currentSpace.name;
		var btn = $("[task=space]", backstageArea);
		btn.empty();
		var uri = "/bags/%0_public/tiddlers/SiteIcon".format([spaceName]);
		imageMacro.renderImage(btn[0], uri,
			{ imageClass:"spaceSiteIcon", height: 24, width: 24 });
		$("").html(config.tasks.space.text).appendTo(btn);
		$('').text(spaceName).appendTo(btn);
	}

	tweb.getUserInfo(function(user) {
		if(!user.anon) {
			var src = tiddlyspace.getAvatar(tweb.status.server_host, user.name);
			var container = $("").appendTo("[task=user]", backstageArea)[0];
			imageMacro.renderImage(container, src,
				{ imageClass:"userSiteIcon", height: 24, width: 24 });
		}

		// override login button to show default avatar
		var loginBtn = $("[task=login]", backstageArea);
		loginBtn.html("%0".format([config.tasks.login.text]));
		imageMacro.renderImage(loginBtn[0], "/bags/tiddlyspace/tiddlers/SiteIcon",
			{ imageClass:"userSiteIcon", height: 24, width: 24 });

		var tasks = $(".backstageTask");
		for(var i = 0; i < tasks.length; i++) {
			var btn = $(tasks[i]);
			var taskName = btn.attr("task");
			btn.addClass("task_%0".format([taskName]));
		}
	});
};

})(jQuery);
//}}}
<commit_msg>corrected logged out SiteIcon uri, reduced size of show button<commit_after>/***
|''Name''|TiddlySpaceBackstage|
|''Version''|0.5.3|
|''Description''|Provides a TiddlySpace version of the backstage|
|''Status''|@@beta@@|
|''Source''|http://github.com/TiddlySpace/tiddlyspace/raw/master/src/plugins/TiddlySpaceBackstage.js|
|''Requires''|TiddlySpaceConfig ImageMacroPlugin|
!Code
***/
//{{{
(function($) {

var tweb = config.extensions.tiddlyweb;
var tiddlyspace = config.extensions.tiddlyspace;
var imageMacro = config.macros.image;

if(config.options.chkBackstage === undefined) {
	config.options.chkBackstage = true;
}

config.backstageTasks = [];

config.tasks.login = {
	text: "login",
	tooltip: "TiddlySpace login",
	content: "<<tiddler BackstageLogin>>"
};
config.backstageTasks.push("login");

config.tasks.user = {
	text: "user: ",
	tooltip: "user control panel",
	content: "<<tiddler BackstageUser>>"
};
config.backstageTasks.push("user");

config.tasks.space = {
	text: "space: ",
	tooltip: "space control panel",
	content: "<<tiddler BackstageSpace>>",
	className: "right"
};
config.backstageTasks.push("space");

config.messages.backstage.prompt = "";
// initialize state
var _show = backstage.show;
backstage.show = function() {
	// selectively hide backstage tasks based on user status
	var tasks = $("#backstageToolbar .backstageTask").show();
	tweb.getUserInfo(function(user) {
		if(user.anon) {
			tasks.slice(1, 2).hide();
		} else {
			tasks.eq(0).hide();
		}
	});
	// display backstage
	return _show.apply(this, arguments);
};

var _init = backstage.init;
backstage.init = function(){
	_init.apply(this, arguments);

	var backstageArea = $("#backstageArea");

	// override user button (logged in) to show username
	var userBtn = $(".backstageTask[task=user]").
		html('%0'.format([
			config.tasks.user.text]));
	config.macros.option.handler($(".txtUserName", userBtn)[0], null, ["txtUserName"]);

	// override show button with an svg image
	var showBtn = $("#backstageShow")[0];
	var altText = $(showBtn).text();
	$(showBtn).empty();
	imageMacro.renderImage(showBtn, "backstage.svg",
		{ alt: altText, width: 60, height: 60 });

	// override hide button
	var hideBtn = $("#backstageHide")[0];
	altText = $(hideBtn).text();
	$(hideBtn).empty();
	imageMacro.renderImage(hideBtn, "close.svg",
		{ alt: altText, width: 24, height: 24 });

	var backstageToolbar = $("#backstageToolbar")[0];
	var backstageLogo = $('
').
		prependTo(backstageToolbar)[0];
	var iconName = readOnly ? "publicIcon" : "privateAndPublicIcon";
	imageMacro.renderImage(backstageLogo, iconName, { width: 24, height: 24 });
	// construct the tiddlyspace logo
	$('tiddly' +
			'space').
		appendTo(backstageLogo);

	// override space button to show SiteIcon
	var siteIcon = store.getTiddler("SiteIcon");
	if(siteIcon) {
		var spaceName = tiddlyspace.currentSpace.name;
		var btn = $("[task=space]", backstageArea);
		btn.empty();
		var uri = "/bags/%0_public/tiddlers/SiteIcon".format([spaceName]);
		imageMacro.renderImage(btn[0], uri,
			{ imageClass:"spaceSiteIcon", height: 24, width: 24 });
		$("").html(config.tasks.space.text).appendTo(btn);
		$('').text(spaceName).appendTo(btn);
	}

	tweb.getUserInfo(function(user) {
		if(!user.anon) {
			var src = tiddlyspace.getAvatar(tweb.status.server_host, user.name);
			var container = $("").appendTo("[task=user]", backstageArea)[0];
			imageMacro.renderImage(container, src,
				{ imageClass:"userSiteIcon", height: 24, width: 24 });
		}

		// override login button to show default avatar
		var loginBtn = $("[task=login]", backstageArea);
		loginBtn.html("%0".format([config.tasks.login.text]));
		imageMacro.renderImage(loginBtn[0], "/bags/common/tiddlers/defaultSiteIcon",
			{ imageClass:"userSiteIcon", height: 24, width: 24 });

		var tasks = $(".backstageTask");
		for(var i = 0; i < tasks.length; i++) {
			var btn = $(tasks[i]);
			var taskName = btn.attr("task");
			btn.addClass("task_%0".format([taskName]));
		}
	});
};

})(jQuery);
//}}}
<|endoftext|>


<commit_before>/*
 * grunticon
 * https://github.com/filamentgroup/grunticon
 *
 * Copyright (c) 2012 Scott Jehl, Filament Group, Inc
 * Licensed under the MIT license.
 */

/*global phantom:true*/
/*global window:true*/
/*global btoa:true*/

/*
phantom args sent from grunticon.js:
	[0] - input directory path
	[1] - output directory path
	[2] - asyncCSS output file path
	[3] - preview.html static file path
	[4] - CSS filename for datasvg css
	[5] - CSS filename for datapng css
	[6] - CSS filename for urlpng css
	[7] - filename for preview HTML file
	[8] - png folder name
	[9] - css classname prefix
	[10] - css basepath prefix
	[11] - png pixel ratio
*/
(function(){
	"use strict";

var fs = require( "fs" );
var inputdir = phantom.args[0];
var outputdir = phantom.args[1];
var pngout =  phantom.args[8];
var cssprefix = phantom.args[9];
var files = fs.list( inputdir );
var currfile = 0;

var datacssrules = [];
var htmlpreviewbody = [];
var fallbackcss = phantom.args[6];
var pngdatacss = phantom.args[5];
var datacss = phantom.args[4];
var cssbasepath = phantom.args[10];

// these three arrays are dependent on each other, the value of an index in pngpixelratio decides which pixel ratio to use
var pngpixelratio = phantom.args[11].split( "," ); // converted to string in the arglist, get it back to an array
var pngpixelratiorules = [[]];
var pngdatacssrules = [[]];

// increment the current file index and process it
function nextFile(){
	currfile++;
	processFile();
}

// files have all been processed. write the css and html files and return
function finishUp(){

	// make the preview HTML file and asyncCSS loader file
	var asyncCSS = fs.read( phantom.args[2] );

	// copy above for a slightly different output in the preview html file (different paths)
	var asyncCSSpreview = asyncCSS;

	// open up the static html document
	var htmldoc = fs.read( phantom.args[3]);

	// noscript for the snippet file
	var noscript = '<link href="' + cssbasepath + outputdir + fallbackcss + '" rel="stylesheet">';

	// noscript for the preview file
	var noscriptpreview = '<link href="' + fallbackcss + '" rel="stylesheet">';

	// add custom function call to asyncCSS
	asyncCSS += '\ngrunticon( [ "' + cssbasepath + outputdir + datacss +'", "' + cssbasepath + outputdir + pngdatacss +'", "' + cssbasepath + outputdir + fallbackcss +'" ],[' + pngpixelratio + ']);';
	asyncCSSpreview += '\ngrunticon( [ "'+ datacss +'", "'+ pngdatacss +'", "'+ fallbackcss +'" ],[' + pngpixelratio + ']);';

	// add async loader to the top
	htmldoc = htmldoc.replace( /\n\t" + noscriptpreview );

	// add icons to the body
	htmldoc = htmldoc.replace( /<\/body>/, htmlpreviewbody.join( "\n\t" ) + "\n" );

	// write the preview html file
	fs.write( outputdir + phantom.args[7], htmldoc );

	// write PNG CSS files
	if ( pngpixelratiorules && pngpixelratio &&  pngpixelratiorules.length === pngpixelratio.length ) {
		for ( var i = 0; i < pngpixelratiorules.length; i++ ) {
			// print out CSS files and add pixelratio name to files that are not 1x
			if ( pngpixelratio[i] === '1' || pngpixelratio.length === 1 ) {
				fs.write( outputdir + fallbackcss, pngpixelratiorules[i].join( "\n\n" ) );
				fs.write( outputdir + pngdatacss, pngdatacssrules[0].join( "\n\n" ) );
			} else {
				fs.write( outputdir + fallbackcss.replace( /\.css$/i, '-' + pngpixelratio[i] + "x.css" ), pngpixelratiorules[i].join( "\n\n" ) );
				fs.write( outputdir + pngdatacss.replace( /\.css$/i, '-' + pngpixelratio[i] + "x.css" ), pngdatacssrules[i].join( "\n\n" ) );
			}
		}
	} else {
		//something is wrong with the pixel ratio, fallback to just print 1x
		fs.write( outputdir + fallbackcss, pngpixelratiorules[0].join( "\n\n" ) );
		fs.write( outputdir + pngdatacss, pngdatacssrules[0].join( "\n\n" ) );
	}

	// write the rest of the CSS files
	fs.write( outputdir + datacss, datacssrules.join( "\n\n" ) );

	// overwrite the snippet HTML
	fs.write( phantom.args[2], "\n\n" + noscript );
}

// process an svg file from the source directory
function processFile(){
	var theFile = files[ currfile ];

	if( theFile ){
		// only parse svg files
		if( theFile.match( /\.svg$/i ) ){
			(function(){
				var page = require( "webpage" ).create();
				var svgdata = fs.read(  inputdir + theFile ) || "";
				var svgdatauri = "data:image/svg+xml;base64,";
				var pngdatauri = "data:image/png;base64,";

				// kill the ".svg" at the end of the filename
				var filenamenoext = theFile.replace( /\.svg$/i, "" );

				// get svg element's dimensions so we can set the viewport dims later
				var frag = window.document.createElement( "div" );
				frag.innerHTML = svgdata;
				var svgelem = frag.querySelector( "svg" );
				var width = svgelem.getAttribute( "width" );
				var height = svgelem.getAttribute( "height" );

				// get base64 of svg file
				svgdatauri += btoa(svgdata);

				// add rules to svg data css file
				datacssrules.push( "." + cssprefix + filenamenoext + " { background-image: url(" + svgdatauri + "); background-repeat: no-repeat; }" );

				// add markup to the preview html file
				htmlpreviewbody.push( '
.' + cssprefix + filenamenoext + ':

' );

				// set page viewport size to svg dimensions
				page.viewportSize = { width: parseFloat(width), height: parseFloat(height) };

				// open svg file in webkit to make a png
				page.open(  inputdir + theFile, function( status ){

				// create png data URI
				page.zoomFactor = 1;

				// create fallback png files, data png and add css rules
				if ( pngpixelratio &&  pngpixelratio.length > 0) {
					// creating files with different pixel densities
					var pxstring;
					for ( var i = 0; i < pngpixelratio.length; i++ ) {
						page.zoomFactor = pngpixelratio[i];
						// add pixel ratio to filename (if it is not 1 or there is only one ratio defined)
						pxstring = ( ( pngpixelratio[i] === '1' || pngpixelratio.length === 1 ) ? '' : ('-' + pngpixelratio[i] + 'x' ) );
						// save png
						page.render( outputdir + pngout + filenamenoext + pxstring + ".png" );

						//make sure the array is defined
						if( !pngpixelratiorules[i] ) {
							pngpixelratiorules[i] = [];
						}

						if( !pngdatacssrules[i] ) {
							pngdatacssrules[i] = [];
						}

						// link to the correct filename and in the correct CSS file and use background-size: 100%
						pngpixelratiorules[i].push( "." + cssprefix + filenamenoext + " { background-image: url(" + pngout + filenamenoext + pxstring + ".png" + "); background-repeat: no-repeat; background-size: 100%; }" );
						pngdatacssrules[i].push( "." + cssprefix + filenamenoext + " { background-image: url(" +  pngdatauri + page.renderBase64( "png" ) + "); background-repeat: no-repeat; background-size: 100%; }" );
					}
				} else {
					// fallback to only creating 1 png version
					page.zoomFactor = 1;
					pngpixelratio = ['1']; // make sure we have pixelratio set to 1
					pngpixelratiorules[0].push( "." + cssprefix + filenamenoext + " { background-image: url(" + pngout + filenamenoext + ".png" + "); background-repeat: no-repeat;  }" );
					pngdatacssrules[0].push( "." + cssprefix + filenamenoext + " { background-image: url(" +  pngdatauri + page.renderBase64( "png" ) + "); background-repeat: no-repeat; }" );
				}

				nextFile();

				} );
			}());
		}
		else {
			// process the next svg
			nextFile();
		}
	}
	else {
		// fin
		finishUp();
		phantom.exit();
	}
}

// go ahead with the first file
processFile();
})();<commit_msg>URIEncode SVG before base64 encoding it<commit_after>/*
 * grunticon
 * https://github.com/filamentgroup/grunticon
 *
 * Copyright (c) 2012 Scott Jehl, Filament Group, Inc
 * Licensed under the MIT license.
 */

/*global phantom:true*/
/*global window:true*/
/*global btoa:true*/

/*
phantom args sent from grunticon.js:
	[0] - input directory path
	[1] - output directory path
	[2] - asyncCSS output file path
	[3] - preview.html static file path
	[4] - CSS filename for datasvg css
	[5] - CSS filename for datapng css
	[6] - CSS filename for urlpng css
	[7] - filename for preview HTML file
	[8] - png folder name
	[9] - css classname prefix
	[10] - css basepath prefix
	[11] - png pixel ratio
*/
(function(){
	"use strict";

var fs = require( "fs" );
var inputdir = phantom.args[0];
var outputdir = phantom.args[1];
var pngout =  phantom.args[8];
var cssprefix = phantom.args[9];
var files = fs.list( inputdir );
var currfile = 0;

var datacssrules = [];
var htmlpreviewbody = [];
var fallbackcss = phantom.args[6];
var pngdatacss = phantom.args[5];
var datacss = phantom.args[4];
var cssbasepath = phantom.args[10];

// these three arrays are dependent on each other, the value of an index in pngpixelratio decides which pixel ratio to use
var pngpixelratio = phantom.args[11].split( "," ); // converted to string in the arglist, get it back to an array
var pngpixelratiorules = [[]];
var pngdatacssrules = [[]];

// increment the current file index and process it
function nextFile(){
	currfile++;
	processFile();
}

// files have all been processed. write the css and html files and return
function finishUp(){

	// make the preview HTML file and asyncCSS loader file
	var asyncCSS = fs.read( phantom.args[2] );

	// copy above for a slightly different output in the preview html file (different paths)
	var asyncCSSpreview = asyncCSS;

	// open up the static html document
	var htmldoc = fs.read( phantom.args[3]);

	// noscript for the snippet file
	var noscript = '<link href="' + cssbasepath + outputdir + fallbackcss + '" rel="stylesheet">';

	// noscript for the preview file
	var noscriptpreview = '<link href="' + fallbackcss + '" rel="stylesheet">';

	// add custom function call to asyncCSS
	asyncCSS += '\ngrunticon( [ "' + cssbasepath + outputdir + datacss +'", "' + cssbasepath + outputdir + pngdatacss +'", "' + cssbasepath + outputdir + fallbackcss +'" ],[' + pngpixelratio + ']);';
	asyncCSSpreview += '\ngrunticon( [ "'+ datacss +'", "'+ pngdatacss +'", "'+ fallbackcss +'" ],[' + pngpixelratio + ']);';

	// add async loader to the top
	htmldoc = htmldoc.replace( /\n\t" + noscriptpreview );

	// add icons to the body
	htmldoc = htmldoc.replace( /<\/body>/, htmlpreviewbody.join( "\n\t" ) + "\n" );

	// write the preview html file
	fs.write( outputdir + phantom.args[7], htmldoc );

	// write PNG CSS files
	if ( pngpixelratiorules && pngpixelratio &&  pngpixelratiorules.length === pngpixelratio.length ) {
		for ( var i = 0; i < pngpixelratiorules.length; i++ ) {
			// print out CSS files and add pixelratio name to files that are not 1x
			if ( pngpixelratio[i] === '1' || pngpixelratio.length === 1 ) {
				fs.write( outputdir + fallbackcss, pngpixelratiorules[i].join( "\n\n" ) );
				fs.write( outputdir + pngdatacss, pngdatacssrules[0].join( "\n\n" ) );
			} else {
				fs.write( outputdir + fallbackcss.replace( /\.css$/i, '-' + pngpixelratio[i] + "x.css" ), pngpixelratiorules[i].join( "\n\n" ) );
				fs.write( outputdir + pngdatacss.replace( /\.css$/i, '-' + pngpixelratio[i] + "x.css" ), pngdatacssrules[i].join( "\n\n" ) );
			}
		}
	} else {
		//something is wrong with the pixel ratio, fallback to just print 1x
		fs.write( outputdir + fallbackcss, pngpixelratiorules[0].join( "\n\n" ) );
		fs.write( outputdir + pngdatacss, pngdatacssrules[0].join( "\n\n" ) );
	}

	// write the rest of the CSS files
	fs.write( outputdir + datacss, datacssrules.join( "\n\n" ) );

	// overwrite the snippet HTML
	fs.write( phantom.args[2], "\n\n" + noscript );
}

// process an svg file from the source directory
function processFile(){
	var theFile = files[ currfile ];

	if( theFile ){
		// only parse svg files
		if( theFile.match( /\.svg$/i ) ){
			(function(){
				var page = require( "webpage" ).create();
				var svgdata = fs.read(  inputdir + theFile ) || "";
				var svgdatauri = "data:image/svg+xml;base64,";
				var pngdatauri = "data:image/png;base64,";

				// kill the ".svg" at the end of the filename
				var filenamenoext = theFile.replace( /\.svg$/i, "" );

				// get svg element's dimensions so we can set the viewport dims later
				var frag = window.document.createElement( "div" );
				frag.innerHTML = svgdata;
				var svgelem = frag.querySelector( "svg" );
				var width = svgelem.getAttribute( "width" );
				var height = svgelem.getAttribute( "height" );

				// get base64 of svg file
				svgdatauri += btoa(unescape(encodeURIComponent( svgdata )));

				// add rules to svg data css file
				datacssrules.push( "." + cssprefix + filenamenoext + " { background-image: url(" + svgdatauri + "); background-repeat: no-repeat; }" );

				// add markup to the preview html file
				htmlpreviewbody.push( '
.' + cssprefix + filenamenoext + ':

' );

				// set page viewport size to svg dimensions
				page.viewportSize = { width: parseFloat(width), height: parseFloat(height) };

				// open svg file in webkit to make a png
				page.open(  inputdir + theFile, function( status ){

				// create png data URI
				page.zoomFactor = 1;

				// create fallback png files, data png and add css rules
				if ( pngpixelratio &&  pngpixelratio.length > 0) {
					// creating files with different pixel densities
					var pxstring;
					for ( var i = 0; i < pngpixelratio.length; i++ ) {
						page.zoomFactor = pngpixelratio[i];
						// add pixel ratio to filename (if it is not 1 or there is only one ratio defined)
						pxstring = ( ( pngpixelratio[i] === '1' || pngpixelratio.length === 1 ) ? '' : ('-' + pngpixelratio[i] + 'x' ) );
						// save png
						page.render( outputdir + pngout + filenamenoext + pxstring + ".png" );

						//make sure the array is defined
						if( !pngpixelratiorules[i] ) {
							pngpixelratiorules[i] = [];
						}

						if( !pngdatacssrules[i] ) {
							pngdatacssrules[i] = [];
						}

						// link to the correct filename and in the correct CSS file and use background-size: 100%
						pngpixelratiorules[i].push( "." + cssprefix + filenamenoext + " { background-image: url(" + pngout + filenamenoext + pxstring + ".png" + "); background-repeat: no-repeat; background-size: 100%; }" );
						pngdatacssrules[i].push( "." + cssprefix + filenamenoext + " { background-image: url(" +  pngdatauri + page.renderBase64( "png" ) + "); background-repeat: no-repeat; background-size: 100%; }" );
					}
				} else {
					// fallback to only creating 1 png version
					page.zoomFactor = 1;
					pngpixelratio = ['1']; // make sure we have pixelratio set to 1
					pngpixelratiorules[0].push( "." + cssprefix + filenamenoext + " { background-image: url(" + pngout + filenamenoext + ".png" + "); background-repeat: no-repeat;  }" );
					pngdatacssrules[0].push( "." + cssprefix + filenamenoext + " { background-image: url(" +  pngdatauri + page.renderBase64( "png" ) + "); background-repeat: no-repeat; }" );
				}

				nextFile();

				} );
			}());
		}
		else {
			// process the next svg
			nextFile();
		}
	}
	else {
		// fin
		finishUp();
		phantom.exit();
	}
}

// go ahead with the first file
processFile();
})();<|endoftext|>


<commit_before>/*
 * Copyright 2015 Samsung Electronics Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

'use strict';

var fs = require('fs');
var path = require('path');
var mkdirp = require('mkdirp');
var inquirer = require('inquirer');
var utils = require('../lib/utils');
var shelljs = require('shelljs');
var mustache = require('mustache');
var child = require('child_process');

function saveUserConfFile(configPath, webosConf) {
    var userConfData = {};
    if (fs.existsSync(configPath)) {
        userConfData = JSON.parse(fs.readFileSync(configPath));
    }
    userConfData.webos = webosConf;
    fs.writeFileSync(configPath, JSON.stringify(userConfData, null, 2), {
        encoding: 'utf8'
    });
}

function getNextSemVersion(curver) {    // just increase revision
    var tmp = curver.split('.');
    var major = parseInt(tmp[0], 10);
    var minor = parseInt(tmp[1], 10);
    var revision = (parseInt(tmp[2], 10) || 0) + 1;

    return major + '.' + minor + '.' + revision;
}

function getValidWebosConfData(configPath) {
    console.log(configPath);
    if (!(fs.existsSync(configPath))) {
        // userconf.json is not exists
        return null;
    }
    // userconf.json is already exists
    var userConfData = JSON.parse(fs.readFileSync(configPath));

    if (!(userConfData.hasOwnProperty('webos'))) {
        // userconf.json doesn't have webos data
        return null;
    }
    var webosData = userConfData.webos;
    if (typeof webosData.name !== 'string' || webosData.name.length <= 0) {
        // name is invalid
        return null;
    }
    if (typeof webosData.vendor !== 'string' || webosData.vendor.length <= 0) {
        // vendor is invalid
        return null;
    }
    if (typeof webosData.icon !== 'string' || webosData.icon.length <= 0) {
        // icon is invalid
        return null;
    }
    if (typeof webosData.largeicon !== 'string' || webosData.largeicon.length <= 0) {
        // largeicon is invalid
        return null;
    }
    if (!validateWebosVersion(webosData.version)) {
        // version is invalid
        return null;
    }

    return webosData;
}

function validateWebosVersion(version) {
    var tmp = version.split('.'),
        i;
    if (tmp.length !== 3) {
        return false;
    }
    for (i = 0; i < tmp.length; i++) {
        if (isNaN(tmp[i])) {
            return false;
        }
    }

    return i === tmp.length;
}

function confirmUseExistingData(userData, callback) {
    console.log('');
    console.log('      > [ Stored Information ]');
    console.log('      > name        : ' + userData.name);
    console.log('      > version     : ' + userData.version);
    console.log('      > vendor      : ' + userData.vendor);

    var ask = [{
        type: 'confirm',
        name: 'useExisting',
        message: 'Already have \'userconf.json\', Do you want to use this data?'
    }];

    inquirer.prompt(ask, function(answers) {
        callback(!!answers.useExisting);
    });
}

function askUserData(cordovaConf, callback, versionOnly, userData) {
    var ask;
    if(versionOnly) {
        var curVer = userData.version;
        var updateVer = getNextSemVersion(userData.version);
        ask = [{
            type: 'input',
            name: 'version',
            message: 'Current version is ' + curVer + '. Application version: ',
            default: updateVer,
            validate: function(input) {
                return /\d.\d.\d/.test(input) ? true : 'invalid version string for webos platform';
            }
        }];
        inquirer.prompt(ask, function(answers) {
            callback(answers);
        });
    }
    else {
        ask = [{
            type: 'input',
            name: 'name',
            message: 'What\'s the application\'s name?',
            default: cordovaConf.name,
            validate: function(input) {
                return /^[a-zA-Z][^~!\.\;\\\/\|\"\'@\#$%<>^&*\()\-=+_\’\n\t\s]*$/.test(input) ? true : 'invalid name for tizen platform';
            }
        },  {
            type: 'input',
            name: 'version',
            message: 'Application Version (Valid RegExp: /\d./\d./\d)',
            default: cordovaConf.version,
            validate: function(input) {
                return /\d.\d.\d/.test(input) ? true : 'invalid version string for webos platform';
            }
        }, {
            type: 'input',
            name: 'vendor',
            message: 'vendor (default is \'My Company\')',
            default: 'My Company',
            validate: function(input) {
                return typeof(input) === 'string' ? true : 'invalid vendor';
            }
        },{
            type: 'input',
            name: 'icon',
            message: 'Icon path (default is \'img/logo.png\')',
            default: 'img/logo.png',
            validate: function(input) {
                return typeof(input) === 'string' ? true : 'invalid iconpath';
            }
        }, {
            type: 'input',
            name: 'largeicon',
            message: 'LargeIcon path (default is \'img/logo.png\')',
            default: 'img/logo.png',
            validate: function(input) {
                return typeof(input) === 'string' ? true : 'invalid iconpath';
            }
        }];
        inquirer.prompt(ask, function(answers) {
            callback(answers);
        });
    }
}

function prepareDir(dir) {
    mkdirp.sync(dir);
}

function getManualWebosConfData(platformsData){
    var i,
        manualWebosConfData = null;

    if (platformsData) {
        for (i=0; i < platformsData.length; i++) {
            if (platformsData[i].$.name === 'tv-webos') {
                delete platformsData[i].$;
                manualWebosConfData = utils.trim(platformsData[i]._);
            }
        }
    }
    return manualWebosConfData;
}

module.exports = {
    prepare: function(successCallback, errorCallback, wwwSrc, dest, platformRepos, scripts) {
        console.log('\nStart preparing codes for LG Webos Platform......');

        // file path
        wwwSrc = path.resolve(wwwSrc);
        dest = path.resolve(dest);
        platformRepos = path.resolve(platformRepos);
        var userConfPath = path.join('platforms', 'userconf.json');

        // config
        var cordovaConf = utils.getCordovaConfig();
        var userData = getValidWebosConfData(userConfPath);
        if(userData) {
            confirmUseExistingData(userData, function (useExisting) {
                if(useExisting) {
                    askUserData(cordovaConf, function (data) {
                        userData.version = data.version;
                        buildProject();
                    }, true, userData);
                }
                else {
                    askUserData(cordovaConf, function (data) {
                        userData = data;
                        userData.manualConfData = getManualWebosConfData(cordovaConf.platform);
                        buildProject();
                    });
                }
            });
        }
        else {
            askUserData(cordovaConf, function (data) {
                userData = data;
                userData.manualConfData = getManualWebosConfData(cordovaConf.platform);
                buildProject();
            });
        }

        function buildProject() {
            copySrcToDest() || (errorCallback && errorCallback());
            buildPlatformAdditions() || (errorCallback && errorCallback());
            replaceTemplates() || (errorCallback && errorCallback());
            console.log('Prepared at ' + dest);

            // warning for existing meta tag for csp setting
            var targetFile = path.join(wwwSrc, 'index.html');
            var target = fs.readFileSync(targetFile, {
                encoding: 'utf8'
            });

            console.log('\nPlease note below.');
            console.log('1. On WebOS, recommended maximum icon size is 80x80.');
            if(target.match(/(<meta.*)(http-equiv)(.*=*.)("Content-Security-Policy"|'Content-Security-Policy')/)) {
                console.log('2. Remove the meta tag for csp setting in [index.html]');
                console.log('   It is not supported on this platform. It would be caused abnormal operation.');
                console.log('   Please check your file : ' + targetFile);
            }

            saveUserConfFile(userConfPath, userData);
            successCallback && successCallback();
        }

        function copySrcToDest() {
            prepareDir(dest);

            for (var key in scripts) {
                if (scripts.hasOwnProperty(key)) {
                    var to = path.join(dest, key);
                    var from = path.resolve(scripts[key]);
                    shelljs.cp('-f', from, to);
                }
            }

            shelljs.cp('-rf', path.join(wwwSrc, '*'), dest);

            return true;
        }

        function buildPlatformAdditions() {
            shelljs.cp('-rf', path.join(platformRepos, 'www', '*'), dest);
            // hack: copying hidden files(starts with dot) at root('www') to the dest directory.
            // if the dest is not exist, we can copy the platform files without this hack by using this command: cp -rf <PLATFORMREPOS>/www <destDir>
            // But the dest directory has the application files at this moment. So we need to use this hack.
            shelljs.cp('-rf', path.join(platformRepos, 'www', '.*'), dest);
            return true;
        }

        function replaceTemplates() {
            var files = fs.readdirSync(dest);
            for(var i=0; i<files.length; i++) {
                var fileName = files[i];
                if(fileName.match(/\.tmpl$/)) {
                    var tmplFilePath = path.join(dest, fileName);
                    var destFilePath = path.join(dest, fileName.replace(/\.tmpl$/, ''));
                    var template = fs.readFileSync(tmplFilePath, {
                        encoding: 'utf8'
                    });
                    var rendered = mustache.render(template, userData);

                    if(fileName.match(/\.json.tmpl$/)) {
                        var renderedJSON = JSON.parse(rendered);
                        var manualConfDataJSON = JSON.parse(userData.manualConfData);

                        var key;
                        for(key in manualConfDataJSON) {
                            if(manualConfDataJSON.hasOwnProperty(key)) {
                                renderedJSON[key] =  manualConfDataJSON[key];
                            }
                        }
                        rendered = JSON.stringify(renderedJSON, '', '\t');
                    }

                    fs.writeFileSync(destFilePath, rendered, {
                        encoding: 'utf8'
                    });
                    shelljs.rm(path.join(dest, fileName));
                }
            }
            return true;
        }
    },
    build: function(successCallback, errorCallback, data) {
        console.log('\nStart packaging LG Webos TV Platform......');
        var www = data.www || path.join('platforms', 'tv-webos', 'www');
        var dest = data.dest || path.join('platforms', 'tv-tizen', 'build');

        www = path.resolve(www);
        dest = path.resolve(dest);
        child.exec('ares --version', function(err, stdout, stderr) {
            if(err) {
                console.log(stderr);
                throw Error('The command \"ares\" failed. Make sure you have the latest Webos SDK installed, and the \"ares\" command (inside the CLI folder) is added to your path.');
            }
            console.log(stdout);

            // reference url : http://developer.lge.com/webOSTV/sdk/web-sdk/webos-tv-cli/using-webos-tv-cli/
            var result = shelljs.exec('ares-package ' + path.resolve(www));
            if(result.code) {
                throw Error(result.output);
            }
            else {
                var packagePath = result.output.match(/Creating package (.*) in/);
                if(packagePath && packagePath[1]) {
                    prepareDir(dest);
                    shelljs.mv('-f', packagePath[1], path.resolve(dest));
                    console.log('Package created at ' + path.join(dest, packagePath[1]));
                }
                else {
                    throw Error('Fail to retrieve Package File Location.');
                }
            }
        });
    }
};
<commit_msg>Remove console.log<commit_after>/*
 * Copyright 2015 Samsung Electronics Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

'use strict';

var fs = require('fs');
var path = require('path');
var mkdirp = require('mkdirp');
var inquirer = require('inquirer');
var utils = require('../lib/utils');
var shelljs = require('shelljs');
var mustache = require('mustache');
var child = require('child_process');

function saveUserConfFile(configPath, webosConf) {
    var userConfData = {};
    if (fs.existsSync(configPath)) {
        userConfData = JSON.parse(fs.readFileSync(configPath));
    }
    userConfData.webos = webosConf;
    fs.writeFileSync(configPath, JSON.stringify(userConfData, null, 2), {
        encoding: 'utf8'
    });
}

function getNextSemVersion(curver) {    // just increase revision
    var tmp = curver.split('.');
    var major = parseInt(tmp[0], 10);
    var minor = parseInt(tmp[1], 10);
    var revision = (parseInt(tmp[2], 10) || 0) + 1;

    return major + '.' + minor + '.' + revision;
}

function getValidWebosConfData(configPath) {
    console.log(configPath);
    if (!(fs.existsSync(configPath))) {
        // userconf.json is not exists
        return null;
    }
    // userconf.json is already exists
    var userConfData = JSON.parse(fs.readFileSync(configPath));

    if (!(userConfData.hasOwnProperty('webos'))) {
        // userconf.json doesn't have webos data
        return null;
    }
    var webosData = userConfData.webos;
    if (typeof webosData.name !== 'string' || webosData.name.length <= 0) {
        // name is invalid
        return null;
    }
    if (typeof webosData.vendor !== 'string' || webosData.vendor.length <= 0) {
        // vendor is invalid
        return null;
    }
    if (typeof webosData.icon !== 'string' || webosData.icon.length <= 0) {
        // icon is invalid
        return null;
    }
    if (typeof webosData.largeicon !== 'string' || webosData.largeicon.length <= 0) {
        // largeicon is invalid
        return null;
    }
    if (!validateWebosVersion(webosData.version)) {
        // version is invalid
        return null;
    }

    return webosData;
}

function validateWebosVersion(version) {
    var tmp = version.split('.'),
        i;
    if (tmp.length !== 3) {
        return false;
    }
    for (i = 0; i < tmp.length; i++) {
        if (isNaN(tmp[i])) {
            return false;
        }
    }

    return i === tmp.length;
}

function confirmUseExistingData(userData, callback) {
    console.log('');
    console.log('      > [ Stored Information ]');
    console.log('      > name        : ' + userData.name);
    console.log('      > version     : ' + userData.version);
    console.log('      > vendor      : ' + userData.vendor);

    var ask = [{
        type: 'confirm',
        name: 'useExisting',
        message: 'Already have \'userconf.json\', Do you want to use this data?'
    }];

    inquirer.prompt(ask, function(answers) {
        callback(!!answers.useExisting);
    });
}

function askUserData(cordovaConf, callback, versionOnly, userData) {
    var ask;
    if(versionOnly) {
        var curVer = userData.version;
        var updateVer = getNextSemVersion(userData.version);
        ask = [{
            type: 'input',
            name: 'version',
            message: 'Current version is ' + curVer + '. Application version: ',
            default: updateVer,
            validate: function(input) {
                return /\d.\d.\d/.test(input) ? true : 'invalid version string for webos platform';
            }
        }];
        inquirer.prompt(ask, function(answers) {
            callback(answers);
        });
    }
    else {
        ask = [{
            type: 'input',
            name: 'name',
            message: 'What\'s the application\'s name?',
            default: cordovaConf.name,
            validate: function(input) {
                return /^[a-zA-Z][^~!\.\;\\\/\|\"\'@\#$%<>^&*\()\-=+_\’\n\t\s]*$/.test(input) ? true : 'invalid name for tizen platform';
            }
        },  {
            type: 'input',
            name: 'version',
            message: 'Application Version (Valid RegExp: /\d./\d./\d)',
            default: cordovaConf.version,
            validate: function(input) {
                return /\d.\d.\d/.test(input) ? true : 'invalid version string for webos platform';
            }
        }, {
            type: 'input',
            name: 'vendor',
            message: 'vendor (default is \'My Company\')',
            default: 'My Company',
            validate: function(input) {
                return typeof(input) === 'string' ? true : 'invalid vendor';
            }
        },{
            type: 'input',
            name: 'icon',
            message: 'Icon path (default is \'img/logo.png\')',
            default: 'img/logo.png',
            validate: function(input) {
                return typeof(input) === 'string' ? true : 'invalid iconpath';
            }
        }, {
            type: 'input',
            name: 'largeicon',
            message: 'LargeIcon path (default is \'img/logo.png\')',
            default: 'img/logo.png',
            validate: function(input) {
                return typeof(input) === 'string' ? true : 'invalid iconpath';
            }
        }];
        inquirer.prompt(ask, function(answers) {
            callback(answers);
        });
    }
}

function prepareDir(dir) {
    mkdirp.sync(dir);
}

function getManualWebosConfData(platformsData){
    var i,
        manualWebosConfData = null;

    if (platformsData) {
        for (i=0; i < platformsData.length; i++) {
            if (platformsData[i].$.name === 'tv-webos') {
                delete platformsData[i].$;
                manualWebosConfData = utils.trim(platformsData[i]._);
            }
        }
    }
    return manualWebosConfData;
}

module.exports = {
    prepare: function(successCallback, errorCallback, wwwSrc, dest, platformRepos, scripts) {
        console.log('\nStart preparing codes for LG Webos Platform......');

        // file path
        wwwSrc = path.resolve(wwwSrc);
        dest = path.resolve(dest);
        platformRepos = path.resolve(platformRepos);
        var userConfPath = path.join('platforms', 'userconf.json');

        // config
        var cordovaConf = utils.getCordovaConfig();

        var userData = getValidWebosConfData(userConfPath);
        if(userData) {
            confirmUseExistingData(userData, function (useExisting) {
                if(useExisting) {
                    askUserData(cordovaConf, function (data) {
                        userData.version = data.version;
                        buildProject();
                    }, true, userData);
                }
                else {
                    askUserData(cordovaConf, function (data) {
                        userData = data;
                        userData.manualConfData = getManualWebosConfData(cordovaConf.platform);
                        buildProject();
                    });
                }
            });
        }
        else {
            askUserData(cordovaConf, function (data) {
                userData = data;
                userData.manualConfData = getManualWebosConfData(cordovaConf.platform);
                buildProject();
            });
        }

        function buildProject() {
            copySrcToDest() || (errorCallback && errorCallback());
            buildPlatformAdditions() || (errorCallback && errorCallback());
            replaceTemplates() || (errorCallback && errorCallback());
            console.log('Prepared at ' + dest);

            // warning for existing meta tag for csp setting
            var targetFile = path.join(wwwSrc, 'index.html');
            var target = fs.readFileSync(targetFile, {
                encoding: 'utf8'
            });

            console.log('\nPlease note below.');
            console.log('1. On WebOS, recommended maximum icon size is 80x80.');
            if(target.match(/(<meta.*)(http-equiv)(.*=*.)("Content-Security-Policy"|'Content-Security-Policy')/)) {
                console.log('2. Remove the meta tag for csp setting in [index.html]');
                console.log('   It is not supported on this platform. It would be caused abnormal operation.');
                console.log('   Please check your file : ' + targetFile);
            }

            saveUserConfFile(userConfPath, userData);
            successCallback && successCallback();
        }

        function copySrcToDest() {
            prepareDir(dest);

            for (var key in scripts) {
                if (scripts.hasOwnProperty(key)) {
                    var to = path.join(dest, key);
                    var from = path.resolve(scripts[key]);
                    shelljs.cp('-f', from, to);
                }
            }

            shelljs.cp('-rf', path.join(wwwSrc, '*'), dest);

            return true;
        }

        function buildPlatformAdditions() {
            shelljs.cp('-rf', path.join(platformRepos, 'www', '*'), dest);
            // hack: copying hidden files(starts with dot) at root('www') to the dest directory.
            // if the dest is not exist, we can copy the platform files without this hack by using this command: cp -rf <PLATFORMREPOS>/www <destDir>
            // But the dest directory has the application files at this moment. So we need to use this hack.
            shelljs.cp('-rf', path.join(platformRepos, 'www', '.*'), dest);
            return true;
        }

        function replaceTemplates() {
            var files = fs.readdirSync(dest);
            for(var i=0; i<files.length; i++) {
                var fileName = files[i];
                if(fileName.match(/\.tmpl$/)) {
                    var tmplFilePath = path.join(dest, fileName);
                    var destFilePath = path.join(dest, fileName.replace(/\.tmpl$/, ''));
                    var template = fs.readFileSync(tmplFilePath, {
                        encoding: 'utf8'
                    });
                    var rendered = mustache.render(template, userData);

                    if(fileName.match(/\.json.tmpl$/)) {
                        var renderedJSON = JSON.parse(rendered);
                        var manualConfDataJSON = JSON.parse(userData.manualConfData);

                        var key;
                        for(key in manualConfDataJSON) {
                            if(manualConfDataJSON.hasOwnProperty(key)) {
                                renderedJSON[key] =  manualConfDataJSON[key];
                            }
                        }
                        rendered = JSON.stringify(renderedJSON, '', '\t');
                    }

                    fs.writeFileSync(destFilePath, rendered, {
                        encoding: 'utf8'
                    });
                    shelljs.rm(path.join(dest, fileName));
                }
            }
            return true;
        }
    },
    build: function(successCallback, errorCallback, data) {
        console.log('\nStart packaging LG Webos TV Platform......');
        var www = data.www || path.join('platforms', 'tv-webos', 'www');
        var dest = data.dest || path.join('platforms', 'tv-tizen', 'build');

        www = path.resolve(www);
        dest = path.resolve(dest);
        child.exec('ares --version', function(err, stdout, stderr) {
            if(err) {
                console.log(stderr);
                throw Error('The command \"ares\" failed. Make sure you have the latest Webos SDK installed, and the \"ares\" command (inside the CLI folder) is added to your path.');
            }
            console.log(stdout);

            // reference url : http://developer.lge.com/webOSTV/sdk/web-sdk/webos-tv-cli/using-webos-tv-cli/
            var result = shelljs.exec('ares-package ' + path.resolve(www));
            if(result.code) {
                throw Error(result.output);
            }
            else {
                var packagePath = result.output.match(/Creating package (.*) in/);
                if(packagePath && packagePath[1]) {
                    prepareDir(dest);
                    shelljs.mv('-f', packagePath[1], path.resolve(dest));
                    console.log('Package created at ' + path.join(dest, packagePath[1]));
                }
                else {
                    throw Error('Fail to retrieve Package File Location.');
                }
            }
        });
    }
};
<|endoftext|>