<commit_before>// Copyright 2012 Pedro P. Candel <kusorbox@gmail.com> All rights reserved.
var fork = require('hydracp').fork,
    util = require('util'),
    uuid = require('node-uuid'),
    async = require('async');

// - opts - configuration options:
//    - identifier: Unique identifier for this runner.
//    - forks: Max number of child processes to fork at the same time.
//    - run_interval: Check for new jobs every 'run_interval' minutes.
//                    (By default, every 2 minutes).
var WorkflowRunner = module.exports = function(backend, opts) {
  this.backend = backend;
  if (!opts) {
    opts = {};
  }
  this.identifier = opts.identifier || null;
  this.forks = opts.forks || 10;
  this.run_interval = (opts.run_interval || 2) * 60 * 1000;
  this.interval = null;
  this.shutting_down = false;
  this.child_processes = [];
};

WorkflowRunner.prototype.init = function(callback) {
  var self = this,
      series = [];
  // Using async.series we can kind of write this sequentially and run some
  // methods only when needed:

  // 1) We need an unique identifier for this runner, also something
  //    human friendly to identify the runner from a bunch of them would be
  //    helpful.
  if (!self.identifier) {
    series.push(function(cb) {
      self.getIdentifier(function(err, uuid) {
        if (err) {
          return cb(err);
        }
        self.identifier = uuid;
        // Not that we really care about the return value here, anyway:
        return cb(null, uuid);
      });
    });
  }
  //    The runner will register itself on the backend, with its unique id.
  series.push(function(cb) {
    self.backend.registerRunner(self.identifier, function(err) {
      if (err) {
        return cb(err);
      }
      return cb(null, null);
    });
  });
  // 2) On init, the runner will check for any job flagged as running with
  //    the runner identifier. This means a previous failure so, first thing
  //    will be take care of such failure.
  //    NOTE: It would be desirable to "hide" the next queued Job to the
  //    other runners when we fetch it for the current one.
  async.series(series, function(err, results) {
    // Note we don't care at all about the results.
    if (err) {
      return callback(err);
    }
    callback();
  });
};

// Wait for children to finish, do not began any other child process.
// Call callback on done.
WorkflowRunner.prototype.quit = function(callback) {
  var self = this;
  self.shutting_down = true;
  clearInterval(self.interval);
  if (self.child_processes.length > 0) {
    process.nextTick(function() {
      self.quit(callback);
    });
  } else {
    callback();
  }
};

WorkflowRunner.prototype.runJob = function(job, callback) {
  var self = this,
  child = fork(
    __dirname + '/runner-child.js', ['some', 'args']
  );

  if (job.chain && typeof job.chain === 'string') {
    job.chain = JSON.parse(job.chain);
  }
  if (job.onerror && typeof job.onerror === 'string') {
    job.onerror = JSON.parse(job.onerror);
  }

  // Save pid for later:
  child._pid = child.pid;
  self.child_processes.push(child._pid);

  child.on('message', function(msg) {
    var objProps = ['chain', 'chain_results', 'onerror', 'onerror_results'];
    console.log('Got a message from child process');
    console.log(util.inspect(msg, false, 8));

    if (!msg.job) {
      console.error('Child process message does not contain job information');
    }

    // TODO: decide about 'failed' jobs before to handle them differently
    if (msg.error !== '') {
      console.error('Child process message error: ' + msg.error);
    }

    objProps.forEach(function(p) {
      if (typeof msg.job[p] === 'object') {
        msg.job[p] = JSON.stringify(msg.job[p]);
      }
    });

    // Decide what to do with the Job depending on its status:
    if (msg.job.status === 'finished') {
      console.log('Finishing job ...');
      self.backend.finishJob(msg.job, function(err) {
        if (err) {
          callback(err);
        }
        callback();
      });
    } else if (msg.job.status === 'queued') {
      console.log('Re queueing job ...');
      self.backend.queueJob(msg.job, function(err) {
        if (err) {
          callback(err);
        }
        callback();
      });
    }
  });

  child.on('exit', function(code) {
    console.log('Child process exited with code: ' + code);
    var idx = self.child_processes.indexOf(child._pid);
    if (idx !== -1) {
      self.child_processes.splice(idx, 1);
    }
  });

  child.send({
    job: job
  });
};

WorkflowRunner.prototype.getIdentifier = function(callback) {
  var self = this,
      fs = require('fs'),
      path = require('path'),
      cfg_file = path.resolve(__dirname, '../config/workflow-indentifier');

  path.exists(cfg_file, function(exist) {
    if (exist) {
      fs.readFile(cfg_file, 'utf-8', function(err, data) {
        if (err) {
          return callback(err);
        }
        return callback(null, data);
      });
    } else {
      var id = uuid();
      fs.writeFile(cfg_file, id, 'utf-8', function(err) {
        if (err) {
          return callback(err);
        }
        return callback(null, id);
      });
    }
  });
};

WorkflowRunner.prototype.runNow = function(job) {
  return (new Date().getTime() >= new Date(job.exec_after).getTime());
};

// Just in case we need to kill it without leaving child processes around:
WorkflowRunner.prototype.kill = function(callback) {
  var self = this;
  self.shutting_down = true;
  if (self.child_processes.length > 0) {
    self.child_processes.forEach(function(p) {
      process.kill(p, 'SIGKILL');
    });
    self.child_processes = [];
  }
  if (callback) {
    callback();
  }
};

WorkflowRunner.prototype.run = function() {
  var self = this,
  // Queue worker. Tries to run a job, including "hiding" it from other
  // runners:
  worker = function(uuid, callback) {
    self.backend.getJob(uuid, function(err, job) {
      if (err) {
        callback(err);
      }

      if (self.runNow(job)) {
        self.backend.runJob(uuid, self.identifier, function(err) {
          if (err) {
            callback(err);
          }
          self.runJob(job, function(err) {
            if (err) {
              callback(err);
            }
            callback();
          });
        });
      }
    });
  },
  // We keep a queue with concurrency limit where we'll be pushing new jobs
  queue = async.queue(worker, self.forks);

  self.interval = setInterval(function() {
    self.backend.runnerActive(self.identifier, function(err) {
      if (err) {
        console.error('Error reporting runner activity: ' + err);
        return;
      }
      var fetch = self.forks - self.child_processes.length;
      self.backend.nextJobs(0, fetch - 1, function(err, jobs) {
        // Error fetching jobs
        if (err) {
          console.error('Error fetching jobs: ' + err);
          return;
        }
        // No queued jobs
        if (!jobs) {
          console.info('No jobs queued');
          return;
        }
        // Got jobs, let's see if we can run them:
        jobs.forEach(function(job) {
          queue.push(job, function(err) {
            // To be called once queue worker finished processing the job
            if (err) {
              console.error('Error running job: ' + err);
            }
            console.info('Job with uuid ' + job + ' ran successfully');
          });
        });
      });
    });
  }, self.run_interval);
};

// Wishlist:
// - Make runners update their status every X seconds (configurable).
// - Runners should be able to pick any job on a "weird" status when
//   we detect a runner isn't upgrading its status after Y seconds
//   (also configurable), obviously Y needs to be greater than X.
<commit_msg>Clear runner queue interval when quiting runner the hard way<commit_after>// Copyright 2012 Pedro P. Candel <kusorbox@gmail.com> All rights reserved.
var fork = require('hydracp').fork,
    util = require('util'),
    uuid = require('node-uuid'),
    async = require('async');

// - opts - configuration options:
//    - identifier: Unique identifier for this runner.
//    - forks: Max number of child processes to fork at the same time.
//    - run_interval: Check for new jobs every 'run_interval' minutes.
//                    (By default, every 2 minutes).
var WorkflowRunner = module.exports = function(backend, opts) {
  this.backend = backend;
  if (!opts) {
    opts = {};
  }
  this.identifier = opts.identifier || null;
  this.forks = opts.forks || 10;
  this.run_interval = (opts.run_interval || 2) * 60 * 1000;
  this.interval = null;
  this.shutting_down = false;
  this.child_processes = [];
};

WorkflowRunner.prototype.init = function(callback) {
  var self = this,
      series = [];
  // Using async.series we can kind of write this sequentially and run some
  // methods only when needed:

  // 1) We need an unique identifier for this runner, also something
  //    human friendly to identify the runner from a bunch of them would be
  //    helpful.
  if (!self.identifier) {
    series.push(function(cb) {
      self.getIdentifier(function(err, uuid) {
        if (err) {
          return cb(err);
        }
        self.identifier = uuid;
        // Not that we really care about the return value here, anyway:
        return cb(null, uuid);
      });
    });
  }
  //    The runner will register itself on the backend, with its unique id.
  series.push(function(cb) {
    self.backend.registerRunner(self.identifier, function(err) {
      if (err) {
        return cb(err);
      }
      return cb(null, null);
    });
  });
  // 2) On init, the runner will check for any job flagged as running with
  //    the runner identifier. This means a previous failure so, first thing
  //    will be take care of such failure.
  //    NOTE: It would be desirable to "hide" the next queued Job to the
  //    other runners when we fetch it for the current one.
  async.series(series, function(err, results) {
    // Note we don't care at all about the results.
    if (err) {
      return callback(err);
    }
    callback();
  });
};

// Wait for children to finish, do not began any other child process.
// Call callback on done.
WorkflowRunner.prototype.quit = function(callback) {
  var self = this;
  self.shutting_down = true;
  clearInterval(self.interval);
  if (self.child_processes.length > 0) {
    process.nextTick(function() {
      self.quit(callback);
    });
  } else {
    callback();
  }
};

