<commit_before>/* jshint camelcase:false */
var gulp = require('gulp');
var pkg = require('./package.json');
var common = require('./gulp/common.js');
var karma = require('karma').server;
var plug = require('gulp-load-plugins')();
var env = plug.util.env;
var log = plug.util.log;

gulp.task('help', plug.taskListing);

/**
 * @desc Lint the code
 */
gulp.task('analyze', function() {
    analyze('./src/client/test/**/*.js', './src/client/test/.jshintrc');
  //  analyze([].concat(pkg.paths.js, pkg.paths.nodejs), './.jshintrc');
});

function analyze(sources, jshintrc) {
    log('Linting: ', sources);
    return gulp
        .src(sources)
        .pipe(plug.jshint(jshintrc))
        .pipe(plug.jshint.reporter('jshint-stylish'))
        .pipe(plug.jscs('./.jscsrc'));
}

/**
 * @desc Create $templateCache from the html templates
 */
gulp.task('templatecache', function() {
    log('Creating an AngularJS $templateCache');

    return gulp
        .src(pkg.paths.htmltemplates)
        .pipe(plug.angularTemplatecache('templates.js', {
            module: 'app.core',
            standalone: false,
            root: 'app/'
        }))
        .pipe(gulp.dest(pkg.paths.stage));
});

/**
 * @desc Minify and bundle the app's JavaScript
 */
gulp.task('js', ['analyze', 'templatecache'], function() {
    log('Bundling, minifying, and copying the app\'s JavaScript');

    var source = [].concat(pkg.paths.js, pkg.paths.stage + 'templates.js');
    return gulp
        .src(source)
       // .pipe(plug.sourcemaps.init()) // get screwed up in the file rev process
        .pipe(plug.concat('all.min.js'))
        .pipe(plug.ngAnnotate({add: true, single_quotes: true}))
        .pipe(plug.bytediff.start())
        .pipe(plug.uglify({mangle: true}))
        .pipe(plug.bytediff.stop(common.bytediffFormatter))
        // .pipe(plug.sourcemaps.write('./'))
        .pipe(gulp.dest(pkg.paths.stage));
});

/**
 * @desc Copy the Vendor JavaScript
 */
gulp.task('vendorjs', function() {
    log('Bundling, minifying, and copying the Vendor JavaScript');
    return gulp.src(pkg.paths.vendorjs)
        .pipe(plug.concat('vendor.min.js'))
        .pipe(plug.bytediff.start())
        .pipe(plug.uglify())
        .pipe(plug.bytediff.stop(common.bytediffFormatter))
        .pipe(gulp.dest(pkg.paths.stage)); // + 'vendor'));
});

/**
 * @desc Minify and bundle the CSS
 */
gulp.task('css', function() {
    log('Bundling, minifying, and copying the app\'s CSS');
    return gulp.src(pkg.paths.css)
        .pipe(plug.concat('all.min.css')) // Before bytediff or after
        .pipe(plug.autoprefixer('last 2 version', '> 5%'))
        .pipe(plug.bytediff.start())
        .pipe(plug.minifyCss({}))
        .pipe(plug.bytediff.stop(common.bytediffFormatter))
//        .pipe(plug.concat('all.min.css')) // Before bytediff or after
        .pipe(gulp.dest(pkg.paths.stage + 'content'));
});

/**
 * @desc Minify and bundle the Vendor CSS
 */
gulp.task('vendorcss', function() {
    log('Compressing, bundling, copying vendor CSS');
    return gulp.src(pkg.paths.vendorcss)
        .pipe(plug.concat('vendor.min.css'))
        .pipe(plug.bytediff.start())
        .pipe(plug.minifyCss({}))
        .pipe(plug.bytediff.stop(common.bytediffFormatter))
        .pipe(gulp.dest(pkg.paths.stage + 'content'));
});

/**
 * @desc Copy fonts
 */
gulp.task('fonts', function() {
    var dest = pkg.paths.stage + 'fonts';
    log('Copying fonts');
    return gulp
        .src(pkg.paths.fonts)
        .pipe(gulp.dest(dest));
});

/**
 * @desc Compress images
 */
gulp.task('images', function() {
    var dest = pkg.paths.stage + 'content/images';
    log('Compressing, caching, and copying images');
    return gulp
        .src(pkg.paths.images)
        .pipe(plug.cache(plug.imagemin({optimizationLevel: 3})))
        .pipe(gulp.dest(dest));
});

/**
 * @desc Inject all the files into the new index.html
 * rev, but no map
 */
gulp.task('rev-and-inject',
    ['js', 'vendorjs', 'css', 'vendorcss'], function() {
        log('Rev\'ing files and building index.html');

        var minified = pkg.paths.stage + '**/*.min.*';
        var index = pkg.paths.client + 'index.html';
        var minFilter = plug.filter(['**/*.min.*', '!**/*.map']);
        var indexFilter = plug.filter(['index.html']);

        var stream = gulp
            // Write the revisioned files
            .src([].concat(minified, index)) // add all staged min files and index.html
            .pipe(minFilter) // filter the stream to minified css and js
            .pipe(plug.rev()) // create files with rev's
            .pipe(gulp.dest(pkg.paths.stage)) // write the rev files
            .pipe(minFilter.restore()) // remove filter, back to original stream

            // inject the files into index.html
            .pipe(indexFilter) // filter to index.html
            .pipe(inject('content/vendor.min.css', 'inject-vendor'))
            .pipe(inject('content/all.min.css'))
            .pipe(inject('vendor.min.js', 'inject-vendor'))
            .pipe(inject('all.min.js'))
            .pipe(gulp.dest(pkg.paths.stage)) // write the rev files
            .pipe(indexFilter.restore()) // remove filter, back to original stream

            // replace the files referenced in index.html with the rev'd files
            .pipe(plug.revReplace())         // Substitute in new filenames
            .pipe(gulp.dest(pkg.paths.stage)) // write the index.html file changes
            .pipe(plug.rev.manifest()) // create the manifest (must happen last or we screw up the injection)
            .pipe(gulp.dest(pkg.paths.stage)); // write the manifest

        function inject(path, name) {
            var glob = pkg.paths.stage + path;
            var options = {
                ignorePath: pkg.paths.stage.substring(1),
                read: false
            };
            if (name) { options.name = name; }
            return plug.inject(gulp.src(glob), options);
        }
    });

/**
 * @desc Stage the optimized app
 */
gulp.task('stage',
    ['rev-and-inject', 'images', 'fonts'], function() {
        log('Staging the optimized app');

        return gulp.src('').pipe(plug.notify({
            onLast: true,
            message: 'Deployed code to stage!'
        }));
    });

/**
 * @desc Remove all files from the build folder
 * One way to run clean before all tasks is to run
 * from the cmd line: gulp clean && gulp stage
 */
gulp.task('clean', function() {
    var paths = pkg.paths.build;
    log('Cleaning: ' + plug.util.colors.blue(paths));

    return gulp
        .src(paths, {read: false})
        .pipe(plug.rimraf({force: true}));
});

/**
 * @desc Watch files and build
 */
gulp.task('watch', function() {
    log('Watching all files');

    var css = ['gulpfile.js'].concat(pkg.paths.css, pkg.paths.vendorcss);
    var images = ['gulpfile.js'].concat(pkg.paths.images);
    var js = ['gulpfile.js'].concat(pkg.paths.js);

    gulp
        .watch(js, ['js', 'vendorjs'])
        .on('change', logWatch);

    gulp
        .watch(css, ['css', 'vendorcss'])
        .on('change', logWatch);

    gulp
        .watch(images, ['images'])
        .on('change', logWatch);

    function logWatch(event) {
        log('*** File ' + event.path + ' was ' + event.type + ', running tasks...');
    }
});

/**
 * Run specs once and exit
 * To start servers and run midway specs as well:
 *    gulp test --startServers
 */
gulp.task('test', function (done) {
    testCore(true /*singleRun*/, done);
});

/**
 * Run specs and wait.
 * Watch for file changes and re-run tests on each change
 * To start servers and run midway specs as well:
 *    gulp autotest --startServers
 */
gulp.task('autotest', function (done) {
    testCore(false /*singleRun*/, done);
});

/**
 * serve the dev environment, with debug,
 * and with node inspector
 */
gulp.task('serve-dev-debug', function() {
    serve({mode: 'dev', debug: '--debug'});
    startLivereload('development');
});

/**
 * serve the dev environment, with debug-brk,
 * and with node inspector
 */
gulp.task('serve-dev-debug-brk', function() {
    serve({mode: 'dev', debug: '--debug-brk'});
    startLivereload('development');
});

/**
 * serve the dev environment
 */
gulp.task('serve-dev', function() {
    serve({mode: 'dev'});
    startLivereload('development');
});

/**
 * serve the staging environment
 */
gulp.task('serve-stage', function() {
    serve({mode: 'stage'});
    startLivereload('stage');
});

function startLivereload(mode) {
    if (!env.liveReload) { return; }

    var path = (env === 'stage' ? [pkg.paths.stage, pkg.paths.client + '/**'] : [pkg.paths.client + '/**']);
    var options = {auto: true};
    plug.livereload.listen(options);
    gulp.watch(path)
        .on('change', function(file) {
            plug.livereload.changed(file.path);
        });

    log('Serving from ' + mode);
}

function serve(args) {
    var options = {
        script: pkg.paths.server + 'app.js',
        delayTime: 1,
        ext: 'html js',
        env: {'NODE_ENV': args.mode},
        watch: [
            'gulpfile.js',
            'package.json',
            pkg.paths.server,
            pkg.paths.client
        ]
    };

    if (args.debug) {
        gulp.src('', {read: false})
            .pipe(plug.shell(['node-inspector']));
        options.nodeArgs = [args.debug + '=5858'];
    }

    return plug.nodemon(options)
        //.on('change', tasks)
        .on('restart', function() {
            log('restarted!');
        });
}

function testCore(singleRun, done) {
    var child;
    var excludeFiles = ['./src/client/app/**/*spaghetti.js'];
    var spawn = require('child_process').spawn;

    if (env.startServers) {
        log('Starting servers');
        var savedEnv = process.env;
        savedEnv.NODE_ENV = 'dev';
        savedEnv.PORT = 8888;
        child = spawn('node', ['src/server/app.js'], {env: savedEnv}, childCompleted);
    } else {
        excludeFiles.push('./src/client/test/midway/**/*.spec.js');
    }

    startTests();

    ////////////////////
    function childCompleted(error, stdout, stderr) {
        log('stdout: ' + stdout);
        log('stderr: ' + stderr);
        if (error !== null) {
            log('exec error: ' + error);
        }
    }

    function startTests() {
        karma.start({
            configFile: __dirname + '/karma.conf.js',
            exclude: excludeFiles,
            singleRun: !!singleRun
        }, function() {
            if (child) {child.kill();}
            done();
        });
    }
}

/**
 * @desc Annotate only
 *  ONLY USED IN PLURALSIGHT COURSE ANGULARJS PATTERNS: CLEAN CODE
 *  ONLY FOR DEMO PURPOSES
 *  ALL OTHER TASKS ARE AWESOME-SAUCE
 *
 *  See the output of each file?
 *      Uncomment rename, comment concat and uglify
 *  See min'd and concat'd output?
 *      Comment rename, uncomment concat and uglify,
 *      add to index.html, then run it with `gulp serve-dev`.
 */
