API Docs for: 0.0.9
Show:

File: lib/dalek/config.js

/*!
 *
 * Copyright (c) 2013 Sebastian Golasch
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

'use strict';

// ext. libs
var path = require('path');
var fs = require('fs');
var _ = require('lodash');
var yaml = require('js-yaml');
var JSON5 = require('json5');
var glob = require('glob');
require('coffee-script/register');

/**
 * Configures the config instance
 *
 * @param {object} defaults Default parameter options
 * @param {object} opts Command line options
 * @constructor
 */

var Config = function (defaults, opts, advOpts) {
  this.customFilename = null;
  this.defaultFilename = 'Dalekfile';
  this.supportedExtensions = ['yml', 'json5', 'json', 'js', 'coffee'];
  this.advancedOptions = advOpts;
  this.config = this.load(defaults, opts.config, opts);
};

/**
 * Parses config data & loads config files for [DalekJS](//github.com/dalekjs/dalek) tests.
 *
 * This module is a driver plugin for [DalekJS](//github.com/dalekjs/dalek).
 * It connects Daleks testsuite with the remote testing environment of [Sauce Labs](https://saucelabs.com).
 *
 * The driver can be installed with the following command:
 *
 * ```bash
 * $ npm install dalek-driver-sauce --save-dev
 * ```
 *
 * You can use the driver by adding a config option to the your [Dalekfile](/docs/config.html)
 *
 * ```javascript
 * "driver": ["sauce"]
 * ```
 *
 * Or you can tell Dalek that it should run your tests via sauces service via the command line:
 *
 * ```bash
 * $ dalek mytest.js -d sauce
 * ```
 *
 * In order to run your tests within the Sauce Labs infrastructure, you must add your sauce username & key
 * to your dalek configuration. Those two parameters must be set in order to get this driver up & running.
 * You can specifiy them within your [Dalekfile](/docs/config.html) like so:
 *
 * ```javascript
 * "driver.sauce": {
 *   "user": "dalekjs",
 *   "key": "aaaaaa-1234-567a-1abc-1br6d9f68689"
 * }
 * ```
 *
 * It is also possible to specify a set of other extra saucy parameters like `name` & `tags`:
 *
 * ```javascript
 * "driver.sauce": {
 *   "user": "dalekjs",
 *   "key": "aaaaaa-1234-567a-1abc-1br6d9f68689",
 *   "name": "Guineapig",
 *   "tags": ["dalek", "testproject"]
 * }
 * ```
 *
 * If you would like to have a more control over the browser/OS combinations that are available, you are able
 * to configure you custom combinations:
 *
 * ```javascript
 * "browsers": [{
 *   "chrome": {
 *     "platform": "OS X 10.6",
 *     "actAs": "chrome",
 *     "version": 27
 *   },
 *   "chromeWin": {
 *     "platform": "Windows 7",
 *     "actAs": "chrome",
 *     "version": 27
 *   },
 *   "chromeLinux": {
 *     "platform": "Linux",
 *     "actAs": "chrome",
 *     "version": 26
 *   }
 * ```
 *
 * You can then call your custom browsers like so:
 *
 * ```bash
 * $ dalek mytest.js -d sauce -b chrome,chromeWin,chromeLinux
 * ```
 *
 * or you can define them in your Dalekfile:
 *
 * ```javascript
 * "browser": ["chrome", "chromeWin", "chromeLinux"]
 * ```
 *
 * A list of all available browser/OS combinations, can be found [here](https://saucelabs.com/docs/platforms).
 *
 * @module DalekJS
 * @class Config
 * @namespace Dalek
 * @part Config
 * @api
 */