WorkflowRunner.prototype.runJob = function(job, callback) {
  var self = this,
  child = fork(
    __dirname + '/runner-child.js', ['some', 'args']
  );

  if (job.chain && typeof job.chain === 'string') {
    job.chain = JSON.parse(job.chain);
  }
  if (job.onerror && typeof job.onerror === 'string') {
    job.onerror = JSON.parse(job.onerror);
  }

  // Save pid for later:
  child._pid = child.pid;
  self.child_processes.push(child._pid);

  child.on('message', function(msg) {
    var objProps = ['chain', 'chain_results', 'onerror', 'onerror_results'];
    console.log('Got a message from child process');
    console.log(util.inspect(msg, false, 8));

    if (!msg.job) {
      console.error('Child process message does not contain job information');
    }

    // TODO: decide about 'failed' jobs before to handle them differently
    if (msg.error !== '') {
      console.error('Child process message error: ' + msg.error);
    }

    objProps.forEach(function(p) {
      if (typeof msg.job[p] === 'object') {
        msg.job[p] = JSON.stringify(msg.job[p]);
      }
    });

    // Decide what to do with the Job depending on its status:
    if (msg.job.status === 'finished') {
      console.log('Finishing job ...');
      self.backend.finishJob(msg.job, function(err) {
        if (err) {
          callback(err);
        }
        callback();
      });
    } else if (msg.job.status === 'queued') {
      console.log('Re queueing job ...');
      self.backend.queueJob(msg.job, function(err) {
        if (err) {
          callback(err);
        }
        callback();
      });
    }
  });

  child.on('exit', function(code) {
    console.log('Child process exited with code: ' + code);
    var idx = self.child_processes.indexOf(child._pid);
    if (idx !== -1) {
      self.child_processes.splice(idx, 1);
    }
  });

  child.send({
    job: job
  });
};

WorkflowRunner.prototype.getIdentifier = function(callback) {
  var self = this,
      fs = require('fs'),
      path = require('path'),
      cfg_file = path.resolve(__dirname, '../config/workflow-indentifier');

  path.exists(cfg_file, function(exist) {
    if (exist) {
      fs.readFile(cfg_file, 'utf-8', function(err, data) {
        if (err) {
          return callback(err);
        }
        return callback(null, data);
      });
    } else {
      var id = uuid();
      fs.writeFile(cfg_file, id, 'utf-8', function(err) {
        if (err) {
          return callback(err);
        }
        return callback(null, id);
      });
    }
  });
};

WorkflowRunner.prototype.runNow = function(job) {
  return (new Date().getTime() >= new Date(job.exec_after).getTime());
};

// Just in case we need to kill it without leaving child processes around:
WorkflowRunner.prototype.kill = function(callback) {
  var self = this;
  self.shutting_down = true;
  clearInterval(self.interval);
  if (self.child_processes.length > 0) {
    self.child_processes.forEach(function(p) {
      process.kill(p, 'SIGKILL');
    });
    self.child_processes = [];
  }
  if (callback) {
    callback();
  }
};

WorkflowRunner.prototype.run = function() {
  var self = this,
  // Queue worker. Tries to run a job, including "hiding" it from other
  // runners:
  worker = function(uuid, callback) {
    self.backend.getJob(uuid, function(err, job) {
      if (err) {
        callback(err);
      }

      if (self.runNow(job)) {
        self.backend.runJob(uuid, self.identifier, function(err) {
          if (err) {
            callback(err);
          }
          self.runJob(job, function(err) {
            if (err) {
              callback(err);
            }
            callback();
          });
        });
      }
    });
  },
  // We keep a queue with concurrency limit where we'll be pushing new jobs
  queue = async.queue(worker, self.forks);

  self.interval = setInterval(function() {
    self.backend.runnerActive(self.identifier, function(err) {
      if (err) {
        console.error('Error reporting runner activity: ' + err);
        return;
      }
      var fetch = self.forks - self.child_processes.length;
      self.backend.nextJobs(0, fetch - 1, function(err, jobs) {
        // Error fetching jobs
        if (err) {
          console.error('Error fetching jobs: ' + err);
          return;
        }
        // No queued jobs
        if (!jobs) {
          console.info('No jobs queued');
          return;
        }
        // Got jobs, let's see if we can run them:
        jobs.forEach(function(job) {
          queue.push(job, function(err) {
            // To be called once queue worker finished processing the job
            if (err) {
              console.error('Error running job: ' + err);
            }
            console.info('Job with uuid ' + job + ' ran successfully');
          });
        });
      });
    });
  }, self.run_interval);
};

// Wishlist:
// - Make runners update their status every X seconds (configurable).
// - Runners should be able to pick any job on a "weird" status when
//   we detect a runner isn't upgrading its status after Y seconds
//   (also configurable), obviously Y needs to be greater than X.
<|endoftext|>


<commit_before>var gulp = require('gulp'),
    argv = require('yargs').argv,
    cache = require('gulp-cache'),
    concat = require('gulp-concat'),
    del = require('del'),
    gulpif = require('gulp-if'),
    gutil = require('gulp-util')
    notify = require('gulp-notify'),
    plumber = require('gulp-plumber'),
    q = require('q'),
    rename = require('gulp-rename'),
    replace = require('gulp-replace'),
    size = require('gulp-size');

var config = require('./package.json');
var settings = config.settings;
    settings.liveReload=false;
    settings.plumberConfig=function(){
      return {'errorHandler': onError};
    };

/**
 * browser-sync task for starting a server. This will open a browser for you. Point multiple browsers / devices to the same url and watch the magic happen.
 * Depends on: watch
 */
gulp.task('browser-sync', ['watch'], function() {
  var browserSync = require('browser-sync'),
  port = argv.port||settings.serverport;

  // Watch any files in dist/*, reload on change
  gulp.watch([settings.dist + '**']).on('change', function(){browserSync.reload({});});

  return browserSync({
      browser: ["google chrome"],
      ghostMode: {
        clicks: true,
        location: true,
        forms: true,
        scroll: true
      },
      injectChanges: true, // inject CSS changes (false force a reload)
      logLevel: "info",
      open: false, // "local", "external", "ui"
      port: port,
      scrollProportionally: true, // Sync viewports to TOP position
      scrollThrottle: 50,
      server: {
          baseDir: settings.dist
      },
      ui: {
        port: port+1,
        weinre: {
            port: port+2
        }
      }
    });
});

/**
 * Build and copy all styles, scripts, images and fonts.
 * Depends on: info, clean
 */
gulp.task('build', ['info', 'clean'], function() {
  gulp.start('styles', 'scripts', 'images', 'copy', 'todo');
});

/**
 * Cleans the `dist` folder and other generated files
 */
gulp.task('clean', ['clear-cache'],  function(cb) {
  del([settings.dist, 'todo.md', 'todo.json'], cb);
});

/**
 * Clears the cache used by gulp-cache
 */
gulp.task('clear-cache', function() {
  // Or, just call this for everything
  cache.clearAll();
});

/**
 * Copies all to dist/
 */
gulp.task('copy', ['copy-fonts', 'copy-template', 'copy-index'], function() {});

/**
 * Task for copying fonts only
 */
gulp.task('copy-fonts', function() {
  var deferred = q.defer();
   // copy all fonts
   setTimeout(function() {
    gulp.src( settings.src + 'fonts/**')
      .pipe(gulp.dest(settings.dist + 'fonts'));
       deferred.resolve();
  }, 1);

  return deferred.promise;
});

/**
 * Task for copying templates. This will lint the HTML and remove comments
 */
gulp.task('copy-template', function() {
  var htmlmin = require('gulp-htmlmin'),
      htmlhint = require("gulp-htmlhint");
  // copy all html && json
  return gulp.src( [settings.src + 'js/app/**/*.html', settings.src + 'js/app/**/*.json'])
    .pipe(htmlhint({
      htmlhintrc: '.htmlhintrc',
    }))
    .pipe(htmlhint.reporter())
    // html min MUST come after the html hinter
    .pipe(htmlmin({
      collapseWhitespace: false,
      removeComments: true,
    }))
    .pipe(cache(gulp.dest(settings.dist + '/js/app')));
});

/**
 * Task for copying index page only. Optionally add live reload script to it
 */
gulp.task('copy-index', function() {
  var htmlmin = require('gulp-htmlmin');
   // copy the index.html
   return gulp.src(settings.src + 'index.html')
    .pipe(htmlmin({
      collapseWhitespace: false,
      removeComments: true,
    }))
    .pipe(gulpif(settings.liveReload, replace(/(\<\/body\>)/g, "<script>document.write('<script src=\"http://' + (location.host || 'localhost').split(':')[0] + ':35729/livereload.js?snipver=1\"></' + 'script>')</script>$1")))
    .pipe(cache(gulp.dest(settings.dist)));
});

/**
 * Default task.
 * Depends on: build
 */
gulp.task('default', ['build']);

/**
 * Create Javascript documentation
 */
gulp.task('docs-js', ['todo'], function(){
  var gulpDoxx = require('gulp-doxx');

  gulp.src([settings.src + '/js/**/*.js', 'README.md', settings.reports + '/TODO.md', settings.tests + "/**" ])
    .pipe(gulpDoxx({
      title: config.name + " docs",
      urlPrefix: "file:///"+__dirname+settings.reports
    }))
    .pipe(gulp.dest(settings.reports));
});

/**
 * Task to optimize and deploy all images found in folder `src/img/**`. Result is copied to `dist/img`
 */
gulp.task('images', function() {
  var imagemin = require('gulp-imagemin');
  var deferred = q.defer();

  setTimeout(function() {
    gulp.src(settings.src + 'img/**/*')
      .pipe(plumber(settings.plumberConfig()))
      .pipe(size({title:"images before"}))
      .pipe(cache(imagemin({ optimizationLevel: 5, progressivee: true, interlaced: true })))
      .pipe(size({title:"images after "}))
      .pipe(gulp.dest(settings.dist + 'img'));
    deferred.resolve();
  }, 1);

  return deferred.promise;
});

gulp.task('list', function() {
  var max = function(){
    var max = 0;
    for (var key in gulp.tasks) {
      if(max < key.length){max = key.length;}
    }
    return max;
  },
  print = function(key, max){
    while (key.length < max){
      key += " ";
    }
    return key;
  }

  for (var key in gulp.tasks) {
    var out = print(key, max()), task = gulp.tasks[key];
    if (task.hasOwnProperty('dep') && task.dep.length > 0){
      out += '  dep: ' + task.dep;
    }

    console.log(out);
  }
});

/**
 * log some info about this app
 */
