<commit_before>'use strict';

angular.module('myApp.view-report', ['ngRoute'])

.config(['$routeProvider', function($routeProvider) {
  $routeProvider.when('/report', {
    templateUrl: 'view-report/view-report.html',
    controller: 'viewRptCtrl'
  });
}])

.controller('viewRptCtrl', ['$scope', '$routeParams', '$location', 'cfWork', 'moment', 'hcSeries', 'TaskList',
	function($scope, $routeParams, $location, cfWork, moment, hcSeries, TaskList) {

  $scope.debug = function (value) {
    console.debug('debug', value);
  };

  var dims, groups, weekdays = 'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday'.split(',');

  $scope.changeSort = function (predicate) {
    $scope.sort.field = predicate;
    $scope.sort.reverse = !$scope.sort.reverse;
  };

  // Builds an array of objects in form { key: '', value: [] }
  function dayRanges () {
    // todo: has to build the ranges relvative to a particular day
    // e.g. you are looking at a particular day for a timesheet

    var eod = moment().endOf('day').valueOf();

    return [
      {key: 'All', value: null},
      {key: 'Today', value: [moment().startOf('day').valueOf(), eod]},
      {key: 'This Week', value: [moment().startOf('week').valueOf(), moment().endOf('week').valueOf()]},
      {key: 'This Month', value: [moment().startOf('month').valueOf(), moment().endOf('month').valueOf()]},
      {key: 'This FY', value: [0,0]},
      {key: 'Last FY', value: [0,0]},
      {key: 'Last 7 days',   value: [moment().startOf('day').subtract(6, 'days').valueOf(), eod]},
      {key: 'Last 14 days',  value: [moment().startOf('day').subtract(13, 'days').valueOf(), eod]},
      {key: 'Last 20 days',  value: [moment().startOf('day').subtract(19, 'days').valueOf(), eod]},
      {key: 'Last 365 days', value: [moment().startOf('day').subtract(364, 'days').valueOf(), eod]}
    ];
  };

  // Helper function to find a value in an array
  function lookup (key, array) {
    let value = null;
    array.some(function (x) {
      if (x.key === key) {
        value = x.value;
        return true;  // break out
      }
    });
    return value;
  }

  function hcMapFuncIn (x, i, a) {
    if (angular.isString(x.key))
      return {name: x.key, y: x.value.income};
    else
      return {x: x.key, y: x.value.income};
  };

  function hcMapFuncHr (x, i, a) {
    if (angular.isString(x.key))
      return {name: x.key, y: x.value.hours};
    else
      return {x: x.key, y: x.value.hours};
  };

  // For each group rebuild the associated series
  $scope.rebuildSeries = function () {
    $scope.charts.week.seriesIn = hcSeries.convert(groups.week, null, hcMapFuncIn);
    $scope.charts.week.seriesHr = hcSeries.convert(groups.week, null, hcMapFuncHr);
  };

  $scope.rebuildTasks = function () {
    $scope.filterTask(null);

    // Populate Task array and create a grand total
    $scope.tasks = [];
    $scope.taskTotals = { hours: 0.0, units: 0.0, income: 0.0 };
    groups.task.all().forEach(function (x, i, a) {
      $scope.tasks.push({
        name: x.key,
        hours: x.value.hours,
        units: x.value.units,
        income: x.value.income
      });
      $scope.taskTotals.hours += x.value.hours;
      $scope.taskTotals.units += x.value.units;
      $scope.taskTotals.income += x.value.income;
    });
  };

  $scope.rebuildAll = function () {
    $scope.rebuildSeries();
    $scope.rebuildTasks();
  };

  // Filters
  $scope.filterProject = function () {
    // Adjust filters
    dims.project.filter(lookup($scope.filters.project, $scope.selects.projects));

    // Rebuild scope variables
    $scope.rebuildAll();
  };

  // todo: this need to rebuild the crossfilter to re-query
  // work from the period selected. This will cut down the
  // data substantially.
  $scope.filterDay = function () {
    console.debug('view-report => filterDay');
    console.debug(Date.now());

    // Re-create the crossfilter
    let range = lookup($scope.filters.day, $scope.selects.dates) || [0, Date.now()];
    let cf = cfWork.create(range);
    dims = cf.dims;
    groups = cf.groups;

    console.debug(Date.now());

    // Rebuild scope variables
    $scope.rebuildAll();

    console.debug(Date.now());
  };

  $scope.filterBilled = function () {
    // Adjust filters
    dims.billed.filter(lookup($scope.filters.billed, $scope.selects.billed));

    // Rebuild scope variables
    $scope.rebuildAll();
  };

  $scope.filterTask = function (value) {
      // Is filter already active? If so, cancel it
    if (value == null || $scope.filters.task == value) {
      $scope.filters.task = null;
      dims.task.filter(null);
      $scope.topWork = null;
    } else {
      $scope.filters.task = value;
      dims.task.filter(value);
      $scope.topWork = dims.day.top(Infinity);
    }

    // Rebuild series only - tasks won't change
    $scope.rebuildSeries();
  };

  $scope.filter = function (dim, select) {
    if (!select) {
      dims[dim].filterAll();
    } else if (angular.isArray(select)) {
      dims[dim].filterRange(select);
    } else {
      dims[dim].filterFunction(function(d) {
        if (d in select)
          return true;
        else
          return false;
      });
    }

    // Trigger charts to redraw
    $scope.rebuildAll();
  }

  $scope.init = function () {
    console.debug('view-report => init()');
    console.debug(Date.now());

    $scope.sort = {};
    $scope.sort.field = 'name';
    $scope.sort.reverse = false;

    $scope.selects = {},
    $scope.selects.projects = TaskList.projects.map(function (x) {
      return {key: x, value: x};
    });
    $scope.selects.projects.unshift({key: 'All', value: null});
    $scope.selects.dates = dayRanges();
    $scope.selects.billed = [
      {key: 'All', value: null},
      {key: 'Billed', value: true},
      {key: 'Unbilled', value: false}
    ];

    $scope.filters = {};
    $scope.filters.project = 'All';
    $scope.filters.day = 'Last 20 days';
    $scope.filters.billed = 'All';

    //
    // Charts
    //
    $scope.charts = {};
    $scope.charts.week = {};
    $scope.charts.week.init = {
      xAxis: {
        type: 'datetime'
      },
      yAxis: [{
        labels: {
          format: '${value}'
        },
        title: {
          text: ''
        }
      }, {
        labels: {
          format: '{value} hr'
        },
        title: {
          text: ''
        },
        opposite: true
      }],
      title: {
        text: ''
      },
      legend: {
        enabled: true
      }
    };

    // Create the crossfilter and prime charts, tables etc
    $scope.filterDay();

    console.debug(Date.now());
  };

  // Initialize
  $scope.init();

}]);

<commit_msg>Implemented financial year searches<commit_after>'use strict';

angular.module('myApp.view-report', ['ngRoute'])

.config(['$routeProvider', function($routeProvider) {
  $routeProvider.when('/report', {
    templateUrl: 'view-report/view-report.html',
    controller: 'viewRptCtrl'
  });
}])

.controller('viewRptCtrl', ['$scope', '$routeParams', '$location', 'cfWork', 'moment', 'hcSeries', 'TaskList',
	function($scope, $routeParams, $location, cfWork, moment, hcSeries, TaskList) {

  $scope.debug = function (value) {
    console.debug('debug', value);
  };

  var dims, groups, weekdays = 'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday'.split(',');

  $scope.changeSort = function (predicate) {
    $scope.sort.field = predicate;
    $scope.sort.reverse = !$scope.sort.reverse;
  };

  // Builds an array of objects in form { key: '', value: [] }
  function dayRanges () {
    // todo: has to build the ranges relvative to a particular day
    // e.g. you are looking at a particular day for a timesheet

    var eod = moment().endOf('day').valueOf();
    var startFY = moment();
    var endFY;
    startFY.month() < 6 ? startFY.startOf('year').subtract(6, 'months') : startFY.startOf('year').add(6, 'months');
    endFY = startFY.clone().add(1,'years').subtract(1,'ms');

    return [
      {key: 'All', value: null},
      {key: 'Today', value: [moment().startOf('day').valueOf(), eod]},
      {key: 'This Week', value: [moment().startOf('week').valueOf(), moment().endOf('week').valueOf()]},
      {key: 'This Month', value: [moment().startOf('month').valueOf(), moment().endOf('month').valueOf()]},
      {key: 'This FY', value: [startFY.valueOf(), endFY.valueOf()]},
      {key: 'Last FY', value: [startFY.subtract(1,'years'),endFY.subtract(1,'years')]},
      {key: 'Last 7 days',   value: [moment().startOf('day').subtract(6, 'days').valueOf(), eod]},
      {key: 'Last 14 days',  value: [moment().startOf('day').subtract(13, 'days').valueOf(), eod]},
      {key: 'Last 20 days',  value: [moment().startOf('day').subtract(19, 'days').valueOf(), eod]},
      {key: 'Last 365 days', value: [moment().startOf('day').subtract(364, 'days').valueOf(), eod]}
    ];
  };

  // Helper function to find a value in an array
  function lookup (key, array) {
    let value = null;
    array.some(function (x) {
      if (x.key === key) {
        value = x.value;
        return true;  // break out
      }
    });
    return value;
  }

  function hcMapFuncIn (x, i, a) {
    if (angular.isString(x.key))
      return {name: x.key, y: x.value.income};
    else
      return {x: x.key, y: x.value.income};
  };

  function hcMapFuncHr (x, i, a) {
    if (angular.isString(x.key))
      return {name: x.key, y: x.value.hours};
    else
      return {x: x.key, y: x.value.hours};
  };

  // For each group rebuild the associated series
  $scope.rebuildSeries = function () {
    $scope.charts.week.seriesIn = hcSeries.convert(groups.week, null, hcMapFuncIn);
    $scope.charts.week.seriesHr = hcSeries.convert(groups.week, null, hcMapFuncHr);
  };

  $scope.rebuildTasks = function () {
    $scope.filterTask(null);

    // Populate Task array and create a grand total
    $scope.tasks = [];
    $scope.taskTotals = { hours: 0.0, units: 0.0, income: 0.0 };
    groups.task.all().forEach(function (x, i, a) {
      $scope.tasks.push({
        name: x.key,
        hours: x.value.hours,
        units: x.value.units,
        income: x.value.income
      });
      $scope.taskTotals.hours += x.value.hours;
      $scope.taskTotals.units += x.value.units;
      $scope.taskTotals.income += x.value.income;
    });
  };

  $scope.rebuildAll = function () {
    $scope.rebuildSeries();
    $scope.rebuildTasks();
  };

  // Filters
  $scope.filterProject = function () {
    // Adjust filters
    dims.project.filter(lookup($scope.filters.project, $scope.selects.projects));

    // Rebuild scope variables
    $scope.rebuildAll();
  };

  // todo: this need to rebuild the crossfilter to re-query
  // work from the period selected. This will cut down the
  // data substantially.
  $scope.filterDay = function () {
    // Re-create the crossfilter
    let range = lookup($scope.filters.day, $scope.selects.dates) || [0, Date.now()];
    let cf = cfWork.create(range);
    dims = cf.dims;
    groups = cf.groups;

    // Rebuild scope variables
    $scope.rebuildAll();
  };

  $scope.filterBilled = function () {
    // Adjust filters
    dims.billed.filter(lookup($scope.filters.billed, $scope.selects.billed));

    // Rebuild scope variables
    $scope.rebuildAll();
  };

  $scope.filterTask = function (value) {
      // Is filter already active? If so, cancel it
    if (value == null || $scope.filters.task == value) {
      $scope.filters.task = null;
      dims.task.filter(null);
      $scope.topWork = null;
    } else {
      $scope.filters.task = value;
      dims.task.filter(value);
      $scope.topWork = dims.day.top(Infinity);
    }

    // Rebuild series only - tasks won't change
    $scope.rebuildSeries();
  };

  $scope.filter = function (dim, select) {
    if (!select) {
      dims[dim].filterAll();
    } else if (angular.isArray(select)) {
      dims[dim].filterRange(select);
    } else {
      dims[dim].filterFunction(function(d) {
        if (d in select)
          return true;
        else
          return false;
      });
    }

    // Trigger charts to redraw
    $scope.rebuildAll();
  }

  $scope.init = function () {
    $scope.sort = {};
    $scope.sort.field = 'name';
    $scope.sort.reverse = false;

    $scope.selects = {},
    $scope.selects.projects = TaskList.projects.map(function (x) {
      return {key: x, value: x};
    });
    $scope.selects.projects.unshift({key: 'All', value: null});
    $scope.selects.dates = dayRanges();
    $scope.selects.billed = [
      {key: 'All', value: null},
      {key: 'Billed', value: true},
      {key: 'Unbilled', value: false}
    ];

    $scope.filters = {};
    $scope.filters.project = 'All';
    $scope.filters.day = 'Last 20 days';
    $scope.filters.billed = 'All';

    //
    // Charts
    //
    $scope.charts = {};
    $scope.charts.week = {};
    $scope.charts.week.init = {
      xAxis: {
        type: 'datetime'
      },
      yAxis: [{
        labels: {
          format: '${value}'
        },
        title: {
          text: ''
        }
      }, {
        labels: {
          format: '{value} hr'
        },
        title: {
          text: ''
        },
        opposite: true
      }],
      title: {
        text: ''
      },
      legend: {
        enabled: true
      }
    };

    // Create the crossfilter and prime charts, tables etc
    $scope.filterDay();

  };

  // Initialize
  $scope.init();

}]);