gulp.task('ngAnnotateTest', function() {
    log('Annotating AngularJS dependencies');
    var source = [].concat(pkg.paths.js);
    return gulp
        // .src(source)
        .src(pkg.paths.client + '/app/avengers/avengers.js')
        .pipe(plug.ngAnnotate({add: true, single_quotes: true}))
        .pipe(plug.rename(function(path) {
            path.extname = '.annotated.js';
        }))
        // .pipe(plug.concat('all.min.js'))
        // .pipe(plug.uglify({mangle: true}))
        // .pipe(gulp.dest(pkg.paths.client + '/app'));
        .pipe(gulp.dest(pkg.paths.client + '/app/avengers'));
});<commit_msg>modular: turned on jshint for tests and source. jscs not working right yet<commit_after>/* jshint camelcase:false */
var gulp = require('gulp');
var pkg = require('./package.json');
var common = require('./gulp/common.js');
var karma = require('karma').server;
var plug = require('gulp-load-plugins')();
var env = plug.util.env;
var log = plug.util.log;

gulp.task('help', plug.taskListing);

/**
 * @desc Lint the code
 */
gulp.task('analyze', function() {
    analyze('./src/client/test/**/*.js', './src/client/test/.jshintrc');
    analyze([].concat(pkg.paths.js, pkg.paths.nodejs), './.jshintrc');
});

function analyze(sources, jshintrc) {
    log('Linting: ', sources);
    return gulp
        .src(sources)
        .pipe(plug.jshint(jshintrc))
        .pipe(plug.jshint.reporter('jshint-stylish'))
        .pipe(plug.jscs('./.jscsrc'));
}

/**
 * @desc Create $templateCache from the html templates
 */
gulp.task('templatecache', function() {
    log('Creating an AngularJS $templateCache');

    return gulp
        .src(pkg.paths.htmltemplates)
        .pipe(plug.angularTemplatecache('templates.js', {
            module: 'app.core',
            standalone: false,
            root: 'app/'
        }))
        .pipe(gulp.dest(pkg.paths.stage));
});

/**
 * @desc Minify and bundle the app's JavaScript
 */
gulp.task('js', ['analyze', 'templatecache'], function() {
    log('Bundling, minifying, and copying the app\'s JavaScript');

    var source = [].concat(pkg.paths.js, pkg.paths.stage + 'templates.js');
    return gulp
        .src(source)
       // .pipe(plug.sourcemaps.init()) // get screwed up in the file rev process
        .pipe(plug.concat('all.min.js'))
        .pipe(plug.ngAnnotate({add: true, single_quotes: true}))
        .pipe(plug.bytediff.start())
        .pipe(plug.uglify({mangle: true}))
        .pipe(plug.bytediff.stop(common.bytediffFormatter))
        // .pipe(plug.sourcemaps.write('./'))
        .pipe(gulp.dest(pkg.paths.stage));
});

/**
 * @desc Copy the Vendor JavaScript
 */
gulp.task('vendorjs', function() {
    log('Bundling, minifying, and copying the Vendor JavaScript');
    return gulp.src(pkg.paths.vendorjs)
        .pipe(plug.concat('vendor.min.js'))
        .pipe(plug.bytediff.start())
        .pipe(plug.uglify())
        .pipe(plug.bytediff.stop(common.bytediffFormatter))
        .pipe(gulp.dest(pkg.paths.stage)); // + 'vendor'));
});

/**
 * @desc Minify and bundle the CSS
 */
gulp.task('css', function() {
    log('Bundling, minifying, and copying the app\'s CSS');
    return gulp.src(pkg.paths.css)
        .pipe(plug.concat('all.min.css')) // Before bytediff or after
        .pipe(plug.autoprefixer('last 2 version', '> 5%'))
        .pipe(plug.bytediff.start())
        .pipe(plug.minifyCss({}))
        .pipe(plug.bytediff.stop(common.bytediffFormatter))
//        .pipe(plug.concat('all.min.css')) // Before bytediff or after
        .pipe(gulp.dest(pkg.paths.stage + 'content'));
});

/**
 * @desc Minify and bundle the Vendor CSS
 */
gulp.task('vendorcss', function() {
    log('Compressing, bundling, copying vendor CSS');
    return gulp.src(pkg.paths.vendorcss)
        .pipe(plug.concat('vendor.min.css'))
        .pipe(plug.bytediff.start())
        .pipe(plug.minifyCss({}))
        .pipe(plug.bytediff.stop(common.bytediffFormatter))
        .pipe(gulp.dest(pkg.paths.stage + 'content'));
});

/**
 * @desc Copy fonts
 */
gulp.task('fonts', function() {
    var dest = pkg.paths.stage + 'fonts';
    log('Copying fonts');
    return gulp
        .src(pkg.paths.fonts)
        .pipe(gulp.dest(dest));
});

/**
 * @desc Compress images
 */
gulp.task('images', function() {
    var dest = pkg.paths.stage + 'content/images';
    log('Compressing, caching, and copying images');
    return gulp
        .src(pkg.paths.images)
        .pipe(plug.cache(plug.imagemin({optimizationLevel: 3})))
        .pipe(gulp.dest(dest));
});

/**
 * @desc Inject all the files into the new index.html
 * rev, but no map
 */
gulp.task('rev-and-inject',
    ['js', 'vendorjs', 'css', 'vendorcss'], function() {
        log('Rev\'ing files and building index.html');

        var minified = pkg.paths.stage + '**/*.min.*';
        var index = pkg.paths.client + 'index.html';
        var minFilter = plug.filter(['**/*.min.*', '!**/*.map']);
        var indexFilter = plug.filter(['index.html']);

        var stream = gulp
            // Write the revisioned files
            .src([].concat(minified, index)) // add all staged min files and index.html
            .pipe(minFilter) // filter the stream to minified css and js
            .pipe(plug.rev()) // create files with rev's
            .pipe(gulp.dest(pkg.paths.stage)) // write the rev files
            .pipe(minFilter.restore()) // remove filter, back to original stream

            // inject the files into index.html
            .pipe(indexFilter) // filter to index.html
            .pipe(inject('content/vendor.min.css', 'inject-vendor'))
            .pipe(inject('content/all.min.css'))
            .pipe(inject('vendor.min.js', 'inject-vendor'))
            .pipe(inject('all.min.js'))
            .pipe(gulp.dest(pkg.paths.stage)) // write the rev files
            .pipe(indexFilter.restore()) // remove filter, back to original stream

            // replace the files referenced in index.html with the rev'd files
            .pipe(plug.revReplace())         // Substitute in new filenames
            .pipe(gulp.dest(pkg.paths.stage)) // write the index.html file changes
            .pipe(plug.rev.manifest()) // create the manifest (must happen last or we screw up the injection)
            .pipe(gulp.dest(pkg.paths.stage)); // write the manifest

        function inject(path, name) {
            var glob = pkg.paths.stage + path;
            var options = {
                ignorePath: pkg.paths.stage.substring(1),
                read: false
            };
            if (name) { options.name = name; }
            return plug.inject(gulp.src(glob), options);
        }
    });

/**
 * @desc Stage the optimized app
 */
gulp.task('stage',
    ['rev-and-inject', 'images', 'fonts'], function() {
        log('Staging the optimized app');

        return gulp.src('').pipe(plug.notify({
            onLast: true,
            message: 'Deployed code to stage!'
        }));
    });

/**
 * @desc Remove all files from the build folder
 * One way to run clean before all tasks is to run
 * from the cmd line: gulp clean && gulp stage
 */
gulp.task('clean', function() {
    var paths = pkg.paths.build;
    log('Cleaning: ' + plug.util.colors.blue(paths));

    return gulp
        .src(paths, {read: false})
        .pipe(plug.rimraf({force: true}));
});

/**
 * @desc Watch files and build
 */
gulp.task('watch', function() {
    log('Watching all files');

    var css = ['gulpfile.js'].concat(pkg.paths.css, pkg.paths.vendorcss);
    var images = ['gulpfile.js'].concat(pkg.paths.images);
    var js = ['gulpfile.js'].concat(pkg.paths.js);

    gulp
        .watch(js, ['js', 'vendorjs'])
        .on('change', logWatch);

    gulp
        .watch(css, ['css', 'vendorcss'])
        .on('change', logWatch);

    gulp
        .watch(images, ['images'])
        .on('change', logWatch);

    function logWatch(event) {
        log('*** File ' + event.path + ' was ' + event.type + ', running tasks...');
    }
});

/**
 * Run specs once and exit
 * To start servers and run midway specs as well:
 *    gulp test --startServers
 */
gulp.task('test', function (done) {
    testCore(true /*singleRun*/, done);
});

/**
 * Run specs and wait.
 * Watch for file changes and re-run tests on each change
 * To start servers and run midway specs as well:
 *    gulp autotest --startServers
 */
gulp.task('autotest', function (done) {
    testCore(false /*singleRun*/, done);
});

/**
 * serve the dev environment, with debug,
 * and with node inspector
 */
gulp.task('serve-dev-debug', function() {
    serve({mode: 'dev', debug: '--debug'});
    startLivereload('development');
});

/**
 * serve the dev environment, with debug-brk,
 * and with node inspector
 */
gulp.task('serve-dev-debug-brk', function() {
    serve({mode: 'dev', debug: '--debug-brk'});
    startLivereload('development');
});

/**
 * serve the dev environment
 */
gulp.task('serve-dev', function() {
    serve({mode: 'dev'});
    startLivereload('development');
});

/**
 * serve the staging environment
 */
gulp.task('serve-stage', function() {
    serve({mode: 'stage'});
    startLivereload('stage');
});

function startLivereload(mode) {
    if (!env.liveReload) { return; }

    var path = (env === 'stage' ? [pkg.paths.stage, pkg.paths.client + '/**'] : [pkg.paths.client + '/**']);
    var options = {auto: true};
    plug.livereload.listen(options);
    gulp.watch(path)
        .on('change', function(file) {
            plug.livereload.changed(file.path);
        });

    log('Serving from ' + mode);
}

function serve(args) {
    var options = {
        script: pkg.paths.server + 'app.js',
        delayTime: 1,
        ext: 'html js',
        env: {'NODE_ENV': args.mode},
        watch: [
            'gulpfile.js',
            'package.json',
            pkg.paths.server,
            pkg.paths.client
        ]
    };

    if (args.debug) {
        gulp.src('', {read: false})
            .pipe(plug.shell(['node-inspector']));
        options.nodeArgs = [args.debug + '=5858'];
    }

    return plug.nodemon(options)
        //.on('change', tasks)
        .on('restart', function() {
            log('restarted!');
        });
}

function testCore(singleRun, done) {
    var child;
    var excludeFiles = ['./src/client/app/**/*spaghetti.js'];
    var spawn = require('child_process').spawn;

    if (env.startServers) {
        log('Starting servers');
        var savedEnv = process.env;
        savedEnv.NODE_ENV = 'dev';
        savedEnv.PORT = 8888;
        child = spawn('node', ['src/server/app.js'], {env: savedEnv}, childCompleted);
    } else {
        excludeFiles.push('./src/client/test/midway/**/*.spec.js');
    }

    startTests();

    ////////////////////
    function childCompleted(error, stdout, stderr) {
        log('stdout: ' + stdout);
        log('stderr: ' + stderr);
        if (error !== null) {
            log('exec error: ' + error);
        }
    }

    function startTests() {
        karma.start({
            configFile: __dirname + '/karma.conf.js',
            exclude: excludeFiles,
            singleRun: !!singleRun
        }, function() {
            if (child) {child.kill();}
            done();
        });
    }
}