gulp.task('info',function(){
  // log project details
  gutil.log( gutil.colors.cyan("Running gulp on project "+config.name+" v"+ config.version) );
  gutil.log( gutil.colors.cyan("Author: " + config.author.name) );
  gutil.log( gutil.colors.cyan("Email : " + config.author.email) );
  gutil.log( gutil.colors.cyan("Site  : " + config.author.url) );
  // log info
  gutil.log("If you have an enhancement or encounter a bug, please report them on", gutil.colors.magenta(config.bugs.url));
});

/**
 * Start the live reload server. Live reload will be triggered when a file in the `dist` folder changes. This will add a live-reload script to the index.html page, which makes it all happen.
 * Depends on: watch
 */
gulp.task('live-reload', ['watch'], function() {
  var livereload = require('gulp-livereload');

  settings.liveReload = true;
  // first, delete the index.html from the dist folder as we will copy it later
  del([settings.dist + 'index.html']);

  // add livereload script to the index.html
  gulp.src([settings.src + 'index.html'])
   .pipe(replace(/(\<\/body\>)/g, "<script>document.write('<script src=\"http://' + (location.host || 'localhost').split(':')[0] + ':35729/livereload.js?snipver=1\"></' + 'script>')</script>$1"))
   .pipe(gulp.dest(settings.dist));

  // Create LiveReload server
  livereload.listen();

  // Watch any files in dist/*, reload on change
  gulp.watch([settings.dist + '**']).on('change', livereload.changed);
});

/**
 * Packaging all compiled resources. Due to the async nature of other tasks, this task cannot depend on build... do a build first and then package it.
 */
gulp.task('package', function(cb) {
  var zip = require('gulp-zip'),
  fileName = config.name + '-' + config.version + '.zip'

  del(settings.dist+fileName);

  return gulp.src([settings.dist+'**'], { base: settings.dist })
  .pipe(zip(fileName))
  .pipe(gulp.dest('dist'));
});

/**
 * Task to handle and deploy all javascript, application & vendor
 *
 * Depends on: scripts-app, scripts-vendor
 */
gulp.task('scripts', ['scripts-app','scripts-vendor']);

/**
 * Removes the node_modules
 */
gulp.task('remove',['clean'], function(cb){
  del('node_modules', cb);
});

/**
 * Minifies all javascript found in the `src/js/**` folder. All files will be concatenated into `app.js`.  Minified and non-minified versions are copied to the dist folder.
 * This will also generete sourcemaps for the minified version. Depends on: docs-js
 */
gulp.task('scripts-app', ['docs-js'], function() {
  var jshint = require('gulp-jshint'),
      jscs = require('gulp-jscs'),
      map = require('map-stream'),
      ngannotate = require('gulp-ng-annotate'),
      stripDebug = require('gulp-strip-debug'),
      stylish = require('jshint-stylish'),
      sourcemaps = require('gulp-sourcemaps'),
      uglify = require('gulp-uglify');

  return gulp.src(settings.src + 'js/app/**/*.js')
    .pipe(plumber())
    .pipe(jscs({
      preset: "node-style-guide",
      verbose: true,
      // disable or change rules
      "requireTrailingComma": null,
      "validateLineBreaks": null,
      "disallowTrailingWhitespace": null,
      "maximumLineLength": 120,
      "disallowMultipleVarDecl": null
    }))

    .pipe(jshint('.jshintrc'))
    .pipe(jshint.reporter(stylish))

    .pipe(ngannotate({gulpWarnings: false}))
    .pipe(concat('app.js'))
    .pipe(gulp.dest(settings.dist + 'js'))

    // make minified
    .pipe(rename({suffix: '.min'}))
    .pipe(gulpif(!argv.dev, stripDebug()))
    .pipe(sourcemaps.init())
    .pipe(gulpif(!argv.dev, uglify()))
    .pipe(sourcemaps.write())
    .pipe(size({"showFiles":true}))
    .pipe(gulp.dest(settings.dist + 'js'));
});

/**
 * Task to handle all vendor specific javasript. All vendor javascript will be copied to the dist directory. Also a concatinated version will be made, available in \dist\js\vendor\vendor.js
 */
gulp.task('scripts-vendor', ['scripts-vendor-maps'], function() {
  var flatten = require('gulp-flatten');
  // mocks should be a separate file
  gulp.src(settings.src + 'js/vendor/*/**/angular-mocks.js')
    .pipe(flatten())
    .pipe(gulp.dest(settings.dist + 'js/vendor'));

  // script must be included in the right order. First include angular, then angular-route
  return gulp.src([settings.src + 'js/vendor/*/**/angular.min.js',settings.src + 'js/vendor/*/**/angular-route.min.js', "!"+settings.src + 'js/vendor/*/**/angular-mocks.js', settings.src + 'js/vendor/**/*.js'])
    .pipe(gulp.dest(settings.dist + 'js/vendor'))
    .pipe(concat('vendor.js'))
    .pipe(gulp.dest(settings.dist + 'js/vendor'));
});

/**
 * Copy all vendor .js.map files to the vendor location
 */
gulp.task('scripts-vendor-maps', function(){
  var flatten = require('gulp-flatten');

  return gulp.src(settings.src + 'js/vendor/**/*.js.map')
  .pipe(flatten())
  .pipe(gulp.dest(settings.dist + 'js/vendor'));
});

/**
 * TTask to start a server, use --port={{port}} to set the port, otherwist the port from the settings will be used (4000)
 */
gulp.task('server', function(){
  var express = require('express'),
  app = express(),
  port = argv.port||settings.serverport;
  app.use(express.static(__dirname + "/" + settings.dist));

  app.listen(port);
  gutil.log('Server started. Port', port,"baseDir",__dirname+"/"+settings.dist);
});

/**
 * Task to start a server on port 4000 and used the live reload functionality.
 * Depends on: server, live-reload
 */
gulp.task('start', ['live-reload', 'server'], function(){});

/**
 * Compile Sass into Css and minify it. Minified and non-minified versions are copied to the dist folder.
 * This will also auto prefix vendor specific rules.
 *
 * @see https://github.com/sass/node-sass for configuration
 */
gulp.task('styles', function() {
  var autoprefixer = require('gulp-autoprefixer'),
      cmq = require('gulp-combine-media-queries'),
      minifycss = require('gulp-minify-css'),
      sass = require('gulp-sass');

  return gulp.src([settings.src + 'styles/**/*.scss'])
    .pipe(plumber(settings.plumberConfig()))
    .pipe(sass({
      style: 'nested',
      precision: 5,
      sourceComments: argv.dev ? true : false
    }))
    .pipe(autoprefixer('last 2 version', 'safari 5', 'ie 8', 'ie 9', 'opera 12.1', 'ios 6', 'android 4'))
    .pipe(cmq({log: true}))
    .pipe(gulp.dest(settings.dist + 'css'))

    .pipe(size({"showFiles":true}))
    .pipe(rename({suffix: '.min'}))
    .pipe(minifycss())
    .pipe(cmq())
    .pipe(size({"showFiles":true}))
    .pipe(gulp.dest(settings.dist + 'css'));
});

/**
 * Run rests and keep watching changes for files
 */
gulp.task('test', function(done) {
  var karma = require('karma').server;
  karma.start({
    configFile: __dirname + '/karma.conf.js',
    singleRun: true
  }, done);
});

/**
 * Output TODO's & FIXME's in markdown and json file as well
 */
gulp.task('todo', function() {
  var todo = require('gulp-todo');
  gulp.src([settings.src + 'js/app/**/*.js',settings.src + 'styles/app/**/*.scss'])
    .pipe( plumber( settings.plumberConfig() ) )
    .pipe( todo() )
    .pipe( gulp.dest( settings.reports ) ) // output todo.md as markdown
    .pipe( todo.reporter('json', {fileName: 'todo.json'} ) )
    .pipe( gulp.dest( settings.reports ) ) // output todo.json as json
});

/**
 * Watches changes to template, Sass, javascript and image files. On change this will run the appropriate task, either: copy styles, templates, scripts or images.
 */
gulp.task('watch', function() {

  // watch index.html
  gulp.watch(settings.src + 'index.html', ['copy-index']);

  // watch html files
  gulp.watch(settings.src + '**/*.html', ['copy-template']);

  // watch fonts
  gulp.watch(settings.src + 'fonts/**', ['copy-fonts']);

  // Watch .scss files
  gulp.watch(settings.src + 'styles/**/*.scss', ['styles']);

  // Watch app .js files
  gulp.watch(settings.src + 'js/app/**/*.js', ['scripts-app']);

  // Watch vendor .js files
  gulp.watch(settings.src + 'js/vendor/**/*.js', ['scripts-vendor']);

  // Watch image files
  gulp.watch(settings.src + 'img/**/*', ['images']);
});

/**
 * Run rests and keep watching changes for files
 */
gulp.task('watch:test', function(done) {
  var karma = require('karma').server;
  karma.start({
    configFile: __dirname + '/karma.conf.js'
  }, done);
});

function onError(error){
  // TODO log error with gutil
  notify.onError(function (error) {
    gutil.log(error);
    return error.message;
  });
  this.emit('end');
}
<commit_msg>package in root folder<commit_after>var gulp = require('gulp'),
    argv = require('yargs').argv,
    cache = require('gulp-cache'),
    concat = require('gulp-concat'),
    del = require('del'),
    gulpif = require('gulp-if'),
    gutil = require('gulp-util')
    notify = require('gulp-notify'),
    plumber = require('gulp-plumber'),
    q = require('q'),
    rename = require('gulp-rename'),
    replace = require('gulp-replace'),
    size = require('gulp-size');

var config = require('./package.json');
var settings = config.settings;
    settings.liveReload=false;
    settings.plumberConfig=function(){
      return {'errorHandler': onError};
    };

/**
 * browser-sync task for starting a server. This will open a browser for you. Point multiple browsers / devices to the same url and watch the magic happen.
 * Depends on: watch
 */
