<commit_before>import gulp     from 'gulp';
import plugins  from 'gulp-load-plugins';
import browser  from 'browser-sync';
import rimraf   from 'rimraf';
import panini   from 'panini';
import yargs    from 'yargs';
import lazypipe from 'lazypipe';
import inky     from 'inky';
import fs       from 'fs';
import siphon   from 'siphon-media-query';
import path     from 'path';
import merge    from 'merge-stream';
import beep     from 'beepbeep';
import colors   from 'colors';

const $ = plugins();

/* Enable/disable prefix workflow - allows for layouts to have different css references
   but page names prefix must match css file name
   e.g.
   > scss/__invite.scss
        .invite { background-color:#333333; ... }
        ...

   > scss/invite.scss
        @import 'settings';
        @import 'foundation-emails';
        @import '_invite';
        ...

   > layouts/invite.html
        ...
        <link rel="stylesheet" type="text/css" href="css/invite.css">
        ...

   > pages/invite.html, invite2.html, invite_audience1.html
        ---
        layout: invite
        ---
        ...

   //So all the pages with the prefix "invite" will be inlined with the css from invite.css
*/
var PREFIX_WORKFLOW=true;
var PREFIX_BUILD_INDEX=true; //prefix/true | alpha | false

// Look for the --production flag
const PRODUCTION = !!(yargs.argv.production);
const EMAIL = yargs.argv.to;

// Declare var so that both AWS and Litmus task can use it.
var CONFIG;

// Build the "dist" folder by running all of the above tasks
gulp.task('build',
  gulp.series(clean, pages, sass, images, inline, prefixIndex));

// Build emails, run the server, and watch for file changes
gulp.task('default',
  gulp.series('build', server, watch));

// Build emails, then send to litmus
gulp.task('litmus',
  gulp.series('build', creds, aws, litmus));

// Build emails, then send to EMAIL
gulp.task('mail',
  gulp.series('build', creds, aws, mail));

// Build emails, then zip
gulp.task('zip',
  gulp.series('build', zip));

// Delete the "dist" folder
// This happens every time a build starts
function clean(done) {
  rimraf('dist', done);
}

// Compile layouts, pages, and partials into flat HTML files
// Then parse using Inky templates
function pages() {
  return gulp.src('src/pages/**/*.html')
    .pipe(panini({
      root: 'src/pages',
      layouts: 'src/layouts',
      partials: 'src/partials',
      helpers: 'src/helpers'
    }))
    .pipe(inky())
    .pipe(gulp.dest('dist'));
}

// Reset Panini's cache of layouts and partials
function resetPages(done) {
  panini.refresh();
  done();
}

// Compile Sass into CSS
function sass() {
  return gulp.src('src/assets/scss/*.scss')
    .pipe($.if(!PRODUCTION, $.sourcemaps.init()))
    .pipe($.sass({
      includePaths: ['node_modules/foundation-emails/scss']
    }).on('error', $.sass.logError))
    .pipe($.if(PRODUCTION, $.uncss(
      {
        html: ['dist/**/*.html']
      })))
    .pipe($.if(!PRODUCTION, $.sourcemaps.write()))
    .pipe(gulp.dest('dist/css'));
}

// Copy and compress images
function images() {
  return gulp.src('src/assets/img/**/*')
    .pipe($.imagemin())
    .pipe(gulp.dest('./dist/assets/img'));
}

// Inline CSS and minify HTML
function inline(done) {

  if (PREFIX_WORKFLOW) {
    var files = fs.readdirSync('./dist/css');
    var prefix = '';
    var result = done();
    for(var i in files) {
      prefix = files[i].slice(0, -4);
      result = gulp.src('dist/**/' + prefix + '*.html')
        .pipe($.if(PRODUCTION, inliner('dist/css/' + prefix + '.css')))
        .pipe($.if(PRODUCTION, $.replace(/<link rel="stylesheet"[^>]*>/, '')))
        .pipe(gulp.dest('dist'));
    }
    return result;
  } else {
    return gulp.src('dist/**/*.html')
      .pipe($.if(PRODUCTION, inliner('dist/css/app.css')))
      .pipe($.replace(/<link rel="stylesheet"[^>]*>/, ''))
      .pipe(gulp.dest('dist'));
  }
}

//generate list of links to template previews
function prefixIndex(done) {

  if (PREFIX_WORKFLOW && PREFIX_BUILD_INDEX) {

    var pages = fs.readdirSync('./src/pages');
    var files = fs.readdirSync('./dist/css');
    var groups = [];
    var prefixes = [];
    var index = [];
    for(var i in files) {
      prefix = files[i].slice(0, -4);

      prefixes.push(prefix);
    }

    var page = '';
    var prefix = '';
    var i = null, j = null;
    for(i in pages) {

      page = pages[i];
      index.push(page);
      for (j in prefixes) {

        prefix = prefixes[j];

        if (page.indexOf(prefix) != -1) {

          if (groups[prefix] === undefined) {
            groups[prefix] = [];
          }
          groups[prefix].push(page);
          break;
        }
      }
    }

    var content = '';
    content += '<container>';
    content += '  <row>';
    content += '    <columns small="12" large="12">';

    switch(PREFIX_BUILD_INDEX) {
      case 'alpha':
        i = null, j = null;
        var title = '';
        content += '
Index
';
        content += '
';
        for(i in index) {

          if (index[i].indexOf('index') == -1) {
            content += '
' + index[i].slice(0, -5) + '
';
          }
        }
        content += '
';
        break;
      case 'prefix':
      case 'true':
      case true:
        i = null, j = null;
        var title = '';
        for(i in groups) {

          title = i.replace(/_/g, ' ');
          title = title.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();});
          content += '
' + title + '
';
          content += '
';
          for (j in groups[i]) {

            content += '
' + groups[i][j].slice(0, -5) + '
';
          }
          content += '
';
        }
        break;
    }

    content += '    </columns>';
    content += '  </row>';
    content += '</container>';

    fs.writeFile("./src/pages/index.html", content, function(err) {
      if(err) {
        console.log('Error writing index');
        done();
      }
    });

    return gulp.src('src/pages/index.html')
    .pipe(panini({
      root: 'src/pages',
      layouts: 'src/layouts',
      partials: 'src/partials',
      helpers: 'src/helpers'
    }))
    .pipe(inky())
    .pipe(gulp.dest('dist'));

  } else {
    done();
  }

}

// Start a server with LiveReload to preview the site in
function server(done) {
  browser.init({
    server: 'dist'
  });
  done();
}

// Watch for file changes
function watch() {
  gulp.watch('src/pages/**/*.html').on('all', gulp.series(pages, inline, browser.reload));
  gulp.watch(['src/layouts/**/*', 'src/partials/**/*']).on('all', gulp.series(resetPages, pages, inline, browser.reload));
  gulp.watch(['../scss/**/*.scss', 'src/assets/scss/**/*.scss']).on('all', gulp.series(resetPages, sass, pages, inline, browser.reload));
  gulp.watch('src/assets/img/**/*').on('all', gulp.series(images, browser.reload));
}

// Inlines CSS into HTML, adds media query CSS into the `)
    .pipe($.replace, '<link rel="stylesheet" type="text/css" href="css/app.css">', '')
    .pipe($.htmlmin, {
      collapseWhitespace: true,
      minifyCSS: true
    });

  return pipe();
}

// Ensure creds for Litmus are at least there.
function creds(done) {
  var configPath = './config.json';
  try { CONFIG = JSON.parse(fs.readFileSync(configPath)); }
  catch(e) {
    beep();
    console.log('[AWS]'.bold.red + ' Sorry, there was an issue locating your config.json. Please see README.md');
    process.exit();
  }
  done();
}

// Post images to AWS S3 so they are accessible to Litmus and manual test
function aws() {
  var publisher = !!CONFIG.aws ? $.awspublish.create(CONFIG.aws) : $.awspublish.create();
  var headers = {
    'Cache-Control': 'max-age=315360000, no-transform, public'
  };

  return gulp.src('./dist/assets/img/*')
    // publisher will add Content-Length, Content-Type and headers specified above
    // If not specified it will set x-amz-acl to public-read by default
    .pipe(publisher.publish(headers))

    // create a cache file to speed up consecutive uploads
    //.pipe(publisher.cache())

    // print upload updates to console
    .pipe($.awspublish.reporter());
}