/**
 * @desc Annotate only
 *  ONLY USED IN PLURALSIGHT COURSE ANGULARJS PATTERNS: CLEAN CODE
 *  ONLY FOR DEMO PURPOSES
 *  ALL OTHER TASKS ARE AWESOME-SAUCE
 *
 *  See the output of each file?
 *      Uncomment rename, comment concat and uglify
 *  See min'd and concat'd output?
 *      Comment rename, uncomment concat and uglify,
 *      add to index.html, then run it with `gulp serve-dev`.
 */
gulp.task('ngAnnotateTest', function() {
    log('Annotating AngularJS dependencies');
    var source = [].concat(pkg.paths.js);
    return gulp
        // .src(source)
        .src(pkg.paths.client + '/app/avengers/avengers.js')
        .pipe(plug.ngAnnotate({add: true, single_quotes: true}))
        .pipe(plug.rename(function(path) {
            path.extname = '.annotated.js';
        }))
        // .pipe(plug.concat('all.min.js'))
        // .pipe(plug.uglify({mangle: true}))
        // .pipe(gulp.dest(pkg.paths.client + '/app'));
        .pipe(gulp.dest(pkg.paths.client + '/app/avengers'));
});<|endoftext|>


<commit_before>//Constants for the different creeps' Bodies that auto spawn
//Tier 1
var hBody = [WORK, CARRY, CARRY, MOVE, MOVE];
var uBody = [WORK, CARRY, CARRY, CARRY, MOVE];
var bBody = [WORK, CARRY, WORK, MOVE];
var rBody = [WORK, WORK, CARRY, MOVE];
var miniHaul = [CARRY,CARRY,CARRY,MOVE,MOVE,MOVE];

//Tier 2
var h2Body = [WORK, WORK, CARRY, CARRY, CARRY, CARRY, MOVE, MOVE, MOVE];
var u2Body = [WORK, WORK, WORK, CARRY, CARRY, CARRY, MOVE, MOVE];
var b2Body = [WORK, WORK, WORK, CARRY, CARRY, MOVE, MOVE, MOVE];
var r2Body = [WORK, WORK, CARRY, CARRY, CARRY, MOVE, MOVE, MOVE, MOVE];

//Tier 3
var h3Body = [WORK, WORK, CARRY, CARRY, CARRY, CARRY,CARRY, CARRY, CARRY, MOVE, MOVE, MOVE, MOVE, MOVE];
var u3Body = [WORK, WORK, WORK, WORK, WORK, CARRY, CARRY, CARRY, MOVE, MOVE];
var b3Body = [WORK, WORK, WORK, WORK, WORK, CARRY, CARRY, MOVE, MOVE, MOVE];
var r3Body = [WORK, WORK, WORK, CARRY, CARRY, CARRY, CARRY, MOVE, MOVE, MOVE, MOVE, MOVE, MOVE];

//Tier4
var haulerBody = [TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,CARRY,CARRY,CARRY,CARRY,CARRY,CARRY,CARRY, CARRY, MOVE, MOVE, MOVE,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE];
var feederBody = [TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,CARRY,CARRY,CARRY,CARRY,CARRY,CARRY,CARRY,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE,WORK];
var minerBody = [WORK,WORK,WORK,WORK,WORK,CARRY,CARRY,CARRY,MOVE,MOVE,MOVE];
var u4Body = [WORK, WORK, WORK, WORK, WORK, WORK, CARRY, MOVE];
var soldierBody = [TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, MOVE, MOVE, MOVE, MOVE, RANGED_ATTACK, RANGED_ATTACK];
//800 energy so that two can spawn at rc5 without refil
var geoMinerBody = [WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,CARRY,CARRY,CARRY,MOVE];
var merchantBody = [MOVE,CARRY];

//TODO Higher tier(s) for lvl 6,7,8

//Tier 5
var u5Body = [WORK, WORK, WORK, WORK, WORK, WORK, WORK, WORK, WORK, WORK, WORK, CARRY, MOVE];
var b5Body = [WORK, WORK, WORK, WORK, WORK, WORK, CARRY, CARRY, CARRY, CARRY, CARRY, CARRY, MOVE, MOVE, MOVE, MOVE, MOVE];
var soldierBody = [TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, MOVE, MOVE, MOVE, MOVE, RANGED_ATTACK, RANGED_ATTACK];

//Tier 6
var hauler6Body = [TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE];
var feeder6Body = [TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,WORK,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE];

//Tier 7
var hauler7Body = [TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE];
var feeder7Body = [TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,WORK,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE];
var geoMiner7Body = [WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,CARRY,CARRY,CARRY,MOVE];
var u7Body = [TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,CARRY,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE];

//NonTiered
var settlerBody = [CLAIM,WORK,WORK,WORK,CARRY,CARRY,CARRY,CARRY,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE];

var costMap = {
  MOVE:50,
  WORK:100,
  CARRY:50,
  ATTACK:80,
  RANGED_ATTACK:150,
  HEAL:250,
  CLAIM:600,
  TOUGH:10
};

//Object that holds a getter for these body types
var bodyObj = {

  calcCost:function(bodyObj){
    var total = 0;
    for(var part in bodyObj){
      switch(part){
        case MOVE, CARRY:
          total = total + 50;
          break;
        case WORK:
          total = total + 100;
          break;
        case ATTACK:
          total = total + 80;
          break;
        case RANGED_ATTACK:
          total = total + 150;
          break;
        case HEAL:
          total = total + 250;
          break;
        case CLAIM:
          total = total + 600;
          break;
        case TOUGH:
          total = total + 10;
          break;
      }
    }
    return total;
  },
  //getter that useses a lot of switch statement to return the appropriate body array for the given role and tier
  getBody:function(role,tier){
      if(role==='harvester'){
        switch(tier){
          default:
            return hBody;
          case 2:
            return h2Body;
          case 3:
          case 4:
          case 5:
          case 6:
          case 7:
            return h3Body;
        }
      }else if(role === 'upgrader'){
        switch(tier){
          default:
            return uBody;
          case 2:
            return u2Body;
          case 3:
            return u3Body;
          case 4:
            return u4Body;
          case 5:
          case 6:
            return u5Body;
          case 7:
            return u7Body;
        }
      }else if(role === 'builder' || role === 'architect'){
        switch(tier){
          default:
            return bBody;
          case 2:
            return b2Body;
          case 3:
          case 4:
            return b3Body;
          case 5:
          case 6:
          case 7:
            return b5Body;
        }
      }else if(role === 'repair'){
        switch(tier){
          default:
            return rBody;
          case 2:
            return r2Body;
          case 3:
          case 4:
          case 5:
          case 6:
          case 7:
            return r3Body;
        }
      }else if(role === 'hauler'){
        switch(tier){
          default:
            return miniHaul;
          case 4:
          case 5:
            return haulerBody;
          case 6:
          case 7:
            return hauler6Body;
        }
      }else if(role === 'miner'){
        return minerBody;
      }else if(role === 'feeder'){
        switch(tier){
          default:
            return miniHaul;
          case 4:
          case 5:
            return feederBody;
          case 6:
            return feeder6Body;
          case 7:
            return feeder7Body;
        }
      }else if(role === 'soldier'){
        return soliderBody;
      }else if(role === 'geo'){
        switch(tier){
          default:
          case 5:
          case 6:
            return geoMinerBody;
          case 7:
            return geoMiner7Body;
        }
      }else if(role === 'merchant'){
        return merchantBody;
      }else if(role === 'settler'){
        return settlerBody;
      }
  }

};

module.exports = bodyObj;
<commit_msg>removed the tier6 & 7 hauler body to see if it the other is more effiecient<commit_after>//Constants for the different creeps' Bodies that auto spawn
//Tier 1
var hBody = [WORK, CARRY, CARRY, MOVE, MOVE];
var uBody = [WORK, CARRY, CARRY, CARRY, MOVE];
var bBody = [WORK, CARRY, WORK, MOVE];
var rBody = [WORK, WORK, CARRY, MOVE];
var miniHaul = [CARRY,CARRY,CARRY,MOVE,MOVE,MOVE];

//Tier 2
var h2Body = [WORK, WORK, CARRY, CARRY, CARRY, CARRY, MOVE, MOVE, MOVE];
var u2Body = [WORK, WORK, WORK, CARRY, CARRY, CARRY, MOVE, MOVE];
var b2Body = [WORK, WORK, WORK, CARRY, CARRY, MOVE, MOVE, MOVE];
var r2Body = [WORK, WORK, CARRY, CARRY, CARRY, MOVE, MOVE, MOVE, MOVE];

//Tier 3
var h3Body = [WORK, WORK, CARRY, CARRY, CARRY, CARRY,CARRY, CARRY, CARRY, MOVE, MOVE, MOVE, MOVE, MOVE];
var u3Body = [WORK, WORK, WORK, WORK, WORK, CARRY, CARRY, CARRY, MOVE, MOVE];
var b3Body = [WORK, WORK, WORK, WORK, WORK, CARRY, CARRY, MOVE, MOVE, MOVE];
var r3Body = [WORK, WORK, WORK, CARRY, CARRY, CARRY, CARRY, MOVE, MOVE, MOVE, MOVE, MOVE, MOVE];

//Tier4
var haulerBody = [TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,CARRY,CARRY,CARRY,CARRY,CARRY,CARRY,CARRY, CARRY, MOVE, MOVE, MOVE,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE];
var feederBody = [TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,CARRY,CARRY,CARRY,CARRY,CARRY,CARRY,CARRY,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE,WORK];
var minerBody = [WORK,WORK,WORK,WORK,WORK,CARRY,CARRY,CARRY,MOVE,MOVE,MOVE];
var u4Body = [WORK, WORK, WORK, WORK, WORK, WORK, CARRY, MOVE];
var soldierBody = [TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, MOVE, MOVE, MOVE, MOVE, RANGED_ATTACK, RANGED_ATTACK];
//800 energy so that two can spawn at rc5 without refil
var geoMinerBody = [WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,CARRY,CARRY,CARRY,MOVE];
var merchantBody = [MOVE,CARRY];

//TODO Higher tier(s) for lvl 6,7,8

//Tier 5
var u5Body = [WORK, WORK, WORK, WORK, WORK, WORK, WORK, WORK, WORK, WORK, WORK, CARRY, MOVE];
var b5Body = [WORK, WORK, WORK, WORK, WORK, WORK, CARRY, CARRY, CARRY, CARRY, CARRY, CARRY, MOVE, MOVE, MOVE, MOVE, MOVE];
var soldierBody = [TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, TOUGH, MOVE, MOVE, MOVE, MOVE, RANGED_ATTACK, RANGED_ATTACK];

//Tier 6
var hauler6Body = [TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE];
var feeder6Body = [TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,WORK,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE];

//Tier 7
var hauler7Body = [TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE];
var feeder7Body = [TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,WORK,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE,CARRY,MOVE];
var geoMiner7Body = [WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,CARRY,CARRY,CARRY,MOVE];
var u7Body = [TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,TOUGH,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,WORK,CARRY,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE];

//NonTiered
var settlerBody = [CLAIM,WORK,WORK,WORK,CARRY,CARRY,CARRY,CARRY,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE,MOVE];