gulp.task('browser-sync', ['watch'], function() {
  var browserSync = require('browser-sync'),
  port = argv.port||settings.serverport;

  // Watch any files in dist/*, reload on change
  gulp.watch([settings.dist + '**']).on('change', function(){browserSync.reload({});});

  return browserSync({
      browser: ["google chrome"],
      ghostMode: {
        clicks: true,
        location: true,
        forms: true,
        scroll: true
      },
      injectChanges: true, // inject CSS changes (false force a reload)
      logLevel: "info",
      open: false, // "local", "external", "ui"
      port: port,
      scrollProportionally: true, // Sync viewports to TOP position
      scrollThrottle: 50,
      server: {
          baseDir: settings.dist
      },
      ui: {
        port: port+1,
        weinre: {
            port: port+2
        }
      }
    });
});

/**
 * Build and copy all styles, scripts, images and fonts.
 * Depends on: info, clean
 */
gulp.task('build', ['info', 'clean'], function() {
  gulp.start('styles', 'scripts', 'images', 'copy', 'todo');
});

/**
 * Cleans the `dist` folder and other generated files
 */
gulp.task('clean', ['clear-cache'],  function(cb) {
  del([settings.dist, 'todo.md', 'todo.json'], cb);
});

/**
 * Clears the cache used by gulp-cache
 */
gulp.task('clear-cache', function() {
  // Or, just call this for everything
  cache.clearAll();
});

/**
 * Copies all to dist/
 */
gulp.task('copy', ['copy-fonts', 'copy-template', 'copy-index'], function() {});

/**
 * Task for copying fonts only
 */
gulp.task('copy-fonts', function() {
  var deferred = q.defer();
   // copy all fonts
   setTimeout(function() {
    gulp.src( settings.src + 'fonts/**')
      .pipe(gulp.dest(settings.dist + 'fonts'));
       deferred.resolve();
  }, 1);

  return deferred.promise;
});

/**
 * Task for copying templates. This will lint the HTML and remove comments
 */
gulp.task('copy-template', function() {
  var htmlmin = require('gulp-htmlmin'),
      htmlhint = require("gulp-htmlhint");
  // copy all html && json
  return gulp.src( [settings.src + 'js/app/**/*.html', settings.src + 'js/app/**/*.json'])
    .pipe(htmlhint({
      htmlhintrc: '.htmlhintrc',
    }))
    .pipe(htmlhint.reporter())
    // html min MUST come after the html hinter
    .pipe(htmlmin({
      collapseWhitespace: false,
      removeComments: true,
    }))
    .pipe(cache(gulp.dest(settings.dist + '/js/app')));
});

/**
 * Task for copying index page only. Optionally add live reload script to it
 */
gulp.task('copy-index', function() {
  var htmlmin = require('gulp-htmlmin');
   // copy the index.html
   return gulp.src(settings.src + 'index.html')
    .pipe(htmlmin({
      collapseWhitespace: false,
      removeComments: true,
    }))
    .pipe(gulpif(settings.liveReload, replace(/(\<\/body\>)/g, "<script>document.write('<script src=\"http://' + (location.host || 'localhost').split(':')[0] + ':35729/livereload.js?snipver=1\"></' + 'script>')</script>$1")))
    .pipe(cache(gulp.dest(settings.dist)));
});

/**
 * Default task.
 * Depends on: build
 */
gulp.task('default', ['build']);

/**
 * Create Javascript documentation
 */
gulp.task('docs-js', ['todo'], function(){
  var gulpDoxx = require('gulp-doxx');

  gulp.src([settings.src + '/js/**/*.js', 'README.md', settings.reports + '/TODO.md', settings.tests + "/**" ])
    .pipe(gulpDoxx({
      title: config.name + " docs",
      urlPrefix: "file:///"+__dirname+settings.reports
    }))
    .pipe(gulp.dest(settings.reports));
});

/**
 * Task to optimize and deploy all images found in folder `src/img/**`. Result is copied to `dist/img`
 */
gulp.task('images', function() {
  var imagemin = require('gulp-imagemin');
  var deferred = q.defer();

  setTimeout(function() {
    gulp.src(settings.src + 'img/**/*')
      .pipe(plumber(settings.plumberConfig()))
      .pipe(size({title:"images before"}))
      .pipe(cache(imagemin({ optimizationLevel: 5, progressivee: true, interlaced: true })))
      .pipe(size({title:"images after "}))
      .pipe(gulp.dest(settings.dist + 'img'));
    deferred.resolve();
  }, 1);

  return deferred.promise;
});

gulp.task('list', function() {
  var max = function(){
    var max = 0;
    for (var key in gulp.tasks) {
      if(max < key.length){max = key.length;}
    }
    return max;
  },
  print = function(key, max){
    while (key.length < max){
      key += " ";
    }
    return key;
  }

  for (var key in gulp.tasks) {
    var out = print(key, max()), task = gulp.tasks[key];
    if (task.hasOwnProperty('dep') && task.dep.length > 0){
      out += '  dep: ' + task.dep;
    }

    console.log(out);
  }
});

/**
 * log some info about this app
 */
gulp.task('info',function(){
  // log project details
  gutil.log( gutil.colors.cyan("Running gulp on project "+config.name+" v"+ config.version) );
  gutil.log( gutil.colors.cyan("Author: " + config.author.name) );
  gutil.log( gutil.colors.cyan("Email : " + config.author.email) );
  gutil.log( gutil.colors.cyan("Site  : " + config.author.url) );
  // log info
  gutil.log("If you have an enhancement or encounter a bug, please report them on", gutil.colors.magenta(config.bugs.url));
});

/**
 * Start the live reload server. Live reload will be triggered when a file in the `dist` folder changes. This will add a live-reload script to the index.html page, which makes it all happen.
 * Depends on: watch
 */
gulp.task('live-reload', ['watch'], function() {
  var livereload = require('gulp-livereload');

  settings.liveReload = true;
  // first, delete the index.html from the dist folder as we will copy it later
  del([settings.dist + 'index.html']);

  // add livereload script to the index.html
  gulp.src([settings.src + 'index.html'])
   .pipe(replace(/(\<\/body\>)/g, "<script>document.write('<script src=\"http://' + (location.host || 'localhost').split(':')[0] + ':35729/livereload.js?snipver=1\"></' + 'script>')</script>$1"))
   .pipe(gulp.dest(settings.dist));

  // Create LiveReload server
  livereload.listen();

  // Watch any files in dist/*, reload on change
  gulp.watch([settings.dist + '**']).on('change', livereload.changed);
});

/**
 * Packaging all compiled resources. Due to the async nature of other tasks, this task cannot depend on build... do a build first and then package it.
 */
gulp.task('package', function(cb) {
  var zip = require('gulp-zip'),
  fileName = config.name + '-' + config.version + '.zip'

  del(settings.dist+fileName);

  return gulp.src([settings.dist+'**'], { base: settings.dist })
  .pipe(zip(fileName))
  .pipe(gulp.dest('.'));
});

/**
 * Task to handle and deploy all javascript, application & vendor
 *
 * Depends on: scripts-app, scripts-vendor
 */
gulp.task('scripts', ['scripts-app','scripts-vendor']);

/**
 * Removes the node_modules
 */
gulp.task('remove',['clean'], function(cb){
  del('node_modules', cb);
});

/**
 * Minifies all javascript found in the `src/js/**` folder. All files will be concatenated into `app.js`.  Minified and non-minified versions are copied to the dist folder.
 * This will also generete sourcemaps for the minified version. Depends on: docs-js
 */
gulp.task('scripts-app', ['docs-js'], function() {
  var jshint = require('gulp-jshint'),
      jscs = require('gulp-jscs'),
      map = require('map-stream'),
      ngannotate = require('gulp-ng-annotate'),
      stripDebug = require('gulp-strip-debug'),
      stylish = require('jshint-stylish'),
      sourcemaps = require('gulp-sourcemaps'),
      uglify = require('gulp-uglify');

  return gulp.src(settings.src + 'js/app/**/*.js')
    .pipe(plumber())
    .pipe(jscs({
      preset: "node-style-guide",
      verbose: true,
      // disable or change rules
      "requireTrailingComma": null,
      "validateLineBreaks": null,
      "disallowTrailingWhitespace": null,
      "maximumLineLength": 120,
      "disallowMultipleVarDecl": null
    }))

    .pipe(jshint('.jshintrc'))
    .pipe(jshint.reporter(stylish))

    .pipe(ngannotate({gulpWarnings: false}))
    .pipe(concat('app.js'))
    .pipe(gulp.dest(settings.dist + 'js'))

    // make minified
    .pipe(rename({suffix: '.min'}))
    .pipe(gulpif(!argv.dev, stripDebug()))
    .pipe(sourcemaps.init())
    .pipe(gulpif(!argv.dev, uglify()))
    .pipe(sourcemaps.write())
    .pipe(size({"showFiles":true}))
    .pipe(gulp.dest(settings.dist + 'js'));
});

/**
 * Task to handle all vendor specific javasript. All vendor javascript will be copied to the dist directory. Also a concatinated version will be made, available in \dist\js\vendor\vendor.js
 */
gulp.task('scripts-vendor', ['scripts-vendor-maps'], function() {
  var flatten = require('gulp-flatten');
  // mocks should be a separate file
  gulp.src(settings.src + 'js/vendor/*/**/angular-mocks.js')
    .pipe(flatten())
    .pipe(gulp.dest(settings.dist + 'js/vendor'));

  // script must be included in the right order. First include angular, then angular-route
  return gulp.src([settings.src + 'js/vendor/*/**/angular.min.js',settings.src + 'js/vendor/*/**/angular-route.min.js', "!"+settings.src + 'js/vendor/*/**/angular-mocks.js', settings.src + 'js/vendor/**/*.js'])
    .pipe(gulp.dest(settings.dist + 'js/vendor'))
    .pipe(concat('vendor.js'))
    .pipe(gulp.dest(settings.dist + 'js/vendor'));
});

/**
 * Copy all vendor .js.map files to the vendor location
 */
gulp.task('scripts-vendor-maps', function(){
  var flatten = require('gulp-flatten');

  return gulp.src(settings.src + 'js/vendor/**/*.js.map')
  .pipe(flatten())
  .pipe(gulp.dest(settings.dist + 'js/vendor'));
});

