<commit_before>var fs = require('fs');
var path = require('path');
var Q = require('q');
var clone = require('lodash.clone');
var defaults = require('lodash.defaultsdeep');

var Runner = require('../run.js');
var Logger = require('../../util/logger.js');
var Utils = require('../../util/utils.js');
var Selenium = require('../selenium.js');
var ChildProcess = require('./child-process.js');
var ErrorHandler = require('./errorhandler.js');

var SETTINGS_DEPRECTED_VAL = './settings.json';
var SETTINGS_JS_FILE = './nightwatch.conf.js';

function CliRunner(argv) {
  this.settings = null;
  this.argv = argv;
  this.test_settings = null;
  this.output_folder = '';
  this.parallelMode = false;
  this.runningProcesses = {};
  this.cli = require('./cli.js');
}

CliRunner.prototype = {
  /**
   * @param {function} [done]
   * @deprecated - use .setup instead
   * @returns {CliRunner}
   */
  init : function(done) {
    this
      .readSettings()
      .parseTestSettings({}, done);

    return this;
  },

  /**
   * @param {object} [settings]
   * @param {function} [done]
   * @returns {CliRunner}
   */
  setup : function(settings, done) {
    this
      .readSettings()
      .parseTestSettings(settings, done);

    return this;
  },

  /**
   * Read the provided config json file; defaults to settings.json if one isn't provided
   */
  readSettings : function() {
    // use default nightwatch.json file if we haven't received another value

    if (this.cli.command('config').isDefault(this.argv.config)) {
      var defaultValue = this.cli.command('config').defaults();
      var localJsValue = path.resolve(SETTINGS_JS_FILE);

      if (fs.existsSync(SETTINGS_JS_FILE)) {
        this.argv.config = localJsValue;
      } else if (fs.existsSync(defaultValue)) {
        this.argv.config = path.join(path.resolve('./'), this.argv.config);
      } else if (fs.existsSync(SETTINGS_DEPRECTED_VAL)) {
        this.argv.config = path.join(path.resolve('./'), SETTINGS_DEPRECTED_VAL);
      } else {
        var binFolder = path.resolve(__dirname + '/../../../bin');
        var defaultFile = path.join(binFolder, this.argv.config);
        if (fs.existsSync(defaultFile)) {
          this.argv.config = defaultFile;
        } else {
          this.argv.config = path.join(binFolder, SETTINGS_DEPRECTED_VAL);
        }
      }
    } else {
      this.argv.config = path.resolve(this.argv.config);
    }

    this.argv.env = typeof this.argv.env == 'string' ? this.argv.env : 'default';

    // reading the settings file
    try {
      this.settings = require(this.argv.config);
      this.replaceEnvVariables();

      this.manageSelenium = !this.isParallelMode() && this.settings.selenium &&
        this.settings.selenium.start_process || false;

      if (typeof this.settings.src_folders == 'string') {
        this.settings.src_folders = [this.settings.src_folders];
      }

      this.settings.output = this.settings.output || typeof this.settings.output == 'undefined';
    } catch (ex) {
      Logger.error(ex.stack);
      this.settings = {};
    }

    return this;
  },

  /**
   * Looks for pattern ${VAR_NAME} in settings
   * @param {Object} [target]
   */
  replaceEnvVariables : function(target) {
    target = target || this.settings;
    for (var key in target) {
      switch(typeof target[key]) {
      case 'object':
        this.replaceEnvVariables(target[key]);
        break;
      case 'string':
        target[key] = target[key].replace(/\$\{(\w+)\}/g, function(match, varName) {
          return process.env[varName] || '${' + varName + '}';
        });
        break;
      }
    }

    return this;
  },


  /**
   * Reads globals from an external js or json file set in the settings file
   * @return {CliRunner}
   */
  readExternalGlobals : function () {
    if (!this.settings.globals_path) {
      return this;
    }

    var fullPath = path.resolve(this.settings.globals_path);

    try {
      var externalGlobals = require(fullPath);

      if (!externalGlobals) {
        return this;
      }

      // if we already have globals, make a copy of them
      var globals = this.test_settings.globals ? clone(this.test_settings.globals, true) : {};

      if (externalGlobals.hasOwnProperty(this.argv.env)) {
        for (var prop in externalGlobals[this.argv.env]) {
          externalGlobals[prop] = externalGlobals[this.argv.env][prop];
        }
      }

      defaults(globals, externalGlobals);
      this.test_settings.globals = globals;

      return this;
    } catch (err) {
      var originalMsg = Logger.colors.red(err.name + ': ' + err.message);

      err.name = 'Error reading external global file failed';
      err.message = 'using '+ fullPath + ':\n' + originalMsg;

      throw err;
    }
  },

  /**
   * @returns {CliRunner}
   */
  setOutputFolder : function() {
    var isDisabled = this.settings.output_folder === false && typeof(this.test_settings.output_folder) == 'undefined' ||
      this.test_settings.output_folder === false;
    var isDefault = this.cli.command('output').isDefault(this.argv.output);
    var folder = this.test_settings.output_folder || this.settings.output_folder;

    this.output_folder = isDisabled ? false : (isDefault && folder || this.argv.output);

    return this;
  },

  singleTestRun: function () {
    return typeof this.argv.test == 'string';
  },

  getTestSourceForSingle: function(targetPath) {
    var testsource;

    if (targetPath && path.resolve(targetPath) === targetPath) {
      testsource = targetPath;
    } else {
      testsource = path.join(process.cwd(), targetPath);
    }

    if (testsource.substr(-3) != '.js') {
      testsource += '.js';
    }

    return testsource;
  },

  /**
   * Returns the path where the tests are located
   * @returns {*}
   */
  getTestSource : function() {
    var testsource;

    if (this.singleTestRun()) {
      testsource = this.getTestSourceForSingle(this.argv.test);

      try {
        fs.statSync(testsource);
      } catch (err) {
        throw new Error('There was a problem reading the test file: ' + testsource);
      }
    } else {
      if (this.argv.testcase) {
        this.argv.testcase = null;
        ErrorHandler.logWarning('Option --testcase used without --test is ignored.');
      }

      if (this.argv.group) {
        // add support for multiple groups
        if (typeof this.argv.group == 'string') {
          this.argv.group = this.argv.group.split(',');
        }

        if (this.argv.group.length == 1) {
          testsource = this.findGroupPath(this.argv.group[0]);
        } else {
          testsource = this.findGroupPathMultiple(this.argv.group);
        }
        console.log('testsource', testsource)
      } else {
        testsource = this.settings.src_folders;
      }
    }

    return testsource;
  },

  /**
   * Attempting to prepend the base source folder to the group name;<commit_msg>removed console log<commit_after>var fs = require('fs');
var path = require('path');
var Q = require('q');
var clone = require('lodash.clone');
var defaults = require('lodash.defaultsdeep');

var Runner = require('../run.js');
var Logger = require('../../util/logger.js');
var Utils = require('../../util/utils.js');
var Selenium = require('../selenium.js');
var ChildProcess = require('./child-process.js');
var ErrorHandler = require('./errorhandler.js');

var SETTINGS_DEPRECTED_VAL = './settings.json';
var SETTINGS_JS_FILE = './nightwatch.conf.js';

function CliRunner(argv) {
  this.settings = null;
  this.argv = argv;
  this.test_settings = null;
  this.output_folder = '';
  this.parallelMode = false;
  this.runningProcesses = {};
  this.cli = require('./cli.js');
}

CliRunner.prototype = {
  /**
   * @param {function} [done]
   * @deprecated - use .setup instead
   * @returns {CliRunner}
   */
  init : function(done) {
    this
      .readSettings()
      .parseTestSettings({}, done);

    return this;
  },

  /**
   * @param {object} [settings]
   * @param {function} [done]
   * @returns {CliRunner}
   */
  setup : function(settings, done) {
    this
      .readSettings()
      .parseTestSettings(settings, done);

    return this;
  },

  /**
   * Read the provided config json file; defaults to settings.json if one isn't provided
   */
  readSettings : function() {
    // use default nightwatch.json file if we haven't received another value

    if (this.cli.command('config').isDefault(this.argv.config)) {
      var defaultValue = this.cli.command('config').defaults();
      var localJsValue = path.resolve(SETTINGS_JS_FILE);

      if (fs.existsSync(SETTINGS_JS_FILE)) {
        this.argv.config = localJsValue;
      } else if (fs.existsSync(defaultValue)) {
        this.argv.config = path.join(path.resolve('./'), this.argv.config);
      } else if (fs.existsSync(SETTINGS_DEPRECTED_VAL)) {
        this.argv.config = path.join(path.resolve('./'), SETTINGS_DEPRECTED_VAL);
      } else {
        var binFolder = path.resolve(__dirname + '/../../../bin');
        var defaultFile = path.join(binFolder, this.argv.config);
        if (fs.existsSync(defaultFile)) {
          this.argv.config = defaultFile;
        } else {
          this.argv.config = path.join(binFolder, SETTINGS_DEPRECTED_VAL);
        }
      }
    } else {
      this.argv.config = path.resolve(this.argv.config);
    }

    this.argv.env = typeof this.argv.env == 'string' ? this.argv.env : 'default';

    // reading the settings file
    try {
      this.settings = require(this.argv.config);
      this.replaceEnvVariables();

      this.manageSelenium = !this.isParallelMode() && this.settings.selenium &&
        this.settings.selenium.start_process || false;

      if (typeof this.settings.src_folders == 'string') {
        this.settings.src_folders = [this.settings.src_folders];
      }

      this.settings.output = this.settings.output || typeof this.settings.output == 'undefined';
    } catch (ex) {
      Logger.error(ex.stack);
      this.settings = {};
    }

    return this;
  },

  /**
   * Looks for pattern ${VAR_NAME} in settings
   * @param {Object} [target]
   */
  replaceEnvVariables : function(target) {
    target = target || this.settings;
    for (var key in target) {
      switch(typeof target[key]) {
      case 'object':
        this.replaceEnvVariables(target[key]);
        break;
      case 'string':
        target[key] = target[key].replace(/\$\{(\w+)\}/g, function(match, varName) {
          return process.env[varName] || '${' + varName + '}';
        });
        break;
      }
    }

    return this;
  },


  /**
   * Reads globals from an external js or json file set in the settings file
   * @return {CliRunner}
   */
  readExternalGlobals : function () {
    if (!this.settings.globals_path) {
      return this;
    }

    var fullPath = path.resolve(this.settings.globals_path);

    try {
      var externalGlobals = require(fullPath);

      if (!externalGlobals) {
        return this;
      }

      // if we already have globals, make a copy of them
      var globals = this.test_settings.globals ? clone(this.test_settings.globals, true) : {};

      if (externalGlobals.hasOwnProperty(this.argv.env)) {
        for (var prop in externalGlobals[this.argv.env]) {
          externalGlobals[prop] = externalGlobals[this.argv.env][prop];
        }
      }

      defaults(globals, externalGlobals);
      this.test_settings.globals = globals;

      return this;
    } catch (err) {
      var originalMsg = Logger.colors.red(err.name + ': ' + err.message);

      err.name = 'Error reading external global file failed';
      err.message = 'using '+ fullPath + ':\n' + originalMsg;

      throw err;
    }
  },

  /**
   * @returns {CliRunner}
   */
  setOutputFolder : function() {
    var isDisabled = this.settings.output_folder === false && typeof(this.test_settings.output_folder) == 'undefined' ||
      this.test_settings.output_folder === false;
    var isDefault = this.cli.command('output').isDefault(this.argv.output);
    var folder = this.test_settings.output_folder || this.settings.output_folder;

    this.output_folder = isDisabled ? false : (isDefault && folder || this.argv.output);

    return this;
  },

  singleTestRun: function () {
    return typeof this.argv.test == 'string';
  },

  getTestSourceForSingle: function(targetPath) {
    var testsource;

    if (targetPath && path.resolve(targetPath) === targetPath) {
      testsource = targetPath;
    } else {
      testsource = path.join(process.cwd(), targetPath);
    }

    if (testsource.substr(-3) != '.js') {
      testsource += '.js';
    }

    return testsource;
  },

  /**
   * Returns the path where the tests are located
   * @returns {*}
   */
  getTestSource : function() {
    var testsource;

    if (this.singleTestRun()) {
      testsource = this.getTestSourceForSingle(this.argv.test);

      try {
        fs.statSync(testsource);
      } catch (err) {
        throw new Error('There was a problem reading the test file: ' + testsource);
      }
    } else {
      if (this.argv.testcase) {
        this.argv.testcase = null;
        ErrorHandler.logWarning('Option --testcase used without --test is ignored.');
      }

      if (this.argv.group) {
        // add support for multiple groups
        if (typeof this.argv.group == 'string') {
          this.argv.group = this.argv.group.split(',');
        }

        if (this.argv.group.length == 1) {
          testsource = this.findGroupPath(this.argv.group[0]);
        } else {
          testsource = this.findGroupPathMultiple(this.argv.group);
        }
      } else {
        testsource = this.settings.src_folders;
      }
    }

    return testsource;
  },

  /**
   * Attempting to prepend the base source folder to the group name;
{'fraction_non_alphanumeric': 0.1452270081490105, 'fraction_numerical': 0.00043655413271245633, 'mean_word_length': 2.588772845953003, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1976299', 'n_tokens_mistral': 4299, 'n_tokens_neox': 4192, 'n_words': 899}