var costMap = {
  MOVE:50,
  WORK:100,
  CARRY:50,
  ATTACK:80,
  RANGED_ATTACK:150,
  HEAL:250,
  CLAIM:600,
  TOUGH:10
};

//Object that holds a getter for these body types
var bodyObj = {

  calcCost:function(bodyObj){
    var total = 0;
    for(var part in bodyObj){
      switch(part){
        case MOVE, CARRY:
          total = total + 50;
          break;
        case WORK:
          total = total + 100;
          break;
        case ATTACK:
          total = total + 80;
          break;
        case RANGED_ATTACK:
          total = total + 150;
          break;
        case HEAL:
          total = total + 250;
          break;
        case CLAIM:
          total = total + 600;
          break;
        case TOUGH:
          total = total + 10;
          break;
      }
    }
    return total;
  },
  //getter that useses a lot of switch statement to return the appropriate body array for the given role and tier
  getBody:function(role,tier){
      if(role==='harvester'){
        switch(tier){
          default:
            return hBody;
          case 2:
            return h2Body;
          case 3:
          case 4:
          case 5:
          case 6:
          case 7:
            return h3Body;
        }
      }else if(role === 'upgrader'){
        switch(tier){
          default:
            return uBody;
          case 2:
            return u2Body;
          case 3:
            return u3Body;
          case 4:
            return u4Body;
          case 5:
          case 6:
            return u5Body;
          case 7:
            return u7Body;
        }
      }else if(role === 'builder' || role === 'architect'){
        switch(tier){
          default:
            return bBody;
          case 2:
            return b2Body;
          case 3:
          case 4:
            return b3Body;
          case 5:
          case 6:
          case 7:
            return b5Body;
        }
      }else if(role === 'repair'){
        switch(tier){
          default:
            return rBody;
          case 2:
            return r2Body;
          case 3:
          case 4:
          case 5:
          case 6:
          case 7:
            return r3Body;
        }
      }else if(role === 'hauler'){
        switch(tier){
          default:
            return miniHaul;
          case 4:
          case 5:
          case 6:
          case 7:
            return haulerBody;
        }
      }else if(role === 'miner'){
        return minerBody;
      }else if(role === 'feeder'){
        switch(tier){
          default:
            return miniHaul;
          case 4:
          case 5:
            return feederBody;
          case 6:
            return feeder6Body;
          case 7:
            return feeder7Body;
        }
      }else if(role === 'soldier'){
        return soliderBody;
      }else if(role === 'geo'){
        switch(tier){
          default:
          case 5:
          case 6:
            return geoMinerBody;
          case 7:
            return geoMiner7Body;
        }
      }else if(role === 'merchant'){
        return merchantBody;
      }else if(role === 'settler'){
        return settlerBody;
      }
  }

};

module.exports = bodyObj;
<|endoftext|>


<commit_before>/*jslint vars: true */
/*global define, $, brackets, window, console */

define(function (require, exports, module) {
  'use strict';

  var HTMLUtils       = brackets.getModule('language/HTMLUtils'),
      CDNLibrary      = require('modules/CDNLibrary');

  /**
   * Represents a CodeHintProvider
   *
   * @param {Array<Library}
   * An array of Library objects.
   */
  function CDNHints(cdnLibraries) {
    this.libraries = cdnLibraries;
  }

  /**
   * Determines whether hints can be offered given the
   * current editor context.
   *
   * @param {Editor} editor
   * A non-null editor object for the active window.
   *
   * @param {string} implicitChar
   * Null if the hinting request was explicit,
   * otherwise a single character that represents the last
   * insertion and that indicates an implicit hinting request.
   *
   * @return {boolean}
   * True if hints can be provided, otherwise false.
   */
  CDNHints.prototype.hasHints = function (editor, implicitChar) {
    this.editor = editor;

    var pos     = editor.getCursorPos(),
        tagInfo = HTMLUtils.getTagInfo(editor, pos);

    console.log(tagInfo);
    if (
      tagInfo.tagName === 'script' &&
      tagInfo.position.tokenType === 'attr.name' &&
      tagInfo.attr.name.length === 0
    ) {
      return true;
    }

    return false;
  }

  /**
   * Returns a list of available hints for the current editor
   * context.
   *
   * @param {string} implicitChar
   * Null, if the request to update the hint list was a result
   * of navigation, otherwise a single character that represents
   * the last insertion.
   *
   * @return {object}
   * {jQuery.Deferred|{
   *    hints: Array.<string|jQueryObject>,
   *    match: string,
   *    selectInitial: boolean,
   *    handleWideResults: boolean
   * }}
   */
  CDNHints.prototype.getHints = function (implicitChar) {
    var pos     = this.editor.getCursorPos(),
        tagInfo = HTMLUtils.getTagInfo(this.editor, pos),
        filter  = new RegExp(tagInfo.attr.name, 'i');

    var libraryNames = this.libraries
    .map(function (library) {
      return library.getName();
    })
    .filter(function (libraryName) {
      return filter.test(libraryName);
    });

    if (tagInfo.attr.name.length === 0) {
      // Sort alphabetically
      libraryNames.sort();
    } else {
      // Sort based on the location of the current attribute
      libraryNames.sort(compareByStrPos);
    }

    /**
     * Compares two string based on the position of the
     * attribute name `tagInfo.attr.name` (RegExp `filter`).
     * This method uses a regular expression to for its
     * case-insensitivity flag.
     */
    function compareByStrPos(a, b) {
      var aIndex = filter.exec(a).index;
      var bIndex = filter.exec(b).index;

      if (aIndex < bIndex) {
        return -1;
      } else if (aIndex > bIndex) {
        return 1;
      } else {
        return 0;
      }
    }

    return {
      hints: libraryNames,
      match: tagInfo.attr.name,
      selectInitial: true,
      handleWideResults: false
    };
  }

  /**
   * Inserts a hint into the current editor context.
   *
   * @param {string} hint
   * The hint selected by the user.
   *
   * @return {boolean}
   * Indicates whether the manager should follow hint
   * insertion with an explicit hint request.
   */
  CDNHints.prototype.insertHint = function (hint) {
    var document = this.editor.document,
        startPos = this.editor.getCursorPos(),
        library  = CDNLibrary.findByName(this.libraries, hint);

    var snippet = library.getLatestSnippet();
    startPos.ch = 0;

    var endPos = {
      line: startPos.line,
      ch: startPos.ch + snippet.length
    };

    document.replaceRange(snippet, startPos, endPos);

    return true;
  }

  exports.CDNHints = CDNHints;
});<commit_msg>Improved sorting methods.<commit_after>/*jslint vars: true */
/*global define, $, brackets, window, console */

define(function (require, exports, module) {
  'use strict';

  var HTMLUtils       = brackets.getModule('language/HTMLUtils'),
      CDNLibrary      = require('modules/CDNLibrary');

  /**
   * Represents a CodeHintProvider
   *
   * @param {Array<Library}
   * An array of Library objects.
   */
  function CDNHints(cdnLibraries) {
    this.libraries = cdnLibraries;
  }

  /**
   * Determines whether hints can be offered given the
   * current editor context.
   *
   * @param {Editor} editor
   * A non-null editor object for the active window.
   *
   * @param {string} implicitChar
   * Null if the hinting request was explicit,
   * otherwise a single character that represents the last
   * insertion and that indicates an implicit hinting request.
   *
   * @return {boolean}
   * True if hints can be provided, otherwise false.
   */
  CDNHints.prototype.hasHints = function (editor, implicitChar) {
    this.editor = editor;

    var pos     = editor.getCursorPos(),
        tagInfo = HTMLUtils.getTagInfo(editor, pos);

    console.log(tagInfo);
    if (
      tagInfo.tagName === 'script' &&
      tagInfo.position.tokenType === 'attr.name' &&
      tagInfo.attr.name.length === 0
    ) {
      return true;
    }

    return false;
  }

  /**
   * Returns a list of available hints for the current editor
   * context.
   *
   * @param {string} implicitChar
   * Null, if the request to update the hint list was a result
   * of navigation, otherwise a single character that represents
   * the last insertion.
   *
   * @return {object}
   * {jQuery.Deferred|{
   *    hints: Array.<string|jQueryObject>,
   *    match: string,
   *    selectInitial: boolean,
   *    handleWideResults: boolean
   * }}
   */
  CDNHints.prototype.getHints = function (implicitChar) {
    var pos     = this.editor.getCursorPos(),
        tagInfo = HTMLUtils.getTagInfo(this.editor, pos),
        filter  = new RegExp(tagInfo.attr.name, 'i');

    var libraryNames = this.libraries
    .map(function (library) {
      return library.getName();
    })
    .filter(function (libraryName) {
      return filter.test(libraryName);
    });

    if (tagInfo.attr.name.length === 0) {
      // Sort alphabetically
      libraryNames.sort(compareAlphabetically);
    } else {
      // Sort based on the location of the current attribute
      libraryNames.sort(compareByStrPos);
    }

    /**
     * Compares two strings based on their character positions.
     */
    function compareAlphabetically(a, b) {
      return a.toLowerCase().localeCompare(b.toLowerCase());
    }

    /**
     * Compares two string based on the position of the
     * attribute name `tagInfo.attr.name` (RegExp `filter`).
     * This method uses a regular expression to for its
     * case-insensitivity flag.
     */
    function compareByStrPos(a, b) {
      var difference = filter.exec(a).index - filter.exec(b).index

      if (difference === 0) {
        return a.localeCompare(b);
      } else {
        return difference;
      }
    }

    return {
      hints: libraryNames,
      match: tagInfo.attr.name,
      selectInitial: true,
      handleWideResults: false
    };
  }

  /**
   * Inserts a hint into the current editor context.
   *
   * @param {string} hint
   * The hint selected by the user.
   *
   * @return {boolean}
   * Indicates whether the manager should follow hint
   * insertion with an explicit hint request.
   */
  CDNHints.prototype.insertHint = function (hint) {
    var document = this.editor.document,
        startPos = this.editor.getCursorPos(),
        library  = CDNLibrary.findByName(this.libraries, hint);

    var snippet = library.getId();
    startPos.ch = 0;

    var endPos = {
      line: startPos.line,
      ch: startPos.ch + snippet.length
    };

    document.replaceRange(snippet, startPos, endPos);

    return true;
  }

  exports.CDNHints = CDNHints;
});<|endoftext|>


<commit_before>/**
 * Controls Core module
 * other modules cas use it so simplify things
 */

/* jslint node: true */
/* global BOT_DIR, LIBS_DIR, MODULES_DIR */
'use strict';

var logger = require(LIBS_DIR + '/logger');

var Command = require('./controls/command').Command;
var Action = require('./controls/action').Action;

//trim some strings
if (!String.prototype.trim) {
	String.prototype.trim = function() {
		return this.replace(/^\s+|\s+$/g, '');
	};
}

function Controls(irc, actions, commands, config) {
	this._irc = irc;
	this.actions = actions;
	this.commands = commands;
	this.groups = config.groups;
	this.config = config;

	Object.defineProperty(this, 'commandDelimiter', {
		enumerable: true,
		configurable: false,
		get: function() {
			return config.commandDelimiter;
		}
	});
}

Controls.prototype.addCommand = function(name, action, access) {
	name = name.replace(/[^a-zA-Z0-9_\-]+/g, '');
	if (typeof this.commands[name] !== 'undefined') {
		throw new Error('Command \'' + name + '\' already exists!');
	}
	this.commands[name] = new Command(name, action, access);
	return this;
};