/**
 * TTask to start a server, use --port={{port}} to set the port, otherwist the port from the settings will be used (4000)
 */
gulp.task('server', function(){
  var express = require('express'),
  app = express(),
  port = argv.port||settings.serverport;
  app.use(express.static(__dirname + "/" + settings.dist));

  app.listen(port);
  gutil.log('Server started. Port', port,"baseDir",__dirname+"/"+settings.dist);
});

/**
 * Task to start a server on port 4000 and used the live reload functionality.
 * Depends on: server, live-reload
 */
gulp.task('start', ['live-reload', 'server'], function(){});

/**
 * Compile Sass into Css and minify it. Minified and non-minified versions are copied to the dist folder.
 * This will also auto prefix vendor specific rules.
 *
 * @see https://github.com/sass/node-sass for configuration
 */
gulp.task('styles', function() {
  var autoprefixer = require('gulp-autoprefixer'),
      cmq = require('gulp-combine-media-queries'),
      minifycss = require('gulp-minify-css'),
      sass = require('gulp-sass');

  return gulp.src([settings.src + 'styles/**/*.scss'])
    .pipe(plumber(settings.plumberConfig()))
    .pipe(sass({
      style: 'nested',
      precision: 5,
      sourceComments: argv.dev ? true : false
    }))
    .pipe(autoprefixer('last 2 version', 'safari 5', 'ie 8', 'ie 9', 'opera 12.1', 'ios 6', 'android 4'))
    .pipe(cmq({log: true}))
    .pipe(gulp.dest(settings.dist + 'css'))

    .pipe(size({"showFiles":true}))
    .pipe(rename({suffix: '.min'}))
    .pipe(minifycss())
    .pipe(cmq())
    .pipe(size({"showFiles":true}))
    .pipe(gulp.dest(settings.dist + 'css'));
});

/**
 * Run rests and keep watching changes for files
 */
gulp.task('test', function(done) {
  var karma = require('karma').server;
  karma.start({
    configFile: __dirname + '/karma.conf.js',
    singleRun: true
  }, done);
});

/**
 * Output TODO's & FIXME's in markdown and json file as well
 */
gulp.task('todo', function() {
  var todo = require('gulp-todo');
  gulp.src([settings.src + 'js/app/**/*.js',settings.src + 'styles/app/**/*.scss'])
    .pipe( plumber( settings.plumberConfig() ) )
    .pipe( todo() )
    .pipe( gulp.dest( settings.reports ) ) // output todo.md as markdown
    .pipe( todo.reporter('json', {fileName: 'todo.json'} ) )
    .pipe( gulp.dest( settings.reports ) ) // output todo.json as json
});

/**
 * Watches changes to template, Sass, javascript and image files. On change this will run the appropriate task, either: copy styles, templates, scripts or images.
 */
gulp.task('watch', function() {

  // watch index.html
  gulp.watch(settings.src + 'index.html', ['copy-index']);

  // watch html files
  gulp.watch(settings.src + '**/*.html', ['copy-template']);

  // watch fonts
  gulp.watch(settings.src + 'fonts/**', ['copy-fonts']);

  // Watch .scss files
  gulp.watch(settings.src + 'styles/**/*.scss', ['styles']);

  // Watch app .js files
  gulp.watch(settings.src + 'js/app/**/*.js', ['scripts-app']);

  // Watch vendor .js files
  gulp.watch(settings.src + 'js/vendor/**/*.js', ['scripts-vendor']);

  // Watch image files
  gulp.watch(settings.src + 'img/**/*', ['images']);
});

/**
 * Run rests and keep watching changes for files
 */
gulp.task('watch:test', function(done) {
  var karma = require('karma').server;
  karma.start({
    configFile: __dirname + '/karma.conf.js'
  }, done);
});

function onError(error){
  // TODO log error with gutil
  notify.onError(function (error) {
    gutil.log(error);
    return error.message;
  });
  this.emit('end');
}
<|endoftext|>