<|endoftext|>


<commit_before>/*
 * grunt-htmlrender
 * https://github.com/skuligowski/grunt-htmlrender
 *
 * Copyright (c) 2014 Sebastian Kuligowski
 * Licensed under the MIT license.
 */

'use strict';

module.exports = function(grunt) {

  var fs = require('fs'),
      path = require('path'),
      _ = require('lodash-node');

  var varsCache = {},
      partialsCache = {},

      includeRegExp = new RegExp("<%include([^>]+)%>", "gim"),
      templateRegExp = new RegExp("<%template([^>]+)%>", "gim");

  var getAttr = function(attr, content) {
    var regExp = new RegExp(attr + '="([0-9a-zA-Z\/_.-]+)"');
    var attrMatch = content.match(regExp);
    if (!attrMatch) {
      return null;
    }

    return attrMatch[1];
  };

  var renderTemplates = function(html) {
    return html.replace(templateRegExp, function(all, content) {
      var id = getAttr('id', content),
        src = getAttr('src', content);

      return '<script id="' + id + '" type="text/ng-template"><%include src="' + src + '"%></script>';
    });
  };

  var renderData = function(html) {
    _.forEach(varsCache, function(value, prop) {
      var re = new RegExp('<%=\\s*'+prop+'\\s*%>', 'g');
      html = html.replace(re, value);
    });
    return html;
  };

  var refreshPartials = function(files) {
    var modifiedPartials = [];
    _.forEach(files, function(partialPath) {
      var mtime = fs.statSync(partialPath).mtime,
        currentPartial = partialsCache[partialPath];

      if (!currentPartial || currentPartial.mtime < mtime) {
        var partial = fs.readFileSync(partialPath);
        partialsCache[partialPath] = {
          content: renderTemplates(renderData(partial.toString())),
          mtime: mtime
        };
        modifiedPartials.push(partialPath);
        grunt.log.writeln('Refreshing ' + partialPath);
      }
    });
    return modifiedPartials.length > 0;
  };

  var includeFile = function(dir, html) {
    var matches = html.match(includeRegExp);

    if (!matches) {
      return html;
    }

    for(var i = 0, max = matches.length; i < max; i++) {
      var src = getAttr('src', matches[i]),
        partialPath = path.join(dir, src),
        partial = partialsCache[partialPath];

      if (typeof partial !== "undefined") {
        var partialContent = includeFile(path.dirname(partialPath), partial.content);
        html = html.replace(matches[i], partialContent);
      } else {
        grunt.log.warn('Partial not found: ' + partialPath);
      }
    }

    return html;
  };

  var renderPartial = function(partialPath) {
    var partial = partialsCache[partialPath];
    if (!partial) {
      grunt.fail.fatal('Partial ' + partialPath + ' not found!');
    }
    return includeFile(path.dirname(partialPath), partial.content);
  };

  var evaluateVars = function(vars) {
    var oldVars = _.clone(varsCache);
    varsCache = {};
    _.forEach(vars, function(value, paramName) {
      varsCache[paramName] = _.isFunction(value) ? value() : value;
    });
    return !_.isEqual(oldVars, varsCache);
  };

  grunt.registerMultiTask('htmlrender', 'Renders html by including its partials', function() {
    var options = this.options({
      src: '*.html',
      vars: {}
    });

    var varsChanged = evaluateVars(options.vars);
    if (varsChanged) {
      partialsCache = {};
    }

    var partialsChanged = refreshPartials(grunt.file.expand(options.src));
    if (!partialsChanged) {
      grunt.log.ok('Nothing changed, exiting...');
      return;
    }

    this.files.forEach(function(f) {
      if (f.src.length > 1) {
        grunt.fail.fatal('Only one file can be rendered, ' + f.orig.src + ' found.');
      }

      if (f.src.length === 0) {
        grunt.fail.fatal(f.orig.src + ' not found!');
      }

      grunt.file.write(f.dest, renderPartial(f.orig.src[0]));
      grunt.log.ok(f.orig.src[0] + ' was successfuly rendered.');
    });
  });
};
<commit_msg>Allowing expanded format of file<commit_after>/*
 * grunt-htmlrender
 * https://github.com/skuligowski/grunt-htmlrender
 *
 * Copyright (c) 2014 Sebastian Kuligowski
 * Licensed under the MIT license.
 */

'use strict';

module.exports = function(grunt) {

  var fs = require('fs'),
      path = require('path'),
      _ = require('lodash-node');

  var varsCache = {},
      partialsCache = {},

      includeRegExp = new RegExp("<%include([^>]+)%>", "gim"),
      templateRegExp = new RegExp("<%template([^>]+)%>", "gim");

  var getAttr = function(attr, content) {
    var regExp = new RegExp(attr + '="([0-9a-zA-Z\/_.-]+)"');
    var attrMatch = content.match(regExp);
    if (!attrMatch) {
      return null;
    }

    return attrMatch[1];
  };

  var renderTemplates = function(html) {
    return html.replace(templateRegExp, function(all, content) {
      var id = getAttr('id', content),
        src = getAttr('src', content);

      return '<script id="' + id + '" type="text/ng-template"><%include src="' + src + '"%></script>';
    });
  };

  var renderData = function(html) {
    _.forEach(varsCache, function(value, prop) {
      var re = new RegExp('<%=\\s*'+prop+'\\s*%>', 'g');
      html = html.replace(re, value);
    });
    return html;
  };

  var refreshPartials = function(files) {
    var modifiedPartials = [];
    _.forEach(files, function(partialPath) {
      var mtime = fs.statSync(partialPath).mtime,
        currentPartial = partialsCache[partialPath];

      if (!currentPartial || currentPartial.mtime < mtime) {
        var partial = fs.readFileSync(partialPath);
        partialsCache[partialPath] = {
          content: renderTemplates(renderData(partial.toString())),
          mtime: mtime
        };
        modifiedPartials.push(partialPath);
        grunt.log.writeln('Refreshing ' + partialPath);
      }
    });
    return modifiedPartials.length > 0;
  };

  var includeFile = function(dir, html) {
    var matches = html.match(includeRegExp);

    if (!matches) {
      return html;
    }

    for(var i = 0, max = matches.length; i < max; i++) {
      var src = getAttr('src', matches[i]),
        partialPath = path.join(dir, src),
        partial = partialsCache[partialPath];

      if (typeof partial !== "undefined") {
        var partialContent = includeFile(path.dirname(partialPath), partial.content);
        html = html.replace(matches[i], partialContent);
      } else {
        grunt.log.warn('Partial not found: ' + partialPath);
      }
    }

    return html;
  };

  var renderPartial = function(partialPath) {
    var partial = partialsCache[partialPath];
    if (!partial) {
      grunt.fail.fatal('Partial ' + partialPath + ' not found!');
    }
    return includeFile(path.dirname(partialPath), partial.content);
  };

  var evaluateVars = function(vars) {
    var oldVars = _.clone(varsCache);
    varsCache = {};
    _.forEach(vars, function(value, paramName) {
      varsCache[paramName] = _.isFunction(value) ? value() : value;
    });
    return !_.isEqual(oldVars, varsCache);
  };

  grunt.registerMultiTask('htmlrender', 'Renders html by including its partials', function() {
    var options = this.options({
      src: '*.html',
      vars: {}
    });

    var varsChanged = evaluateVars(options.vars);
    if (varsChanged) {
      partialsCache = {};
    }

    var partialsChanged = refreshPartials(grunt.file.expand(options.src));
    if (!partialsChanged) {
      grunt.log.ok('Nothing changed, exiting...');
      return;
    }

    this.files.forEach(function(f) {
      if (f.src.length > 1) {
        grunt.fail.fatal('Only one file can be rendered, ' + f.orig.src + ' found.');
      }

      if (f.src.length === 0) {
        grunt.fail.fatal(f.orig.src + ' not found!');
      }

      grunt.file.write(f.dest, renderPartial(f.src[0]));
      grunt.log.ok(f.src[0] + ' was successfuly rendered.');
    });
  });
};
<|endoftext|>


<commit_before>/*
 * grunt-require-rev
 * https://github.com/heavymery/grunt-require-rev
 *
 * Copyright (c) 2014 Shindeok Kang
 * Licensed under the MIT license.
 */

'use strict';

// Console color code
var black   = '\u001b[30m';
var red     = '\u001b[31m';
var green   = '\u001b[32m';
var yellow  = '\u001b[33m';
var blue    = '\u001b[34m';
var magenta = '\u001b[35m';
var cyan    = '\u001b[36m';
var white   = '\u001b[37m';
var reset   = '\u001b[0m';