// Send email to Litmus for testing. If no AWS creds then do not replace img urls.
function litmus() {
  var awsURL = !!CONFIG && !!CONFIG.aws && !!CONFIG.aws.url ? CONFIG.aws.url : false;

  return gulp.src('dist/**/*.html')
    .pipe($.if(!!awsURL, $.replace(/=('|")(\/?assets\/img)/g, "=$1"+ awsURL)))
    .pipe($.litmus(CONFIG.litmus))
    .pipe(gulp.dest('dist'));
}

// Send email to specified email for testing. If no AWS creds then do not replace img urls.
function mail() {
  var awsURL = !!CONFIG && !!CONFIG.aws && !!CONFIG.aws.url ? CONFIG.aws.url : false;

  if (EMAIL) {
    CONFIG.mail.to = [EMAIL];
  }

  return gulp.src('dist/**/*.html')
    .pipe($.if(!!awsURL, $.replace(/=('|")(\/?assets\/img)/g, "=$1"+ awsURL)))
    .pipe($.mail(CONFIG.mail))
    .pipe(gulp.dest('dist'));
}

// Copy and compress into Zip
function zip() {
  var dist = 'dist';
  var ext = '.html';

  function getHtmlFiles(dir) {
    return fs.readdirSync(dir)
      .filter(function(file) {
        var fileExt = path.join(dir, file);
        var isHtml = path.extname(fileExt) == ext;
        return fs.statSync(fileExt).isFile() && isHtml;
      });
  }

  var htmlFiles = getHtmlFiles(dist);

  var moveTasks = htmlFiles.map(function(file){
    var sourcePath = path.join(dist, file);
    var fileName = path.basename(sourcePath, ext);

    var moveHTML = gulp.src(sourcePath)
      .pipe($.rename(function (path) {
        path.dirname = fileName;
        return path;
      }));

    var moveImages = gulp.src(sourcePath)
      .pipe($.htmlSrc({ selector: 'img'}))
      .pipe($.rename(function (path) {
        path.dirname = fileName + '/' + path.dirname;
        return path;
      }));

    return merge(moveHTML, moveImages)
      .pipe($.zip(fileName+ '.zip'))
      .pipe(gulp.dest('dist'));
  });

  return merge(moveTasks);
}<commit_msg>renamed index to not override any existing index.html<commit_after>import gulp     from 'gulp';
import plugins  from 'gulp-load-plugins';
import browser  from 'browser-sync';
import rimraf   from 'rimraf';
import panini   from 'panini';
import yargs    from 'yargs';
import lazypipe from 'lazypipe';
import inky     from 'inky';
import fs       from 'fs';
import siphon   from 'siphon-media-query';
import path     from 'path';
import merge    from 'merge-stream';
import beep     from 'beepbeep';
import colors   from 'colors';

const $ = plugins();

/* Enable/disable prefix workflow - allows for layouts to have different css references
   but page names prefix must match css file name
   e.g.
   > scss/__invite.scss
        .invite { background-color:#333333; ... }
        ...

   > scss/invite.scss
        @import 'settings';
        @import 'foundation-emails';
        @import '_invite';
        ...

   > layouts/invite.html
        ...
        <link rel="stylesheet" type="text/css" href="css/invite.css">
        ...

   > pages/invite.html, invite2.html, invite_audience1.html
        ---
        layout: invite
        ---
        ...

   //So all the pages with the prefix "invite" will be inlined with the css from invite.css
*/
var PREFIX_WORKFLOW=true;
var PREFIX_BUILD_INDEX=true; //prefix/true | alpha | false

// Look for the --production flag
const PRODUCTION = !!(yargs.argv.production);
const EMAIL = yargs.argv.to;

// Declare var so that both AWS and Litmus task can use it.
var CONFIG;

// Build the "dist" folder by running all of the above tasks
gulp.task('build',
  gulp.series(clean, pages, sass, images, inline, prefixIndex));

// Build emails, run the server, and watch for file changes
gulp.task('default',
  gulp.series('build', server, watch));

// Build emails, then send to litmus
gulp.task('litmus',
  gulp.series('build', creds, aws, litmus));

// Build emails, then send to EMAIL
gulp.task('mail',
  gulp.series('build', creds, aws, mail));

// Build emails, then zip
gulp.task('zip',
  gulp.series('build', zip));

// Delete the "dist" folder
// This happens every time a build starts
function clean(done) {
  rimraf('dist', done);
}

// Compile layouts, pages, and partials into flat HTML files
// Then parse using Inky templates
function pages() {
  return gulp.src('src/pages/**/*.html')
    .pipe(panini({
      root: 'src/pages',
      layouts: 'src/layouts',
      partials: 'src/partials',
      helpers: 'src/helpers'
    }))
    .pipe(inky())
    .pipe(gulp.dest('dist'));
}

// Reset Panini's cache of layouts and partials
function resetPages(done) {
  panini.refresh();
  done();
}

// Compile Sass into CSS
function sass() {
  return gulp.src('src/assets/scss/*.scss')
    .pipe($.if(!PRODUCTION, $.sourcemaps.init()))
    .pipe($.sass({
      includePaths: ['node_modules/foundation-emails/scss']
    }).on('error', $.sass.logError))
    .pipe($.if(PRODUCTION, $.uncss(
      {
        html: ['dist/**/*.html']
      })))
    .pipe($.if(!PRODUCTION, $.sourcemaps.write()))
    .pipe(gulp.dest('dist/css'));
}

// Copy and compress images
function images() {
  return gulp.src('src/assets/img/**/*')
    .pipe($.imagemin())
    .pipe(gulp.dest('./dist/assets/img'));
}

// Inline CSS and minify HTML
function inline(done) {

  if (PREFIX_WORKFLOW) {
    var files = fs.readdirSync('./dist/css');
    var prefix = '';
    var result = done();
    for(var i in files) {
      prefix = files[i].slice(0, -4);
      result = gulp.src('dist/**/' + prefix + '*.html')
        .pipe($.if(PRODUCTION, inliner('dist/css/' + prefix + '.css')))
        .pipe($.if(PRODUCTION, $.replace(/<link rel="stylesheet"[^>]*>/, '')))
        .pipe(gulp.dest('dist'));
    }
    return result;
  } else {
    return gulp.src('dist/**/*.html')
      .pipe($.if(PRODUCTION, inliner('dist/css/app.css')))
      .pipe($.replace(/<link rel="stylesheet"[^>]*>/, ''))
      .pipe(gulp.dest('dist'));
  }
}

//generate list of links to template previews
function prefixIndex(done) {

  if (PREFIX_WORKFLOW && PREFIX_BUILD_INDEX) {

    var pages = fs.readdirSync('./src/pages');
    var files = fs.readdirSync('./dist/css');
    var groups = [];
    var prefixes = [];
    var index = [];
    for(var i in files) {
      prefix = files[i].slice(0, -4);

      prefixes.push(prefix);
    }

    var page = '';
    var prefix = '';
    var i = null, j = null;
    for(i in pages) {

      page = pages[i];
      index.push(page);
      for (j in prefixes) {

        prefix = prefixes[j];

        if (page.indexOf(prefix) != -1) {

          if (groups[prefix] === undefined) {
            groups[prefix] = [];
          }
          groups[prefix].push(page);
          break;
        }
      }
    }

    var content = '';
    content += '<container>';
    content += '  <row>';
    content += '    <columns small="12" large="12">';

    switch(PREFIX_BUILD_INDEX) {
      case 'alpha':
        i = null, j = null;
        var title = '';
        content += '
Index
';
        content += '
';
        for(i in index) {

          if (index[i].indexOf('index') == -1) {
            content += '
' + index[i].slice(0, -5) + '
';
          }
        }
        content += '
';
        break;
      case 'prefix':
      case 'true':
      case true:
        i = null, j = null;
        var title = '';
        for(i in groups) {

          title = i.replace(/_/g, ' ');
          title = title.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();});
          content += '
' + title + '
';
          content += '
';
          for (j in groups[i]) {

            content += '
' + groups[i][j].slice(0, -5) + '
';
          }
          content += '
';
        }
        break;
    }

    content += '    </columns>';
    content += '  </row>';
    content += '</container>';

    fs.writeFile("./src/pages/prefix_index.html", content, function(err) {
      if(err) {
        console.log('Error writing index');
        done();
      }
    });

    return gulp.src('src/pages/prefix_index.html')
    .pipe(panini({
      root: 'src/pages',
      layouts: 'src/layouts',
      partials: 'src/partials',
      helpers: 'src/helpers'
    }))
    .pipe(inky())
    .pipe(gulp.dest('dist'));

  } else {
    done();
  }

}

// Start a server with LiveReload to preview the site in
function server(done) {
  browser.init({
    server: {
      baseDir: 'dist',
      index: (PREFIX_BUILD_INDEX != false) ? "prefix_index.html" : "index.html"
    }
  });
  done();
}

// Watch for file changes
function watch() {
  gulp.watch('src/pages/**/*.html').on('all', gulp.series(pages, inline, browser.reload));
  gulp.watch(['src/layouts/**/*', 'src/partials/**/*']).on('all', gulp.series(resetPages, pages, inline, browser.reload));
  gulp.watch(['../scss/**/*.scss', 'src/assets/scss/**/*.scss']).on('all', gulp.series(resetPages, sass, pages, inline, browser.reload));
  gulp.watch('src/assets/img/**/*').on('all', gulp.series(images, browser.reload));
}

// Inlines CSS into HTML, adds media query CSS into the `)
    .pipe($.replace, '<link rel="stylesheet" type="text/css" href="css/app.css">', '')
    .pipe($.htmlmin, {
      collapseWhitespace: true,
      minifyCSS: true
    });

  return pipe();
}

// Ensure creds for Litmus are at least there.
function creds(done) {
  var configPath = './config.json';
  try { CONFIG = JSON.parse(fs.readFileSync(configPath)); }
  catch(e) {
    beep();
    console.log('[AWS]'.bold.red + ' Sorry, there was an issue locating your config.json. Please see README.md');
    process.exit();
  }
  done();
}

// Post images to AWS S3 so they are accessible to Litmus and manual test
function aws() {
  var publisher = !!CONFIG.aws ? $.awspublish.create(CONFIG.aws) : $.awspublish.create();
  var headers = {
    'Cache-Control': 'max-age=315360000, no-transform, public'
  };

  return gulp.src('./dist/assets/img/*')
    // publisher will add Content-Length, Content-Type and headers specified above
    // If not specified it will set x-amz-acl to public-read by default
    .pipe(publisher.publish(headers))

    // create a cache file to speed up consecutive uploads
    //.pipe(publisher.cache())

    // print upload updates to console
    .pipe($.awspublish.reporter());
}

// Send email to Litmus for testing. If no AWS creds then do not replace img urls.
function litmus() {
  var awsURL = !!CONFIG && !!CONFIG.aws && !!CONFIG.aws.url ? CONFIG.aws.url : false;

  return gulp.src('dist/**/*.html')
    .pipe($.if(!!awsURL, $.replace(/=('|")(\/?assets\/img)/g, "=$1"+ awsURL)))
    .pipe($.litmus(CONFIG.litmus))
    .pipe(gulp.dest('dist'));
}

// Send email to specified email for testing. If no AWS creds then do not replace img urls.
function mail() {
  var awsURL = !!CONFIG && !!CONFIG.aws && !!CONFIG.aws.url ? CONFIG.aws.url : false;

  if (EMAIL) {
    CONFIG.mail.to = [EMAIL];
  }

  return gulp.src('dist/**/*.html')
    .pipe($.if(!!awsURL, $.replace(/=('|")(\/?assets\/img)/g, "=$1"+ awsURL)))
    .pipe($.mail(CONFIG.mail))
    .pipe(gulp.dest('dist'));
}

// Copy and compress into Zip
function zip() {
  var dist = 'dist';
  var ext = '.html';

  function getHtmlFiles(dir) {
    return fs.readdirSync(dir)
      .filter(function(file) {
        var fileExt = path.join(dir, file);
        var isHtml = path.extname(fileExt) == ext;
        return fs.statSync(fileExt).isFile() && isHtml;
      });
  }

  var htmlFiles = getHtmlFiles(dist);

  var moveTasks = htmlFiles.map(function(file){
    var sourcePath = path.join(dist, file);
    var fileName = path.basename(sourcePath, ext);

    var moveHTML = gulp.src(sourcePath)
      .pipe($.rename(function (path) {
        path.dirname = fileName;
        return path;
      }));

    var moveImages = gulp.src(sourcePath)
      .pipe($.htmlSrc({ selector: 'img'}))
      .pipe($.rename(function (path) {
        path.dirname = fileName + '/' + path.dirname;
        return path;
      }));

    return merge(moveHTML, moveImages)
      .pipe($.zip(fileName+ '.zip'))
      .pipe(gulp.dest('dist'));
  });

  return merge(moveTasks);
}<|endoftext|>


<commit_before>;(function (root, factory) {

  if (typeof define === 'function' && define.amd) {
    define(['ractive'], factory);
  } else if (typeof exports === 'object') {
    module.exports = factory(require('ractive'));
  } else {
    factory(root.Ractive);
  }

}(this, function (Ractive) {

  var Adaptor = Ractive.adaptors.Ractive = {
    filter: filter,
    wrap: wrap
  };

  /*
   * Advanced options:
   * You can adjust these settings via `Ractive.adaptors.Ractive.maxKeyLength`
   * and so on. There's usually no need to do that, but it may be good for
   * optimizing tests.
   */

  Adaptor.fireWrapEvents = true;
  Adaptor.maxKeyLength = 2048;

  /*
   * Check if the child is an Ractive instance.
   *
   * Also, if this key has been wrapped before, don't rewrap it. (Happens on
   * deeply-nested values, and .reset() for some reason.)
   */

  function filter (child, keypath, parent) {
    if (!(child instanceof Ractive))
      return false;

    if (parent &&
        parent._ractiveWraps &&
        parent._ractiveWraps[keypath])
      return false;

    return true;
  }

  /*
   * Global write lock.
   * This prevents infinite loops from happening where a parent will set a
   * value on the child, and the child will attempt to write back to the
   * parent, and so on.
   */

  var locked = Adaptor.locked = {};

  function lock (key, fn) {
    if (locked[key]) return;
    try {
      locked[key] = true;
      return fn();
    } finally {
      delete locked[key];
    }
  }

  /*
   * Returns a wrapped Adaptor for Ractive.
   * See: http://docs.ractivejs.org/latest/writing-adaptor-plugins
   */

  function wrap (parent, child, keypath, prefixer) {
    setup();

    return {
      get: get,
      set: set,
      reset: reset,
      teardown: teardown
    };

    /*
     * Initializes the adaptor. Performs a few tricks:
     *
     * [1] If the child has its own Ractive instances, recurse upwards. This
     * will do `parent.set('child.grandchild', instance)` so that the
     * `parent` can listen to the grandchild.
     */

    function setup () {
      checkForRecursion();
      markAsWrapped();
      parent.set(prefixer(get())); // [1]
      child.on('change', onChange);

      if (Adaptor.fireWrapEvents) {
        child.fire('wrap', parent, keypath);
        parent.fire('wrapchild', child, keypath);
      }
    }

    function teardown () {
      delete parent._ractiveWraps[keypath];
      child.off('change', onChange);

      if (Adaptor.fireWrapEvents) {
        child.fire('unwrap', parent, keypath);
        parent.fire('unwrapchild', child, keypath);
      }
    }

    /*
     * Propagate changes from child to parent.
     * We well break it apart into key/vals and set those individually because
     * some values may be locked.
     */

    function onChange (updates) {
      each(updates, function (value, key) {
        lock(child._guid + key, function () {
          parent.set(keypath + '.' + key, value);
        });
      });
    }

    /*
     * Returns all attributes of the child, including computed properties.
     * See: https://github.com/ractivejs/ractive/issues/1250
     */

    function get () {
      // Optimization: if there are no computed properties, returning all
      // non-computed data should suffice.
      if (!child.computed) return child.get();

      var re = {};

      each(child.get(), function (val, key) {
        re[key] = val;
      });

      each(child.computed, function (_, key) {
        if (typeof re[key] === 'undefined')
          re[key] = child.get(key);
      });

      return re;
    }

    function set (key, value) {
      lock(child._guid + key, function () {
        child.set(key, value);
      });
    }

    /*
     * Allow setting values by passing a POJO to .set(), for instance,
     * `.set('child', { ... })`. If anything else is passed onto .set()
     * (like another Ractive instance, or another adaptor'able), destroy
     * this wrapper.
     */

    function reset (object) {
      if (object && object.constructor === Object) {
        child.set(object);
      } else {
        return false;
      }
    }

    /*
     * Die on recursion.
     * Keypath will look like 'child.sub.parent.child.sub.parent' ad nauseum.
     */

    function checkForRecursion () {
      if (keypath && keypath.length > Adaptor.maxKeyLength)
        throw new Error("Keypath too long (possible circular dependency)");
    }

    /*
     * Let future wrappers know what we have wrapped Ractive instances.
     * This value is used on `filter()`.
     */

    function markAsWrapped () {
      if (!parent._ractiveWraps) parent._ractiveWraps = {};
      parent._ractiveWraps[keypath] = child;
    }
  }

  /*
   * Cross-browser forEach helper
   */

  function each (obj, fn) {
    for (var key in obj) {
      if (obj.hasOwnProperty(key)) fn(obj[key], key);
    }
  }

}));
<commit_msg>feat: Accessing Ractive child instances are now fully proxied.<commit_after>;(function (root, factory) {

  if (typeof define === 'function' && define.amd) {
    define(['ractive'], factory);
  } else if (typeof exports === 'object') {
    module.exports = factory(require('ractive'));
  } else {
    factory(root.Ractive);
  }

}(this, function (Ractive) {

  var Adaptor = Ractive.adaptors.Ractive = {
    filter: filter,
    wrap: wrap
  };

  /*
   * Advanced options:
   * You can adjust these settings via `Ractive.adaptors.Ractive.maxKeyLength`
   * and so on. There's usually no need to do that, but it may be good for
   * optimizing tests.
   */

  Adaptor.fireWrapEvents = true;
  Adaptor.maxKeyLength = 2048;

  /*
   * Ractive instance accessor methods that will be proxied by Ractive-Ractive.
   */

  var ractiveMethods = {
    add: {
      modifiesData: true,
      mapAllowed: false
    },
    animate: {
      modifiesData: true,
      mapAllowed: true
    },
    get: {
      modifiesData: false,
      mapAllowed: false
    },
    merge: {
      modifiesData: true,
      mapAllowed: false
    },
    observe: {
      modifiesData: false,
      mapAllowed: true
    },
    observeOnce: {
      modifiesData: false,
      mapAllowed: false
    },
    pop: {
      modifiesData: true,
      mapAllowed: false
    },
    push: {
      modifiesData: true,
      mapAllowed: false
    },
    set: {
      modifiesData: true,
      mapAllowed: true
    },
    shift: {
      modifiesData: true,
      mapAllowed: false
    },
    splice: {
      modifiesData: true,
      mapAllowed: false
    },
    subtract: {
      modifiesData: true,
      mapAllowed: false
    },
    toggle: {
      modifiesData: true,
      mapAllowed: false
    },
    unshift: {
      modifiesData: true,
      mapAllowed: false
    },
    update: {
      modifiesData: false,
      mapAllowed: false
    },
    updateModel: {
      modifiesData: false,
      mapAllowed: false
    }
  };

  /*
   * Check if the child is an Ractive instance.
   *
   * Also, if this key has been wrapped before, don't rewrap it. (Happens on
   * deeply-nested values, and .reset() for some reason.)
   */

  function filter (child, keypath, parent) {
    if (!(child instanceof Ractive))
      return false;

    if (parent &&
        parent._ractiveWraps &&
        parent._ractiveWraps[keypath])
      return false;

    return true;
  }

  /*
   * Global write lock.
   * This prevents infinite loops from happening where a parent will set a
   * value on the child, and the child will attempt to write back to the
   * parent, and so on.
   */

  var locked = Adaptor.locked = {};

  function lock (key, fn) {
    if (locked[key]) return;
    try {
      locked[key] = true;
      return fn();
    } finally {
      delete locked[key];
    }
  }

  /*
   * Returns a wrapped Adaptor for Ractive.
   * See: http://docs.ractivejs.org/latest/writing-adaptor-plugins
   */

  function wrap (parent, child, keypath, prefixer) {
    setup();

    return {
      get: get,
      set: set,
      reset: reset,
      teardown: teardown
    };

    /*
     * Initializes the adaptor.
     */

    function setup () {
      hookAccessors();
      checkForRecursion();
      markAsWrapped();
      child.on('change', onChange);

      if (Adaptor.fireWrapEvents) {
        child.fire('wrap', parent, keypath);
        parent.fire('wrapchild', child, keypath);
      }
    }

    function teardown () {
      delete parent._ractiveWraps[keypath];
      updateChildrenPattern();
      unhookAccessors();
      child.off('change', onChange);

      if (Adaptor.fireWrapEvents) {
        child.fire('unwrap', parent, keypath);
        parent.fire('unwrapchild', child, keypath);
      }
    }

    /*
     * Propagate changes from child to parent.
     * We well break it apart into key/vals and set those individually because
     * some values may be locked.
     */

    function onChange (updates) {
      each(updates, function (value, key) {
        lock(child._guid + key, function () {
          parent.set(keypath + '.' + key, value);
        });
      });
    }

    /*
     * Returns all attributes of the child, including computed properties.
     * See: https://github.com/ractivejs/ractive/issues/1250
     */

    function get () {
      // Optimization: if there are no computed properties, returning all
      // non-computed data should suffice.
      if (!child.computed) return child.get();

      var re = {};

      each(child.get(), function (val, key) {
        re[key] = val;
      });

      each(child.computed, function (_, key) {
        if (typeof re[key] === 'undefined')
          re[key] = child.get(key);
      });

      return re;
    }

    function set (key, value) {
      lock(child._guid + key, function () {
        child.set(key, value);
      });
    }

    /*
     * Allow setting values by passing a POJO to .set(), for instance,
     * `.set('child', { ... })`. If anything else is passed onto .set()
     * (like another Ractive instance, or another adaptor'able), destroy
     * this wrapper.
     */

    function reset (object) {
      if (object && object.constructor === Object) {
        child.set(object);
      } else {
        return false;
      }
    }

    /*
     * Update the parent's child key RegEx pattern used by accessors
     */

    function updateChildrenPattern () {
      if (!parent._ractiveWraps) {
        delete parent._childrenPattern;
        return;
      }

      // Collect the current `_ractiveWraps` keys
      var keys = [];
      each(parent._ractiveWraps, function (value, key) {
        keys.push(key);
      });

      if (keys.length === 0) {
        delete parent._childrenPattern;
        return;
      }

      parent._childrenPattern = new RegExp("^(" + keys.join(")\\..+?|(") + ")\\..+?$");
    }

    /*
     * Generates a Ractive accessor proxy for the given method and configuration.
     */

    function generateAccessor(method, config) {
      config = config || {};

      return function (keypath) {
        var child;
        var childKey;
        var result;

        // Exit early if there aren't any children or we're not targeting a keypath
        if (parent._childrenPattern == null || keypath == null) {
          return parent["_" + method].apply(parent, arguments);
        }

        // Handle a `key, value` set
        if (isString(keypath) === true) {
           childKey = parseAccessorKey(keypath);
           child = parent._ractiveWraps[childKey];

          // The key isn't a child of parent
          if (child == null) {
            return parent["_" + method].apply(parent, arguments);
          }

          // Remove the root of the key before marshalling it to the child
          arguments[0] = arguments[0].replace(childKey + ".", "");
          result = child[method].apply(child, arguments);

          if (config.modifiesData === true) {
            parent._update(childKey + "." + arguments[0]);
          }

          return result;
        }

        // Handle when only a value object is passed to the Ractive instance
        if (config.mapAllowed === true) {
          // Filter out and marshall keys which apply to children
          each(keypath, function (value, key) {
            childKey = parseAccessorKey(keypath);
            child = parent._ractiveWraps[childKey];

            if (child == null) {
              return;
            }

            delete keypath[key];

            // Remove the root of the key before marshalling it to the child
            key = key.replace(childKey + ".", "");
            child[method].call(child, key, value);

            if (config.modifiesData === true) {
              parent._update(childKey + "." + key);
            }
          });

          // Apply the remaining keys to the parent
          return parent["_" + method].apply(parent, arguments);
        }

        // Default behavior
        return parent["_" + method].apply(parent, arguments);
      };
    }

    /*
     * Check all children to see if there are any matches and return the result.
     */

    function parseAccessorKey(keypath) {
      if (parent._childrenPattern == null) {
        return;
      }

      var childKeys = parent._childrenPattern.exec(keypath);

      if (childKeys == null) {
        return;
      }

      return childKeys[1];
    }

    /*
     * Overwrite the original Ractive methods with Ractive-Ractive accessor proxies.
     */

    function hookAccessors () {
      // Only hook the accessors once
      if (parent._accessorsHooked === true) {
        return;
      }

      parent._accessorsHooked = true;

      each(ractiveMethods, function (config, method) {
        parent["_" + method] = parent[method];
        parent[method] = generateAccessor(method, config);
      });
    }

    /*
     * Replace the Ractive-Ractive accessor proxies with the original implementations.
     */

    function unhookAccessors () {
      // Only hook the accessors once
      if (parent._accessorsHooked === true) {
        delete parent._accessorsHooked;

        each(ractiveMethods, function (config, method) {
          parent[method] = parent["_" + method];
          delete parent["_" + method];
        });
      }
    }

    /*
     * Die on recursion.
     * Keypath will look like 'child.sub.parent.child.sub.parent' ad nauseum.
     */

    function checkForRecursion () {
      if (keypath && keypath.length > Adaptor.maxKeyLength)
        throw new Error("Keypath too long (possible circular dependency)");
    }

    /*
     * Let future wrappers know what we have wrapped Ractive instances.
     * This value is used on `filter()`.
     */

    function markAsWrapped () {
      if (!parent._ractiveWraps) parent._ractiveWraps = {};
      parent._ractiveWraps[keypath] = child;
      updateChildrenPattern();
    }
  }

  /*
   * Cross-browser forEach helper
   */

  function each (obj, fn) {
    for (var key in obj) {
      if (obj.hasOwnProperty(key)) fn(obj[key], key);
    }
  }

  /*
   * isString helper
   */

   function isString (string) {
    return typeof string == 'string' || (!!string && typeof string == 'object' && Object.prototype.toString.call(string) == '[object String]');
   }
}));
<|endoftext|>


<commit_before>var TBB_UAS = [
  "Mozilla/5.0 (Windows NT 6.1; rv:10.0) Gecko/20100101 Firefox/10.0",
  "Mozilla/5.0 (Windows NT 6.1; rv:17.0) Gecko/20100101 Firefox/17.0",
];

function is_likely_tor_browser() {
  return TBB_UAS.indexOf(window.navigator.userAgent) > -1
    && (window.navigator.mimeTypes && window.navigator.mimeTypes.length === 0);
}

function warn_user(messageDiv) {
  var messageDiv = $('
').html(
    'WARNING:Your browser currently has Javascript enabled.' +
    'Click here' +
    'to learn how. Or click on this warning to continue. We recommend' +
    'disabling Javascript to protect your anonymity. ');
  var warningDiv = $('
').append(messageDiv);

  $('#warning').remove();
  $(document.body).append(warningDiv);
  warningDiv.fadeIn(1000);
}

$(window).load(function() {
  warn_user();
  if(is_likely_tor_browser())
  $("#disable-js-anchor").click(function(){
    $('#warning').remove();
    var infoP = $('
').html(
      'You appear to be using the Tor Browser Bundle.' +
      'You can disable Javascript with 3 quick steps!
' +
      '
' +
      '

Click the NoScript icon in the toolbar above
' +
      '
Click on  "Forbid Scripts Globally (advised)"
' +
      '
Refresh this page
' +
      '

Not using the Tor Browser Bundle?'
    );
    $(document.body).append(infoP);
    infoP.fadeIn(1000);
    infoP.click(function() {
      infoP.remove();
    });
    return false; // don't follow link
  });
});

<commit_msg>Fixing some wording<commit_after>var TBB_UAS = [
  "Mozilla/5.0 (Windows NT 6.1; rv:10.0) Gecko/20100101 Firefox/10.0",
  "Mozilla/5.0 (Windows NT 6.1; rv:17.0) Gecko/20100101 Firefox/17.0",
];

function is_likely_tor_browser() {
  return TBB_UAS.indexOf(window.navigator.userAgent) > -1
    && (window.navigator.mimeTypes && window.navigator.mimeTypes.length === 0);
}

function warn_user(messageDiv) {
  var messageDiv = $('
').html(
    'WARNING: Your browser currently has Javascript enabled. ' +
    'Click here ' +
    'to learn how to disable it, or click on this warning to continue.
We recommend' +
    'disabling Javascript to protect your anonymity. ');
  var warningDiv = $('
').append(messageDiv);

  $('#warning').remove();
  $(document.body).append(warningDiv);
  warningDiv.fadeIn(1000);
}

$(window).load(function() {
  warn_user();
  if(is_likely_tor_browser())
  $("#disable-js-anchor").click(function(){
    $('#warning').remove();
    var infoP = $('
').html(
      'You appear to be using the Tor Browser Bundle.' +
      'You can disable Javascript with 3 quick steps!
' +
      '
' +
      '

Click the NoScript icon in the toolbar above
' +
      '
Click on  "Forbid Scripts Globally (advised)"
' +
      '
Refresh this page
' +
      '

Not using the Tor Browser Bundle?'
    );
    $(document.body).append(infoP);
    infoP.fadeIn(1000);
    infoP.click(function() {
      infoP.remove();
    });
    return false; // don't follow link
  });
});

<|endoftext|>


<commit_before>import { default as register } from     'babel-core/register';
register({
    only: [
        '**/node_modules/angie*/**',
        '**/src/**',
        '**/test/**'
    ],
    stage: 0
});

// System Modules
import fs from                          'fs';
import gulp from                        'gulp';
import { argv } from                    'yargs';
import eslint from                      'gulp-eslint';
import jscs from                        'gulp-jscs';
import { Instrumenter } from            'isparta';
import mocha from                       'gulp-mocha';
import istanbul from                    'gulp-istanbul';
import cobertura from                   'istanbul-cobertura-badger';
import esdoc from                       'gulp-esdoc';
import babel from                       'gulp-babel';
import { bold, red } from               'chalk';

const bread = str => bold(red(str));

const SRC_DIR = './src',
    SRC = `${SRC_DIR}/**/*.js`,
    TRANSPILED_SRC_DIR = './dist',
    TRANSPILED_SRC = `${TRANSPILED_SRC_DIR}/**/*.js`,
    TEST_SRC = './test/src/**/*.spec.js',
    TRANSPILED_TEST_SRC = './test/dist/**/*.spec.js',
    DOC_SRC = './doc',
    COVERAGE_SRC = './coverage';

// Build Tasks
gulp.task('eslint', function () {
    gulp.src([ SRC, TEST_SRC ]).pipe(
        eslint()
    ).pipe(
        eslint.format()
    ).pipe(
        eslint.failOnError()
    );
});
gulp.task('jscs', [ 'eslint' ], function () {
    return gulp.src([ SRC, TEST_SRC ])
        .pipe(jscs({
            fix: true,
            configPath: '.jscsrc',
            esnext: true
        }));
});
gulp.task('istanbul:src', [ 'jscs' ], istanbulHandler.bind(null, SRC));
gulp.task('istanbul:dist', [ 'babel' ], istanbulHandler.bind(null, TRANSPILED_SRC));
gulp.task(
    'mocha:src',
    [ 'istanbul:src' ],
    mochaHandler.bind(null, 'src', COVERAGE_SRC)
);
gulp.task(
    'mocha:dist',
    [ 'istanbul:dist' ],
    mochaHandler.bind(null, 'dist', undefined)
);
gulp.task('cobertura', [ 'mocha:src' ], function(cb) {
    cobertura('coverage/cobertura-coverage.xml', 'svg', cb);
});

gulp.task('esdoc', function() {
    return gulp.src(SRC_DIR).pipe(esdoc({ destination: DOC_SRC }));
});
gulp.task('babel', function() {
    return gulp.src(SRC).pipe(babel({
        comments: false
    })).pipe(gulp.dest(TRANSPILED_SRC_DIR));
});

// Bundled Tasks
gulp.task('test:src', [ 'jscs', 'mocha:src' ]);
gulp.task('test:dist', [ 'mocha:dist' ]);
gulp.task('test', [ 'test:src' ]);
gulp.task('watch', [ 'test' ], function() {
    gulp.watch([ SRC, TEST_SRC ], [ 'test' ]);
});
gulp.task('default', [ 'cobertura', 'babel', 'esdoc' ]);

// Utility Tasks
gulp.task('bump', function() {
    const version = argv.version,
        bump = f => fs.writeFileSync(f, fs.readFileSync(f, 'utf8').replace(
            /[0-9]{1,2}\.[0-9]{1,2}\.[0-9]{1,2}/,
            version
        ));
    if (version) {

        // Verify that the version is in the CHANGELOG
        if (fs.readFileSync('CHANGELOG.md', 'utf8').indexOf(version) === -1) {
            throw new Error(bread('Version has no entry in CHANGELOG.md'));
        }

        bump('bin/angie-orm');
        bump('bin/angie-orm-dist');
        bump('package.json');
    } else {
        throw new Error(bread('No version specified!!'));
    }
});

function istanbulHandler(src, cb) {
    gulp.src(src).pipe(istanbul({
        instrumenter: Instrumenter,
        includeUntested: true,
        babel: {
            stage: 0
        }
    })).pipe(istanbul.hookRequire()).on('finish', cb);
}

function mochaHandler(src, coverage = '/tmp') {
    global.TEST_ENV = src;
    return gulp.src(TEST_SRC).pipe(mocha({
        reporter: 'spec'
    })).pipe(istanbul.writeReports({
        dir: coverage,
        reportOpts: {
            dir: coverage
        },
        reporters: [ 'text', 'text-summary', 'html', 'cobertura' ]
    }));
}<commit_msg>Modified eslint gulp task<commit_after>import { default as register } from     'babel-core/register';
register({
    only: [
        '**/node_modules/angie*/**',
        '**/src/**',
        '**/test/**'
    ],
    stage: 0
});

// System Modules
import fs from                          'fs';
import gulp from                        'gulp';
import { argv } from                    'yargs';
import eslint from                      'gulp-eslint';
import jscs from                        'gulp-jscs';
import { Instrumenter } from            'isparta';
import mocha from                       'gulp-mocha';
import istanbul from                    'gulp-istanbul';
import cobertura from                   'istanbul-cobertura-badger';
import esdoc from                       'gulp-esdoc';
import babel from                       'gulp-babel';
import { bold, red } from               'chalk';

const bread = str => bold(red(str));

const SRC_DIR = './src',
    SRC = `${SRC_DIR}/**/*.js`,
    TRANSPILED_SRC_DIR = './dist',
    TRANSPILED_SRC = `${TRANSPILED_SRC_DIR}/**/*.js`,
    TEST_SRC = './test/src/**/*.spec.js',
    TRANSPILED_TEST_SRC = './test/dist/**/*.spec.js',
    DOC_SRC = './doc',
    COVERAGE_SRC = './coverage';

// Build Tasks
gulp.task('eslint', function () {
    gulp.src([ SRC, TEST_SRC ]).pipe(eslint().on('error', function(e) {
        throw e;
    }));
});
gulp.task('jscs', [ 'eslint' ], function () {
    return gulp.src([ SRC, TEST_SRC ])
        .pipe(jscs({
            fix: true,
            configPath: '.jscsrc',
            esnext: true
        }));
});
gulp.task('istanbul:src', [ 'jscs' ], istanbulHandler.bind(null, SRC));
gulp.task('istanbul:dist', [ 'babel' ], istanbulHandler.bind(null, TRANSPILED_SRC));
gulp.task(
    'mocha:src',
    [ 'istanbul:src' ],
    mochaHandler.bind(null, 'src', COVERAGE_SRC)
);
gulp.task(
    'mocha:dist',
    [ 'istanbul:dist' ],
    mochaHandler.bind(null, 'dist', undefined)
);
gulp.task('cobertura', [ 'mocha:src' ], function(cb) {
    cobertura('coverage/cobertura-coverage.xml', 'svg', cb);
});

gulp.task('esdoc', function() {
    return gulp.src(SRC_DIR).pipe(esdoc({ destination: DOC_SRC }));
});
gulp.task('babel', function() {
    return gulp.src(SRC).pipe(babel({
        comments: false
    })).pipe(gulp.dest(TRANSPILED_SRC_DIR));
});

// Bundled Tasks
gulp.task('test:src', [ 'jscs', 'mocha:src' ]);
gulp.task('test:dist', [ 'mocha:dist' ]);
gulp.task('test', [ 'test:src' ]);
gulp.task('watch', [ 'test' ], function() {
    gulp.watch([ SRC, TEST_SRC ], [ 'test' ]);
});
gulp.task('default', [ 'cobertura', 'babel', 'esdoc' ]);

// Utility Tasks
gulp.task('bump', function() {
    const version = argv.version,
        bump = f => fs.writeFileSync(f, fs.readFileSync(f, 'utf8').replace(
            /[0-9]{1,2}\.[0-9]{1,2}\.[0-9]{1,2}/,
            version
        ));
    if (version) {

        // Verify that the version is in the CHANGELOG
        if (fs.readFileSync('CHANGELOG.md', 'utf8').indexOf(version) === -1) {
            throw new Error(bread('Version has no entry in CHANGELOG.md'));
        }

        bump('bin/angie-orm');
        bump('bin/angie-orm-dist');
        bump('package.json');
    } else {
        throw new Error(bread('No version specified!!'));
    }
});

function istanbulHandler(src, cb) {
    gulp.src(src).pipe(istanbul({
        instrumenter: Instrumenter,
        includeUntested: true,
        babel: {
            stage: 0
        }
    })).pipe(istanbul.hookRequire()).on('finish', cb);
}

function mochaHandler(src, coverage = '/tmp') {
    global.TEST_ENV = src;
    return gulp.src(TEST_SRC).pipe(mocha({
        reporter: 'spec'
    })).pipe(istanbul.writeReports({
        dir: coverage,
        reportOpts: {
            dir: coverage
        },
        reporters: [ 'text', 'text-summary', 'html', 'cobertura' ]
    }));
}<|endoftext|>


<commit_before>var mongoose = require('mongoose'),
    Schema = mongoose.Schema,
    Model = mongoose.Model,
    util = require('util');

module.exports = function (schema, options) {
    schema.add({ deleted: Boolean });

    if (options && options.deletedAt === true) {
        schema.add({ deletedAt: { type: Date} });
    }

    if (options && options.deletedBy === true) {
        schema.add({ deletedBy: Schema.Types.ObjectId });
    }

    schema.pre('save', function (next) {
        if (!this.deleted) {
            this.deleted = false;
        }
        next();
    });

    if (options && options.overrideMethods) {
        var overrideItems = options.overrideMethods;
        var overridableMethods = ['count', 'find', 'findOne', 'findOneAndUpdate', 'update'];
        var finalList = [];

        if ((typeof overrideItems === 'string' || overrideItems instanceof String) && overrideItems === 'all') {
            finalList = overridableMethods;
        }

        if (typeof(overrideItems) === "boolean" && overrideItems === true) {
            finalList = overridableMethods;
        }

        if (Array.isArray(overrideItems)) {
            overrideItems.forEach(function(method) {
                if (overridableMethods.indexOf(method) > -1) {
                    finalList.push(method);
                }
            });
        }

        /**
         * This code is taken from official mongoose repository
         * https://github.com/Automattic/mongoose/blob/master/lib/query.js#L1996-L2018
         */
        /* istanbul ignore next */
        function parseUpdateArguments (conditions, doc, options, callback) {
            if ('function' === typeof options) {
                // .update(conditions, doc, callback)
                callback = options;
                options = null;
            } else if ('function' === typeof doc) {
                // .update(doc, callback);
                callback = doc;
                doc = conditions;
                conditions = {};
                options = null;
            } else if ('function' === typeof conditions) {
                // .update(callback)
                callback = conditions;
                conditions = undefined;
                doc = undefined;
                options = undefined;
            } else if (typeof conditions === 'object' && !doc && !options && !callback) {
                // .update(doc)
                doc = conditions;
                conditions = undefined;
                options = undefined;
                callback = undefined;
            }

            var args = [];

            if (conditions) args.push(conditions);
            if (doc) args.push(doc);
            if (options) args.push(options);
            if (callback) args.push(callback);

            return args;
        }

        finalList.forEach(function(method) {
            if (method === 'count' || method === 'find' || method === 'findOne') {
                schema.statics[method] = function () {
                    return Model[method].apply(this, arguments).where('deleted').equals(false);
                };
                schema.statics[method + 'Deleted'] = function () {
                    return Model[method].apply(this, arguments).where('deleted').ne(false);
                };
                schema.statics[method + 'WithDeleted'] = function () {
                    return Model[method].apply(this, arguments);
                };
            } else {
                schema.statics[method] = function () {
                    var args = parseUpdateArguments.apply(undefined, arguments);

                    args[0].deleted = {'$ne': true};

                    return Model[method].apply(this, args);
                };

                schema.statics[method + 'Deleted'] = function () {
                    var args = parseUpdateArguments.apply(undefined, arguments);

                    args[0].deleted = {'$ne': false};

                    return Model[method].apply(this, args);
                };

                schema.statics[method + 'WithDeleted'] = function () {
                    return Model[method].apply(this, arguments);
                };
            }
        });
    }

    schema.methods.delete = function (first, second) {
        var callback = typeof first === 'function' ? first : second,
            deletedBy = second !== undefined ? first : null;

        if (typeof callback !== 'function') {
            throw 'Wrong arguments!';
        }

        this.deleted = true;

        if (schema.path('deletedAt')) {
            this.deletedAt = new Date();
        }

        if (schema.path('deletedBy')) {
            this.deletedBy = deletedBy;
        }

        this.save(callback);
    };

    schema.methods.restore = function (callback) {
        this.deleted = false;
        this.deletedAt = undefined;
        this.deletedBy = undefined;
        this.save(callback);
    };
};<commit_msg>Refactoring, move parseUpdateArguments function from exports functin block<commit_after>var mongoose = require('mongoose'),
    Schema = mongoose.Schema,
    Model = mongoose.Model,
    util = require('util');

/**
 * This code is taken from official mongoose repository
 * https://github.com/Automattic/mongoose/blob/master/lib/query.js#L1996-L2018
 */
/* istanbul ignore next */
function parseUpdateArguments (conditions, doc, options, callback) {
    if ('function' === typeof options) {
        // .update(conditions, doc, callback)
        callback = options;
        options = null;
    } else if ('function' === typeof doc) {
        // .update(doc, callback);
        callback = doc;
        doc = conditions;
        conditions = {};
        options = null;
    } else if ('function' === typeof conditions) {
        // .update(callback)
        callback = conditions;
        conditions = undefined;
        doc = undefined;
        options = undefined;
    } else if (typeof conditions === 'object' && !doc && !options && !callback) {
        // .update(doc)
        doc = conditions;
        conditions = undefined;
        options = undefined;
        callback = undefined;
    }

    var args = [];

    if (conditions) args.push(conditions);
    if (doc) args.push(doc);
    if (options) args.push(options);
    if (callback) args.push(callback);

    return args;
}

module.exports = function (schema, options) {
    schema.add({ deleted: Boolean });

    if (options && options.deletedAt === true) {
        schema.add({ deletedAt: { type: Date} });
    }

    if (options && options.deletedBy === true) {
        schema.add({ deletedBy: Schema.Types.ObjectId });
    }

    schema.pre('save', function (next) {
        if (!this.deleted) {
            this.deleted = false;
        }
        next();
    });

    if (options && options.overrideMethods) {
        var overrideItems = options.overrideMethods;
        var overridableMethods = ['count', 'find', 'findOne', 'findOneAndUpdate', 'update'];
        var finalList = [];

        if ((typeof overrideItems === 'string' || overrideItems instanceof String) && overrideItems === 'all') {
            finalList = overridableMethods;
        }

        if (typeof(overrideItems) === "boolean" && overrideItems === true) {
            finalList = overridableMethods;
        }

        if (Array.isArray(overrideItems)) {
            overrideItems.forEach(function(method) {
                if (overridableMethods.indexOf(method) > -1) {
                    finalList.push(method);
                }
            });
        }

        finalList.forEach(function(method) {
            if (method === 'count' || method === 'find' || method === 'findOne') {
                schema.statics[method] = function () {
                    return Model[method].apply(this, arguments).where('deleted').equals(false);
                };
                schema.statics[method + 'Deleted'] = function () {
                    return Model[method].apply(this, arguments).where('deleted').ne(false);
                };
                schema.statics[method + 'WithDeleted'] = function () {
                    return Model[method].apply(this, arguments);
                };
            } else {
                schema.statics[method] = function () {
                    var args = parseUpdateArguments.apply(undefined, arguments);

                    args[0].deleted = {'$ne': true};

                    return Model[method].apply(this, args);
                };

                schema.statics[method + 'Deleted'] = function () {
                    var args = parseUpdateArguments.apply(undefined, arguments);

                    args[0].deleted = {'$ne': false};

                    return Model[method].apply(this, args);
                };

                schema.statics[method + 'WithDeleted'] = function () {
                    return Model[method].apply(this, arguments);
                };
            }
        });
    }

    schema.methods.delete = function (first, second) {
        var callback = typeof first === 'function' ? first : second,
            deletedBy = second !== undefined ? first : null;

        if (typeof callback !== 'function') {
            throw 'Wrong arguments!';
        }

        this.deleted = true;

        if (schema.path('deletedAt')) {
            this.deletedAt = new Date();
        }

        if (schema.path('deletedBy')) {
            this.deletedBy = deletedBy;
        }

        this.save(callback);
    };

    schema.methods.restore = function (callback) {
        this.deleted = false;
        this.deletedAt = undefined;
        this.deletedBy = undefined;
        this.save(callback);
    };
};
<|endoftext|>


<commit_before>$(document).ready(function(){

  /*
  Konfigurační proměnné pro debug
  */
  var debug_mode = false; // TRUE -> Bude vypisovat průběh scriptu do konzole (např. chyby při kontrolách inputů, druhy inputů atp.)
  var send_form = true; // Má se po dokončení skriptu ( a pokud jsou všechny inputy validní ) odeslat formulář ? ( Pro účely debugu, pokud nechceme spamovat emailovou schránku příjemce )

  /*
  Další konfigurační proměnné
  */
  var add_id_to_all_forms = true;
  var wrong_input_class_name = "wrong-input";

  /*
    Funkce, která ve stránce vyhledá všechny formuláře a každému přidělí id.
    Pokud formulář už id má, funkce id nezmění a ponechá původní
    */
    function add_id_to_all_forms(){
      var iteration = 1; // Formuláře počítáme od 1
      var id_text = ""; // Pomocná proměnná

      $("form").each(function(){
        id_text = "form_" + iteration; // Připravený text pro formulář
        if(!$(this).attr('id')){ // Pokud formulř nemá svoje ID
          $(this).attr('id',id_text); // Přidělí se automaticky vygenerované
          iteration++;
        }
      });
    }

    if(add_id_to_all_forms == true){
      add_id_to_all_forms(); // Pustíme funkci pro všechny formuláře
    }

    /*
    ------ Pravidla pro formuláře: ------

    Dostupné inputy:
    -------------------------------------
    Název (pravidlo) - (příklad)
    -------------------------------------
    telefon (čísla, +, mezery) - (+123 123 456 789)
    jmeno (velká + malá písmena, mezery) - (Jan Někdo Něco)
    email (text, tečka, čísla @ text . text) - (nekdo.neco23@domana.com)
    datum (2 čísla <= 31 . 2 čísla <= 12 . 4 čísla <= aktualní rok) - (13.10.1856)
    text (nekontrolování)
    .... možné další
    */

    /*
    Funkce pro validaci inputu.
    Vrací
      -> True: Validní
      -> False: Nevalidní
    */
    function validuj_input(value, type){
      if(type == "telefon"){
        upraveny_string = value.replace(/\s+/g, ''); // Pro zjednodušení výrazů nahradíme případné mezery v tel. čísle

        if(debug_mode == true){
          console.log('Upravené číslo je: ' + upraveny_string);
        }
        var key = /^(\+420)?[0-9]{9}$/;

        if(key.test(upraveny_string)) {
          return true;
        }else {
          return false;
        }
      }else if (type == "jmeno") {
        var key = /^\D{1,} \D{1,}$/;

        if(key.test(value)) {
          return true;
        }else {
          return false;
        }
      }else if (type == "email") {
        var key = /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$/;

        if(key.test(value)) {
          return true;
        }else {
          return false;
        }
      }else if (type == "datum") {
        var key = /^\d{1,2}.\d{1,2}.\d{4}$/;

        if(key.test(value)) {
          return true;
        }else {
          return false;
        }
      }else if (type == "text") {
        return true;
      }else if (type == "soubor") {
        // Musí být nahrán soubor
      }else if (type == "checkbox") {
        // Checkbox
      }else {
        // Není vyplněno nebo má neznámý typ, vracíme false
        return false;
      }
    }

    $(".form-validate").on('click',function(e){ // Při kliknutí na tlačítko s třídou form-validate
      e.preventDefault();

      /*
      Pole, které obsahuje správnost jednotlivých inputů
      */
      var inputs = Array();

      var validni = true;
      /*
      Proměnná, který definuje, zda je formulář validní
      Formulář je na začátku nastaven na true (validní). Pokud se během kontroly objeví chyba,
      je nastaven na false (nevalidní) a označí se input, který chybu vyvolal.
      */

      var parent_form = $(this).closest('form'); // Vybereme rodičovský formlulář

      var form_object_selector = "form#" + parent_form.attr('id') + " :input"; // Sestavíme selektor pro výběr inputů formuláře

      var input_iteration = 0; // Nastavení čísla pro průchod všemi inputy a následného zanesení do pole inputs

      $(form_object_selector).each(function(){
        var input_objekt = $(this); // Nastavení objektu

        input_objekt.removeClass('wrong-input'); // Před validací odstraníme classu wrong input ze všech inputů ve formuláři

        /*
        Proměnné potřebné pro práci s inputem
        */
        var value = input_objekt.val(); // Hodnota (obsah) inputu
        var jmeno = input_objekt.attr('name'); // Zjištění jména inputu
        var druh = input_objekt.data('typ'); // Zjištění o jaký druh inputu se jedná
        var vyzadovan = false; // Zjištění, zda input musí být vyplněn (defaultně nastaveno na false -> není vyžadován)
        var slovo = ""; // Pomocná proměnné pro výpis do konzole

        if(input_objekt.prop('required')){ // Zjištěné, jestli má u sebe input atribut requiered
          vyzadovan = true; // Má u sebe napsáno reqired
          slovo = " je ";
        } else {
          vyzadovan = false; // Nemá u sebe napsáno reqired
          slovo = " není ";
        }
        /*
        Debugovací výpis jednotlivých položek formuláře (druh a jestli jsou requiered)
        */
        if(debug_mode == true){
          console.log("Input " + jmeno + " je druh " + druh + " a" + slovo + "vyžadováno vyplnění.");
        }

        /*
        Pokud input nemá nastavený druh, bude se považovat za validní
        */
        if(!druh){

          inputs[input_iteration] = true;

        }else{

          /*
          Pokud má input nastaveno reqired a není vyplněn, bude se s
          ním počítat jako s nevalidním
          */
          if(vyzadovan == true && !value){

            if(debug_mode == true){
              console.log("Chyba při kontrole. Input " + jmeno + " není vyplněn");
            }

            inputs[input_iteration] = false;
            $(this).addClass('wrong-input');
          }else{
            if(!validuj_input(value, druh)){

              if(debug_mode == true){
                console.log("Chyba při kontrole. Input " + jmeno + " neprošel validací");
              }

              input_objekt.addClass('wrong-input');
              inputs[input_iteration] = false;
            }else{
              inputs[input_iteration] = true;
            }
          }
        }
        input_iteration++;
      });

      for(var i = 0; i <= inputs.length; i++){
        if(inputs[i] == false){
          validni = false;
          break;
        }
      }

      if(validni == true){ // Zjištění, zda je formulář validní
        if(debug_mode == true){
          console.log('Formulář ' + parent_form.attr('id') + " je validní a lze jej odeslat.");
        }
        if(send_form == true){
          parent_form.submit();
        }
      }else {
        if(debug_mode == true){
          console.log('Formulář ' + parent_form.attr('id') + " není validní.");
        }
      }
    });
});
<commit_msg>Dynamic wrong input<commit_after>$(document).ready(function(){

  /*
  Konfigurační proměnné pro debug
  */
  var debug_mode = false; // TRUE -> Bude vypisovat průběh scriptu do konzole (např. chyby při kontrolách inputů, druhy inputů atp.)
  var send_form = true; // Má se po dokončení skriptu ( a pokud jsou všechny inputy validní ) odeslat formulář ? ( Pro účely debugu, pokud nechceme spamovat emailovou schránku příjemce )

  /*
  Další konfigurační proměnné
  */
  var add_id_to_all_forms = true;
  var wrong_input_class_name = "wrong-input";

  /*
    Funkce, která ve stránce vyhledá všechny formuláře a každému přidělí id.
    Pokud formulář už id má, funkce id nezmění a ponechá původní
    */
    function add_id_to_all_forms(){
      var iteration = 1; // Formuláře počítáme od 1
      var id_text = ""; // Pomocná proměnná

      $("form").each(function(){
        id_text = "form_" + iteration; // Připravený text pro formulář
        if(!$(this).attr('id')){ // Pokud formulř nemá svoje ID
          $(this).attr('id',id_text); // Přidělí se automaticky vygenerované
          iteration++;
        }
      });
    }

    if(add_id_to_all_forms == true){
      add_id_to_all_forms(); // Pustíme funkci pro všechny formuláře
    }

    /*
    ------ Pravidla pro formuláře: ------

    Dostupné inputy:
    -------------------------------------
    Název (pravidlo) - (příklad)
    -------------------------------------
    telefon (čísla, +, mezery) - (+123 123 456 789)
    jmeno (velká + malá písmena, mezery) - (Jan Někdo Něco)
    email (text, tečka, čísla @ text . text) - (nekdo.neco23@domana.com)
    datum (2 čísla <= 31 . 2 čísla <= 12 . 4 čísla <= aktualní rok) - (13.10.1856)
    text (nekontrolování)
    .... možné další
    */

    /*
    Funkce pro validaci inputu.
    Vrací
      -> True: Validní
      -> False: Nevalidní
    */
    function validuj_input(value, type){
      if(type == "telefon"){
        upraveny_string = value.replace(/\s+/g, ''); // Pro zjednodušení výrazů nahradíme případné mezery v tel. čísle

        if(debug_mode == true){
          console.log('Upravené číslo je: ' + upraveny_string);
        }
        var key = /^(\+420)?[0-9]{9}$/;

        if(key.test(upraveny_string)) {
          return true;
        }else {
          return false;
        }
      }else if (type == "jmeno") {
        var key = /^\D{1,} \D{1,}$/;

        if(key.test(value)) {
          return true;
        }else {
          return false;
        }
      }else if (type == "email") {
        var key = /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$/;

        if(key.test(value)) {
          return true;
        }else {
          return false;
        }
      }else if (type == "datum") {
        var key = /^\d{1,2}.\d{1,2}.\d{4}$/;

        if(key.test(value)) {
          return true;
        }else {
          return false;
        }
      }else if (type == "text") {
        return true;
      }else if (type == "soubor") {
        // Musí být nahrán soubor
      }else if (type == "checkbox") {
        // Checkbox
      }else {
        // Není vyplněno nebo má neznámý typ, vracíme false
        return false;
      }
    }

    $(".form-validate").on('click',function(e){ // Při kliknutí na tlačítko s třídou form-validate
      e.preventDefault();

      /*
      Pole, které obsahuje správnost jednotlivých inputů
      */
      var inputs = Array();

      var validni = true;
      /*
      Proměnná, který definuje, zda je formulář validní
      Formulář je na začátku nastaven na true (validní). Pokud se během kontroly objeví chyba,
      je nastaven na false (nevalidní) a označí se input, který chybu vyvolal.
      */

      var parent_form = $(this).closest('form'); // Vybereme rodičovský formlulář

      var form_object_selector = "form#" + parent_form.attr('id') + " :input"; // Sestavíme selektor pro výběr inputů formuláře

      var input_iteration = 0; // Nastavení čísla pro průchod všemi inputy a následného zanesení do pole inputs

      $(form_object_selector).each(function(){
        var input_objekt = $(this); // Nastavení objektu

        input_objekt.removeClass(wrong_input_class_name); // Před validací odstraníme classu wrong input ze všech inputů ve formuláři

        /*
        Proměnné potřebné pro práci s inputem
        */
        var value = input_objekt.val(); // Hodnota (obsah) inputu
        var jmeno = input_objekt.attr('name'); // Zjištění jména inputu
        var druh = input_objekt.data('typ'); // Zjištění o jaký druh inputu se jedná
        var vyzadovan = false; // Zjištění, zda input musí být vyplněn (defaultně nastaveno na false -> není vyžadován)
        var slovo = ""; // Pomocná proměnné pro výpis do konzole

        if(input_objekt.prop('required')){ // Zjištěné, jestli má u sebe input atribut requiered
          vyzadovan = true; // Má u sebe napsáno reqired
          slovo = " je ";
        } else {
          vyzadovan = false; // Nemá u sebe napsáno reqired
          slovo = " není ";
        }
        /*
        Debugovací výpis jednotlivých položek formuláře (druh a jestli jsou requiered)
        */
        if(debug_mode == true){
          console.log("Input " + jmeno + " je druh " + druh + " a" + slovo + "vyžadováno vyplnění.");
        }

        /*
        Pokud input nemá nastavený druh, bude se považovat za validní
        */
        if(!druh){

          inputs[input_iteration] = true;

        }else{

          /*
          Pokud má input nastaveno reqired a není vyplněn, bude se s
          ním počítat jako s nevalidním
          */
          if(vyzadovan == true && !value){

            if(debug_mode == true){
              console.log("Chyba při kontrole. Input " + jmeno + " není vyplněn");
            }

            inputs[input_iteration] = false;
            $(this).addClass('wrong-input');
          }else{
            if(!validuj_input(value, druh)){

              if(debug_mode == true){
                console.log("Chyba při kontrole. Input " + jmeno + " neprošel validací");
              }

              input_objekt.addClass('wrong-input');
              inputs[input_iteration] = false;
            }else{
              inputs[input_iteration] = true;
            }
          }
        }
        input_iteration++;
      });

      for(var i = 0; i <= inputs.length; i++){
        if(inputs[i] == false){
          validni = false;
          break;
        }
      }

      if(validni == true){ // Zjištění, zda je formulář validní
        if(debug_mode == true){
          console.log('Formulář ' + parent_form.attr('id') + " je validní a lze jej odeslat.");
        }
        if(send_form == true){
          parent_form.submit();
        }
      }else {
        if(debug_mode == true){
          console.log('Formulář ' + parent_form.attr('id') + " není validní.");
        }
      }
    });
});
<|endoftext|>


<commit_before>describe('$meteorCollection service', function() {
  var $meteorCollection,
      MyCollection,
      $rootScope,
      $timeout,
      meteorArray,
      testObjects = [
      {
        'a' : 1,
        'b' : 2
      },
      {
        'a' : 3,
        'b' : 4
      },
      {
        'a' : 5,
        'b' : 6
      }
      ];

  beforeEach(angular.mock.module('angular-meteor'));

  beforeEach(function() {
    jasmine.addMatchers(customMatchers);
  });

  beforeEach(angular.mock.inject(function(_$meteorCollection_, _$rootScope_, _$timeout_) {
    $meteorCollection = _$meteorCollection_;
    $rootScope = _$rootScope_;
    $timeout = _$timeout_;

    spyOn($rootScope, '$apply').and.callThrough();

    MyCollection = new Mongo.Collection();
    MyCollection.insert(testObjects[0]);
    MyCollection.insert(testObjects[1]);
    MyCollection.insert(testObjects[2]);

    meteorArray = $meteorCollection(MyCollection, false);
  }));

  describe('initialisation', function() {
    it('should return an array with all items in the Mongo Collection', function() {
      // assert
      expect(meteorArray).toEqualCollection(MyCollection);
    });
  });

  describe('collection updates', function() {
    it('should update the array when a new item is inserted into the collection', function() {
      // act
      MyCollection.insert({ a : '7', b: '8'});

      // assert
      expect(meteorArray).toEqualCollection(MyCollection);
      expect($rootScope.$apply).toHaveBeenCalled();
    });

    it('should update the array when an item is updated in the collection', function() {
      // arrange
      var anyItem = MyCollection.findOne({});

      // act
      MyCollection.update({ _id : anyItem._id }, { a : '7', b : '8'});

      // assert
      expect(meteorArray).toEqualCollection(MyCollection);
      expect($rootScope.$apply).toHaveBeenCalled();
    });

    it('should update the array when an item is removed from the collection', function() {
      // arrange
      var anyItem = MyCollection.findOne({});

      // act
      MyCollection.remove({ _id : anyItem._id });

      // assert
      expect(meteorArray).toEqualCollection(MyCollection);
      expect($rootScope.$apply).toHaveBeenCalled();
    });

    it('should update the array when multiple changed occur on the collection', function() {
      // arrange
      var anyItem = MyCollection.findOne({});
      var anotherItem = MyCollection.findOne({a : '1'});

      // act
      MyCollection.remove({ _id : anyItem._id });
      MyCollection.update({ _id : anotherItem }, { $set : { b : '100 '}});
      MyCollection.insert({ a : '7', b : '8'});

      // assert
      expect(meteorArray).toEqualCollection(MyCollection);
      expect($rootScope.$apply).toHaveBeenCalled();
    });
  });

  describe('reactive cursor', function() {
    it('should update the array when the reactive cursor function recomputes', function() {
      var dependency = new Tracker.Dependency();
      var reactiveFunc = jasmine.createSpy().and.callFake(function() {
        dependency.depend();
        return MyCollection.find({});
      });
      var meteorReactiveArray = $meteorCollection(reactiveFunc);

      MyCollection.update({ a: 1}, { a : 6});
      dependency.changed();
      Tracker.flush();

      expect(reactiveFunc).toHaveBeenCalled();
      expect(meteorReactiveArray).toEqualCollection(MyCollection);
    });
  });

  describe('autobind on', function() {
    beforeEach(function() {
      meteorArray = $meteorCollection(MyCollection);
      $timeout.flush();
    });

    it('should update the collection when a new item is pushed into the array', function() {
      // act
      meteorArray.push({
        a : '7',
        b: '8'
      });
      $rootScope.$apply();

      // assert
      expect(meteorArray).toEqualCollection(MyCollection);
    });

    it('should update the collection when an item is changed in the array', function() {
      // act
      meteorArray[0].a = 888;

      $rootScope.$apply();

      // assert
      expect(meteorArray).toEqualCollection(MyCollection);
    });

    it('should update the collection when an item is removed from the array', function() {
      // act
      meteorArray.splice(0, 1);

      $rootScope.$apply();

      // assert
      expect(meteorArray).toEqualCollection(MyCollection);
    });
  });

  describe('observe server changes', function() {

    beforeEach(function() {
      // Cleaning up after each test.
      bigCollection.find({}).forEach(function(doc) {
        bigCollection.remove(doc._id);
      });

      for (var i = 0; i < 100; i++) {
        bigCollection.insert({count: i});
      }
    });

    it('$apply executed twice', function() {

      var $ngCol = $meteorCollection(bigCollection);

      expect($rootScope.$apply).toHaveBeenCalled();

      expect($ngCol).toEqualCollection(bigCollection);

      // No matter how much elements MyCollection contains
      // $rootScope.$apply should be called twice.
      expect($rootScope.$apply.calls.count()).toEqual(2);

    });

    it('push updates from client handled correctly', function() {

      $rootScope.limit = 10;
      var $ngCol = $meteorCollection(function() {
        return bigCollection.find({}, {
          limit: $rootScope.getReactively('limit')
        });
      });
      spyOn($ngCol, 'save');

      // Adds docs on the client.
      for (var i = 100; i < 106; i++) {
        $ngCol.push({count: i});
      }

      $timeout.flush();

      expect($ngCol.length).toEqual(10);
      expect($ngCol.save).toHaveBeenCalled();
      expect($ngCol.save.calls.count()).toEqual(6);
    });

    it('remove updates from client handled correctly', function() {

      $rootScope.limit = 10;
      var $ngCol = $meteorCollection(function() {
        return bigCollection.find({}, {
          limit: $rootScope.getReactively('limit')
        });
      });
      spyOn($ngCol, 'remove').and.callThrough();

      // Removes last two docs on the client.
      $ngCol.pop();
      $ngCol.pop();

      $timeout.flush();

      // Size should stay at 10 after removing because
      // limit is set.
      expect($ngCol.length).toEqual(10);
      expect($ngCol.remove).toHaveBeenCalled();
      expect($ngCol.remove.calls.count()).toEqual(2);
    });

  });

  describe('objects with $$hashkey', function() {
    it('should be saved to the collection when save is called', function(done) {
      // add haskeys to objects in the array
      meteorArray.forEach(function(item, index) {
        item.$$hashKey = index;
      });

      var itemChanged = meteorArray[0];
      itemChanged.a = 444;

      meteorArray.save().then(function() {
        expect({a : 444, b: 2}).toBeFoundExactlyInCollection(MyCollection);
        done();
      }, function() {
        done();
      });
    });

    it('should save objects with nested $$haskey fields when save is called', function(done) {
      meteorArray.save(itemWithNestedHashkeyArrays).then(function() {
        expect(itemWithNestedHashkeysRemoved).toBeFoundExactlyInCollection(MyCollection);
        done();
      }, function() {
        done();
      });
    });
  });
});
<commit_msg>(testing): add more tests for auto bind off on meteor collection<commit_after>describe('$meteorCollection service', function() {
  var $meteorCollection,
      MyCollection,
      $rootScope,
      $timeout,
      meteorArray,
      testObjects = [
      {
        'a' : 1,
        'b' : 2
      },
      {
        'a' : 3,
        'b' : 4
      },
      {
        'a' : 5,
        'b' : 6
      }
      ];

  beforeEach(angular.mock.module('angular-meteor'));

  beforeEach(function() {
    jasmine.addMatchers(customMatchers);
  });

  beforeEach(angular.mock.inject(function(_$meteorCollection_, _$rootScope_, _$timeout_) {
    $meteorCollection = _$meteorCollection_;
    $rootScope = _$rootScope_;
    $timeout = _$timeout_;

    spyOn($rootScope, '$apply').and.callThrough();

    MyCollection = new Mongo.Collection();
    MyCollection.insert(testObjects[0]);
    MyCollection.insert(testObjects[1]);
    MyCollection.insert(testObjects[2]);

    meteorArray = $meteorCollection(MyCollection, false);
  }));

  describe('initialisation', function() {
    it('should return an array with all items in the Mongo Collection', function() {
      // assert
      expect(meteorArray).toEqualCollection(MyCollection);
    });
  });

  describe('collection updates', function() {
    it('should update the array when a new item is inserted into the collection', function() {
      // act
      MyCollection.insert({ a : '7', b: '8'});

      // assert
      expect(meteorArray).toEqualCollection(MyCollection);
      expect($rootScope.$apply).toHaveBeenCalled();
    });

    it('should update the array when an item is updated in the collection', function() {
      // arrange
      var anyItem = MyCollection.findOne({});

      // act
      MyCollection.update({ _id : anyItem._id }, { a : '7', b : '8'});

      // assert
      expect(meteorArray).toEqualCollection(MyCollection);
      expect($rootScope.$apply).toHaveBeenCalled();
    });

    it('should update the array when an item is removed from the collection', function() {
      // arrange
      var anyItem = MyCollection.findOne({});

      // act
      MyCollection.remove({ _id : anyItem._id });

      // assert
      expect(meteorArray).toEqualCollection(MyCollection);
      expect($rootScope.$apply).toHaveBeenCalled();
    });

    it('should update the array when multiple changed occur on the collection', function() {
      // arrange
      var anyItem = MyCollection.findOne({});
      var anotherItem = MyCollection.findOne({a : '1'});

      // act
      MyCollection.remove({ _id : anyItem._id });
      MyCollection.update({ _id : anotherItem }, { $set : { b : '100 '}});
      MyCollection.insert({ a : '7', b : '8'});

      // assert
      expect(meteorArray).toEqualCollection(MyCollection);
      expect($rootScope.$apply).toHaveBeenCalled();
    });
  });

  describe('reactive cursor', function() {
    it('should update the array when the reactive cursor function recomputes', function() {
      var dependency = new Tracker.Dependency();
      var reactiveFunc = jasmine.createSpy().and.callFake(function() {
        dependency.depend();
        return MyCollection.find({});
      });
      var meteorReactiveArray = $meteorCollection(reactiveFunc);

      MyCollection.update({ a: 1}, { a : 6});
      dependency.changed();
      Tracker.flush();

      expect(reactiveFunc).toHaveBeenCalled();
      expect(meteorReactiveArray).toEqualCollection(MyCollection);
    });
  });

  describe('autobind off', function() {
    var notAutoArray;

    beforeEach(function() {
      notAutoArray = $meteorCollection(MyCollection, false);
    });

    it('should not update the collection when a changes a happens to the array', function() {
      var newItem = {
        a : 10,
        b : 11
      };
      notAutoArray.push(newItem);

      $rootScope.$apply();

      expect(newItem).not.toBeFoundExactlyInCollection(MyCollection);
    });

    it('should not save an item from the server when an item is received from the server', function() {
      var saveSpy = spyOn(notAutoArray, 'save').and.callThrough();

      MyCollection.insert({
        a : 10,
        b : 11
      });

      $timeout.flush();

      expect(saveSpy).not.toHaveBeenCalled();
      expect(notAutoArray).toEqualCollection(MyCollection);
    });
  });

  describe('autobind on', function() {
    beforeEach(function() {
      meteorArray = $meteorCollection(MyCollection);
      $timeout.flush();
    });

    it('should update the collection when a new item is pushed into the array', function() {
      // act
      meteorArray.push({
        a : '7',
        b: '8'
      });
      $rootScope.$apply();

      // assert
      expect(meteorArray).toEqualCollection(MyCollection);
    });

    it('should update the collection when an item is changed in the array', function() {
      // act
      meteorArray[0].a = 888;

      $rootScope.$apply();

      // assert
      expect(meteorArray).toEqualCollection(MyCollection);
    });

    it('should update the collection when an item is removed from the array', function() {
      // act
      meteorArray.splice(0, 1);

      $rootScope.$apply();

      // assert
      expect(meteorArray).toEqualCollection(MyCollection);
    });
  });

  describe('observe server changes', function() {

    beforeEach(function() {
      // Cleaning up after each test.
      bigCollection.find({}).forEach(function(doc) {
        bigCollection.remove(doc._id);
      });

      for (var i = 0; i < 100; i++) {
        bigCollection.insert({count: i});
      }
    });

    it('$apply executed twice', function() {

      var $ngCol = $meteorCollection(bigCollection);

      expect($rootScope.$apply).toHaveBeenCalled();

      expect($ngCol).toEqualCollection(bigCollection);

      // No matter how much elements MyCollection contains
      // $rootScope.$apply should be called twice.
      expect($rootScope.$apply.calls.count()).toEqual(2);

    });

    it('push updates from client handled correctly', function() {

      $rootScope.limit = 10;
      var $ngCol = $meteorCollection(function() {
        return bigCollection.find({}, {
          limit: $rootScope.getReactively('limit')
        });
      });
      spyOn($ngCol, 'save');

      // Adds docs on the client.
      for (var i = 100; i < 106; i++) {
        $ngCol.push({count: i});
      }

      $timeout.flush();

      expect($ngCol.length).toEqual(10);
      expect($ngCol.save).toHaveBeenCalled();
      expect($ngCol.save.calls.count()).toEqual(6);
    });

    it('remove updates from client handled correctly', function() {

      $rootScope.limit = 10;
      var $ngCol = $meteorCollection(function() {
        return bigCollection.find({}, {
          limit: $rootScope.getReactively('limit')
        });
      });
      spyOn($ngCol, 'remove').and.callThrough();

      // Removes last two docs on the client.
      $ngCol.pop();
      $ngCol.pop();

      $timeout.flush();

      // Size should stay at 10 after removing because
      // limit is set.
      expect($ngCol.length).toEqual(10);
      expect($ngCol.remove).toHaveBeenCalled();
      expect($ngCol.remove.calls.count()).toEqual(2);
    });

  });

  describe('objects with $$hashkey', function() {
    it('should be saved to the collection when save is called', function(done) {
      // add haskeys to objects in the array
      meteorArray.forEach(function(item, index) {
        item.$$hashKey = index;
      });

      var itemChanged = meteorArray[0];
      itemChanged.a = 444;

      meteorArray.save().then(function() {
        expect({a : 444, b: 2}).toBeFoundExactlyInCollection(MyCollection);
        done();
      }, function() {
        done();
      });
    });

    it('should save objects with nested $$haskey fields when save is called', function(done) {
      meteorArray.save(itemWithNestedHashkeyArrays).then(function() {
        expect(itemWithNestedHashkeysRemoved).toBeFoundExactlyInCollection(MyCollection);
        done();
      }, function() {
        done();
      });
    });
  });
});
<|endoftext|>


<commit_before>// System Modules
import fs from              'fs';
import gulp from            'gulp';
import {argv} from          'yargs';
import {exec} from          'child_process';
import eslint from          'gulp-eslint';
import jscs from            'gulp-jscs';
import {Instrumenter} from  'isparta';
import mocha from           'gulp-mocha';
import istanbul from        'gulp-istanbul';
import cobertura from       'istanbul-cobertura-badger';
import esdoc from           'gulp-esdoc';
import babel from           'gulp-babel';
import {bold, red} from     'chalk';

const bread = (str) => bold(red(str));

const SRC_DIR = 'src',
    SRC = `${SRC}/**/*.js`,
    TRANSPILED_SRC = 'dist',
    TEST_SRC = 'test/**/*.spec.js',
    DOC_SRC = 'doc',
    COVERAGE_SRC = 'coverage';

// Build Tasks
gulp.task('eslint', function () {
    gulp.src([ SRC, TEST_SRC ]).pipe(
        eslint()
    ).pipe(
        eslint.format()
    ).pipe(
        eslint.failOnError()
    );
});
gulp.task('jscs', [ 'eslint' ], function () {
    return gulp.src([ SRC, TEST_SRC ])
        .pipe(jscs({
            fix: true,
            configPath: '.jscsrc',
            esnext: true
        }));
});
gulp.task('istanbul', [ 'jscs' ], function(cb) {
    gulp.src('src/**/*.js').pipe(istanbul({
        instrumenter: Instrumenter,
        includeUntested: true,
        babel: {
            stage: 0
        }
    })).pipe(istanbul.hookRequire()).on('finish', cb);
});
gulp.task('mocha', [ 'istanbul' ], function() {
    return gulp.src([
        'test/src/testUtil.spec.js',
        'test/**/*.spec.js'
    ]).pipe(mocha({
        reporter: 'spec'
    })).pipe(istanbul.writeReports({
        dir: 'coverage',
        reportOpts: {
            dir: 'coverage'
        },
        reporters: [ 'text', 'text-summary', 'html', 'cobertura' ]
    }));
});
gulp.task('cobertura', [ 'mocha' ], function(cb) {
    cobertura('coverage/cobertura-coverage.xml', 'svg', cb);
});
gulp.task('esdoc', [ 'cobertura' ], function() {
    return gulp.src(SRC_DIR).pipe(esdoc({ destination: DOC_SRC }));
});
gulp.task('babel', [ 'esdoc' ], function() {
    return gulp.src('src/**').pipe(babel({
        comments: false
    })).pipe(gulp.dest('dist'));
});

// Bundled Tasks
gulp.task('test', [ 'mocha' ]);
gulp.task('watch', [ 'test' ], function() {
    gulp.watch([ SRC, TEST_SRC ], [ 'test' ]);
});
gulp.task('default', [ 'babel' ]);

// Utility Tasks
gulp.task('bump', function() {
    const version = argv.version,
        bump = (f) => fs.writeFileSync(f, fs.readFileSync(f, 'utf8').replace(
            /[0-9]{1,2}\.[0-9]{1,2}\.[0-9]{1,2}/,
            version
        ));
    if (version) {

        // Verify that the version is in the CHANGELOG
        if (fs.readFileSync('.CHANGELOG.md', 'utf8').indexOf(version) === -1) {
            throw new Error(bread('Version has no entry in CHANGELOG.md'));
        }

        bump('bin/angie-template');
        bump('bin/angie-template-dist');
        bump('package.json');
    } else {
        throw new Error(bread('No version specified!!'));
    }
});<commit_msg>Removed exec<commit_after>// System Modules
import fs from              'fs';
import gulp from            'gulp';
import {argv} from          'yargs';
import eslint from          'gulp-eslint';
import jscs from            'gulp-jscs';
import {Instrumenter} from  'isparta';
import mocha from           'gulp-mocha';
import istanbul from        'gulp-istanbul';
import cobertura from       'istanbul-cobertura-badger';
import esdoc from           'gulp-esdoc';
import babel from           'gulp-babel';
import {bold, red} from     'chalk';

const bread = (str) => bold(red(str));

const SRC_DIR = 'src',
    SRC = `${SRC}/**/*.js`,
    TRANSPILED_SRC = 'dist',
    TEST_SRC = 'test/**/*.spec.js',
    DOC_SRC = 'doc',
    COVERAGE_SRC = 'coverage';

// Build Tasks
gulp.task('eslint', function () {
    gulp.src([ SRC, TEST_SRC ]).pipe(
        eslint()
    ).pipe(
        eslint.format()
    ).pipe(
        eslint.failOnError()
    );
});
gulp.task('jscs', [ 'eslint' ], function () {
    return gulp.src([ SRC, TEST_SRC ])
        .pipe(jscs({
            fix: true,
            configPath: '.jscsrc',
            esnext: true
        }));
});
gulp.task('istanbul', [ 'jscs' ], function(cb) {
    gulp.src('src/**/*.js').pipe(istanbul({
        instrumenter: Instrumenter,
        includeUntested: true,
        babel: {
            stage: 0
        }
    })).pipe(istanbul.hookRequire()).on('finish', cb);
});
gulp.task('mocha', [ 'istanbul' ], function() {
    return gulp.src([
        'test/src/testUtil.spec.js',
        'test/**/*.spec.js'
    ]).pipe(mocha({
        reporter: 'spec'
    })).pipe(istanbul.writeReports({
        dir: 'coverage',
        reportOpts: {
            dir: 'coverage'
        },
        reporters: [ 'text', 'text-summary', 'html', 'cobertura' ]
    }));
});
gulp.task('cobertura', [ 'mocha' ], function(cb) {
    cobertura('coverage/cobertura-coverage.xml', 'svg', cb);
});
gulp.task('esdoc', [ 'cobertura' ], function() {
    return gulp.src(SRC_DIR).pipe(esdoc({ destination: DOC_SRC }));
});
gulp.task('babel', [ 'esdoc' ], function() {
    return gulp.src('src/**').pipe(babel({
        comments: false
    })).pipe(gulp.dest('dist'));
});

// Bundled Tasks
gulp.task('test', [ 'mocha' ]);
gulp.task('watch', [ 'test' ], function() {
    gulp.watch([ SRC, TEST_SRC ], [ 'test' ]);
});
gulp.task('default', [ 'babel' ]);

// Utility Tasks
gulp.task('bump', function() {
    const version = argv.version,
        bump = (f) => fs.writeFileSync(f, fs.readFileSync(f, 'utf8').replace(
            /[0-9]{1,2}\.[0-9]{1,2}\.[0-9]{1,2}/,
            version
        ));
    if (version) {

        // Verify that the version is in the CHANGELOG
        if (fs.readFileSync('.CHANGELOG.md', 'utf8').indexOf(version) === -1) {
            throw new Error(bread('Version has no entry in CHANGELOG.md'));
        }

        bump('bin/angie-template');
        bump('bin/angie-template-dist');
        bump('package.json');
    } else {
        throw new Error(bread('No version specified!!'));
    }
});<|endoftext|>


<commit_before>/*
 * Copyright (C) 2017 Steven Soloff
 *
 * This software is licensed under the terms of the GNU Affero General Public
 * License version 3 or later (https://www.gnu.org/licenses/).
 */

import babel from 'gulp-babel'
import coveralls from 'gulp-coveralls'
import del from 'del'
import eslint from 'gulp-eslint'
import excludeGitignore from 'gulp-exclude-gitignore'
import gulp from 'gulp'
import istanbul from 'gulp-babel-istanbul'
import jasmine from 'gulp-jasmine'
import nsp from 'gulp-nsp'
import path from 'path'

const dirs = {
  coverage: 'coverage',
  dist: 'dist',
  js: {
    main: 'lib',
    test: 'spec'
  }
}
const paths = {
  js: {
    all: '**/*.js',
    main: `${dirs.js.main}/**/*.js`,
    test: `${dirs.js.test}/**/*.js`
  }
}

function beforeTest () {
  return gulp.src(paths.js.main)
    .pipe(excludeGitignore())
    .pipe(istanbul({
      includeUntested: true
    }))
    .pipe(istanbul.hookRequire())
}

export function checkSecurity (done) {
  nsp({package: path.resolve('package.json')}, done)
}

export function clean () {
  return del([dirs.coverage, dirs.dist])
}

export function compile () {
  return gulp.src(paths.js.main)
    .pipe(babel())
    .pipe(gulp.dest(dirs.dist))
}

function coreTest () {
  return gulp.src(paths.js.test)
    .pipe(jasmine({
      verbose: true
    }))
    .pipe(istanbul.writeReports())
    .pipe(istanbul.enforceThresholds({
      thresholds: {
        global: 90
      }
    }))
}

export function lint () {
  return gulp.src(paths.js.all)
    .pipe(excludeGitignore())
    .pipe(eslint())
    .pipe(eslint.format())
    .pipe(eslint.failAfterError())
}

export function publishCoverage () {
  if (!process.env.CI) {
    return Promise.resolve()
  }

  return gulp.src(path.join(__dirname, `${dirs.coverage}/lcov.info`))
    .pipe(coveralls())
}

export function test (...args) {
  return gulp.series(beforeTest, coreTest)(...args)
}

export function watch () {
  gulp.watch([paths.js.main, paths.js.test], gulp.parallel(test))
    .on('error', () => {}) // ignore errors during watch so Gulp does not exit
}

gulp.task('default', gulp.parallel(lint, test))

gulp.task('prepublish', gulp.series(clean, gulp.parallel(checkSecurity, compile)))
<commit_msg>Restore test task as a named task rather than as a named function<commit_after>/*
 * Copyright (C) 2017 Steven Soloff
 *
 * This software is licensed under the terms of the GNU Affero General Public
 * License version 3 or later (https://www.gnu.org/licenses/).
 */

import babel from 'gulp-babel'
import coveralls from 'gulp-coveralls'
import del from 'del'
import eslint from 'gulp-eslint'
import excludeGitignore from 'gulp-exclude-gitignore'
import gulp from 'gulp'
import istanbul from 'gulp-babel-istanbul'
import jasmine from 'gulp-jasmine'
import nsp from 'gulp-nsp'
import path from 'path'

const dirs = {
  coverage: 'coverage',
  dist: 'dist',
  js: {
    main: 'lib',
    test: 'spec'
  }
}
const paths = {
  js: {
    all: '**/*.js',
    main: `${dirs.js.main}/**/*.js`,
    test: `${dirs.js.test}/**/*.js`
  }
}

function beforeTest () {
  return gulp.src(paths.js.main)
    .pipe(excludeGitignore())
    .pipe(istanbul({
      includeUntested: true
    }))
    .pipe(istanbul.hookRequire())
}

export function checkSecurity (done) {
  nsp({package: path.resolve('package.json')}, done)
}

export function clean () {
  return del([dirs.coverage, dirs.dist])
}

export function compile () {
  return gulp.src(paths.js.main)
    .pipe(babel())
    .pipe(gulp.dest(dirs.dist))
}

function coreTest () {
  return gulp.src(paths.js.test)
    .pipe(jasmine({
      verbose: true
    }))
    .pipe(istanbul.writeReports())
    .pipe(istanbul.enforceThresholds({
      thresholds: {
        global: 90
      }
    }))
}

export function lint () {
  return gulp.src(paths.js.all)
    .pipe(excludeGitignore())
    .pipe(eslint())
    .pipe(eslint.format())
    .pipe(eslint.failAfterError())
}

export function publishCoverage () {
  if (!process.env.CI) {
    return Promise.resolve()
  }

  return gulp.src(path.join(__dirname, `${dirs.coverage}/lcov.info`))
    .pipe(coveralls())
}

export function watch () {
  gulp.watch([paths.js.main, paths.js.test], gulp.parallel('test'))
    .on('error', () => {}) // ignore errors during watch so Gulp does not exit
}

gulp.task('prepublish', gulp.series(clean, gulp.parallel(checkSecurity, compile)))

gulp.task('test', gulp.series(beforeTest, coreTest))

gulp.task('default', gulp.parallel(lint, 'test'))
<|endoftext|>


<commit_before>import gulp from 'gulp'
import webpack from 'webpack'
import minimist from 'minimist'
import childProcess from 'child_process'

import config from './webpack.config.js'
const argv = minimist(process.argv.slice(2))
const watch = !! argv.watch

// Bundle Application
gulp.task('bundle', (callback) => {
  const bundler = webpack(config)

  let count = 0
  function bundle(error, stats) {
    if (error) throw new error

    console.log(stats.toString({
      hash: !! argv.verbose,
      colors: true,
      version: !! argv.verbose,
      timings: !! argv.verbose,
      chunks: !! argv.verbose,
      chunkModules: !! argv.verbose,
      cached: !! argv.verbose,
      cachedAssets: !! argv.verbose
    }))

    if (++count === (watch ? config.length : 1)) {
      callback()
    }
  }

  if (watch) {
    bundler.watch(200, bundle)
  } else {
    bundler.run(bundle)
  }
})

// Move Static Files
gulp.task('assets', () => {
  return gulp.src([
      'src/public*/**',
      'src/views*/**'
    ])
    .pipe(gulp.dest('dist'))
})

// Build Application
gulp.task('build', ['bundle', 'assets'])

// Watch Application
gulp.task('watch', ['assets', 'bundle'], () => {
  gulp.watch([
      'src/public*/**',
      'src/views*/**'
    ], ['assets'])
})

// Serve Application
gulp.task('serve', ['watch'], () => {
  let jobs = {}
  Promise.all([
    new Promise((resolve) => {
      let started = false
      jobs.server = (function startup() {
        let child = childProcess.fork('dist/server.js')
          .once('message', (message) => {
            if (message.match(/^online$/) && ! started) {
              started = true
              gulp.watch('dist/server.js', () => {
                jobs.server.kill('SIGTERM')
                jobs.server = startup()
              })
              resolve()
            }
          })
        return child
      })()
    })
  ])

  process.on('exit', () => {
    jobs.server.kill('SIGTERM')
  })
})
<commit_msg>Chore: Add gulp default task<commit_after>import gulp from 'gulp'
import webpack from 'webpack'
import minimist from 'minimist'
import childProcess from 'child_process'

import config from './webpack.config.js'
const argv = minimist(process.argv.slice(2))
const watch = !! argv.watch

gulp.task('default', ['serve'])

// Bundle Application
gulp.task('bundle', (callback) => {
  const bundler = webpack(config)

  let count = 0
  function bundle(error, stats) {
    if (error) throw new error

    console.log(stats.toString({
      hash: !! argv.verbose,
      colors: true,
      version: !! argv.verbose,
      timings: !! argv.verbose,
      chunks: !! argv.verbose,
      chunkModules: !! argv.verbose,
      cached: !! argv.verbose,
      cachedAssets: !! argv.verbose
    }))

    if (++count === (watch ? config.length : 1)) {
      callback()
    }
  }

  if (watch) {
    bundler.watch(200, bundle)
  } else {
    bundler.run(bundle)
  }
})

// Move Static Files
gulp.task('assets', () => {
  return gulp.src([
      'src/public*/**',
      'src/views*/**'
    ])
    .pipe(gulp.dest('dist'))
})

// Build Application
gulp.task('build', ['bundle', 'assets'])

// Watch Application
gulp.task('watch', ['assets', 'bundle'], () => {
  gulp.watch([
      'src/public*/**',
      'src/views*/**'
    ], ['assets'])
})

// Serve Application
gulp.task('serve', ['watch'], () => {
  let jobs = {}
  Promise.all([
    new Promise((resolve) => {
      let started = false
      jobs.server = (function startup() {
        let child = childProcess.fork('dist/server.js')
          .once('message', (message) => {
            if (message.match(/^online$/) && ! started) {
              started = true
              gulp.watch('dist/server.js', () => {
                jobs.server.kill('SIGTERM')
                jobs.server = startup()
              })
              resolve()
            }
          })
        return child
      })()
    })
  ])

  process.on('exit', () => {
    jobs.server.kill('SIGTERM')
  })
})
<|endoftext|>