<commit_before>((typeof define === "function" && define.amd && function (m) { define("samsam", m); }) ||
 (typeof module === "object" &&
      function (m) { module.exports = m(); }) || // Node
 function (m) { this.samsam = m(); } // Browser globals
)(function () {
    var o = Object.prototype;
    var div = typeof document !== "undefined" && document.createElement("div");

    function isNaN(value) {
        // Unlike global isNaN, this avoids type coercion
        // typeof check avoids IE host object issues, hat tip to
        // lodash
        var val = value; // JsLint thinks value !== value is "weird"
        return typeof value === "number" && value !== val;
    }

    function getClass(value) {
        // Returns the internal [[Class]] by calling Object.prototype.toString
        // with the provided value as this. Return value is a string, naming the
        // internal class, e.g. "Array"
        return o.toString.call(value).split(/[ \]]/)[1];
    }

    /**
     * @name samsam.isArguments
     * @param Object object
     *
     * Returns ``true`` if ``object`` is an ``arguments`` object,
     * ``false`` otherwise.
     */
    function isArguments(object) {
        if (getClass(object) === 'Arguments') { return true; }
        if (typeof object !== "object" || typeof object.length !== "number" ||
                getClass(object) === "Array") {
            return false;
        }
        if (typeof object.callee == "function") { return true; }
        try {
            object[object.length] = 6;
            delete object[object.length];
        } catch (e) {
            return true;
        }
        return false;
    }

    /**
     * @name samsam.isElement
     * @param Object object
     *
     * Returns ``true`` if ``object`` is a DOM element node. Unlike
     * Underscore.js/lodash, this function will return ``false`` if ``object``
     * is an *element-like* object, i.e. a regular object with a ``nodeType``
     * property that holds the value ``1``.
     */
    function isElement(object) {
        if (!object || object.nodeType !== 1 || !div) { return false; }
        try {
            object.appendChild(div);
            object.removeChild(div);
        } catch (e) {
            return false;
        }
        return true;
    }

    /**
     * @name samsam.keys
     * @param Object object
     *
     * Return an array of own property names.
     */
    function keys(object) {
        var ks = [], prop;
        for (prop in object) {
            if (o.hasOwnProperty.call(object, prop)) { ks.push(prop); }
        }
        return ks;
    }

    /**
     * @name samsam.isDate
     * @param Object value
     *
     * Returns true if the object is a ``Date``, or *date-like*. Duck typing
     * of date objects work by checking that the object has a ``getTime``
     * function whose return value equals the return value from the object's
     * ``valueOf``.
     */
    function isDate(value) {
        return typeof value.getTime == "function" &&
            value.getTime() == value.valueOf();
    }

    /**
     * @name samsam.isNegZero
     * @param Object value
     *
     * Returns ``true`` if ``value`` is ``-0``.
     */
    function isNegZero(value) {
        return value === 0 && 1 / value === -Infinity;
    }

    /**
     * @name samsam.equal
     * @param Object obj1
     * @param Object obj2
     *
     * Returns ``true`` if two objects are strictly equal. Compared to
     * ``===`` there are two exceptions:
     *
     *   - NaN is considered equal to NaN
     *   - -0 and +0 are not considered equal
     */
    function identical(obj1, obj2) {
        if (obj1 === obj2 || (isNaN(obj1) && isNaN(obj2))) {
            return obj1 !== 0 || isNegZero(obj1) === isNegZero(obj2);
        }
    }

    function isSet(val) {
        if (typeof Set !== 'undefined' && val instanceof Set) {
            return true;
        }
    }

    function checkSetUnion(s1, s2, compare) {
        var values1 = Array.from(s1);
        var values2 = Array.from(s2);

        for (var i = 0; i < values1.length; i++) {
            var includes = false;

            for (var j = 0; j < values2.length; j++) {
                if (compare(values1[i], values2[j])) {
                    includes = true;
                    break;
                }
            }

            if (!includes) {
                return false;
            }
        }

        return true;
    }

    /**
     * @name samsam.deepEqual
     * @param Object obj1
     * @param Object obj2
     *
     * Deep equal comparison. Two values are "deep equal" if:
     *
     *   - They are equal, according to samsam.identical
     *   - They are both date objects representing the same time
     *   - They are both arrays containing elements that are all deepEqual
     *   - They are objects with the same set of properties, and each property
     *     in ``obj1`` is deepEqual to the corresponding property in ``obj2``
     *
     * Supports cyclic objects.
     */
    function deepEqualCyclic(obj1, obj2) {

        // used for cyclic comparison
        // contain already visited objects
        var objects1 = [],
            objects2 = [],
        // contain pathes (position in the object structure)
        // of the already visited objects
        // indexes same as in objects arrays
            paths1 = [],
            paths2 = [],
        // contains combinations of already compared objects
        // in the manner: { "$1['ref']$2['ref']": true }
            compared = {};

        /**
         * used to check, if the value of a property is an object
         * (cyclic logic is only needed for objects)
         * only needed for cyclic logic
         */
        function isObject(value) {

            if (typeof value === 'object' && value !== null &&
                    !(value instanceof Boolean) &&
                    !(value instanceof Date)    &&
                    !(value instanceof Number)  &&
                    !(value instanceof RegExp)  &&
                    !(value instanceof String)) {

                return true;
            }

            return false;
        }

        /**
         * returns the index of the given object in the
         * given objects array, -1 if not contained
         * only needed for cyclic logic
         */
        function getIndex(objects, obj) {

            var i;
            for (i = 0; i < objects.length; i++) {
                if (objects[i] === obj) {
                    return i;
                }
            }

            return -1;
        }

        // does the recursion for the deep equal check
        return (function deepEqual(obj1, obj2, path1, path2) {
            var type1 = typeof obj1;
            var type2 = typeof obj2;

            // == null also matches undefined
            if (obj1 === obj2 ||
                    isNaN(obj1) || isNaN(obj2) ||
                    obj1 == null || obj2 == null ||
                    type1 !== "object" || type2 !== "object") {

                return identical(obj1, obj2);
            }

            // Elements are only equal if identical(expected, actual)
            if (isElement(obj1) || isElement(obj2)) { return false; }

            var isDate1 = isDate(obj1), isDate2 = isDate(obj2);
            if (isDate1 || isDate2) {
                if (!isDate1 || !isDate2 || obj1.getTime() !== obj2.getTime()) {
                    return false;
                }
            }

            if (obj1 instanceof RegExp && obj2 instanceof RegExp) {
                if (obj1.toString() !== obj2.toString()) { return false; }
            }

            var class1 = getClass(obj1);
            var class2 = getClass(obj2);
            var keys1 = keys(obj1);
            var keys2 = keys(obj2);

            if (isArguments(obj1) || isArguments(obj2)) {
                if (obj1.length !== obj2.length) { return false; }
            } else {
                if (type1 !== type2 || class1 !== class2 ||
                        keys1.length !== keys2.length) {
                    return false;
                }
            }

            if (isSet(obj1) || isSet(obj2)) {
                if (!isSet(obj1) || !isSet(obj2) || obj1.size !== obj2.size) {
                    return false;
                }

                return checkSetUnion(obj1, obj2, deepEqual);
            }

            var key, i, l,
                // following vars are used for the cyclic logic
                value1, value2,
                isObject1, isObject2,
                index1, index2,
                newPath1, newPath2;

            for (i = 0, l = keys1.length; i < l; i++) {
                key = keys1[i];
                if (!o.hasOwnProperty.call(obj2, key)) {
                    return false;
                }

                // Start of the cyclic logic

                value1 = obj1[key];
                value2 = obj2[key];

                isObject1 = isObject(value1);
                isObject2 = isObject(value2);

                // determine, if the objects were already visited
                // (it's faster to check for isObject first, than to
                // get -1 from getIndex for non objects)
                index1 = isObject1 ? getIndex(objects1, value1) : -1;
                index2 = isObject2 ? getIndex(objects2, value2) : -1;

                // determine the new pathes of the objects
                // - for non cyclic objects the current path will be extended
                //   by current property name
                // - for cyclic objects the stored path is taken
                newPath1 = index1 !== -1
                    ? paths1[index1]
                    : path1 + '[' + JSON.stringify(key) + ']';
                newPath2 = index2 !== -1
                    ? paths2[index2]
                    : path2 + '[' + JSON.stringify(key) + ']';

                // stop recursion if current objects are already compared
                if (compared[newPath1 + newPath2]) {
                    return true;
                }

                // remember the current objects and their pathes
                if (index1 === -1 && isObject1) {
                    objects1.push(value1);
                    paths1.push(newPath1);
                }
                if (index2 === -1 && isObject2) {
                    objects2.push(value2);
                    paths2.push(newPath2);
                }

                // remember that the current objects are already compared
                if (isObject1 && isObject2) {
                    compared[newPath1 + newPath2] = true;
                }

                // End of cyclic logic

                // neither value1 nor value2 is a cycle
                // continue with next level
                if (!deepEqual(value1, value2, newPath1, newPath2)) {
                    return false;
                }
            }

            return true;

        }(obj1, obj2, '$1', '$2'));
    }

    var match;

    function arrayContains(array, subset) {
        if (subset.length === 0) { return true; }
        var i, l, j, k;
        for (i = 0, l = array.length; i < l; ++i) {
            if (match(array[i], subset[0])) {
                for (j = 0, k = subset.length; j < k; ++j) {
                    if ((i + j) >= l) { return false; }
                    if (!match(array[i + j], subset[j])) { return false; }
                }
                return true;
            }
        }
        return false;
    }

    /**
     * @name samsam.match
     * @param Object object
     * @param Object matcher
     *
     * Compare arbitrary value ``object`` with matcher.
     */
    match = function match(object, matcher) {
        if (matcher && typeof matcher.test === "function") {
            return matcher.test(object);
        }

        if (typeof matcher === "function") {
            return matcher(object) === true;
        }

        if (typeof matcher === "string") {
            matcher = matcher.toLowerCase();
            var notNull = typeof object === "string" || !!object;
            return notNull &&
                (String(object)).toLowerCase().indexOf(matcher) >= 0;
        }

        if (typeof matcher === "number") {
            return matcher === object;
        }

        if (typeof matcher === "boolean") {
            return matcher === object;
        }

        if (typeof(matcher) === "undefined") {
            return typeof(object) === "undefined";
        }

        if (matcher === null) {
            return object === null;
        }

        if (getClass(object) === "Array" && getClass(matcher) === "Array") {
            return arrayContains(object, matcher);
        }

        if (matcher && typeof matcher === "object") {
            if (matcher === object) {
                return true;
            }
            var prop;
            for (prop in matcher) {
                var value = object[prop];
                if (typeof value === "undefined" &&
                        typeof object.getAttribute === "function") {
                    value = object.getAttribute(prop);
                }
                if (matcher[prop] === null || typeof matcher[prop] === 'undefined') {
                    if (value !== matcher[prop]) {
                        return false;
                    }
                } else if (typeof  value === "undefined" || !match(value, matcher[prop])) {
                    return false;
                }
            }
            return true;
        }

        throw new Error("Matcher was not a string, a number, a " +
                        "function, a boolean or an object");
    };

    return {
        isArguments: isArguments,
        isElement: isElement,
        isDate: isDate,
        isNegZero: isNegZero,
        identical: identical,
        deepEqual: deepEqualCyclic,
        match: match,
        keys: keys
    };
});
<commit_msg>Remove cyclic dependency<commit_after>((typeof define === "function" && define.amd && function (m) { define("samsam", m); }) ||
 (typeof module === "object" &&
      function (m) { module.exports = m(); }) || // Node
 function (m) { this.samsam = m(); } // Browser globals
)(function () {
    var o = Object.prototype;
    var div = typeof document !== "undefined" && document.createElement("div");

    function isNaN(value) {
        // Unlike global isNaN, this avoids type coercion
        // typeof check avoids IE host object issues, hat tip to
        // lodash
        var val = value; // JsLint thinks value !== value is "weird"
        return typeof value === "number" && value !== val;
    }

    function getClass(value) {
        // Returns the internal [[Class]] by calling Object.prototype.toString
        // with the provided value as this. Return value is a string, naming the
        // internal class, e.g. "Array"
        return o.toString.call(value).split(/[ \]]/)[1];
    }

    /**
     * @name samsam.isArguments
     * @param Object object
     *
     * Returns ``true`` if ``object`` is an ``arguments`` object,
     * ``false`` otherwise.
     */
    function isArguments(object) {
        if (getClass(object) === 'Arguments') { return true; }
        if (typeof object !== "object" || typeof object.length !== "number" ||
                getClass(object) === "Array") {
            return false;
        }
        if (typeof object.callee == "function") { return true; }
        try {
            object[object.length] = 6;
            delete object[object.length];
        } catch (e) {
            return true;
        }
        return false;
    }

    /**
     * @name samsam.isElement
     * @param Object object
     *
     * Returns ``true`` if ``object`` is a DOM element node. Unlike
     * Underscore.js/lodash, this function will return ``false`` if ``object``
     * is an *element-like* object, i.e. a regular object with a ``nodeType``
     * property that holds the value ``1``.
     */
    function isElement(object) {
        if (!object || object.nodeType !== 1 || !div) { return false; }
        try {
            object.appendChild(div);
            object.removeChild(div);
        } catch (e) {
            return false;
        }
        return true;
    }

    /**
     * @name samsam.keys
     * @param Object object
     *
     * Return an array of own property names.
     */
    function keys(object) {
        var ks = [], prop;
        for (prop in object) {
            if (o.hasOwnProperty.call(object, prop)) { ks.push(prop); }
        }
        return ks;
    }

    /**
     * @name samsam.isDate
     * @param Object value
     *
     * Returns true if the object is a ``Date``, or *date-like*. Duck typing
     * of date objects work by checking that the object has a ``getTime``
     * function whose return value equals the return value from the object's
     * ``valueOf``.
     */
    function isDate(value) {
        return typeof value.getTime == "function" &&
            value.getTime() == value.valueOf();
    }

    /**
     * @name samsam.isNegZero
     * @param Object value
     *
     * Returns ``true`` if ``value`` is ``-0``.
     */
    function isNegZero(value) {
        return value === 0 && 1 / value === -Infinity;
    }

    /**
     * @name samsam.equal
     * @param Object obj1
     * @param Object obj2
     *
     * Returns ``true`` if two objects are strictly equal. Compared to
     * ``===`` there are two exceptions:
     *
     *   - NaN is considered equal to NaN
     *   - -0 and +0 are not considered equal
     */
    function identical(obj1, obj2) {
        if (obj1 === obj2 || (isNaN(obj1) && isNaN(obj2))) {
            return obj1 !== 0 || isNegZero(obj1) === isNegZero(obj2);
        }
    }

    function isSet(val) {
        if (typeof Set !== 'undefined' && val instanceof Set) {
            return true;
        }
    }

    function checkSetUnion(s1, s2, compare) {
        var values1 = Array.from(s1);
        var values2 = Array.from(s2);

        for (var i = 0; i < values1.length; i++) {
            var includes = false;

            for (var j = 0; j < values2.length; j++) {
                if (compare(values1[i], values2[j])) {
                    includes = true;
                    break;
                }
            }

            if (!includes) {
                return false;
            }
        }

        return true;
    }

    /**
     * @name samsam.deepEqual
     * @param Object obj1
     * @param Object obj2
     *
     * Deep equal comparison. Two values are "deep equal" if:
     *
     *   - They are equal, according to samsam.identical
     *   - They are both date objects representing the same time
     *   - They are both arrays containing elements that are all deepEqual
     *   - They are objects with the same set of properties, and each property
     *     in ``obj1`` is deepEqual to the corresponding property in ``obj2``
     *
     * Supports cyclic objects.
     */
    function deepEqualCyclic(obj1, obj2) {

        // used for cyclic comparison
        // contain already visited objects
        var objects1 = [],
            objects2 = [],
        // contain pathes (position in the object structure)
        // of the already visited objects
        // indexes same as in objects arrays
            paths1 = [],
            paths2 = [],
        // contains combinations of already compared objects
        // in the manner: { "$1['ref']$2['ref']": true }
            compared = {};

        /**
         * used to check, if the value of a property is an object
         * (cyclic logic is only needed for objects)
         * only needed for cyclic logic
         */
        function isObject(value) {

            if (typeof value === 'object' && value !== null &&
                    !(value instanceof Boolean) &&
                    !(value instanceof Date)    &&
                    !(value instanceof Number)  &&
                    !(value instanceof RegExp)  &&
                    !(value instanceof String)) {

                return true;
            }

            return false;
        }

        /**
         * returns the index of the given object in the
         * given objects array, -1 if not contained
         * only needed for cyclic logic
         */
        function getIndex(objects, obj) {

            var i;
            for (i = 0; i < objects.length; i++) {
                if (objects[i] === obj) {
                    return i;
                }
            }

            return -1;
        }

        // does the recursion for the deep equal check
        return (function deepEqual(obj1, obj2, path1, path2) {
            var type1 = typeof obj1;
            var type2 = typeof obj2;

            // == null also matches undefined
            if (obj1 === obj2 ||
                    isNaN(obj1) || isNaN(obj2) ||
                    obj1 == null || obj2 == null ||
                    type1 !== "object" || type2 !== "object") {

                return identical(obj1, obj2);
            }

            // Elements are only equal if identical(expected, actual)
            if (isElement(obj1) || isElement(obj2)) { return false; }

            var isDate1 = isDate(obj1), isDate2 = isDate(obj2);
            if (isDate1 || isDate2) {
                if (!isDate1 || !isDate2 || obj1.getTime() !== obj2.getTime()) {
                    return false;
                }
            }

            if (obj1 instanceof RegExp && obj2 instanceof RegExp) {
                if (obj1.toString() !== obj2.toString()) { return false; }
            }

            var class1 = getClass(obj1);
            var class2 = getClass(obj2);
            var keys1 = keys(obj1);
            var keys2 = keys(obj2);

            if (isArguments(obj1) || isArguments(obj2)) {
                if (obj1.length !== obj2.length) { return false; }
            } else {
                if (type1 !== type2 || class1 !== class2 ||
                        keys1.length !== keys2.length) {
                    return false;
                }
            }

            if (isSet(obj1) || isSet(obj2)) {
                if (!isSet(obj1) || !isSet(obj2) || obj1.size !== obj2.size) {
                    return false;
                }

                return checkSetUnion(obj1, obj2, deepEqual);
            }

            var key, i, l,
                // following vars are used for the cyclic logic
                value1, value2,
                isObject1, isObject2,
                index1, index2,
                newPath1, newPath2;

            for (i = 0, l = keys1.length; i < l; i++) {
                key = keys1[i];
                if (!o.hasOwnProperty.call(obj2, key)) {
                    return false;
                }

                // Start of the cyclic logic

                value1 = obj1[key];
                value2 = obj2[key];

                isObject1 = isObject(value1);
                isObject2 = isObject(value2);

                // determine, if the objects were already visited
                // (it's faster to check for isObject first, than to
                // get -1 from getIndex for non objects)
                index1 = isObject1 ? getIndex(objects1, value1) : -1;
                index2 = isObject2 ? getIndex(objects2, value2) : -1;

                // determine the new pathes of the objects
                // - for non cyclic objects the current path will be extended
                //   by current property name
                // - for cyclic objects the stored path is taken
                newPath1 = index1 !== -1
                    ? paths1[index1]
                    : path1 + '[' + JSON.stringify(key) + ']';
                newPath2 = index2 !== -1
                    ? paths2[index2]
                    : path2 + '[' + JSON.stringify(key) + ']';

                // stop recursion if current objects are already compared
                if (compared[newPath1 + newPath2]) {
                    return true;
                }

                // remember the current objects and their pathes
                if (index1 === -1 && isObject1) {
                    objects1.push(value1);
                    paths1.push(newPath1);
                }
                if (index2 === -1 && isObject2) {
                    objects2.push(value2);
                    paths2.push(newPath2);
                }

                // remember that the current objects are already compared
                if (isObject1 && isObject2) {
                    compared[newPath1 + newPath2] = true;
                }

                // End of cyclic logic

                // neither value1 nor value2 is a cycle
                // continue with next level
                if (!deepEqual(value1, value2, newPath1, newPath2)) {
                    return false;
                }
            }

            return true;

        }(obj1, obj2, '$1', '$2'));
    }

    function arrayContains(array, subset, compare) {
        if (subset.length === 0) { return true; }
        var i, l, j, k;
        for (i = 0, l = array.length; i < l; ++i) {
            if (compare(array[i], subset[0])) {
                for (j = 0, k = subset.length; j < k; ++j) {
                    if ((i + j) >= l) { return false; }
                    if (!compare(array[i + j], subset[j])) { return false; }
                }
                return true;
            }
        }
        return false;
    }

    /**
     * @name samsam.match
     * @param Object object
     * @param Object matcher
     *
     * Compare arbitrary value ``object`` with matcher.
     */
    function match(object, matcher) {
        if (matcher && typeof matcher.test === "function") {
            return matcher.test(object);
        }

        if (typeof matcher === "function") {
            return matcher(object) === true;
        }

        if (typeof matcher === "string") {
            matcher = matcher.toLowerCase();
            var notNull = typeof object === "string" || !!object;
            return notNull &&
                (String(object)).toLowerCase().indexOf(matcher) >= 0;
        }

        if (typeof matcher === "number") {
            return matcher === object;
        }

        if (typeof matcher === "boolean") {
            return matcher === object;
        }

        if (typeof(matcher) === "undefined") {
            return typeof(object) === "undefined";
        }

        if (matcher === null) {
            return object === null;
        }

        if (getClass(object) === "Array" && getClass(matcher) === "Array") {
            return arrayContains(object, matcher, match);
        }

        if (matcher && typeof matcher === "object") {
            if (matcher === object) {
                return true;
            }
            var prop;
            for (prop in matcher) {
                var value = object[prop];
                if (typeof value === "undefined" &&
                        typeof object.getAttribute === "function") {
                    value = object.getAttribute(prop);
                }
                if (matcher[prop] === null || typeof matcher[prop] === 'undefined') {
                    if (value !== matcher[prop]) {
                        return false;
                    }
                } else if (typeof  value === "undefined" || !match(value, matcher[prop])) {
                    return false;
                }
            }
            return true;
        }

        throw new Error("Matcher was not a string, a number, a " +
                        "function, a boolean or an object");
    };

    return {
        isArguments: isArguments,
        isElement: isElement,
        isDate: isDate,
        isNegZero: isNegZero,
        identical: identical,
        deepEqual: deepEqualCyclic,
        match: match,
        keys: keys
    };
});
<|endoftext|>