module.exports = function(grunt) {

  var createHashFromFile = function(filepath, fileEncoding, hashAlgorithm, hashEncoding) {
    var hash = crypto.createHash(hashAlgorithm);
    hash.update(grunt.file.read(filepath), fileEncoding);
    grunt.verbose.ok('Hashing ' + filepath + '...');
    return hash.digest(hashEncoding);
  }

  grunt.registerMultiTask('requireRev', 'File revisioning for requrejs.', function() {

    // Merge task-specific and/or target-specific options with these defaults.
    var options = this.options({
      hash: {
        algorithm: 'md5',
        encoding: 'hex',
        inputEncoding: 'utf8',
        length: 8
      },
      requirejs: {
        baseUrl: '(scripts|styles)', // カッコは必須
      }
    });

    var dependencyPathPattern
      = new RegExp('(\'|")[\\w\\d-_/.!]+(\'|")','g');
      // e.g. 'app', "controllers/main", "css!/styles/main"

    var definePattern
      = new RegExp('define\\s*\\(\\s*(' + dependencyPathPattern.source + '\\s*,?\\s*)*\\s*\\[\\s*(' + dependencyPathPattern.source + '\\s*,?\\s*)*\\s*\\]', 'ig');
      // e.g. define("moduleName",["app","controllers/main"]... define(["app","controllers/main"]...

    var requirePattern
      = new RegExp('require\\s*\\(\\s*\\[\\s*(' + dependencyPathPattern.source + '\\s*,?\\s*)*\\]\\s*,', 'ig');
      // e.g. require(['app'],

    var dependenciesPattern
      = new RegExp('(dependencies\\s*:\\s*\\[\\s*(' + dependencyPathPattern.source + '\\s*,?\\s*)*\\]\\s*)|(dependencies\\s*\\.\\s*push\\(\\s*(' + dependencyPathPattern.source + '\\s*,?\\s*)*)', 'ig');
      // e.g. dependencies: ['css!../~','~']... dependencies.push('~');

    var dirPathPattern = new RegExp('[\\w\\d-_/.]*');

    var filePathPattern = new RegExp('([a-z0-9]{' + options.hash.length + '}\\.)?(([\\w\\d-_/.!]+)\\.(js|css|html))');

    var fileToDependencyPath = function(filePath) {
      var regexp = new RegExp(options.requirejs.baseUrl + '/?(' + dirPathPattern.source + ')/' + filePathPattern.source);
      var pathMatch = filePath.match(regexp);
      if(!pathMatch) return '';

      var path = '';
      var pathOrigin = '';
      var patternSource = '';

      // TODO: オプション化
      if(pathMatch[1] === 'styles') {
        // CSSはRequireCSS用の特殊なパスに変換
        path += 'css!/styles/';
        pathOrigin += 'css!/styles/';
        patternSource += 'css!/styles/'.replace(/\//g,'\\/');
      } else {
        if(pathMatch[2] && pathMatch[2].length>0) {
          path += pathMatch[2] + '/';
          pathOrigin += pathMatch[2] + '/';
          patternSource += pathMatch[2].replace(/\//g,'\\/') + '\\/';
        }
      }

      // add hash prefix pattern
      patternSource += '([a-z0-9]{' + options.hash.length + '}\\.)?';

      // ハッシュプレーフィックス
      if(pathMatch[3] && pathMatch[3].length>0) {
        path += pathMatch[3];

        // ハッシュプレーフィックスではない場合もあるのでチィック（数字が入っていれば）
        if(!pathMatch[3].match(/\d/)) {
          pathOrigin += pathMatch[3];
          patternSource += pathMatch[3];
        }
      }

      // 拡張子を除いたファイル名
      if(pathMatch[5] && pathMatch[5].length>0) {
        path += pathMatch[5];
        pathOrigin += pathMatch[5];
        patternSource += pathMatch[5];
      }

      var pattern = new RegExp(patternSource, 'ig');

      return {
        path: path,
        pathOrigin: pathOrigin,
        pattern: pattern
      }
    };

    // キャッシュ対応に使う NodeJS モジュール
    var fs = require('fs');
    var path = require('path');
    var crypto = require('crypto');

    // 対象ファイルを全て抽出
    var targetFiles = [];

    this.files.forEach(function(filePair) {
      filePair.src.forEach(function(file) {
        targetFiles.push(file);
      });
    });

    grunt.verbose.writeln();
    grunt.verbose.writeln('Target files:');
    grunt.verbose.writeln(green + targetFiles.join('\n') + reset);
    grunt.verbose.writeln();

    // 各ファイルの依存関係マップ（key: ファイルパス、value: 依存関係パスの配列）
    var dependenciesMap = {};

    // 対象のファイルを読み込んで RequireJS の依存関係が記載された部分を抽出
    targetFiles.forEach(function(targetPath) {

      var contents = grunt.file.read(targetPath);

      var defineMatchs = contents.match(definePattern);
      var requireMatchs = contents.match(requirePattern);
      var dependenciesMatchs = contents.match(dependenciesPattern);

      // ちゃんと検索されてるか確認
      grunt.verbose.writeln('Define matchs:\n' + cyan + defineMatchs + reset);
      grunt.verbose.writeln('Require matchs:\n' + cyan + requireMatchs + reset);
      grunt.verbose.writeln('Dependencies matchs:\n' + cyan + dependenciesMatchs + reset);
      grunt.verbose.writeln();

      // その中から依存関係パスのみを抽出

      var dependencies = [];

      if(defineMatchs) {
        defineMatchs.forEach(function(defineMatch) {
          var pathMatchs = defineMatch.match(dependencyPathPattern);
          if(pathMatchs) {
            dependencies = dependencies.concat(defineMatch.match(dependencyPathPattern));
          }
        });
      }

      if(requireMatchs) {
        requireMatchs.forEach(function(requireMatch) {
          var pathMatchs = requireMatch.match(dependencyPathPattern);
          if(pathMatchs) {
            dependencies = dependencies.concat(pathMatchs);
          }
        });
      }

      if(dependenciesMatchs) {
        dependenciesMatchs.forEach(function(dependenciesMatch) {
          var pathMatchs = dependenciesMatch.match(dependencyPathPattern);
          if(pathMatchs) {
            dependencies = dependencies.concat(pathMatchs);
          }
        });
      }

      // 依存関係パスから「'」と「"」を除去
      for(var i=0; i<dependencies.length; i++) {
        dependencies[i] = dependencies[i].replace(/'|"/g,'');
      }

      // 重複するパスアイテムを削除
      for(var i=0; i<dependencies.length; i++) {
        var target = dependencies[i];
        var indexOfNext = dependencies.indexOf(target, i + 1);
        if(indexOfNext > 0) {
          dependencies.splice(indexOfNext, 1);
          i--;
        }
      }

      dependenciesMap[targetPath] = {};
      dependenciesMap[targetPath].requirePath = fileToDependencyPath(targetPath);
      dependenciesMap[targetPath].dependencies = dependencies;
    }); // targetFiles.forEach

    // 配列の中からパターンマッチ結果を返す
    var matchPatternArray = function(array, pattern) {
      //console.log(array);
      //console.log(pattern);

      for(var i=0; i<array.length; i++) {
        //console.log(array[i]);
        var match = array[i].match(pattern);
        if(match) {
          //console.log('match!');
          return match;
        }
      }

      return null;
    };

    //console.log();
    //console.log(green + targetFiles.join('\n') + reset);
    //console.log();

    // 対象のファイルリストを依存関係順にソート

    var targetFilesSorted = [];

    while(targetFiles.length > 0) {
      if(targetFilesSorted.length > 0) {
        var nextTarget = targetFiles.pop();

        for(var i=0; i<targetFilesSorted.length; i++) {
          var targetFile = targetFilesSorted[i];
          var dependencies = dependenciesMap[targetFile].dependencies;

          //console.log(nextTarget);
          var dependencyMatch = matchPatternArray(dependencies, dependenciesMap[nextTarget].requirePath.pattern);
          //console.log(red + dependencyMatch + reset);
          if(dependencyMatch) {
            //console.log(red + 'break!' + reset);
            break;
          }
        }

        // 依存しているファイルを前に持って行く
        targetFilesSorted.splice(i, 0, nextTarget);

      } else {
        targetFilesSorted.push(targetFiles.pop());
      }
    }

    grunt.verbose.writeln();

    // ファイル名を変更し、参照する箇所のパスも更新

    targetFilesSorted.forEach(function(targetPath) {
      var dependencies = dependenciesMap[targetPath].dependencies;

      grunt.verbose.ok('Target ' + targetPath + ' ('
        + dependenciesMap[targetPath].requirePath.path + ' | '
        + dependenciesMap[targetPath].requirePath.pathOrigin + ' | '
        + dependenciesMap[targetPath].requirePath.pattern + ')');

      grunt.verbose.writeln(red + dependencies.length + reset + ' dependencies found.');
      if(dependencies && dependencies.length > 0) {
        grunt.verbose.writeln(magenta + dependencies.join('\n') + reset);
      }
      grunt.verbose.writeln();
    });

    for(var i=0; i<targetFilesSorted.length; i++) {
      var targetPath = targetFilesSorted[i];

      var hash = createHashFromFile(targetPath, options.hash.inputEncoding, options.hash.algorithm, options.hash.encoding);
      var prefix = hash.slice(0, options.hash.length);
      //console.log(targetPath);
      var originName = path.basename(targetPath);
      //console.log(originName);

      // すでにハッシュプレーフィックスが付いてる場合は除去
      var filePathMatch = originName.match(filePathPattern);
      if(filePathMatch && filePathMatch[1] && filePathMatch[1].match(/\d/)) {
        originName = filePathMatch[2];
      }

      var targetPathNew = path.dirname(targetPath) + '/' + prefix + '.' +  originName;
      grunt.verbose.write('Rename ' + targetPath + ' to ' + targetPathNew + '...');

      // ファイル名を更新
      fs.renameSync(targetPath, targetPathNew);
      grunt.verbose.ok();

      // RequireJS用パス情報を更新
      dependenciesMap[targetPath].requirePath = fileToDependencyPath(targetPathNew);

      for(var j=i+1; j<targetFilesSorted.length; j++) {
        var otherPath = targetFilesSorted[j];

        var dependencyMatch = matchPatternArray(dependenciesMap[otherPath].dependencies, dependenciesMap[targetPath].requirePath.pattern);
        if(dependencyMatch) {
          grunt.verbose.ok('Replace require path for ' + otherPath + '...');

          var contents = grunt.file.read(otherPath);

          grunt.file.write(otherPath, contents.replace(
            new RegExp('("|\')' + dependenciesMap[targetPath].requirePath.pattern.source + '("|\')', 'ig'),
            '$1' + dependenciesMap[targetPath].requirePath.path + '$3'
          ));
        }
      }

      grunt.verbose.writeln();
    }

  });

};
<commit_msg>Update requireRev.js<commit_after>/*
 * grunt-require-rev
 * https://github.com/heavymery/grunt-require-rev
 *
 * Copyright (c) 2014 Shindeok Kang
 * Licensed under the MIT license.
 */

'use strict';

// Console color code
var black   = '\u001b[30m';
var red     = '\u001b[31m';
var green   = '\u001b[32m';
var yellow  = '\u001b[33m';
var blue    = '\u001b[34m';
var magenta = '\u001b[35m';
var cyan    = '\u001b[36m';
var white   = '\u001b[37m';
var reset   = '\u001b[0m';

// file hashing modules
var fs = require('fs');
var path = require('path');
var crypto = require('crypto');

module.exports = function(grunt) {

  var createHashFromFile = function(filepath, fileEncoding, hashAlgorithm, hashEncoding) {
    var hash = crypto.createHash(hashAlgorithm);
    hash.update(grunt.file.read(filepath), fileEncoding);
    grunt.verbose.ok('Hashing ' + filepath + '...');
    return hash.digest(hashEncoding);
  }

  grunt.registerMultiTask('requireRev', 'File revisioning for requrejs.', function() {

    // Merge task-specific and/or target-specific options with these defaults.
    var options = this.options({
      hash: {
        algorithm: 'md5',
        encoding: 'hex',
        inputEncoding: 'utf8',
        length: 8
      },
      requirejs: {
        baseUrl: '(scripts|styles)', // カッコは必須
      }
    });

    var dirPathPattern = new RegExp('[\\w\\d-_/.]*');

    var filePathPattern = new RegExp('([a-z0-9]{' + options.hash.length + '}\\.)?(([\\w\\d-_/.!]+)\\.(js|css|html))');

    var dependencyPathPattern
      = new RegExp('(\'|")[\\w\\d-_/.!]+(\'|")','g');
      // e.g. 'app', "controllers/main", "css!/styles/main"

    var definePattern
      = new RegExp('define\\s*\\(\\s*(' + dependencyPathPattern.source + '\\s*,?\\s*)*\\s*\\[\\s*(' + dependencyPathPattern.source + '\\s*,?\\s*)*\\s*\\]', 'ig');
      // e.g. define("moduleName",["app","controllers/main"]... define(["app","controllers/main"]...

    var requirePattern
      = new RegExp('(require\\s*\\(\\s*\\[\\s*(' + dependencyPathPattern.source + '\\s*,?\\s*)*\\]\\s*)|(require\\s*\\(\\s*(' + dependencyPathPattern.source + '\\s*,?\\s*)*)', 'ig');
      // e.g. require(['app']... require('app'...

    var dependenciesPattern
      = new RegExp('(dependencies\\s*(:|=)\\s*\\[\\s*(' + dependencyPathPattern.source + '\\s*,?\\s*)*\\]\\s*)|(dependencies\\s*\\.\\s*push\\(\\s*(' + dependencyPathPattern.source + '\\s*,?\\s*)*)', 'ig');
      // e.g. dependencies: ['css!../~','~']... dependencies = ['css!../~','~']... dependencies.push('~');

    var matchPatternFromArray = function(array, pattern) {
      for(var i=0; i<array.length; i++) {
        var match = array[i].match(pattern);
        if(match) {
          return match;
        }
      }

      return null;
    };

    var fileToDependencyPath = function(filePath) {
      var regexp = new RegExp(options.requirejs.baseUrl + '/?(' + dirPathPattern.source + ')/' + filePathPattern.source);
      var pathMatch = filePath.match(regexp);
      if(!pathMatch) return '';

      var path = '';
      var pathOrigin = '';
      var patternSource = '';

      // set dir name
      // TODO: オプション化
      if(pathMatch[1] === 'styles') {
        // CSSはRequireCSS用の特殊なパスに変換
        path += 'css!/styles/';
        pathOrigin += 'css!/styles/';
        patternSource += 'css!/styles/'.replace(/\//g,'\\/');
      } else {
        if(pathMatch[2] && pathMatch[2].length>0) {
          path += pathMatch[2] + '/';
          pathOrigin += pathMatch[2] + '/';
          patternSource += pathMatch[2].replace(/\//g,'\\/') + '\\/';
        }
      }

      // add hash prefix pattern
      patternSource += '([a-z0-9]{' + options.hash.length + '}\\.)?';

      // add hash prefix
      if(pathMatch[3] && pathMatch[3].length>0) {
        path += pathMatch[3];

        // ハッシュプレーフィックスではない場合もあるのでチィック（数字が入っていれば）
        if(!pathMatch[3].match(/\d/)) {
          pathOrigin += pathMatch[3];
          patternSource += pathMatch[3];
        }
      }

      // add file name (exclude extension)
      if(pathMatch[5] && pathMatch[5].length>0) {
        path += pathMatch[5];
        pathOrigin += pathMatch[5];
        patternSource += pathMatch[5];
      }

      // create regular expression
      var pattern = new RegExp(patternSource, 'ig');

      return {
        path: path,
        pathOrigin: pathOrigin,
        pattern: pattern
      }
    };

    //--------------------------------------
    // 1. get target files
    //--------------------------------------

    var targetFiles = [];

    this.files.forEach(function(filePair) {
      filePair.src.forEach(function(file) {
        targetFiles.push(file);
      });
    });

    grunt.verbose.writeln();
    grunt.verbose.writeln('Target files:');
    grunt.verbose.writeln(green + targetFiles.join('\n') + reset);
    grunt.verbose.writeln();

    //--------------------------------------
    // 2. create dependencies map
    //  (key: filePath, value: dependencies)
    //--------------------------------------

    var dependenciesMap = {};

    targetFiles.forEach(function(targetPath) {

      var contents = grunt.file.read(targetPath);

      var defineMatchs = contents.match(definePattern);
      var requireMatchs = contents.match(requirePattern);
      var dependenciesMatchs = contents.match(dependenciesPattern);

      grunt.verbose.writeln('Define matchs:\n' + cyan + defineMatchs + reset);
      grunt.verbose.writeln('Require matchs:\n' + cyan + requireMatchs + reset);
      grunt.verbose.writeln('Dependencies matchs:\n' + cyan + dependenciesMatchs + reset);
      grunt.verbose.writeln();

      // dependency path array
      var dependencies = [];

      if(defineMatchs) {
        defineMatchs.forEach(function(defineMatch) {
          var pathMatchs = defineMatch.match(dependencyPathPattern);
          if(pathMatchs) {
            dependencies = dependencies.concat(defineMatch.match(dependencyPathPattern));
          }
        });
      }

      if(requireMatchs) {
        requireMatchs.forEach(function(requireMatch) {
          var pathMatchs = requireMatch.match(dependencyPathPattern);
          if(pathMatchs) {
            dependencies = dependencies.concat(pathMatchs);
          }
        });
      }

      if(dependenciesMatchs) {
        dependenciesMatchs.forEach(function(dependenciesMatch) {
          var pathMatchs = dependenciesMatch.match(dependencyPathPattern);
          if(pathMatchs) {
            dependencies = dependencies.concat(pathMatchs);
          }
        });
      }

      // remove quotation mark
      for(var i=0; i<dependencies.length; i++) {
        dependencies[i] = dependencies[i].replace(/'|"/g,'');
      }

      // remove duplicate dependency path
      for(var i=0; i<dependencies.length; i++) {
        var target = dependencies[i];
        var indexOfNext = dependencies.indexOf(target, i + 1);
        if(indexOfNext > 0) {
          dependencies.splice(indexOfNext, 1);
          i--;
        }
      }

      dependenciesMap[targetPath] = {};
      dependenciesMap[targetPath].dependencyPath = fileToDependencyPath(targetPath);
      dependenciesMap[targetPath].dependencies = dependencies;
    });

    //--------------------------------------
    // 3. order by dependencies
    //--------------------------------------

    var targetFilesSorted = [];

    while(targetFiles.length > 0) {
      if(targetFilesSorted.length > 0) {
        var nextTarget = targetFiles.pop();

        for(var i=0; i<targetFilesSorted.length; i++) {
          var targetFile = targetFilesSorted[i];
          var dependencies = dependenciesMap[targetFile].dependencies;

          var dependencyMatch = matchPatternFromArray(dependencies, dependenciesMap[nextTarget].dependencyPath.pattern);

          if(dependencyMatch) {
            break;
          }
        }

        // move dependent file to front
        targetFilesSorted.splice(i, 0, nextTarget);

      } else {
        targetFilesSorted.push(targetFiles.pop());
      }
    }

    grunt.verbose.writeln();

    //--------------------------------------
    // 4. hashing & replace dependency path
    //--------------------------------------

    targetFilesSorted.forEach(function(targetPath) {
      var dependencies = dependenciesMap[targetPath].dependencies;

      grunt.verbose.ok('Target ' + targetPath + ' ('
        + dependenciesMap[targetPath].dependencyPath.path + ' | '
        + dependenciesMap[targetPath].dependencyPath.pathOrigin + ' | '
        + dependenciesMap[targetPath].dependencyPath.pattern + ')');

      grunt.verbose.writeln(red + dependencies.length + reset + ' dependencies found.');
      if(dependencies && dependencies.length > 0) {
        grunt.verbose.writeln(magenta + dependencies.join('\n') + reset);
      }
      grunt.verbose.writeln();
    });

    for(var i=0; i<targetFilesSorted.length; i++) {
      var targetPath = targetFilesSorted[i];
      var originName = path.basename(targetPath);

      var hash = createHashFromFile(targetPath, options.hash.inputEncoding, options.hash.algorithm, options.hash.encoding);
      var prefix = hash.slice(0, options.hash.length);

      // clear hash prefix from file name
      var filePathMatch = originName.match(filePathPattern);
      if(filePathMatch && filePathMatch[1] && filePathMatch[1].match(/\d/)) {
        originName = filePathMatch[2];
      }

      var targetPathNew = path.dirname(targetPath) + '/' + prefix + '.' +  originName;
      grunt.verbose.write('Rename ' + targetPath + ' to ' + targetPathNew + '...');

      // rename file (adding hash prefix)
      fs.renameSync(targetPath, targetPathNew);
      grunt.verbose.ok();

      // replace dependency path
      dependenciesMap[targetPath].dependencyPath = fileToDependencyPath(targetPathNew);

      for(var j=i+1; j<targetFilesSorted.length; j++) {
        var otherPath = targetFilesSorted[j];

        var dependencyMatch = matchPatternFromArray(dependenciesMap[otherPath].dependencies, dependenciesMap[targetPath].dependencyPath.pattern);
        if(dependencyMatch) {
          grunt.verbose.ok('Replace require path for ' + otherPath + '...');

          var contents = grunt.file.read(otherPath);

          grunt.file.write(otherPath, contents.replace(
            new RegExp('("|\')' + dependenciesMap[targetPath].dependencyPath.pattern.source + '("|\')', 'ig'),
            '$1' + dependenciesMap[targetPath].dependencyPath.path + '$3'
          ));
        }
      }

      grunt.verbose.writeln();
    }

  }); // grunt.registerMultiTask

}; // module.exports
<|endoftext|>


<commit_before>/*
 * grunt-svg-modify
 * https://github.com/yoksel/grunt-svg-modify
 *
 * Copyright (c) 2014 yoksel
 * Licensed under the MIT license.
 */

'use strict';

var path = require('path'),
    svgmodify = require("svg-modify"),
    mustache = require("mustache"),
    open = require("open");

module.exports = function(grunt) {

    grunt.registerMultiTask('svg_modify', 'Resize and colorize svg-images', function() {

        var cwd = this.data.cwd,
            src = cwd + this.data.src,
            dest = cwd + this.data.dest,
            currentFolder = __dirname,
            templatesFolder = path.resolve(currentFolder, "../templates"),
            assetsFolder = path.resolve(currentFolder, "../assets");

        function getFolder(filePath) {
            var pathArray = filePath.split(path.sep);
            return pathArray[pathArray.length - 2];
        }

        function createControlPage(dest, folderName, color) {

            var folderPath = dest + folderName;
            var files = grunt.file.expand(folderPath + "**/*.svg");
            var svgs = "";
            var resultSvg = [];

            files.forEach(function(filePath) {
                var fileContent = grunt.file.read(filePath);
                var fileName = path.basename(filePath);
                svgs += "" + fileName + "" + fileContent + "";
            });

            resultSvg.push({
                "name": folderName,
                "color": color,
                "content": svgs
            });

            var destIndex = dest + folderName + "/index.html";
            var indexTemplate = grunt.file.read(templatesFolder + "/index.html");
            var jsFile = grunt.file.read(assetsFolder + "/script.js");
            var cssFile = grunt.file.read(assetsFolder + "/style.css");

            var indexData = {
                "js": jsFile,
                "css": cssFile,
                "resultSvg": resultSvg
            };

            var outputIndex = mustache.render(indexTemplate, indexData);
            grunt.file.write(destIndex, outputIndex, "utf8");

            open(destIndex);

        }

        function processFolder(folderPath) {
            var configPath = grunt.file.expand(folderPath + "/*.json")[0];
            var folderName = path.basename(folderPath);

            if (!configPath) {
                grunt.log.error("No config in folder '" + folderName + "'");
                return;
            }

            var changesParams = {
                "inputFolder": folderPath,
                "outputFolder": dest
            };

            var folderOptionsFile = grunt.file.readJSON(configPath);
            var folderOptions = {};

            var defaults = folderOptionsFile["defaults"];
            var variations = folderOptionsFile["variations"];
            var color = folderOptionsFile["defaultColor"];

            // colorize after setting defaults
            if (color) {
                changesParams["defaultColor"] = color;
            }

            if (defaults && variations) {

                // 1. defaults
                changesParams["inputFolder"] = folderPath;
                changesParams["outputFolder"] = "temp/";
                changesParams["folderOptions"] = defaults;

                svgmodify.makeChanges(changesParams);

                // 2. variations
                changesParams["inputFolder"] = "temp/" + folderName;
                changesParams["outputFolder"] = dest;
                changesParams["folderOptions"] = variations;
                changesParams["defaultColor"] = "";

                svgmodify.makeChanges(changesParams);

            } else {

                if (defaults) {
                    folderOptions = defaults;
                } else if (variations) {
                    folderOptions = variations;
                }
                changesParams["folderOptions"] = folderOptions;
                svgmodify.makeChanges(changesParams);

            }

            createControlPage(dest, folderName, color);

        }

        //--------------------------------------------

        // if files exist in scr folder
        var sources = grunt.file.expand(src + "*.svg");

        if (sources.length > 0) {
            processFolder(src);
        }

        // if folders exist in scr folder
        var folders = grunt.file.expand(src + "*/");

        if (folders.length > 0) {
            folders.forEach(function(folderPath) {
                processFolder(folderPath);
            });
        }
    });

};<commit_msg>Added options & previewFile option<commit_after>/*
 * grunt-svg-modify
 * https://github.com/yoksel/grunt-svg-modify
 *
 * Copyright (c) 2014 yoksel
 * Licensed under the MIT license.
 */

'use strict';

var path = require('path'),
    svgmodify = require("svg-modify"),
    mustache = require("mustache"),
    open = require("open");

module.exports = function(grunt) {

    grunt.registerMultiTask('svg_modify', 'Resize and colorize svg-images', function() {

        var cwd = this.data.cwd,
            src = cwd + this.data.src,
            dest = cwd + this.data.dest,
            currentFolder = __dirname,
            templatesFolder = path.resolve(currentFolder, "../templates"),
            assetsFolder = path.resolve(currentFolder, "../assets"),
            options = this.options({
                previewFile: true
            });

        function getFolder(filePath) {
            var pathArray = filePath.split(path.sep);
            return pathArray[pathArray.length - 2];
        }

        function createControlPage(dest, folderName, color) {

            var folderPath = dest + folderName;
            var files = grunt.file.expand(folderPath + "**/*.svg");
            var svgs = "";
            var resultSvg = [];

            files.forEach(function(filePath) {
                var fileContent = grunt.file.read(filePath);
                var fileName = path.basename(filePath);
                svgs += "" + fileName + "" + fileContent + "";
            });

            resultSvg.push({
                "name": folderName,
                "color": color,
                "content": svgs
            });

            var destIndex = dest + folderName + "/index.html";
            var indexTemplate = grunt.file.read(templatesFolder + "/index.html");
            var jsFile = grunt.file.read(assetsFolder + "/script.js");
            var cssFile = grunt.file.read(assetsFolder + "/style.css");

            var indexData = {
                "js": jsFile,
                "css": cssFile,
                "resultSvg": resultSvg
            };

            var outputIndex = mustache.render(indexTemplate, indexData);
            grunt.file.write(destIndex, outputIndex, "utf8");

            open(destIndex);

        }

        function processFolder(folderPath) {
            var configPath = grunt.file.expand(folderPath + "/*.json")[0];
            var folderName = path.basename(folderPath);

            if (!configPath) {
                grunt.log.error("No config in folder '" + folderName + "'");
                return;
            }

            var changesParams = {
                "inputFolder": folderPath,
                "outputFolder": dest
            };

            var folderOptionsFile = grunt.file.readJSON(configPath);
            var folderOptions = {};

            var defaults = folderOptionsFile["defaults"];
            var variations = folderOptionsFile["variations"];
            var color = folderOptionsFile["defaultColor"];

            // colorize after setting defaults
            if (color) {
                changesParams["defaultColor"] = color;
            }

            if (defaults && variations) {

                // 1. defaults
                changesParams["inputFolder"] = folderPath;
                changesParams["outputFolder"] = "temp/";
                changesParams["folderOptions"] = defaults;

                svgmodify.makeChanges(changesParams);

                // 2. variations
                changesParams["inputFolder"] = "temp/" + folderName;
                changesParams["outputFolder"] = dest;
                changesParams["folderOptions"] = variations;
                changesParams["defaultColor"] = "";

                svgmodify.makeChanges(changesParams);

            } else {

                if (defaults) {
                    folderOptions = defaults;
                } else if (variations) {
                    folderOptions = variations;
                }
                changesParams["folderOptions"] = folderOptions;
                svgmodify.makeChanges(changesParams);

            }

            if(options.previewFile) createControlPage(dest, folderName, color);

        }

        //--------------------------------------------

        // if files exist in scr folder
        var sources = grunt.file.expand(src + "*.svg");

        if (sources.length > 0) {
            processFolder(src);
        }

        // if folders exist in scr folder
        var folders = grunt.file.expand(src + "*/");

        if (folders.length > 0) {
            folders.forEach(function(folderPath) {
                processFolder(folderPath);
            });
        }
    });

};<|endoftext|>


<commit_before>/**
 * webcrawler.js - PhantomJS based web crawler with support for #! routing.
 */

'use strict';

var phantom = require('node-phantom'),
URI = require('URIjs'),
URIFragment = require('URIjs/src/URI.fragmentURI'),
document;

function Crawler(url, depth, options) {
    var self = this;

    this.baseUrl = url;
    this.depth = depth;
    this.options = options;
    this.urls = {};
    this.crawledUrls = [];
    this.sitemap = {};
    this.phantom = null;

    this.createPhantom = function(error, ph) {
        if (error) {
            ph.exit();

            throw 'Could not create phantom instance';
        }

        self.setPhantom(ph);
    };
}

/**
 * Start node-phantom.
 *
 * @returns {this}
 */
Crawler.prototype.startPhantom = function() {

    phantom.create(this.createPhantom);
    return this;
};

/**
 * Stop node-phantom.
 *
 * @returns {this{
 */
Crawler.prototype.stopPhantom = function() {

    this.phantom.exit();
    return this;
};

/**
 * Set the phantom object.
 *
 * @param {Object} ph - The node-phantom object.
 * @returns {this}
 */
Crawler.prototype.setPhantom = function(ph) {

    this.phantom = ph;
    return this;
};

/**
 * Add url to list of urls to crawl.
 *
 * @param {String} url - valid url
 * @param {Integer} depth - the depth of the url.
 * @returns {this}
 */
Crawler.prototype.addUrl = function(url, depth) {

    this.urls[url] = {
        crawled: false,
        crawling: false,
        url: url,
        depth: depth + 1,
        content: null
    };
    return this;
};

/**
 * Get the absolute url for a given url string.
 *
 * @param {String} url - valid url.
 * @returns {String} the absolute url for url.
 */
Crawler.prototype.getAbsoluteUrl = function(url) {

    var uri = URI(url);

    if (uri.is('absolute')) {
        return uri.toString();
    }

    return this.baseUrl + url;
};

/**
 * Get the next url to crawl.
 *
 * @returns {String} the next url to crawl or an empty string.
 */
Crawler.prototype.getNextUrl = function() {

    var n, url = '';

    for (n in this.urls) {
        if (!this.urls[n].crawled && !this.urls[n].crawling) {
            url = n;
            break;
        }
    }

    return url;
};

/**
 * Determine if there are any other urls to crawl.
 *
 * @returns {boolean} true if nothing left to crawl.
 */
Crawler.prototype.doneCrawling = function() {

  var n, done = true;

  for (n in this.urls) {
      if (!this.urls[n].crawled || this.urls[n].crawling) {
          done = false;
          break;
      }
  }

  return done;
};

/**
 * Return the file name to use for the provided url.
 *
 * @param {String} url - valid url.
 * @returns {String} A relative url with html suffix.
 */
Crawler.prototype.getStaticFilename = function(url) {

    var uri = URI(url);
    var furi = uri.fragment(this.options.followFragment);
    var name = uri.pathname(true);

    if (this.options.followFragment) {
        uri.fragmentPrefix(this.options.fragmentPrefix);
        name = furi.pathname(true);
    }

    if (uri.filename().length === 0) {
        if (this.baseUrl === url) {
            name = '/index.html';
        } else {
            name = name.replace(/\/?$/, '.html');
        }
    }

    return name;
};

/**
 * Filter method to filter urls.
 *
 * @param {String} url - An url to filter out.
 * @returns {boolean}
 */
Crawler.prototype.filterUrl = function(url) {

    var ret = true;
    var uri = URI(url);

    if (this.urls[url] !== undefined && this.urls[url].crawled) {
        // Exact match aleady in crawl list.
        ret = false;
    } else if (this.baseUrl === url) {
        // Exact match to base url.
        ret = false;
    } else if (this.options.followFragment && this.options.fragmentPrefix + '/' === uri.fragment()) {
        // Fragment routing base url.
        ret = false;
    } else if (this.isUrlRelative(uri) || this.isUrlSameOrigin(uri)) {
        // Relative urls or urls of the same origin
        ret = true;
    } else {
        ret = false;
    }

    // Exclude the path based on regular expression match for user-defined list
    // of patterns.
    if (ret && this.options.exclude.length) {
        ret = this.options.exclude.reduce(function(prev, path) {
            var regex = new RegExp(path, 'i');
            if (uri.path().match(regex) !== null) {
                prev = false;
            }
            return prev;
        }, true);
    }

    return ret;
};

/**
 * Check if a given URI object is relative. Fragment and an empty route will
 * pass with URI.js method of determining relativeness, but in this case it
 * should only be considered relative if fragment routing is enabled.
 *
 * @param {URI} uri - A URI object.
 * @returns {Boolean} true if the uri is relative.
 */
Crawler.prototype.isUrlRelative = function(uri) {

    var ret = false;
    var furi = uri.fragment(this.options.followFragment);

    if (uri.is('relative')) {
        if (this.options.followFragment) {
            uri.fragmentPrefix(this.options.fragmentPrefix);

            ret = furi.pathname().length > 0 && furi.pathname() !== '/';
        } else if (uri.path().length > 0) {
            ret = true;
        }
    }

    return ret;
};

/**
 * Checks if the given URI is of the same origin as the baseUrl.
 *
 * @param {URI} uri A URI.js object.
 * @returns {Boolean} TRUE if url is the same origin as base.
 */
Crawler.prototype.isUrlSameOrigin = function (uri) {
    var baseUri = URI(this.baseUrl);

    return baseUri.normalizeProtocol().protocol() === uri.normalizeProtocol().protocol() &&
        baseUri.normalizeHostname().host() === uri.normalizeHostname().host();
};

/**
 * Render a snapshot of the page with phantom.
 *
 * @param {Object} page The page object.
 * @param {String} path The relative path of the file to snapshot.
 */
Crawler.prototype.createSnapshot = function (page, path) {
    // Only render if options set.
    if (this.options.render && undefined !== page) {
        page.render(this.options.renderDir + "/" + this.getStaticFilename(path) + ".png");
    }
};

/**
 * Crawl a url.
 *
 * @param {Object} page page object to call render method.
 */
Crawler.prototype.crawl = function(url) {

    var self = this;
    var currDepth = this.urls[url].depth;

    if (this.urls[url] !== undefined &&
        currDepth <= this.depth &&
        !this.urls[url].crawled) {

        console.log('Crawling ' + url + ' ...');
        this.urls[url].crawling = true;

        this.phantom.createPage(function(error, page) {

            var absoluteUrl = self.getAbsoluteUrl(url);

            if (error) {
                console.log('   ...failed to create node-phantom page.');
                return false;
            }

            page.viewPortSize = {
                width: self.options.viewPortWidth,
                height: self.options.viewPortHeight
            };

            page.onConsoleMessage = function(msg) {
                console.log('    ...' + msg);
            };

            page.open(absoluteUrl, function(status) {

                var wait, waitForPage;

                if ('fail' === status) {
                    console.log('   ...failed to open page with phantom.');
                    return;
                }

                // Evaluate the page for dynamically loaded content and links.
                waitForPage = function() {
                    page.evaluate(function(s) {
                        var body;
                        var html;

                        if (s === false) {
                            html = document.documentElement && document.documentElement.outerHTML ? document.documentElement.outerHTML : "";
                        } else {
                            body = document.querySelector(s);

                            if ('ready' === body.getAttribute('data-status')) {
                                html = document.documentElement && document.documentElement.outerHTML ? document.documentElement.outerHTML : "";
                            }
                        }
                        return html;
                    }, function(error, documentHtml) {

                        var delay, waitForLinks;

                        // Document has not loaded completely yet.
                        if (documentHtml === null || error) {
                            return;
                        }

                        // Add content to url list.
                        self.urls[url].content = documentHtml;

                        // Create a snapshot.
                        self.createSnapshot(page, url);

                        clearInterval(wait);

                        // Get links from the page as well.
                        waitForLinks = function() {
                            page.evaluate(function() {

                                var nav = document.querySelector('a');

                                if (nav !== undefined) {
                                    return [].map.call(document.querySelectorAll('a'), function(link) {
                                        return link.getAttribute('href');
                                    });
                                }
                            }, function(error, links) {

                                clearTimeout(delay);

                                // No links found or error.
                                if (links === null || error) {
                                    return;
                                }

                                // Filter the urls and add each to the crawl list.
                                links.filter(self.filterUrl, self).forEach(function(curr) {
                                    if (currDepth < this.depth) {
                                        this.addUrl(curr, currDepth);
                                    }
                                }, self);

                                page.close(function() {
                                    console.log('   ...finished crawling.');
                                });
                            });
                        };

                        delay = setTimeout(waitForLinks, 1000);
                    }, self.options.readySelector);
                };

                wait = setInterval(waitForPage, 3000);

            });
        });
    }

    this.urls[url].crawling = false;
    this.urls[url].crawled = true;
};

module.exports = Crawler;
<commit_msg>Fix regression in doc block for crawl method.<commit_after>/**
 * webcrawler.js - PhantomJS based web crawler with support for #! routing.
 */

'use strict';

var phantom = require('node-phantom'),
URI = require('URIjs'),
URIFragment = require('URIjs/src/URI.fragmentURI'),
document;

function Crawler(url, depth, options) {
    var self = this;

    this.baseUrl = url;
    this.depth = depth;
    this.options = options;
    this.urls = {};
    this.crawledUrls = [];
    this.sitemap = {};
    this.phantom = null;

    this.createPhantom = function(error, ph) {
        if (error) {
            ph.exit();

            throw 'Could not create phantom instance';
        }

        self.setPhantom(ph);
    };
}

/**
 * Start node-phantom.
 *
 * @returns {this}
 */
Crawler.prototype.startPhantom = function() {

    phantom.create(this.createPhantom);
    return this;
};

/**
 * Stop node-phantom.
 *
 * @returns {this{
 */
Crawler.prototype.stopPhantom = function() {

    this.phantom.exit();
    return this;
};

/**
 * Set the phantom object.
 *
 * @param {Object} ph - The node-phantom object.
 * @returns {this}
 */
Crawler.prototype.setPhantom = function(ph) {

    this.phantom = ph;
    return this;
};

/**
 * Add url to list of urls to crawl.
 *
 * @param {String} url - valid url
 * @param {Integer} depth - the depth of the url.
 * @returns {this}
 */
Crawler.prototype.addUrl = function(url, depth) {

    this.urls[url] = {
        crawled: false,
        crawling: false,
        url: url,
        depth: depth + 1,
        content: null
    };
    return this;
};

/**
 * Get the absolute url for a given url string.
 *
 * @param {String} url - valid url.
 * @returns {String} the absolute url for url.
 */
Crawler.prototype.getAbsoluteUrl = function(url) {

    var uri = URI(url);

    if (uri.is('absolute')) {
        return uri.toString();
    }

    return this.baseUrl + url;
};

/**
 * Get the next url to crawl.
 *
 * @returns {String} the next url to crawl or an empty string.
 */
Crawler.prototype.getNextUrl = function() {

    var n, url = '';

    for (n in this.urls) {
        if (!this.urls[n].crawled && !this.urls[n].crawling) {
            url = n;
            break;
        }
    }

    return url;
};

/**
 * Determine if there are any other urls to crawl.
 *
 * @returns {boolean} true if nothing left to crawl.
 */
Crawler.prototype.doneCrawling = function() {

  var n, done = true;

  for (n in this.urls) {
      if (!this.urls[n].crawled || this.urls[n].crawling) {
          done = false;
          break;
      }
  }

  return done;
};

/**
 * Return the file name to use for the provided url.
 *
 * @param {String} url - valid url.
 * @returns {String} A relative url with html suffix.
 */
Crawler.prototype.getStaticFilename = function(url) {

    var uri = URI(url);
    var furi = uri.fragment(this.options.followFragment);
    var name = uri.pathname(true);

    if (this.options.followFragment) {
        uri.fragmentPrefix(this.options.fragmentPrefix);
        name = furi.pathname(true);
    }

    if (uri.filename().length === 0) {
        if (this.baseUrl === url) {
            name = '/index.html';
        } else {
            name = name.replace(/\/?$/, '.html');
        }
    }

    return name;
};

/**
 * Filter method to filter urls.
 *
 * @param {String} url - An url to filter out.
 * @returns {boolean}
 */
Crawler.prototype.filterUrl = function(url) {

    var ret = true;
    var uri = URI(url);

    if (this.urls[url] !== undefined && this.urls[url].crawled) {
        // Exact match aleady in crawl list.
        ret = false;
    } else if (this.baseUrl === url) {
        // Exact match to base url.
        ret = false;
    } else if (this.options.followFragment && this.options.fragmentPrefix + '/' === uri.fragment()) {
        // Fragment routing base url.
        ret = false;
    } else if (this.isUrlRelative(uri) || this.isUrlSameOrigin(uri)) {
        // Relative urls or urls of the same origin
        ret = true;
    } else {
        ret = false;
    }

    // Exclude the path based on regular expression match for user-defined list
    // of patterns.
    if (ret && this.options.exclude.length) {
        ret = this.options.exclude.reduce(function(prev, path) {
            var regex = new RegExp(path, 'i');
            if (uri.path().match(regex) !== null) {
                prev = false;
            }
            return prev;
        }, true);
    }

    return ret;
};

/**
 * Check if a given URI object is relative. Fragment and an empty route will
 * pass with URI.js method of determining relativeness, but in this case it
 * should only be considered relative if fragment routing is enabled.
 *
 * @param {URI} uri - A URI object.
 * @returns {Boolean} true if the uri is relative.
 */
Crawler.prototype.isUrlRelative = function(uri) {

    var ret = false;
    var furi = uri.fragment(this.options.followFragment);

    if (uri.is('relative')) {
        if (this.options.followFragment) {
            uri.fragmentPrefix(this.options.fragmentPrefix);

            ret = furi.pathname().length > 0 && furi.pathname() !== '/';
        } else if (uri.path().length > 0) {
            ret = true;
        }
    }

    return ret;
};

/**
 * Checks if the given URI is of the same origin as the baseUrl.
 *
 * @param {URI} uri A URI.js object.
 * @returns {Boolean} TRUE if url is the same origin as base.
 */
Crawler.prototype.isUrlSameOrigin = function (uri) {
    var baseUri = URI(this.baseUrl);

    return baseUri.normalizeProtocol().protocol() === uri.normalizeProtocol().protocol() &&
        baseUri.normalizeHostname().host() === uri.normalizeHostname().host();
};

/**
 * Render a snapshot of the page with phantom.
 *
 * @param {Object} page The page object.
 * @param {String} path The relative path of the file to snapshot.
 */
Crawler.prototype.createSnapshot = function (page, path) {
    // Only render if options set.
    if (this.options.render && undefined !== page) {
        page.render(this.options.renderDir + "/" + this.getStaticFilename(path) + ".png");
    }
};

/**
 * Crawl a url.
 *
 * @param {String} url - The absolute or relative url to crawl.
 */
Crawler.prototype.crawl = function(url) {

    var self = this;
    var currDepth = this.urls[url].depth;

    if (this.urls[url] !== undefined &&
        currDepth <= this.depth &&
        !this.urls[url].crawled) {

        console.log('Crawling ' + url + ' ...');
        this.urls[url].crawling = true;

        this.phantom.createPage(function(error, page) {

            var absoluteUrl = self.getAbsoluteUrl(url);

            if (error) {
                console.log('   ...failed to create node-phantom page.');
                return false;
            }

            page.viewPortSize = {
                width: self.options.viewPortWidth,
                height: self.options.viewPortHeight
            };

            page.onConsoleMessage = function(msg) {
                console.log('    ...' + msg);
            };

            page.open(absoluteUrl, function(status) {

                var wait, waitForPage;

                if ('fail' === status) {
                    console.log('   ...failed to open page with phantom.');
                    return;
                }

                // Evaluate the page for dynamically loaded content and links.
                waitForPage = function() {
                    page.evaluate(function(s) {
                        var body;
                        var html;

                        if (s === false) {
                            html = document.documentElement && document.documentElement.outerHTML ? document.documentElement.outerHTML : "";
                        } else {
                            body = document.querySelector(s);

                            if ('ready' === body.getAttribute('data-status')) {
                                html = document.documentElement && document.documentElement.outerHTML ? document.documentElement.outerHTML : "";
                            }
                        }
                        return html;
                    }, function(error, documentHtml) {

                        var delay, waitForLinks;

                        // Document has not loaded completely yet.
                        if (documentHtml === null || error) {
                            return;
                        }

                        // Add content to url list.
                        self.urls[url].content = documentHtml;

                        // Create a snapshot.
                        self.createSnapshot(page, url);

                        clearInterval(wait);

                        // Get links from the page as well.
                        waitForLinks = function() {
                            page.evaluate(function() {

                                var nav = document.querySelector('a');

                                if (nav !== undefined) {
                                    return [].map.call(document.querySelectorAll('a'), function(link) {
                                        return link.getAttribute('href');
                                    });
                                }
                            }, function(error, links) {

                                clearTimeout(delay);

                                // No links found or error.
                                if (links === null || error) {
                                    return;
                                }

                                // Filter the urls and add each to the crawl list.
                                links.filter(self.filterUrl, self).forEach(function(curr) {
                                    if (currDepth < this.depth) {
                                        this.addUrl(curr, currDepth);
                                    }
                                }, self);

                                page.close(function() {
                                    console.log('   ...finished crawling.');
                                });
                            });
                        };

                        delay = setTimeout(waitForLinks, 1000);
                    }, self.options.readySelector);
                };

                wait = setInterval(waitForPage, 3000);

            });
        });
    }

    this.urls[url].crawling = false;
    this.urls[url].crawled = true;
};

module.exports = Crawler;
<|endoftext|>


<commit_before>import {
  c,
  field,
  fieldError,
  fieldErrors,
  mockMazemapApi,
  selectEditor,
  setDatePickerTime,
  selectFromSelectField,
  selectFieldDropdown,
  selectField,
} from '../support/utils.js';
import moment from 'moment-timezone';
import config from '../support/config';

describe('Create meeting', () => {
  beforeEach(() => {
    cy.resetDb();
    cy.cachedLogin();
  });

  it('should show correct validation errors', () => {
    cy.visit('/meetings/create');
    // Check that validation errors show only after we try to submit
    fieldErrors().should('not.exist');

    // verify initial values are set
    field('useMazemap').should('be.checked');
    field('startTime').should('not.have.value', '');
    field('endTime').should('not.have.value', '');

    cy.contains('button', 'Opprett møte').should('be.disabled');
    // change the meeting time to enable submit button
    field('startTime').click();
    cy.get(c('TimePicker__arrowIcon')).first().click();

    cy.contains('button', 'Opprett møte').should('not.be.disabled').click();

    fieldErrors().should('exist');

    fieldError('title').should('be.visible');
    fieldError('report').should('be.visible');
    fieldError('mazemapPoi').should('be.visible');

    fieldError('startTime').should('not.exist');
    fieldError('endTime').should('not.exist');
    fieldError('description').should('not.exist');

    setDatePickerTime('startTime', '20', '00');
    setDatePickerTime('endTime', '19', '30');

    fieldError('endTime').should('be.visible');

    setDatePickerTime('endTime', '20', '30');

    fieldError('endTime').should('not.exist');

    field('useMazemap').click();

    cy.contains('button', 'Opprett møte').click();

    fieldError('location').should('be.visible');

    // remove validation errors
    field('title').type('test');
    fieldError('title').should('not.exist');

    selectEditor().type('test');
    fieldError('report').should('not.exist');

    field('location').type('test');
    fieldError('location').should('not.exist');

    fieldErrors().should('not.exist');
  });

  it('should create basic meeting', () => {
    cy.intercept('POST', '/api/v1/meetings/1/bulk_invite/').as(
      'inviteToMeeting'
    );

    cy.visit('/meetings/create');

    field('title').type('Test meeting');
    selectEditor().type('Meeting plan');
    setDatePickerTime('startTime', '10', '00');
    setDatePickerTime('endTime', '13', '37');
    field('useMazemap').click();
    field('location').type('Test location');
    selectFromSelectField('users', 'bedkom bedkom (bedkom)', 'bedkom');
    selectFromSelectField('reportAuthor', 'bedkom bedkom', 'bedkom');
    selectFromSelectField('groups', 'Arrkom');

    fieldError('title').should('not.exist');
    fieldError('report').should('not.exist');
    fieldError('startTime').should('not.exist');
    fieldError('endTime').should('not.exist');
    fieldError('location').should('not.exist');
    fieldError('reportAuthor').should('not.exist');
    fieldError('users').should('not.exist');
    fieldError('groups').should('not.exist');

    cy.contains('button', 'Opprett møte').click();

    cy.wait('@inviteToMeeting').then((interception) => {
      expect(interception.request.body).to.deep.eq({ groups: [4], users: [4] });
    });

    cy.contains('h1', 'Test meeting').should('be.visible');
    cy.contains('time', '10:00').should('be.visible');
    cy.contains(c('legoEditor_disabled'), 'Meeting plan').should('be.visible');
    cy.contains('tr', 'Når')
      .should('contain.text', '10:00 - 13:37')
      .should('be.visible');
    cy.contains('tr', 'Sted')
      .should('contain.text', 'Test location')
      .should('be.visible');
    cy.contains('tr', 'Forfatter')
      .should('contain.text', 'webkom webkom')
      .should('be.visible');
    cy.contains('tr', 'Referent')
      .should('contain.text', 'bedkom bedkom')
      .should('be.visible');

    cy.contains(c('AttendanceStatus__poolBox'), 'Ikke svart')
      .should('contain.text', '2/2')
      .find('button')
      .click();

    cy.get(c('AttendanceModal__list'))
      .find(c('AttendanceModal__row'))
      .should('have.length', 2)
      .should('contain', 'bedkom bedkom')
      .should('contain', 'webkom webkom');

    cy.get(c('Modal__closeButton')).click();
  });

  it('should edit meeting', () => {
    mockMazemapApi();
    moment.locale('nb-NO');

    const meeting = {
      title: 'Test meeting',
      report: '
Meeting plan
',
      description: 'test description',
      location: 'Test location',
      startTime: moment()
        .tz(config.timezone)
        .startOf('day')
        .add({ hours: 17, minutes: 14 })
        .toISOString(),
      endTime: moment()
        .tz(config.timezone)
        .startOf('day')
        .add({ hours: 20, minutes: 0 })
        .toISOString(),
      reportAuthor: 3,
      mazemapPoi: null,
    };

    cy.apiRequest({
      method: 'POST',
      url: '/api/v1/meetings/',
      body: JSON.stringify(meeting),
    });

    cy.intercept('POST', '/api/v1/meetings/1/bulk_invite/').as(
      'inviteToMeeting'
    );

    cy.visit('/meetings');

    cy.contains('h3', meeting.title).click();

    cy.contains('a', 'Rediger').click();

    field('title').should('have.value', meeting.title);
    selectEditor().should('contain', meeting.report.replaceAll(/<.*?>/g, ''));
    field('startTime').should(
      'have.value',
      moment(meeting.startTime).tz(config.timezone).format('lll')
    );
    field('endTime').should(
      'have.value',
      moment(meeting.endTime).tz(config.timezone).format('lll')
    );
    field('description').should('have.value', meeting.description);
    field('location').should('have.value', meeting.location);
    selectField('reportAuthor').should('contain.text', 'webkom webkom');

    // verify invited users modal
    cy.get(c('AttendanceStatus__poolBox')).find('button').click();
    cy.contains(c('AttendanceModal__row'), 'webkom webkom').should(
      'be.visible'
    );
    cy.get(c('Modal__closeButton')).click();

    setDatePickerTime('startTime', '17', '15');
    field('useMazemap').click();
    selectFromSelectField('mazemapPoi', 'Abakus, Realfagbygget', 'a');
    selectFromSelectField('users', 'bedkom bedkom (bedkom)', 'bedkom');

    selectEditor().type('{enter}{enter}Meeting report');
    cy.wait(100); // wait for lego-editor debounce

    fieldError('title').should('not.exist');
    fieldError('report').should('not.exist');
    fieldError('startTime').should('not.exist');
    fieldError('endTime').should('not.exist');
    fieldError('location').should('not.exist');
    fieldError('reportAuthor').should('not.exist');
    fieldError('users').should('not.exist');
    fieldError('groups').should('not.exist');

    cy.contains('button', 'Lagre endringer').click();

    cy.wait('@inviteToMeeting').then((interception) => {
      expect(interception.request.body).to.deep.eq({ groups: [], users: [4] });
    });

    cy.contains('h1', meeting.title).should('be.visible');
    cy.contains('time', '17:15').should('be.visible');
    cy.contains(c('legoEditor_disabled'), 'Meeting plan').should('be.visible');
    cy.contains(c('legoEditor_disabled'), 'Meeting report').should(
      'be.visible'
    );
    cy.contains('tr', 'Når')
      .should('contain.text', '17:15 - 20:00')
      .should('be.visible');
    cy.contains('tr', 'Sted')
      .should('contain.text', 'Abakus, Realfagbygget')
      .should('be.visible');
    cy.contains('tr', 'Forfatter')
      .should('contain.text', 'webkom webkom')
      .should('be.visible');
    cy.contains('tr', 'Referent')
      .should('contain.text', 'webkom webkom')
      .should('be.visible');

    cy.contains(c('AttendanceStatus__poolBox'), 'Ikke svart')
      .should('contain.text', '2/2')
      .find('button')
      .click();

    cy.get(c('AttendanceModal__list'))
      .find(c('AttendanceModal__row'))
      .should('have.length', 2)
      .should('contain', 'bedkom bedkom')
      .should('contain', 'webkom webkom');

    cy.get(c('Modal__closeButton')).click();
  });

  it('should show correct options for referent', () => {
    cy.visit('/meetings/create');

    const verifyAuthors = (expectedAuthors) => {
      selectField('reportAuthor').click();
      selectFieldDropdown('reportAuthor')
        .find(c('option'))
        .should('have.length', expectedAuthors.length)
        .each((author, index) => {
          cy.wrap(author).should('contain.text', expectedAuthors[index]);
        });
      selectField('reportAuthor').click();
    };
    verifyAuthors(['webkom webkom']);

    selectFromSelectField('users', 'bedkom bedkom (bedkom)', 'bedkom');
    verifyAuthors(['webkom webkom', 'bedkom bedkom (bedkom)']);

    selectFromSelectField(
      'users',
      'Quinton Armstrong (quintonarmstrong)',
      'Quinton'
    );
    verifyAuthors([
      'webkom webkom',
      'bedkom bedkom (bedkom)',
      'Quinton Armstrong (quintonarmstrong)',
    ]);
  });
});
<commit_msg>Set end time explicitly in cypress meeting test<commit_after>import {
  c,
  field,
  fieldError,
  fieldErrors,
  mockMazemapApi,
  selectEditor,
  setDatePickerTime,
  selectFromSelectField,
  selectFieldDropdown,
  selectField,
} from '../support/utils.js';
import moment from 'moment-timezone';
import config from '../support/config';

describe('Create meeting', () => {
  beforeEach(() => {
    cy.resetDb();
    cy.cachedLogin();
  });

  it('should show correct validation errors', () => {
    cy.visit('/meetings/create');
    // Check that validation errors show only after we try to submit
    fieldErrors().should('not.exist');

    // verify initial values are set
    field('useMazemap').should('be.checked');
    field('startTime').should('not.have.value', '');
    field('endTime').should('not.have.value', '');

    cy.contains('button', 'Opprett møte').should('be.disabled');
    // change the meeting time to enable submit button
    field('startTime').click();
    cy.get(c('TimePicker__arrowIcon')).first().click();

    cy.contains('button', 'Opprett møte').should('not.be.disabled').click();

    fieldErrors().should('exist');

    fieldError('title').should('be.visible');
    fieldError('report').should('be.visible');
    fieldError('mazemapPoi').should('be.visible');

    fieldError('startTime').should('not.exist');
    fieldError('endTime').should('not.exist');
    fieldError('description').should('not.exist');

    setDatePickerTime('startTime', '20', '00');
    setDatePickerTime('endTime', '19', '30');

    fieldError('endTime').should('be.visible');

    setDatePickerTime('endTime', '20', '30');

    fieldError('endTime').should('not.exist');

    field('useMazemap').click();

    cy.contains('button', 'Opprett møte').click();

    fieldError('location').should('be.visible');

    // remove validation errors
    field('title').type('test');
    fieldError('title').should('not.exist');

    selectEditor().type('test');
    fieldError('report').should('not.exist');

    field('location').type('test');
    fieldError('location').should('not.exist');

    fieldErrors().should('not.exist');
  });

  it('should create basic meeting', () => {
    cy.intercept('POST', '/api/v1/meetings/1/bulk_invite/').as(
      'inviteToMeeting'
    );

    cy.visit('/meetings/create');

    field('title').type('Test meeting');
    selectEditor().type('Meeting plan');
    setDatePickerTime('startTime', '10', '00');
    setDatePickerTime('endTime', '13', '37');
    field('useMazemap').click();
    field('location').type('Test location');
    selectFromSelectField('users', 'bedkom bedkom (bedkom)', 'bedkom');
    selectFromSelectField('reportAuthor', 'bedkom bedkom', 'bedkom');
    selectFromSelectField('groups', 'Arrkom');

    fieldError('title').should('not.exist');
    fieldError('report').should('not.exist');
    fieldError('startTime').should('not.exist');
    fieldError('endTime').should('not.exist');
    fieldError('location').should('not.exist');
    fieldError('reportAuthor').should('not.exist');
    fieldError('users').should('not.exist');
    fieldError('groups').should('not.exist');

    cy.contains('button', 'Opprett møte').click();

    cy.wait('@inviteToMeeting').then((interception) => {
      expect(interception.request.body).to.deep.eq({ groups: [4], users: [4] });
    });

    cy.contains('h1', 'Test meeting').should('be.visible');
    cy.contains('time', '10:00').should('be.visible');
    cy.contains(c('legoEditor_disabled'), 'Meeting plan').should('be.visible');
    cy.contains('tr', 'Når')
      .should('contain.text', '10:00 - 13:37')
      .should('be.visible');
    cy.contains('tr', 'Sted')
      .should('contain.text', 'Test location')
      .should('be.visible');
    cy.contains('tr', 'Forfatter')
      .should('contain.text', 'webkom webkom')
      .should('be.visible');
    cy.contains('tr', 'Referent')
      .should('contain.text', 'bedkom bedkom')
      .should('be.visible');

    cy.contains(c('AttendanceStatus__poolBox'), 'Ikke svart')
      .should('contain.text', '2/2')
      .find('button')
      .click();

    cy.get(c('AttendanceModal__list'))
      .find(c('AttendanceModal__row'))
      .should('have.length', 2)
      .should('contain', 'bedkom bedkom')
      .should('contain', 'webkom webkom');

    cy.get(c('Modal__closeButton')).click();
  });

  it('should edit meeting', () => {
    mockMazemapApi();
    moment.locale('nb-NO');

    const meeting = {
      title: 'Test meeting',
      report: '
Meeting plan
',
      description: 'test description',
      location: 'Test location',
      startTime: moment()
        .tz(config.timezone)
        .startOf('day')
        .add({ hours: 17, minutes: 14 })
        .toISOString(),
      endTime: moment()
        .tz(config.timezone)
        .startOf('day')
        .add({ hours: 20, minutes: 0 })
        .toISOString(),
      reportAuthor: 3,
      mazemapPoi: null,
    };

    cy.apiRequest({
      method: 'POST',
      url: '/api/v1/meetings/',
      body: JSON.stringify(meeting),
    });

    cy.intercept('POST', '/api/v1/meetings/1/bulk_invite/').as(
      'inviteToMeeting'
    );

    cy.visit('/meetings');

    cy.contains('h3', meeting.title).click();

    cy.contains('a', 'Rediger').click();

    field('title').should('have.value', meeting.title);
    selectEditor().should('contain', meeting.report.replaceAll(/<.*?>/g, ''));
    field('startTime').should(
      'have.value',
      moment(meeting.startTime).tz(config.timezone).format('lll')
    );
    field('endTime').should(
      'have.value',
      moment(meeting.endTime).tz(config.timezone).format('lll')
    );
    field('description').should('have.value', meeting.description);
    field('location').should('have.value', meeting.location);
    selectField('reportAuthor').should('contain.text', 'webkom webkom');

    // verify invited users modal
    cy.get(c('AttendanceStatus__poolBox')).find('button').click();
    cy.contains(c('AttendanceModal__row'), 'webkom webkom').should(
      'be.visible'
    );
    cy.get(c('Modal__closeButton')).click();

    setDatePickerTime('startTime', '17', '15');
    setDatePickerTime('endTime', '20', '00');
    field('useMazemap').click();
    selectFromSelectField('mazemapPoi', 'Abakus, Realfagbygget', 'a');
    selectFromSelectField('users', 'bedkom bedkom (bedkom)', 'bedkom');

    selectEditor().type('{enter}{enter}Meeting report');
    cy.wait(100); // wait for lego-editor debounce

    fieldError('title').should('not.exist');
    fieldError('report').should('not.exist');
    fieldError('startTime').should('not.exist');
    fieldError('endTime').should('not.exist');
    fieldError('location').should('not.exist');
    fieldError('reportAuthor').should('not.exist');
    fieldError('users').should('not.exist');
    fieldError('groups').should('not.exist');

    cy.contains('button', 'Lagre endringer').click();

    cy.wait('@inviteToMeeting').then((interception) => {
      expect(interception.request.body).to.deep.eq({ groups: [], users: [4] });
    });

    cy.contains('h1', meeting.title).should('be.visible');
    cy.contains('time', '17:15').should('be.visible');
    cy.contains(c('legoEditor_disabled'), 'Meeting plan').should('be.visible');
    cy.contains(c('legoEditor_disabled'), 'Meeting report').should(
      'be.visible'
    );
    cy.contains('tr', 'Når')
      .should('contain.text', '17:15 - 20:00')
      .should('be.visible');
    cy.contains('tr', 'Sted')
      .should('contain.text', 'Abakus, Realfagbygget')
      .should('be.visible');
    cy.contains('tr', 'Forfatter')
      .should('contain.text', 'webkom webkom')
      .should('be.visible');
    cy.contains('tr', 'Referent')
      .should('contain.text', 'webkom webkom')
      .should('be.visible');

    cy.contains(c('AttendanceStatus__poolBox'), 'Ikke svart')
      .should('contain.text', '2/2')
      .find('button')
      .click();

    cy.get(c('AttendanceModal__list'))
      .find(c('AttendanceModal__row'))
      .should('have.length', 2)
      .should('contain', 'bedkom bedkom')
      .should('contain', 'webkom webkom');

    cy.get(c('Modal__closeButton')).click();
  });

  it('should show correct options for referent', () => {
    cy.visit('/meetings/create');

    const verifyAuthors = (expectedAuthors) => {
      selectField('reportAuthor').click();
      selectFieldDropdown('reportAuthor')
        .find(c('option'))
        .should('have.length', expectedAuthors.length)
        .each((author, index) => {
          cy.wrap(author).should('contain.text', expectedAuthors[index]);
        });
      selectField('reportAuthor').click();
    };
    verifyAuthors(['webkom webkom']);

    selectFromSelectField('users', 'bedkom bedkom (bedkom)', 'bedkom');
    verifyAuthors(['webkom webkom', 'bedkom bedkom (bedkom)']);

    selectFromSelectField(
      'users',
      'Quinton Armstrong (quintonarmstrong)',
      'Quinton'
    );
    verifyAuthors([
      'webkom webkom',
      'bedkom bedkom (bedkom)',
      'Quinton Armstrong (quintonarmstrong)',
    ]);
  });
});
<|endoftext|>