Config.prototype = {

  /**
   * Checks if a config file is available
   *
   * @method checkAvailabilityOfConfigFile
   * @param {String} pathname
   * @return {String} config File path
   */

  checkAvailabilityOfConfigFile: function (pathname) {
    // check if a pathname is given,
    // then check if the file is available
    if (pathname && fs.existsSync(pathname)) {
      return fs.realpathSync(pathname);
    }

    // check if any of the default configuration files is available
    return this.supportedExtensions.reduce(this._checkFile.bind(this));
  },

  /**
   * Iterator function that checks the existance of a given file
   *
   * @method _checkFile
   * @param {String} previousValue Last iterations result
   * @param {String} ext File extension to check
   * @param {integer} idx Iteration index
   * @param {object} data File data
   * @return {String} config File path
   * @private
   */

  _checkFile: function (previousValue, ext, idx, data) {
    if (previousValue.length > 6) {
      return previousValue;
    }

    var fileToCheck = this.defaultFilename + '.' + previousValue;
    if (fs.existsSync(fileToCheck)) {
      return fs.realpathSync(fileToCheck);
    }

    return this._checkDefaultFile(ext, data);
  },

  /**
   * Iterator function that checks the existance of a the default file
   *
   * @method _checkDefaultFile
   * @param {String} ext File extension to check
   * @param {object} data File data
   * @return {String} config File path
   * @private
   */

  _checkDefaultFile: function (ext, data) {
    if (ext === data[data.length - 1]) {
      var fileToCheck = this.defaultFilename + '.' + ext;
      if (fs.existsSync(fileToCheck)) {
        return fs.realpathSync(fileToCheck);
      }
    }

    return ext;
  },

  /**
   * Loads a file & merges the results with the
   * commandline options & the default config
   *
   * @method load
   * @param {object} defaults Default config
   * @param {String} pathname Filename of the config file to load
   * @param {object} opts Command line options
   * @return {object} config Merged config data
   */

  load: function (defaults, pathname, opts) {
    var file = this.checkAvailabilityOfConfigFile(pathname);
    var data = {};

    if (!this.advancedOptions || this.advancedOptions.dalekfile !== false) {
      data = this.loadFile(file);
    }

    // remove the tests property if the array length is 0
    if (opts.tests.length === 0) {
      delete opts.tests;
    }

    if (data.tests && _.isArray(data.tests) && data.tests.length > 0) {
      var tests = [];

      //get all the files that match
      _.forEach(data.tests, function(search) {
        tests = tests.concat(glob.sync(search));
      });

      //remove duplicate files
      tests = tests.filter(function(elem, pos, self) {
        return self.indexOf(elem) === pos;
      });

      data.tests = tests;
    }

    return _.merge(defaults, data, opts, (this.advancedOptions || {}));
  },

  /**
   * Loads a config file & parses it based on the file extension
   *
   * @method loadFile
   * @param {String} pathname Filename of the config file to load
   * @return {object} data Config data
   */

  loadFile: function (pathname) {
    var ext = path.extname(pathname).replace('.', '');
    return this['read' + ext] ? this['read' + ext](pathname) : {};
  },

  /**
   * Fetches & returns a config item
   *
   * @method get
   * @param {String} item Key of the item to load
   * @return {mixed|null} data Requested config data
   */

  get: function (item) {
    return this.config[item] || null;
  },

  /**
   * Loads a json config file
   *
   * @method readjson
   * @return {object} data Parsed config data
   */

  readjson: function (pathname) {
    var contents = fs.readFileSync((pathname || this.defaultFilename + '.json'), 'utf8');
    return JSON.parse(contents);
  },

  /**
   * Loads a json5 config file
   *
   * @method readJson5
   * @return {object} data Parsed config data
   */

  readjson5: function (pathname) {
    var contents = fs.readFileSync((pathname || this.defaultFilename + '.json5'), 'utf8');
    return JSON5.parse(contents);
  },

  /**
   * Loads a yaml config file
   *
   * @method readyaml
   * @return {object} data Parsed config data
   */

  readyml: function (pathname) {
    var contents = fs.readFileSync((pathname || this.defaultFilename + '.yml'), 'utf8');
    return yaml.load(contents);
  },

  /**
   * Loads a javascript config file
   *
   * @method readjs
   * @return {object} data Parsed config data
   */

  readjs: function (pathname) {
    return require((pathname || this.defaultFilename));
  },

  /**
   * Loads a coffescript config file
   *
   * @method readcoffee
   * @return {object} data Parsed config data
   */

  readcoffee: function (pathname) {
    return require((pathname || this.defaultFilename));
  },

  /**
   * Verifies if a reporter is given, exists & is valid
   *
   * @method verifyReporters
   * @return {array} data List of verified reporters
   */

  verifyReporters: function (reporters, reporter) {
    return _.compact(this._verify(reporters, 'isReporter', reporter));
  },

  /**
   * Verifies if a driver is given, exists & is valid
   *
   * @method verifyDrivers
   * @return {array} data List of verified drivers
   */

  verifyDrivers: function (drivers, driver) {
    return _.compact(this._verify(drivers, 'isDriver', driver));
  },

  /**
   * Verifies if a driver is given, exists & is valid
   *
   * @method _verify
   * @param {array} check Data that should be mapped
   * @param {string} fn Name of the function that should be invoked on the veryify object
   * @param {object} instance Object instance where the verify function should be invoked
   * @return {array} data List of verified items
   * @private
   */

  _verify: function (check, fn, instance) {
    return check.map(this._verifyIterator.bind(this, fn, instance));
  },

  /**
   * Verifies if a driver is given, exists & is valid
   *
   * @method _verifyIterator
   * @param {string} fn Name of the function that should be invoked on the veryify object
   * @param {object} instance Object instance where the verify function should be invoked
   * @param {string} elm Name of the element that should be checked
   * @return {string|null} element name of the verified element or false if checked failed
   * @priavte
   */

  _verifyIterator: function (fn, instance, elm) {
    return instance[fn](elm) ? elm : false;
  }
};

// export the module
module.exports = Config;