<commit_before>'use strict';

var arrayOfFlashcards = [];
var numFlashcardsShown = 0;
var imgEl = document.getElementById('picture_tag');
var liElEng = document.getElementById('english_translation');
var liElLus = document.getElementById('lushootseed_translation');
var buttonNextFlashcard = document.getElementById('next_flashcard');
var buttonTakeTest = document.getElementById('take_test');
var timesAllShown = 0;

buttonNextFlashcard.addEventListener('click', displayFlashcard);

function Flashcard(eng, lush, url, idNum, sound, phonetic) {
  this.english = eng;
  this.lushootseed = lush;
  this.pictureurl = url;
  this.idNum = idNum;
  this.sound = sound;
  this.phonetic = phonetic;
  this.shown = false;
  this.timesShown = 0;
  arrayOfFlashcards.push(this);
}

//make Flashcard objects here.  For now these are test objects
function makeFlashcards() {
  new Flashcard('carrot','\u0161\u0259g\u02B7aq', 'image/wordphotos/carrots_basket.jpeg', 1, 0, 0);
  new Flashcard('salmon','s\u0294uladx\u02B7', 'image/wordphotos/salmon.jpg', 2, 0, 0);
  new Flashcard('oyster', '\u019B\u0315ux\u030C\u02B7\u019B\u0315ux\u030C\u02B7', 'image/wordphotos/oyster.jpeg', 3, 0, 0);
  new Flashcard('coffee', 'kupi', 'image/wordphotos/coffee.jpg', 4, 0, 0);
  new Flashcard( 'berry/fruit','sq\u0313\u02B7\u0259la\u026C\u0259d', 'image/wordphotos/berries.jpeg', 5, 0, 0);
  new Flashcard('soup','s\u026Cub', 'image/wordphotos/soup.jpg', 6, 0, 0);
  new Flashcard('eggs','\u0294\u0259\u0294us', 'image/wordphotos/eggs.jpg', 7, 0, 0);
  new Flashcard('clam', 's\u0294ax\u030C\u02B7u\u0294','image/wordphotos/clam.jpeg', 8, 0, 0);
  new Flashcard('mussels','tulq\u02B7', 'image/wordphotos/mussels.jpg', 9, 0, 0);
  new Flashcard('crab','b\u0259sq\u02B7', 'image/wordphotos/crab.jpg', 10, 0, 0);
  new Flashcard('geoduck', 'g\u02B7id\u0259q','image/wordphotos/geoduck.jpg', 11, 0, 0);
  new Flashcard('potatoes', 'spiq\u02B7uc','image/wordphotos/potatoes.jpg', 12, 0, 0);
  new Flashcard('octopus', 'sqibk\u0313\u02B7', 'image/wordphotos/octopus.jpg', 13, 0, 0);
}
//randomly select Flashcard by id # for display to the user, display it, and change it's shown property to True, and save array to local storage
var generateRandomNumber = function() {
  return Math.floor(Math.random() * (arrayOfFlashcards.length));
};

function delimitRandom(){
  var placeholder = generateRandomNumber();
  while(arrayOfFlashcards[placeholder].timesShown > timesAllShown){
    placeholder = generateRandomNumber();
  }
  return placeholder;
}

function displayFlashcard() {
  limitDuplicates();
  var placeholder = delimitRandom();
  arrayOfFlashcards[placeholder].shown = true;
  imgEl.src = arrayOfFlashcards[placeholder].pictureurl;
  liElEng.textContent = arrayOfFlashcards[placeholder].english;
  liElLus.textContent = arrayOfFlashcards[placeholder].lushootseed;
  numFlashcardsShown++;
  arrayOfFlashcards[placeholder].timesShown++;
  console.log('times shown incremented', arrayOfFlashcards[placeholder].timesShown);
  localStorage.setItem('arrayOfFlashcards', JSON.stringify(arrayOfFlashcards));
  showTestButton();
}