Controls.prototype.addAction = function(name, action, regexp, access) {
	name = name.replace(/[^a-zA-Z0-9_\-]+/g, '');
	if (typeof this.actions[name] !== 'undefined') {
		throw new Error('Action \'' + name + '\' already exists!');
	}
	this.actions[name] = new Action(name, action, regexp, access);
	return this;
};

Controls.prototype.removeCommand = function(name) {
	name = name.replace(/[^a-zA-Z0-9_\-]+/g, '');
	if (typeof this.commands[name] !== 'undefined') {
		delete this.commands[name];
	} else {
		throw new Error('Command \'' + name + '\' does not exists!');
	}
	return this;
};

Controls.prototype.removeAction = function(name) {
	name = name.replace(/[^a-zA-Z0-9_\-]+/g, '');
	if (typeof this.actions[name] !== 'undefined') {
		delete this.actions[name];
	} else {
		throw new Error('Action \'' + name + '\' does not exists!');
	}
	return this;
};

Controls.prototype.removeActions = function(list) {
	if (!(list instanceof Array)) {
		throw new Error('You need to pass Array as first argument!');
	}
	list.forEach(function(name) {
		this.removeAction(name);
	}, this);
	return this;
};

Controls.prototype.removeCommands = function(list) {
	if (!(list instanceof Array)) {
		throw new Error('You need to pass Array as first argument!');
	}
	list.forEach(function(name) {
		this.removeCommand(name);
	}, this);
	return this;
};

Controls.prototype.parse = function(source, text) {
	if (typeof text !== 'string' || text.length <= 0) return;

	if (!source.channel) { //itz direct message
		this.processAction(source, text.trim());
	} else if (source.channel && text.substr(0, this.config.commandDelimiter.length) === this.config.commandDelimiter) { //itz command in channel
		//remove the delimiter
		text = text.substr(this.config.commandDelimiter.length);

		this.processCommand(source, text.trim());
	} else if ((new RegExp('^' + this._irc.currentNick + '[ ,;:]')).test(text.substr(0, this._irc.currentNick.length + 1))) { //itz higlight in channel
		//remove the trigger
		text = text.substr(this._irc.currentNick.length + 1);

		this.processAction(source, text.trim());
	}
};