function limitDuplicates(){
  if (numFlashcardsShown === arrayOfFlashcards.length){
    timesAllShown++;
    numFlashcardsShown = 0;
  }
}

function showTestButton() {
  if(numFlashcardsShown > 6) {
    buttonTakeTest.className = 'show_take_test';
  }
}

//call functions
makeFlashcards();
displayFlashcard();
<commit_msg>changes some of the file paths to match the new cropped image file names<commit_after>'use strict';

var arrayOfFlashcards = [];
var numFlashcardsShown = 0;
var imgEl = document.getElementById('picture_tag');
var liElEng = document.getElementById('english_translation');
var liElLus = document.getElementById('lushootseed_translation');
var buttonNextFlashcard = document.getElementById('next_flashcard');
var buttonTakeTest = document.getElementById('take_test');
var timesAllShown = 0;

buttonNextFlashcard.addEventListener('click', displayFlashcard);

function Flashcard(eng, lush, url, idNum, sound, phonetic) {
  this.english = eng;
  this.lushootseed = lush;
  this.pictureurl = url;
  this.idNum = idNum;
  this.sound = sound;
  this.phonetic = phonetic;
  this.shown = false;
  this.timesShown = 0;
  arrayOfFlashcards.push(this);
}

//make Flashcard objects here.  For now these are test objects
function makeFlashcards() {
  new Flashcard('carrot','\u0161\u0259g\u02B7aq', 'image/wordphotos/carrots.jpeg', 1, 0, 0);
  new Flashcard('salmon','s\u0294uladx\u02B7', 'image/wordphotos/salmon.jpg', 2, 0, 0);
  new Flashcard('oyster', '\u019B\u0315ux\u030C\u02B7\u019B\u0315ux\u030C\u02B7', 'image/wordphotos/oyster.jpeg', 3, 0, 0);
  new Flashcard('coffee', 'kupi', 'image/wordphotos/coffee.jpg', 4, 0, 0);
  new Flashcard( 'berry/fruit','sq\u0313\u02B7\u0259la\u026C\u0259d', 'image/wordphotos/berries.jpeg', 5, 0, 0);
  new Flashcard('soup','s\u026Cub', 'image/wordphotos/soup.jpg', 6, 0, 0);
  new Flashcard('eggs','\u0294\u0259\u0294us', 'image/wordphotos/eggs.jpg', 7, 0, 0);
  new Flashcard('clam', 's\u0294ax\u030C\u02B7u\u0294','image/wordphotos/clam.jpeg', 8, 0, 0);
  new Flashcard('mussels','tulq\u02B7', 'image/wordphotos/mussels.jpeg', 9, 0, 0);
  new Flashcard('crab','b\u0259sq\u02B7', 'image/wordphotos/crab.jpg', 10, 0, 0);
  new Flashcard('geoduck', 'g\u02B7id\u0259q','image/wordphotos/geoduck.jpg', 11, 0, 0);
  new Flashcard('potatoes', 'spiq\u02B7uc','image/wordphotos/potatoes.jpg', 12, 0, 0);
  new Flashcard('octopus', 'sqibk\u0313\u02B7', 'image/wordphotos/octopus.jpg', 13, 0, 0);
}
//randomly select Flashcard by id # for display to the user, display it, and change it's shown property to True, and save array to local storage
var generateRandomNumber = function() {
  return Math.floor(Math.random() * (arrayOfFlashcards.length));
};

function delimitRandom(){
  var placeholder = generateRandomNumber();
  while(arrayOfFlashcards[placeholder].timesShown > timesAllShown){
    placeholder = generateRandomNumber();
  }
  return placeholder;
}

function displayFlashcard() {
  limitDuplicates();
  var placeholder = delimitRandom();
  arrayOfFlashcards[placeholder].shown = true;
  imgEl.src = arrayOfFlashcards[placeholder].pictureurl;
  liElEng.textContent = arrayOfFlashcards[placeholder].english;
  liElLus.textContent = arrayOfFlashcards[placeholder].lushootseed;
  numFlashcardsShown++;
  arrayOfFlashcards[placeholder].timesShown++;
  console.log('times shown incremented', arrayOfFlashcards[placeholder].timesShown);
  localStorage.setItem('arrayOfFlashcards', JSON.stringify(arrayOfFlashcards));
  showTestButton();
}

function limitDuplicates(){
  if (numFlashcardsShown === arrayOfFlashcards.length){
    timesAllShown++;
    numFlashcardsShown = 0;
  }
}

function showTestButton() {
  if(numFlashcardsShown > 6) {
    buttonTakeTest.className = 'show_take_test';
  }
}

//call functions
makeFlashcards();
displayFlashcard();
<|endoftext|>


<commit_before>/**
 * Workflow
 */

// declare vars
var gulp, sass, sourcemaps, prefix, concat, config;

// init plugins
gulp = require('gulp');
sass = require('gulp-sass');
sourcemaps = require('gulp-sourcemaps');
prefix = require('gulp-autoprefixer');
concat = require('gulp-concat');

// config
config = {

    paths: {

        app: {
            scss_src: 'app/scss/**/*.scss',
            css_dest: 'app/css'
        },

        dist: {
            // dist paths
        }

    },

    browserSupport: ['last 2 version', 'safari 5', 'ie 8', 'ie 9', 'opera 12.1', 'ios 6', 'android 4']
}

// default gulp task
gulp.task('default', function() {

    // watch tasks

});

gulp.task('sass', function() {

    return gulp.src(config.paths.app.scss_src)

        .pipe(sourcemaps.init())

        .pipe(sass({
            errLogToConsole: true,
            includePaths: [
                'app/scss/'
            ]
        }))

        .pipe(prefix({
            browsers: config.browserSupport,
            cascade:  true
        }))

        .pipe(concat('styles.css'))

        .pipe(sourcemaps.write('../maps'))

        .pipe(gulp.dest(config.paths.app.css_dest))

});<commit_msg>Added watch task for sass<commit_after>/**
 * Workflow
 */

// declare vars
var gulp, sass, sourcemaps, prefix, concat, config;

// init plugins
gulp = require('gulp');
sass = require('gulp-sass');
sourcemaps = require('gulp-sourcemaps');
prefix = require('gulp-autoprefixer');
concat = require('gulp-concat');

// config
config = {

    paths: {

        app: {
            scss_src: 'app/scss/**/*.scss',
            css_dest: 'app/css'
        },

        dist: {
            // dist paths
        }

    },

    browserSupport: ['last 2 version', 'safari 5', 'ie 8', 'ie 9', 'opera 12.1', 'ios 6', 'android 4']
}

// default gulp task
gulp.task('default', ['sass'], function() {

    gulp.watch(config.paths.app.scss_src, ['sass']);

});

gulp.task('sass', function() {

    return gulp.src(config.paths.app.scss_src)

        .pipe(sourcemaps.init())

        .pipe(sass({
            errLogToConsole: true,
            includePaths: [
                'app/scss/'
            ]
        }))

        .pipe(prefix({
            browsers: config.browserSupport,
            cascade:  true
        }))

        .pipe(concat('styles.css'))

        .pipe(sourcemaps.write('../maps'))

        .pipe(gulp.dest(config.paths.app.css_dest))

});<|endoftext|>


<commit_before>// server.js

// set up ======================================================================
// get all the tools we need
var express  = require('express');
var app      = express();
var ipadress = process.env.OPENSHIFT_NODEJS_IP;
var port     = process.env.OPENSHIFT_NODEJS_PORT || 8080;
var mongoose = require('mongoose');
var passport = require('passport');
var flash    = require('connect-flash');

var morgan       = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser   = require('body-parser');
var session      = require('express-session');

var configDB = require('./config/database.js');

// configuration ===============================================================
mongoose.connect(configDB.url); // connect to our database

require('./config/passport')(passport); // pass passport for configuration

// set up our express application
app.use(morgan('dev')); // log every request to the console
app.use(cookieParser()); // read cookies (needed for auth)
app.use(bodyParser.json()); // get information from html forms
app.use(bodyParser.urlencoded({ extended: true }));

app.set('view engine', 'ejs'); // set up ejs for templating

// required for passport
app.use(session({ secret: 'Chickenstinkt' })); // session secret
app.use(passport.initialize());
app.use(passport.session()); // persistent login sessions
app.use(flash()); // use connect-flash for flash messages stored in session

// routes ======================================================================
require('./app/routes.js')(app, passport); // load our routes and pass in our app and fully configured passport

// launch ======================================================================
app.listen(80 || 8080, ipadress);
console.log('The magic happens on port ' + port);
<commit_msg>0.2.14<commit_after>// server.js

// set up ======================================================================
// get all the tools we need
var express  = require('express');
var app      = express();
var ipadress = process.env.OPENSHIFT_NODEJS_IP;
var port     = process.env.OPENSHIFT_NODEJS_PORT || 8080;
var mongoose = require('mongoose');
var passport = require('passport');
var flash    = require('connect-flash');

var morgan       = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser   = require('body-parser');
var session      = require('express-session');

var configDB = require('./config/database.js');

// configuration ===============================================================
mongoose.connect(configDB.url); // connect to our database

require('./config/passport')(passport); // pass passport for configuration

// set up our express application
app.use(morgan('dev')); // log every request to the console
app.use(cookieParser()); // read cookies (needed for auth)
app.use(bodyParser.json()); // get information from html forms
app.use(bodyParser.urlencoded({ extended: true }));

app.set('view engine', 'ejs'); // set up ejs for templating

// required for passport
app.use(session({ secret: 'Chickenstinkt' })); // session secret
app.use(passport.initialize());
app.use(passport.session()); // persistent login sessions
app.use(flash()); // use connect-flash for flash messages stored in session

// routes ======================================================================
require('./app/routes.js')(app, passport); // load our routes and pass in our app and fully configured passport

// launch ======================================================================
app.listen(port);
console.log('The magic happens on port ' + port);
<|endoftext|>