Controls.prototype.processCommand = function(source, text) {
	var args = text.match(/[^\s\"\']+|\"([^\"]*)\"|\'([^\']*)\'/g).map(function(v) {
		return v.replace(/\"|\'/g, '');
	});
	var command = args.shift();
	//execute the command
	Object.keys(this.commands).some(function(name) {
		if (this.commands[name] && command == this.commands[name].name) { //use == to not check type
			if (this.checkAccess(source, this.commands[name])) { //run the command only if the user has access
				logger.debug('Processing Command \'' + name + '\'');
				this.commands[name].action(source, args, text, command);
			} else {
				logger.debug('Command \'' + name + '\' access for user \'' + source + '\' denied!');
				source.notice('Command Access denied! Do i know you?');
			}
			return true; //stop searching
		}
		return false; //try next
	}, this);
};

Controls.prototype.processAction = function(source, text) {
	//proces all actions that triggers rule
	Object.keys(this.actions).forEach(function(name) {
		if (this.actions[name]) {
			var args = text.match(this.actions[name].rule);
			if (args !== null) {
				if (this.checkAccess(source, this.actions[name])) { //run the action only if the user has access
					logger.debug('Processing Action \'' + name + '\'');
					this.actions[name].action(source, args, text);
				} else {
					logger.debug('Action \'' + name + '\' access for user \'' + source + '\' denied!');
					source.notice('Action Access denied! Derpy in action!');
				}
			}
		}
	}, this);
};

Controls.prototype.checkAccess = function(source, CorA) {
	//allow access if no rule
	if (!CorA.access) {
		return true;
	}

	//shell can run everything
	if (source.toString() === 'ItzAInternallPonyShell') {
		return true;
	}

	//array means list of allowed groups
	if (CorA.access instanceof Array) {
		return CorA.access.some(function(name) {
			if (this.groups[name]) {
				return this.groups[name].some(function(user) {
					return source.toString().match(user) !== null;
				});
			}
			return false;
		}, this);
	}

	//else try matching it directly
	return source.toString().match(CorA.access) !== null;
};

module.exports.init = function(reload) {
	if (!reload) {
		this.actions = {};
		this.commands = {};
	}
	if (typeof this.config.groups !== 'object') {
		this.config.groups = {};
	}
	if (typeof this.config.commandDelimiter !== 'string') {
		this.config.commandDelimiter = '.';
	}

	this.controls = new Controls(this.require('irc'), this.actions, this.commands, this.config);

	var module = this;

	if (!reload) {
		Object.defineProperty(this, 'commandDelimiter', {
			enumerable: true,
			configurable: false,
			get: function() {
				return module.config.commandDelimiter;
			}
		});
	}

	//export some functions from Controls
	//require(LIBS_DIR + '/helpers').export(this, this.controls, ['addCommand', 'addAction', 'removeCommand', 'removeAction', 'removeCommands', 'removeActions']);
	require(LIBS_DIR + '/helpers').export(this, this.controls, ['removeCommand', 'removeAction', 'removeCommands', 'removeActions']);

	/**
	 * bind the controls to module core
	 */

	if (!reload) {
		this.moduleActions = {};
		this.moduleCommands = {};
	}

	var proto = require(LIBS_DIR + '/module').Module.prototype;
	proto.addCommand = function(name) {
		module.controls.addCommand.apply(module.controls, arguments);
		if (typeof module.moduleCommands[this.name] === 'undefined') module.moduleCommands[this.name] = {};
		module.moduleCommands[this.name][name] = name;
		return this;
	};
	proto.addAction = function(name) {
		module.controls.addAction.apply(module.controls, arguments);
		if (typeof module.moduleActions[this.name] === 'undefined') module.moduleActions[this.name] = {};
		module.moduleActions[this.name][name] = name;
		return this;
	};
	proto.removeCommand = function() {
		module.controls.removeCommand.apply(module.controls, arguments);
		return this;
	};
	proto.removeAction = function() {
		module.controls.removeAction.apply(module.controls, arguments);
		return this;
	};

	this.addCommand = this.addAction = function() {
		throw new Error('Deprecated! Dont use \'controls\' module directly!');
	};

	//clean up the module commands/actions on unload

	function clean(name) {
		if (typeof module.moduleActions[name] !== 'undefined') {
			Object.keys(module.moduleActions[name]).forEach(function(action) {
				if (typeof module.actions[action] !== 'undefined') {
					module.controls.removeAction(action);
				}
			});
			delete module.moduleActions[name];
		}
		if (typeof module.moduleCommands[name] !== 'undefined') {
			Object.keys(module.moduleCommands[name]).forEach(function(command) {
				if (typeof module.commands[command] !== 'undefined') {
					module.controls.removeCommand(command);
				}
			});
			delete module.moduleCommands[name];
		}
	}

	this.dispatcher.on('unload', clean).on('reload-unload', clean);

	//bind
	this.dispatcher.on('irc/PRIVMSG', this.controls.parse.bind(this.controls)).on('irc/NOTICE', this.controls.parse.bind(this.controls));
};

module.exports.halt = function() {
	var proto = require(LIBS_DIR + '/module').Module.prototype;
	delete proto.addCommand;
	delete proto.addAction;
	delete proto.removeCommand;
	delete proto.removeAction;
};<commit_msg>fix live groups in controls<commit_after>/**
 * Controls Core module
 * other modules cas use it so simplify things
 */

/* jslint node: true */
/* global BOT_DIR, LIBS_DIR, MODULES_DIR */
'use strict';

var logger = require(LIBS_DIR + '/logger');

var Command = require('./controls/command').Command;
var Action = require('./controls/action').Action;

//trim some strings
if (!String.prototype.trim) {
	String.prototype.trim = function() {
		return this.replace(/^\s+|\s+$/g, '');
	};
}

function Controls(irc, actions, commands, config) {
	this._irc = irc;
	this.actions = actions;
	this.commands = commands;
	this.config = config;

	Object.defineProperty(this, 'commandDelimiter', {
		enumerable: true,
		configurable: false,
		get: function() {
			return config.commandDelimiter;
		}
	});
}

Controls.prototype.addCommand = function(name, action, access) {
	name = name.replace(/[^a-zA-Z0-9_\-]+/g, '');
	if (typeof this.commands[name] !== 'undefined') {
		throw new Error('Command \'' + name + '\' already exists!');
	}
	this.commands[name] = new Command(name, action, access);
	return this;
};

Controls.prototype.addAction = function(name, action, regexp, access) {
	name = name.replace(/[^a-zA-Z0-9_\-]+/g, '');
	if (typeof this.actions[name] !== 'undefined') {
		throw new Error('Action \'' + name + '\' already exists!');
	}
	this.actions[name] = new Action(name, action, regexp, access);
	return this;
};

Controls.prototype.removeCommand = function(name) {
	name = name.replace(/[^a-zA-Z0-9_\-]+/g, '');
	if (typeof this.commands[name] !== 'undefined') {
		delete this.commands[name];
	} else {
		throw new Error('Command \'' + name + '\' does not exists!');
	}
	return this;
};

Controls.prototype.removeAction = function(name) {
	name = name.replace(/[^a-zA-Z0-9_\-]+/g, '');
	if (typeof this.actions[name] !== 'undefined') {
		delete this.actions[name];
	} else {
		throw new Error('Action \'' + name + '\' does not exists!');
	}
	return this;
};

Controls.prototype.removeActions = function(list) {
	if (!(list instanceof Array)) {
		throw new Error('You need to pass Array as first argument!');
	}
	list.forEach(function(name) {
		this.removeAction(name);
	}, this);
	return this;
};

Controls.prototype.removeCommands = function(list) {
	if (!(list instanceof Array)) {
		throw new Error('You need to pass Array as first argument!');
	}
	list.forEach(function(name) {
		this.removeCommand(name);
	}, this);
	return this;
};

Controls.prototype.parse = function(source, text) {
	if (typeof text !== 'string' || text.length <= 0) return;

	if (!source.channel) { //itz direct message
		this.processAction(source, text.trim());
	} else if (source.channel && text.substr(0, this.config.commandDelimiter.length) === this.config.commandDelimiter) { //itz command in channel
		//remove the delimiter
		text = text.substr(this.config.commandDelimiter.length);

		this.processCommand(source, text.trim());
	} else if ((new RegExp('^' + this._irc.currentNick + '[ ,;:]')).test(text.substr(0, this._irc.currentNick.length + 1))) { //itz higlight in channel
		//remove the trigger
		text = text.substr(this._irc.currentNick.length + 1);

		this.processAction(source, text.trim());
	}
};

Controls.prototype.processCommand = function(source, text) {
	var args = text.match(/[^\s\"\']+|\"([^\"]*)\"|\'([^\']*)\'/g).map(function(v) {
		return v.replace(/\"|\'/g, '');
	});
	var command = args.shift();
	//execute the command
	Object.keys(this.commands).some(function(name) {
		if (this.commands[name] && command == this.commands[name].name) { //use == to not check type
			if (this.checkAccess(source, this.commands[name])) { //run the command only if the user has access
				logger.debug('Processing Command \'' + name + '\'');
				this.commands[name].action(source, args, text, command);
			} else {
				logger.debug('Command \'' + name + '\' access for user \'' + source + '\' denied!');
				source.notice('Command Access denied! Do i know you?');
			}
			return true; //stop searching
		}
		return false; //try next
	}, this);
};

Controls.prototype.processAction = function(source, text) {
	//proces all actions that triggers rule
	Object.keys(this.actions).forEach(function(name) {
		if (this.actions[name]) {
			var args = text.match(this.actions[name].rule);
			if (args !== null) {
				if (this.checkAccess(source, this.actions[name])) { //run the action only if the user has access
					logger.debug('Processing Action \'' + name + '\'');
					this.actions[name].action(source, args, text);
				} else {
					logger.debug('Action \'' + name + '\' access for user \'' + source + '\' denied!');
					source.notice('Action Access denied! Derpy in action!');
				}
			}
		}
	}, this);
};

Controls.prototype.checkAccess = function(source, CorA) {
	//allow access if no rule
	if (!CorA.access) {
		return true;
	}

	//shell can run everything
	if (source.toString() === 'ItzAInternallPonyShell') {
		return true;
	}

	//array means list of allowed groups
	if (CorA.access instanceof Array) {
		return CorA.access.some(function(name) {
			if (this.config.groups[name]) {
				return this.config.groups[name].some(function(user) {
					return source.toString().match(user) !== null;
				});
			}
			return false;
		}, this);
	}

	//else try matching it directly
	return source.toString().match(CorA.access) !== null;
};

module.exports.init = function(reload) {
	if (!reload) {
		this.actions = {};
		this.commands = {};
	}
	if (typeof this.config.groups !== 'object') {
		this.config.groups = {};
	}
	if (typeof this.config.commandDelimiter !== 'string') {
		this.config.commandDelimiter = '.';
	}

	this.controls = new Controls(this.require('irc'), this.actions, this.commands, this.config);

	var module = this;

	if (!reload) {
		Object.defineProperty(this, 'commandDelimiter', {
			enumerable: true,
			configurable: false,
			get: function() {
				return module.config.commandDelimiter;
			}
		});
	}

	//export some functions from Controls
	//require(LIBS_DIR + '/helpers').export(this, this.controls, ['addCommand', 'addAction', 'removeCommand', 'removeAction', 'removeCommands', 'removeActions']);
	require(LIBS_DIR + '/helpers').export(this, this.controls, ['removeCommand', 'removeAction', 'removeCommands', 'removeActions']);

	/**
	 * bind the controls to module core
	 */

	if (!reload) {
		this.moduleActions = {};
		this.moduleCommands = {};
	}

	var proto = require(LIBS_DIR + '/module').Module.prototype;
	proto.addCommand = function(name) {
		module.controls.addCommand.apply(module.controls, arguments);
		if (typeof module.moduleCommands[this.name] === 'undefined') module.moduleCommands[this.name] = {};
		module.moduleCommands[this.name][name] = name;
		return this;
	};
	proto.addAction = function(name) {
		module.controls.addAction.apply(module.controls, arguments);
		if (typeof module.moduleActions[this.name] === 'undefined') module.moduleActions[this.name] = {};
		module.moduleActions[this.name][name] = name;
		return this;
	};
	proto.removeCommand = function() {
		module.controls.removeCommand.apply(module.controls, arguments);
		return this;
	};
	proto.removeAction = function() {
		module.controls.removeAction.apply(module.controls, arguments);
		return this;
	};

	this.addCommand = this.addAction = function() {
		throw new Error('Deprecated! Dont use \'controls\' module directly!');
	};

	//clean up the module commands/actions on unload

	function clean(name) {
		if (typeof module.moduleActions[name] !== 'undefined') {
			Object.keys(module.moduleActions[name]).forEach(function(action) {
				if (typeof module.actions[action] !== 'undefined') {
					module.controls.removeAction(action);
				}
			});
			delete module.moduleActions[name];
		}
		if (typeof module.moduleCommands[name] !== 'undefined') {
			Object.keys(module.moduleCommands[name]).forEach(function(command) {
				if (typeof module.commands[command] !== 'undefined') {
					module.controls.removeCommand(command);
				}
			});
			delete module.moduleCommands[name];
		}
	}

	this.dispatcher.on('unload', clean).on('reload-unload', clean);

	//bind
	this.dispatcher.on('irc/PRIVMSG', this.controls.parse.bind(this.controls)).on('irc/NOTICE', this.controls.parse.bind(this.controls));
};

module.exports.halt = function() {
	var proto = require(LIBS_DIR + '/module').Module.prototype;
	delete proto.addCommand;
	delete proto.addAction;
	delete proto.removeCommand;
	delete proto.removeAction;
};<|endoftext|>


<commit_before>const Joi = require('joi')
const Netmask = require('netmask').Netmask
const l10nparser = require('accept-language-parser')
const boom = require('boom')
const bson = require('bson')
const underscore = require('underscore')
const uuid = require('uuid')

const utils = require('bat-utils')
const braveJoi = utils.extras.joi
const braveHapi = utils.extras.hapi
const braveUtils = utils.extras.utils
const whitelist = utils.hapi.auth.whitelist

const grantSchema = Joi.object().keys({
  grantId: Joi.string().guid().required().description('the grant-identifier'),
  promotionId: Joi.string().guid().required().description('the associated promotion'),
  altcurrency: braveJoi.string().altcurrencyCode().required().description('the grant altcurrency'),
  probi: braveJoi.string().numeric().required().description('the grant amount in probi'),
  maturityTime: Joi.number().positive().required().description('the time the grant becomes redeemable'),
  expiryTime: Joi.number().positive().required().description('the time the grant expires')
})

const v1 = {}
const v2 = {}

const qalist = { addresses: process.env.IP_QA_WHITELIST && process.env.IP_QA_WHITELIST.split(',') }

const claimRate = {
  limit: 100,
  window: 24 * 60 * 60
}

if (qalist.addresses) {
  qalist.authorizedAddrs = []
  qalist.authorizedBlocks = []

  qalist.addresses.forEach((entry) => {
    if ((entry.indexOf('/') === -1) && (entry.split('.').length === 4)) return qalist.authorizedAddrs.push(entry)

    qalist.authorizedBlocks.push(new Netmask(entry))
  })
}

const qaOnlyP = (request) => {
  const ipaddr = whitelist.ipaddr(request)

  return (qalist.authorizedAddrs) && (qalist.authorizedAddrs.indexOf(ipaddr) === -1) &&
    (!underscore.find(qalist.authorizedBlocks, (block) => { return block.contains(ipaddr) }))
}

/*
   GET /v1/promotions
 */

v1.all = { handler: (runtime) => {
  return async (request, reply) => {
    const debug = braveHapi.debug(module, request)
    const promotions = runtime.database.get('promotions', debug)
    let entries, results

    if (qaOnlyP(request)) return reply(boom.notFound())

    entries = await promotions.find({}, { sort: { priority: 1 } })

    results = []
    entries.forEach((entry) => {
      if (entry.promotionId === '') return

      results.push(underscore.omit(entry, [ '_id', 'batchId', 'timestamp' ]))
    })
    reply(results)
  }
},
  description: 'See if a promotion is available',
  tags: [ 'api' ],

  validate: { query: {} },

  response: {
    schema: Joi.array().min(0).items(Joi.object().keys({
      promotionId: Joi.string().required().description('the promotion-identifier')
    }).unknown(true).description('promotion properties'))
  }
}

/*
   GET /v1/grants
 */

// from https://github.com/opentable/accept-language-parser/blob/master/index.js#L1
const localeRegExp = /((([a-zA-Z]+(-[a-zA-Z0-9]+){0,2})|\*)(;q=[0-1](\.[0-9]+)?)?)*/g

v1.read = { handler: (runtime) => {
  return async (request, reply) => {
    const lang = request.query.lang
    const paymentId = request.query.paymentId
    const languages = l10nparser.parse(lang)
    const query = { active: true, count: { $gt: 0 } }
    const debug = braveHapi.debug(module, request)
    const wallets = runtime.database.get('wallets', debug)
    const promotions = runtime.database.get('promotions', debug)
    let candidates, entries, priority, promotion, promotionIds

    const l10n = (o) => {
      const labels = [ 'greeting', 'message', 'text' ]

      for (let key in o) {
        let f = {
          object: () => {
            l10n(o[key])
          },
          string: () => {
            if ((labels.indexOf(key) === -1) && !(key.endsWith('Button') || key.endsWith('Markup') || key.endsWith('Text'))) {
//            return
            }

            // TBD: localization here...
          }
        }[typeof o[key]]
        if (f) f()
      }
    }

    if (qaOnlyP(request)) return reply(boom.notFound())

    if (paymentId) {
      promotionIds = []
      const wallet = await wallets.findOne({ paymentId: paymentId })
      if (!wallet) return reply(boom.notFound('no such wallet: ' + paymentId))
      if (wallet.grants) {
        wallet.grants.forEach((grant) => { promotionIds.push(grant.promotionId) })
      }
      underscore.extend(query, { promotionId: { $nin: promotionIds } })
    }

    entries = await promotions.find(query)
    if ((!entries) || (!entries[0])) return reply(boom.notFound('no promotions available'))

    candidates = []
    priority = Number.POSITIVE_INFINITY
    entries.forEach((entry) => {
      if (entry.priority > priority) return

      if (priority < entry.priority) {
        candidates = []
        priority = entry.priority
      }
      candidates.push(entry)
    })
    promotion = underscore.shuffle(candidates)[0]

    debug('grants', { languages: languages })
    l10n(promotion)

    reply(underscore.omit(promotion, [ '_id', 'priority', 'active', 'count', 'batchId', 'timestamp' ]))
  }
},
  description: 'See if a promotion is available',
  tags: [ 'api' ],

  validate: {
    query: {
      lang: Joi.string().regex(localeRegExp).optional().default('en').description('the l10n language'),
      paymentId: Joi.string().guid().optional().description('identity of the wallet')
    }
  },

  response: {
    schema: Joi.object().keys({
      promotionId: Joi.string().required().description('the promotion-identifier')
    }).unknown(true).description('promotion properties')
  }
}

/*
   PUT /v1/grants/{paymentId}
 */

v1.write = { handler: (runtime) => {
  return async (request, reply) => {
    const paymentId = request.params.paymentId.toLowerCase()
    const promotionId = request.payload.promotionId
    const debug = braveHapi.debug(module, request)
    const grants = runtime.database.get('grants', debug)
    const promotions = runtime.database.get('promotions', debug)
    const wallets = runtime.database.get('wallets', debug)
    let grant, result, state, wallet

    if (!runtime.config.redeemer) return reply(boom.badGateway('not configured for promotions'))

    const promotion = await promotions.findOne({ promotionId: promotionId })
    if (!promotion) return reply(boom.notFound('no such promotion: ' + promotionId))
    if (!promotion.active) return reply(boom.badData('promotion is not active: ' + promotionId))

    wallet = await wallets.findOne({ paymentId: paymentId })
    if (!wallet) return reply(boom.notFound('no such wallet: ' + paymentId))

    if (wallet.grants && wallet.grants.some(x => x.promotionId === promotionId)) {
      // promotion already applied to wallet
      return reply({})
    }

    // pop off one grant
    grant = await grants.findOneAndDelete({ status: 'active', promotionId: promotionId })
    if (!grant) return reply(boom.badData('promotion not available'))

    const grantInfo = underscore.extend(underscore.pick(grant, ['token', 'grantId', 'promotionId', 'status']),
      { claimTimestamp: Date.now(), claimIP: whitelist.ipaddr(request) }
    )

    // atomic find & update, only one request is able to add a grant for the given promotion to this wallet
    wallet = await wallets.findOneAndUpdate({ 'paymentId': paymentId, 'grants.promotionId': { '$ne': promotionId } },
                            { $push: { grants: grantInfo } }
    )
    if (!wallet) {
      // reinsert grant, another request already added a grant for this promotion to the wallet
      await grants.insert(grant)
      // promotion already applied to wallet
      return reply({})
    }

    // register the users claim to the grant with the redemption server
    const payload = { wallet: underscore.pick(wallet, ['altcurrency', 'provider', 'providerId']) }
    try {
      result = await braveHapi.wreck.put(runtime.config.redeemer.url + '/v1/grants/' + grant.grantId, {
        headers: {
          'Authorization': 'Bearer ' + runtime.config.redeemer.access_token,
          'Content-Type': 'application/json',
          // Only pass "trusted" IP, not previous value of X-Forwarded-For
          'X-Forwarded-For': whitelist.ipaddr(request),
          'User-Agent': request.headers['user-agent']
        },
        payload: JSON.stringify(payload),
        useProxyP: true
      })
    } catch (ex) {
      runtime.captureException(ex, { req: request })
    }

    if (runtime.config.balance) {
      // invalidate any cached balance
      try {
        await braveHapi.wreck.delete(runtime.config.balance.url + '/v2/wallet/' + paymentId + '/balance',
          {
            headers: {
              authorization: 'Bearer ' + runtime.config.balance.access_token,
              'content-type': 'application/json'
            },
            useProxyP: true
          })
      } catch (ex) {
        runtime.captureException(ex, { req: request })
      }
    }

    state = {
      $currentDate: { timestamp: { $type: 'timestamp' } },
      $inc: { count: -1 }
    }
    await promotions.update({ promotionId: promotionId }, state, { upsert: true })

    const grantContent = braveUtils.extractJws(grant.token)

    result = underscore.extend(underscore.pick(grantContent, [ 'altcurrency', 'probi' ]))
    await runtime.queue.send(debug, 'grant-report', underscore.extend({
      grantId: grantContent.grantId,
      paymentId: paymentId,
      promotionId: promotionId
    }, result))

    return reply(result)
  }
},
  description: 'Request a grant for a wallet',
  tags: [ 'api' ],

  plugins: {
    rateLimit: {
      enabled: true,
      rate: (request) => claimRate
    }
  },

  validate: {
    params: { paymentId: Joi.string().guid().required().description('identity of the wallet') },

    payload: Joi.object().keys({
      promotionId: Joi.string().required().description('the promotion-identifier')
    }).required().description('promotion derails')
  },

  response: {
    schema: Joi.object().keys({
      altcurrency: braveJoi.string().altcurrencyCode().optional().default('BAT').description('the grant altcurrency'),
      probi: braveJoi.string().numeric().optional().description('the grant amount in probi')
    }).unknown(true).description('grant properties')
  }
}

const grantsUploadSchema = {
  grants: Joi.array().min(0).items(
      Joi.string().required().description('the jws encoded grant')
    ).description('grants for bulk upload'),
  promotions: Joi.array().min(0).items(Joi.object().keys({
    promotionId: Joi.string().required().description('the promotion-identifier'),
    priority: Joi.number().integer().min(0).required().description('the promotion priority (lower is better)'),
    active: Joi.boolean().optional().default(true).description('the promotion status')
  }).unknown(true).description('promotions for bulk upload'))
}

/*
   POST /v1/grants
   POST /v2/grants
*/

const uploadGrants = function (runtime) {
  return async (request, reply) => {
    const batchId = uuid.v4().toLowerCase()
    const debug = braveHapi.debug(module, request)
    const grants = runtime.database.get('grants', debug)
    const promotions = runtime.database.get('promotions', debug)
    let state

    let payload = request.payload

    if (payload.file) {
      payload = payload.file
      const validity = Joi.validate(payload, grantsUploadSchema)
      if (validity.error) {
        return reply(boom.badData(validity.error))
      }
    }

    const grantsToInsert = []
    const promotionCounts = {}
    for (let entry of payload.grants) {
      const grantContent = braveUtils.extractJws(entry)
      const validity = Joi.validate(grantContent, grantSchema)
      if (validity.error) {
        return reply(boom.badData(validity.error))
      }
      grantsToInsert.push({ grantId: grantContent.grantId, token: entry, promotionId: grantContent.promotionId, status: 'active', batchId: batchId })
      if (!promotionCounts[grantContent.promotionId]) {
        promotionCounts[grantContent.promotionId] = 0
      }
      promotionCounts[grantContent.promotionId]++
    }

    await grants.insert(grantsToInsert)

    for (let entry of payload.promotions) {
      state = {
        $currentDate: { timestamp: { $type: 'timestamp' } },
        $set: underscore.omit(entry, ['promotionId']),
        $inc: { count: promotionCounts[entry.promotionId] }
      }
      await promotions.update({ promotionId: entry.promotionId }, state, { upsert: true })
    }

    reply({})
  }
}

v1.create =
{ handler: uploadGrants,

  auth: {
    strategy: 'session',
    scope: [ 'ledger' ],
    mode: 'required'
  },

  description: 'Create one or more grants',
  tags: [ 'api' ],

  validate: { payload: Joi.object().keys(grantsUploadSchema).required().description('data for bulk upload') },

  payload: { output: 'data', maxBytes: 1024 * 1024 * 20 },

  response:
    { schema: Joi.object().length(0) }
}

v2.create =
{ handler: uploadGrants,

  auth: {
    strategy: 'session',
    scope: [ 'ledger' ],
    mode: 'required'
  },

  description: 'Create one or more grants via file upload',
  tags: [ 'api' ],

  plugins: {
    'hapi-swagger': {
      payloadType: 'form',
      validate: {
        payload: {
          file: Joi.any()
                      .meta({ swaggerType: 'file' })
                      .description('json file')
        }
      }
    }
  },

  validate: { headers: Joi.object({ authorization: Joi.string().optional() }).unknown() },
  payload: { output: 'data', maxBytes: 1024 * 1024 * 20 },

  response:
    { schema: Joi.object().length(0) }
}

module.exports.routes = [
  braveHapi.routes.async().path('/v1/promotions').config(v1.all),
  braveHapi.routes.async().path('/v1/grants').config(v1.read),
  braveHapi.routes.async().put().path('/v1/grants/{paymentId}').config(v1.write),
  braveHapi.routes.async().post().path('/v1/grants').config(v1.create),
  braveHapi.routes.async().post().path('/v2/grants').config(v2.create)
]

module.exports.initialize = async (debug, runtime) => {
  runtime.database.checkIndices(debug, [
    {
      category: runtime.database.get('grants', debug),
      name: 'grants',
      property: 'grantId',
      empty: {
        token: '',

        // duplicated from "token" for unique
        grantId: '',
        // duplicated from "token" for filtering
        promotionId: '',

        status: '', // active, completed, expired

        batchId: '',
        timestamp: bson.Timestamp.ZERO
      },
      unique: [ { grantId: 1 } ],
      others: [ { promotionId: 1 }, { altcurrency: 1 }, { probi: 1 },
                { status: 1 },
                { batchId: 1 }, { timestamp: 1 } ]
    },
    {
      category: runtime.database.get('promotions', debug),
      name: 'promotions',
      property: 'promotionId',
      empty: {
        promotionId: '',
        priority: 99999,

        active: false,
        count: 0,

        batchId: '',
        timestamp: bson.Timestamp.ZERO
      },
      unique: [ { promotionId: 1 } ],
      others: [ { active: 1 }, { count: 1 },
                { batchId: 1 }, { timestamp: 1 } ]
    }
  ])

  await runtime.queue.create('grant-report')
  await runtime.queue.create('redeem-report')
}
<commit_msg>lower claim rate (#124)<commit_after>const Joi = require('joi')
const Netmask = require('netmask').Netmask
const l10nparser = require('accept-language-parser')
const boom = require('boom')
const bson = require('bson')
const underscore = require('underscore')
const uuid = require('uuid')

const utils = require('bat-utils')
const braveJoi = utils.extras.joi
const braveHapi = utils.extras.hapi
const braveUtils = utils.extras.utils
const whitelist = utils.hapi.auth.whitelist

const grantSchema = Joi.object().keys({
  grantId: Joi.string().guid().required().description('the grant-identifier'),
  promotionId: Joi.string().guid().required().description('the associated promotion'),
  altcurrency: braveJoi.string().altcurrencyCode().required().description('the grant altcurrency'),
  probi: braveJoi.string().numeric().required().description('the grant amount in probi'),
  maturityTime: Joi.number().positive().required().description('the time the grant becomes redeemable'),
  expiryTime: Joi.number().positive().required().description('the time the grant expires')
})

const v1 = {}
const v2 = {}

const qalist = { addresses: process.env.IP_QA_WHITELIST && process.env.IP_QA_WHITELIST.split(',') }

const claimRate = {
  limit: 10,
  window: 24 * 60 * 60
}

if (qalist.addresses) {
  qalist.authorizedAddrs = []
  qalist.authorizedBlocks = []

  qalist.addresses.forEach((entry) => {
    if ((entry.indexOf('/') === -1) && (entry.split('.').length === 4)) return qalist.authorizedAddrs.push(entry)

    qalist.authorizedBlocks.push(new Netmask(entry))
  })
}

const qaOnlyP = (request) => {
  const ipaddr = whitelist.ipaddr(request)

  return (qalist.authorizedAddrs) && (qalist.authorizedAddrs.indexOf(ipaddr) === -1) &&
    (!underscore.find(qalist.authorizedBlocks, (block) => { return block.contains(ipaddr) }))
}

/*
   GET /v1/promotions
 */

v1.all = { handler: (runtime) => {
  return async (request, reply) => {
    const debug = braveHapi.debug(module, request)
    const promotions = runtime.database.get('promotions', debug)
    let entries, results

    if (qaOnlyP(request)) return reply(boom.notFound())

    entries = await promotions.find({}, { sort: { priority: 1 } })

    results = []
    entries.forEach((entry) => {
      if (entry.promotionId === '') return

      results.push(underscore.omit(entry, [ '_id', 'batchId', 'timestamp' ]))
    })
    reply(results)
  }
},
  description: 'See if a promotion is available',
  tags: [ 'api' ],

  validate: { query: {} },

  response: {
    schema: Joi.array().min(0).items(Joi.object().keys({
      promotionId: Joi.string().required().description('the promotion-identifier')
    }).unknown(true).description('promotion properties'))
  }
}

/*
   GET /v1/grants
 */

// from https://github.com/opentable/accept-language-parser/blob/master/index.js#L1
const localeRegExp = /((([a-zA-Z]+(-[a-zA-Z0-9]+){0,2})|\*)(;q=[0-1](\.[0-9]+)?)?)*/g

v1.read = { handler: (runtime) => {
  return async (request, reply) => {
    const lang = request.query.lang
    const paymentId = request.query.paymentId
    const languages = l10nparser.parse(lang)
    const query = { active: true, count: { $gt: 0 } }
    const debug = braveHapi.debug(module, request)
    const wallets = runtime.database.get('wallets', debug)
    const promotions = runtime.database.get('promotions', debug)
    let candidates, entries, priority, promotion, promotionIds

    const l10n = (o) => {
      const labels = [ 'greeting', 'message', 'text' ]

      for (let key in o) {
        let f = {
          object: () => {
            l10n(o[key])
          },
          string: () => {
            if ((labels.indexOf(key) === -1) && !(key.endsWith('Button') || key.endsWith('Markup') || key.endsWith('Text'))) {
//            return
            }

            // TBD: localization here...
          }
        }[typeof o[key]]
        if (f) f()
      }
    }

    if (qaOnlyP(request)) return reply(boom.notFound())

    if (paymentId) {
      promotionIds = []
      const wallet = await wallets.findOne({ paymentId: paymentId })
      if (!wallet) return reply(boom.notFound('no such wallet: ' + paymentId))
      if (wallet.grants) {
        wallet.grants.forEach((grant) => { promotionIds.push(grant.promotionId) })
      }
      underscore.extend(query, { promotionId: { $nin: promotionIds } })
    }

    entries = await promotions.find(query)
    if ((!entries) || (!entries[0])) return reply(boom.notFound('no promotions available'))

    candidates = []
    priority = Number.POSITIVE_INFINITY
    entries.forEach((entry) => {
      if (entry.priority > priority) return

      if (priority < entry.priority) {
        candidates = []
        priority = entry.priority
      }
      candidates.push(entry)
    })
    promotion = underscore.shuffle(candidates)[0]

    debug('grants', { languages: languages })
    l10n(promotion)

    reply(underscore.omit(promotion, [ '_id', 'priority', 'active', 'count', 'batchId', 'timestamp' ]))
  }
},
  description: 'See if a promotion is available',
  tags: [ 'api' ],

  validate: {
    query: {
      lang: Joi.string().regex(localeRegExp).optional().default('en').description('the l10n language'),
      paymentId: Joi.string().guid().optional().description('identity of the wallet')
    }
  },

  response: {
    schema: Joi.object().keys({
      promotionId: Joi.string().required().description('the promotion-identifier')
    }).unknown(true).description('promotion properties')
  }
}

/*
   PUT /v1/grants/{paymentId}
 */

v1.write = { handler: (runtime) => {
  return async (request, reply) => {
    const paymentId = request.params.paymentId.toLowerCase()
    const promotionId = request.payload.promotionId
    const debug = braveHapi.debug(module, request)
    const grants = runtime.database.get('grants', debug)
    const promotions = runtime.database.get('promotions', debug)
    const wallets = runtime.database.get('wallets', debug)
    let grant, result, state, wallet

    if (!runtime.config.redeemer) return reply(boom.badGateway('not configured for promotions'))

    const promotion = await promotions.findOne({ promotionId: promotionId })
    if (!promotion) return reply(boom.notFound('no such promotion: ' + promotionId))
    if (!promotion.active) return reply(boom.badData('promotion is not active: ' + promotionId))

    wallet = await wallets.findOne({ paymentId: paymentId })
    if (!wallet) return reply(boom.notFound('no such wallet: ' + paymentId))

    if (wallet.grants && wallet.grants.some(x => x.promotionId === promotionId)) {
      // promotion already applied to wallet
      return reply({})
    }

    // pop off one grant
    grant = await grants.findOneAndDelete({ status: 'active', promotionId: promotionId })
    if (!grant) return reply(boom.badData('promotion not available'))

    const grantInfo = underscore.extend(underscore.pick(grant, ['token', 'grantId', 'promotionId', 'status']),
      { claimTimestamp: Date.now(), claimIP: whitelist.ipaddr(request) }
    )

    // atomic find & update, only one request is able to add a grant for the given promotion to this wallet
    wallet = await wallets.findOneAndUpdate({ 'paymentId': paymentId, 'grants.promotionId': { '$ne': promotionId } },
                            { $push: { grants: grantInfo } }
    )
    if (!wallet) {
      // reinsert grant, another request already added a grant for this promotion to the wallet
      await grants.insert(grant)
      // promotion already applied to wallet
      return reply({})
    }

    // register the users claim to the grant with the redemption server
    const payload = { wallet: underscore.pick(wallet, ['altcurrency', 'provider', 'providerId']) }
    try {
      result = await braveHapi.wreck.put(runtime.config.redeemer.url + '/v1/grants/' + grant.grantId, {
        headers: {
          'Authorization': 'Bearer ' + runtime.config.redeemer.access_token,
          'Content-Type': 'application/json',
          // Only pass "trusted" IP, not previous value of X-Forwarded-For
          'X-Forwarded-For': whitelist.ipaddr(request),
          'User-Agent': request.headers['user-agent']
        },
        payload: JSON.stringify(payload),
        useProxyP: true
      })
    } catch (ex) {
      runtime.captureException(ex, { req: request })
    }

    if (runtime.config.balance) {
      // invalidate any cached balance
      try {
        await braveHapi.wreck.delete(runtime.config.balance.url + '/v2/wallet/' + paymentId + '/balance',
          {
            headers: {
              authorization: 'Bearer ' + runtime.config.balance.access_token,
              'content-type': 'application/json'
            },
            useProxyP: true
          })
      } catch (ex) {
        runtime.captureException(ex, { req: request })
      }
    }

    state = {
      $currentDate: { timestamp: { $type: 'timestamp' } },
      $inc: { count: -1 }
    }
    await promotions.update({ promotionId: promotionId }, state, { upsert: true })

    const grantContent = braveUtils.extractJws(grant.token)

    result = underscore.extend(underscore.pick(grantContent, [ 'altcurrency', 'probi' ]))
    await runtime.queue.send(debug, 'grant-report', underscore.extend({
      grantId: grantContent.grantId,
      paymentId: paymentId,
      promotionId: promotionId
    }, result))

    return reply(result)
  }
},
  description: 'Request a grant for a wallet',
  tags: [ 'api' ],

  plugins: {
    rateLimit: {
      enabled: true,
      rate: (request) => claimRate
    }
  },

  validate: {
    params: { paymentId: Joi.string().guid().required().description('identity of the wallet') },

    payload: Joi.object().keys({
      promotionId: Joi.string().required().description('the promotion-identifier')
    }).required().description('promotion derails')
  },

  response: {
    schema: Joi.object().keys({
      altcurrency: braveJoi.string().altcurrencyCode().optional().default('BAT').description('the grant altcurrency'),
      probi: braveJoi.string().numeric().optional().description('the grant amount in probi')
    }).unknown(true).description('grant properties')
  }
}

const grantsUploadSchema = {
  grants: Joi.array().min(0).items(
      Joi.string().required().description('the jws encoded grant')
    ).description('grants for bulk upload'),
  promotions: Joi.array().min(0).items(Joi.object().keys({
    promotionId: Joi.string().required().description('the promotion-identifier'),
    priority: Joi.number().integer().min(0).required().description('the promotion priority (lower is better)'),
    active: Joi.boolean().optional().default(true).description('the promotion status')
  }).unknown(true).description('promotions for bulk upload'))
}

/*
   POST /v1/grants
   POST /v2/grants
*/

const uploadGrants = function (runtime) {
  return async (request, reply) => {
    const batchId = uuid.v4().toLowerCase()
    const debug = braveHapi.debug(module, request)
    const grants = runtime.database.get('grants', debug)
    const promotions = runtime.database.get('promotions', debug)
    let state

    let payload = request.payload

    if (payload.file) {
      payload = payload.file
      const validity = Joi.validate(payload, grantsUploadSchema)
      if (validity.error) {
        return reply(boom.badData(validity.error))
      }
    }

    const grantsToInsert = []
    const promotionCounts = {}
    for (let entry of payload.grants) {
      const grantContent = braveUtils.extractJws(entry)
      const validity = Joi.validate(grantContent, grantSchema)
      if (validity.error) {
        return reply(boom.badData(validity.error))
      }
      grantsToInsert.push({ grantId: grantContent.grantId, token: entry, promotionId: grantContent.promotionId, status: 'active', batchId: batchId })
      if (!promotionCounts[grantContent.promotionId]) {
        promotionCounts[grantContent.promotionId] = 0
      }
      promotionCounts[grantContent.promotionId]++
    }

    await grants.insert(grantsToInsert)

    for (let entry of payload.promotions) {
      state = {
        $currentDate: { timestamp: { $type: 'timestamp' } },
        $set: underscore.omit(entry, ['promotionId']),
        $inc: { count: promotionCounts[entry.promotionId] }
      }
      await promotions.update({ promotionId: entry.promotionId }, state, { upsert: true })
    }

    reply({})
  }
}

v1.create =
{ handler: uploadGrants,

  auth: {
    strategy: 'session',
    scope: [ 'ledger' ],
    mode: 'required'
  },

  description: 'Create one or more grants',
  tags: [ 'api' ],

  validate: { payload: Joi.object().keys(grantsUploadSchema).required().description('data for bulk upload') },

  payload: { output: 'data', maxBytes: 1024 * 1024 * 20 },

  response:
    { schema: Joi.object().length(0) }
}

v2.create =
{ handler: uploadGrants,

  auth: {
    strategy: 'session',
    scope: [ 'ledger' ],
    mode: 'required'
  },

  description: 'Create one or more grants via file upload',
  tags: [ 'api' ],

  plugins: {
    'hapi-swagger': {
      payloadType: 'form',
      validate: {
        payload: {
          file: Joi.any()
                      .meta({ swaggerType: 'file' })
                      .description('json file')
        }
      }
    }
  },

  validate: { headers: Joi.object({ authorization: Joi.string().optional() }).unknown() },
  payload: { output: 'data', maxBytes: 1024 * 1024 * 20 },

  response:
    { schema: Joi.object().length(0) }
}

module.exports.routes = [
  braveHapi.routes.async().path('/v1/promotions').config(v1.all),
  braveHapi.routes.async().path('/v1/grants').config(v1.read),
  braveHapi.routes.async().put().path('/v1/grants/{paymentId}').config(v1.write),
  braveHapi.routes.async().post().path('/v1/grants').config(v1.create),
  braveHapi.routes.async().post().path('/v2/grants').config(v2.create)
]

module.exports.initialize = async (debug, runtime) => {
  runtime.database.checkIndices(debug, [
    {
      category: runtime.database.get('grants', debug),
      name: 'grants',
      property: 'grantId',
      empty: {
        token: '',

        // duplicated from "token" for unique
        grantId: '',
        // duplicated from "token" for filtering
        promotionId: '',

        status: '', // active, completed, expired

        batchId: '',
        timestamp: bson.Timestamp.ZERO
      },
      unique: [ { grantId: 1 } ],
      others: [ { promotionId: 1 }, { altcurrency: 1 }, { probi: 1 },
                { status: 1 },
                { batchId: 1 }, { timestamp: 1 } ]
    },
    {
      category: runtime.database.get('promotions', debug),
      name: 'promotions',
      property: 'promotionId',
      empty: {
        promotionId: '',
        priority: 99999,

        active: false,
        count: 0,

        batchId: '',
        timestamp: bson.Timestamp.ZERO
      },
      unique: [ { promotionId: 1 } ],
      others: [ { active: 1 }, { count: 1 },
                { batchId: 1 }, { timestamp: 1 } ]
    }
  ])

  await runtime.queue.create('grant-report')
  await runtime.queue.create('redeem-report')
}
<|endoftext|>