<commit_before>var gulp = require('gulp');
var s3 = require('gulp-s3');
var browserify = require('gulp-browserify');
var concat = require('gulp-concat');
var uglify = require('gulp-uglify')
var rename = require('gulp-rename');
var connect = require('gulp-connect');
var less = require('gulp-less');
var clean = require('gulp-clean');
var jshint = require('gulp-jshint');
var gzip = require('gulp-gzip');
var template = require('gulp-template');
var watch = require('gulp-watch');
var path = require('path');
var combineCSS = require('combine-css');
var gulpif = require('gulp-if');
var gutil = require('gulp-util');
var handlebars = require('gulp-handlebars');
// var styl = require('gulp-styl');
// var refresh = require('gulp-livereload');
// var lr = require('tiny-lr');
// var server = lr();
var header = require('gulp-header');
var hbsfy = require("hbsfy").configure({
  extensions: ["handlebars"]
});
var fs = require('fs');

var useJsHint = false;
var destRoot = __dirname + "/devel";
var devMode = true;

gulp.task('connect', connect.server({
  root: destRoot,
  port: 8000,
  // livereload: true,
  // open: {
  //   browser: 'chrome' // if not working OS X browser: 'Google Chrome'
  // }
}));

gulp.task('cleancss', function(){
  gulp.src(destRoot + '/css', {read: false})
      .pipe(clean())
})

gulp.task('less', function(){
  gulp.src([
    devMode ? "css/polis_main_devel.less" : "css/polis_main_dist.less",
    ])
      .pipe(less())
      .pipe(concat("polis.css"))
      .pipe(gulp.dest(destRoot + '/css'))
});

gulp.task('fontawesome', function() {
  gulp.src('bower_components/font-awesome/font/**/*')
    .pipe(gulp.dest(destRoot + "/font"));
});
// TODO remove
gulp.task('sparklines', function() {
  var s = gulp.src('sparklines.svg')
    .pipe(gulp.dest(destRoot));
});
gulp.task('index', [
  'sparklines',
], function() {
  var s = gulp.src('index.html');
  if (devMode) {
    s = s.pipe(template({
      basepath: '',
      d3Filename: 'd3.js',
    }))
  } else {
    s = s.pipe(template({
      basepath: 'https://s3.amazonaws.com/www.polis.io',
      d3Filename: 'd3.min.js',
    }));
  }
  return s.pipe(gulp.dest(destRoot));
});

gulp.task('templates', function(){
  gulp.src(['js/templates/*.hbs', 'js/templates/*.handlebars'])
    .pipe(handlebars({
      outputType: 'node',
      wrapped: true,
    }))
    .pipe(gulp.dest('js/tmpl'));
});

gulp.task('jshint', function(){
  gulp.src('js/**/*.js')
      .pipe(gulpif(useJsHint, jshint(

      {
         // reporter: 'jslint',
          curly: true, // require if,else blocks to have {}
          eqeqeq: true,
          trailing: true, // no trailing whitespace allowed
          // immed: true,
          // latedef: true,
          // newcap: true,
          // noarg: true,
          // sub: true,
          // undef: true,
        //  unused: true,
          quotmark: "double",
         // plusplus: true, // no ++ or --
        //  nonew: true,
          noarg: true, // no arguments.caller and arguments.callee (allow for optimizations)
          newcap: true, // constructors must be capitalized
        //  latedef: "nofunc",
         // indent: 2,
          immed: true,
//          forin: true, require hasOwnProperty checks
          boss: true,
//          debug: true, // uncomment temporarily when you want to allow debugger; statements.
          // browser: true,
          es3: true,
          globals: {
            d3: true,
            jQuery: true,
            console: true,
            require: true,
            define: true,
            requirejs: true,
            describe: true,
            expect: true,
            module: true,
            // it: true
          },
          // relax: eventually we should get rid of these
            //expr: true,
           // loopfunc: true,
            //shadow: true,
        }
        )))
      .pipe(jshint.reporter('default'))
})

gulp.task('scripts', ['templates', 'jshint'], function() {
  // Single entry point to browserify
  var s = gulp.src('js/main.js')
      .pipe(browserify({
        insertGlobals : true,
        debug : false, //!gulp.env.production
        // transform: ['hbsfy'],
        shim : {
          //TODO 'handlebars': 'templates/helpers/handlebarsWithHelpers', //this one has polis custom template helpers
          handlebars: {
            path : 'bower_components/handlebars/handlebars.runtime.js', //original handlebars
            exports: 'Handlebars',
          },
          originalbackbone: {
            path: 'bower_components/backbone/backbone', // backbone before modifications
            depends: { jquery: '$', underscore: '_' },
            exports: 'Backbone',
          },
          backbone: {
            path: 'js/net/backbonePolis', // polis-specific backbone modifications
            depends: { originalbackbone: "Backbone" },
            exports: "Backbone",
          },
          underscore: {
            path: 'bower_components/underscore/underscore',
            exports: '_',
          },
          thorax: {
            path: 'bower_components/thorax/thorax',
            depends: { handlebars: 'Handlebars', backbone: 'Backbone' },
            exports: 'Thorax',
          },
          bootstrap_alert: {  //all bootstrap files need to be added to the dependency array of js/main.js
            path: 'bower_components/bootstrap/js/alert',
            depends: { jquery: "jQuery" },
            exports: null,
          },
          bootstrap_tab: {
            path : 'bower_components/bootstrap/js/tab',
            depends: { jquery: "jQuery" },
            exports: null,
          },
          bootstrap_popover: {
            path: 'bower_components/bootstrap/js/popover',
            depends: { jquery: "jQuery" },
            exports: null,
          },
          bootstrap_collapse: {
            path: 'bower_components/bootstrap/js/collapse',
            depends: { jquery: "jQuery" },
            exports: null,
          },
          bootstrap_dropdown: {
            path: 'bower_components/bootstrap/js/dropdown',
            depends: { jquery: "jQuery" },
            exports: null,
          },
          bootstrap_affix: {
            path: 'bower_components/bootstrap/js/affix',
            depends: { jquery: "jQuery" },
            exports: null,
          },
          d3tooltips: {
            path: 'bower_components/d3-tip/index',
            depends: { jquery: "jQuery" },
            exports: null,
          },
          bootstrap_tooltip: {
            path: 'bower_components/bootstrap/js/tooltip',
            depends: { jquery: "jQuery" },
            exports: null,
          },
          bootstrap_button: {
            path: 'bower_components/bootstrap/js/button',
            depends: { jquery: "jQuery" },
            exports: null,
          },
          bootstrap_transition: {
            path: 'bower_components/bootstrap/js/transition',
            depends: { jquery: "jQuery" },
            exports: null,
          },
          VisView: {
            path: 'js/lib/VisView',
            depends: { d3tooltips: 'foo' }, // added to d3 object as d3.tip
            exports: 'VisView',
          },
        },
      }))
      .pipe(concat('polis.js'))
  // TODO      .pipe(header("copyright Polis... (except that libs are mixed in)
      if (!devMode) {
        s = s
          .pipe(uglify())
          .pipe(gzip())
      }
      s = s.pipe(rename('polis.js'));
      return s.pipe(gulp.dest(destRoot + "/js"));
});

// for big infrequently changing scripts that we don't want to concatenate
// on each dev build.
gulp.task("scriptsOther", function() {

  var files = [];
  if (devMode) {
    files.push('bower_components/d3/d3.js');
  } else {
    files.push('bower_components/d3/d3.min.js');
  }
  var s = gulp.src(files);
  if (!devMode) {
    s = s
      .pipe(uglify())
      .pipe(gzip())
      .pipe(rename(function (path) {
        // path.dirname += "/ciao";
        // path.basename += "-goodbye";
        // path.extname = ".md"

        // remove .gz extension
        var ext = path.extname;
        path.extname = ext.substr(0, ext.length- ".gz".length);
      }));
  }
  return s.pipe(gulp.dest(destRoot + "/js"));
});

gulp.task("configureForProduction", function() {
  destRoot = "./dist";
  devMode = false;
});

gulp.task('common', [
  "scriptsOther",
  "scripts",
  "less",
  "fontawesome",
  "index",
  ], function() {
    // if (devMode) {
    //   connect.reload();
    // }
});

gulp.task('dev', [
  "common",
  ], function(){
});

gulp.task('dist', [
  "configureForProduction",
  "common",
  ], function(){
});

gulp.task("watchForDev", [
  "connect",
  ], function() {
    gulp.watch([
      "js/**",
      "!js/tmpl/**", // These are genterated, so don't watch!
      "css/**",
      "*.html",
    ], function(e) {
      console.log("watch saw: " + e.path + " " + e.type);
      gulp.run("dev");
    });
});

gulp.task('deploy', [
  // "dist"
  "configureForProduction",
  ], function() {

    var creds = JSON.parse(fs.readFileSync('.polis_s3_creds_client.json'));
    creds.bucket = "www.polis.io";

    // Files without Gzip
    gulp.src([
      destRoot + '/**',
      '!' + destRoot + '/js/**',
      ], {read: false}).pipe(s3(creds, {
        delay: 1000,
        headers: {
          'x-amz-acl': 'public-read',
        }
      }));

    // Gzipped Files
    gulp.src([
      destRoot + '/**/js/**', // simply saying "/js/**" causes the 'js' prefix to be stripped, and the files end up in the root of the bucket.
      ], {read: false}).pipe(s3(creds, {
        delay: 1000,
        headers: {
          'x-amz-acl': 'public-read',
          'Content-Encoding': 'gzip',
        }
      }));

});

// For now, you'll have to copy the assets from the other repo into the "about" directory
gulp.task('deployAboutPage', [
  ], function() {

    var creds = JSON.parse(fs.readFileSync('.polis_s3_creds_client.json'));
    creds.bucket = "about.polis.io";

    var root = "../about-polis";
    gulp.src([
      root + "/index.html",
      root + "/**/bower_components/bootstrap/dist/css/bootstrap.css", // ** to preserve path
      root + "/sparklines.svg",
      root + "/snowcity.JPG",
      // './about/**',
      // '!' + root + '/*.jpeg',
      // '!' + root + '/*.jpg',
      // '!' + root + '/*.png',
      ], {read: false}).pipe(s3(creds, {
        delay: 1000,
        headers: {
          'x-amz-acl': 'public-read',
        }
      }));

    // gulp.src([
    //   // root + '/*.jpeg',
    //   // root + '/*.jpg',
    //   // root + '/*.png',
    //   ], {read: false}).pipe(s3(creds, {
    //     delay: 1000,
    //     headers: {
    //       'x-amz-acl': 'public-read',
    //       'Content-Encoding': 'gzip',
    //     }
    //   }));

});

gulp.task('default', [
  "dev",
  "watchForDev",
  ], function() {
});

<commit_msg>gulp snowcity*<commit_after>var gulp = require('gulp');
var s3 = require('gulp-s3');
var browserify = require('gulp-browserify');
var concat = require('gulp-concat');
var uglify = require('gulp-uglify')
var rename = require('gulp-rename');
var connect = require('gulp-connect');
var less = require('gulp-less');
var clean = require('gulp-clean');
var jshint = require('gulp-jshint');
var gzip = require('gulp-gzip');
var template = require('gulp-template');
var watch = require('gulp-watch');
var path = require('path');
var combineCSS = require('combine-css');
var gulpif = require('gulp-if');
var gutil = require('gulp-util');
var handlebars = require('gulp-handlebars');
// var styl = require('gulp-styl');
// var refresh = require('gulp-livereload');
// var lr = require('tiny-lr');
// var server = lr();
var header = require('gulp-header');
var hbsfy = require("hbsfy").configure({
  extensions: ["handlebars"]
});
var fs = require('fs');

var useJsHint = false;
var destRoot = __dirname + "/devel";
var devMode = true;

gulp.task('connect', connect.server({
  root: destRoot,
  port: 8000,
  // livereload: true,
  // open: {
  //   browser: 'chrome' // if not working OS X browser: 'Google Chrome'
  // }
}));

gulp.task('cleancss', function(){
  gulp.src(destRoot + '/css', {read: false})
      .pipe(clean())
})

gulp.task('less', function(){
  gulp.src([
    devMode ? "css/polis_main_devel.less" : "css/polis_main_dist.less",
    ])
      .pipe(less())
      .pipe(concat("polis.css"))
      .pipe(gulp.dest(destRoot + '/css'))
});

gulp.task('fontawesome', function() {
  gulp.src('bower_components/font-awesome/font/**/*')
    .pipe(gulp.dest(destRoot + "/font"));
});
// TODO remove
gulp.task('sparklines', function() {
  var s = gulp.src('sparklines.svg')
    .pipe(gulp.dest(destRoot));
});
gulp.task('index', [
  'sparklines',
], function() {
  var s = gulp.src('index.html');
  if (devMode) {
    s = s.pipe(template({
      basepath: '',
      d3Filename: 'd3.js',
    }))
  } else {
    s = s.pipe(template({
      basepath: 'https://s3.amazonaws.com/www.polis.io',
      d3Filename: 'd3.min.js',
    }));
  }
  return s.pipe(gulp.dest(destRoot));
});

gulp.task('templates', function(){
  gulp.src(['js/templates/*.hbs', 'js/templates/*.handlebars'])
    .pipe(handlebars({
      outputType: 'node',
      wrapped: true,
    }))
    .pipe(gulp.dest('js/tmpl'));
});

gulp.task('jshint', function(){
  gulp.src('js/**/*.js')
      .pipe(gulpif(useJsHint, jshint(

      {
         // reporter: 'jslint',
          curly: true, // require if,else blocks to have {}
          eqeqeq: true,
          trailing: true, // no trailing whitespace allowed
          // immed: true,
          // latedef: true,
          // newcap: true,
          // noarg: true,
          // sub: true,
          // undef: true,
        //  unused: true,
          quotmark: "double",
         // plusplus: true, // no ++ or --
        //  nonew: true,
          noarg: true, // no arguments.caller and arguments.callee (allow for optimizations)
          newcap: true, // constructors must be capitalized
        //  latedef: "nofunc",
         // indent: 2,
          immed: true,
//          forin: true, require hasOwnProperty checks
          boss: true,
//          debug: true, // uncomment temporarily when you want to allow debugger; statements.
          // browser: true,
          es3: true,
          globals: {
            d3: true,
            jQuery: true,
            console: true,
            require: true,
            define: true,
            requirejs: true,
            describe: true,
            expect: true,
            module: true,
            // it: true
          },
          // relax: eventually we should get rid of these
            //expr: true,
           // loopfunc: true,
            //shadow: true,
        }
        )))
      .pipe(jshint.reporter('default'))
})

gulp.task('scripts', ['templates', 'jshint'], function() {
  // Single entry point to browserify
  var s = gulp.src('js/main.js')
      .pipe(browserify({
        insertGlobals : true,
        debug : false, //!gulp.env.production
        // transform: ['hbsfy'],
        shim : {
          //TODO 'handlebars': 'templates/helpers/handlebarsWithHelpers', //this one has polis custom template helpers
          handlebars: {
            path : 'bower_components/handlebars/handlebars.runtime.js', //original handlebars
            exports: 'Handlebars',
          },
          originalbackbone: {
            path: 'bower_components/backbone/backbone', // backbone before modifications
            depends: { jquery: '$', underscore: '_' },
            exports: 'Backbone',
          },
          backbone: {
            path: 'js/net/backbonePolis', // polis-specific backbone modifications
            depends: { originalbackbone: "Backbone" },
            exports: "Backbone",
          },
          underscore: {
            path: 'bower_components/underscore/underscore',
            exports: '_',
          },
          thorax: {
            path: 'bower_components/thorax/thorax',
            depends: { handlebars: 'Handlebars', backbone: 'Backbone' },
            exports: 'Thorax',
          },
          bootstrap_alert: {  //all bootstrap files need to be added to the dependency array of js/main.js
            path: 'bower_components/bootstrap/js/alert',
            depends: { jquery: "jQuery" },
            exports: null,
          },
          bootstrap_tab: {
            path : 'bower_components/bootstrap/js/tab',
            depends: { jquery: "jQuery" },
            exports: null,
          },
          bootstrap_popover: {
            path: 'bower_components/bootstrap/js/popover',
            depends: { jquery: "jQuery" },
            exports: null,
          },
          bootstrap_collapse: {
            path: 'bower_components/bootstrap/js/collapse',
            depends: { jquery: "jQuery" },
            exports: null,
          },
          bootstrap_dropdown: {
            path: 'bower_components/bootstrap/js/dropdown',
            depends: { jquery: "jQuery" },
            exports: null,
          },
          bootstrap_affix: {
            path: 'bower_components/bootstrap/js/affix',
            depends: { jquery: "jQuery" },
            exports: null,
          },
          d3tooltips: {
            path: 'bower_components/d3-tip/index',
            depends: { jquery: "jQuery" },
            exports: null,
          },
          bootstrap_tooltip: {
            path: 'bower_components/bootstrap/js/tooltip',
            depends: { jquery: "jQuery" },
            exports: null,
          },
          bootstrap_button: {
            path: 'bower_components/bootstrap/js/button',
            depends: { jquery: "jQuery" },
            exports: null,
          },
          bootstrap_transition: {
            path: 'bower_components/bootstrap/js/transition',
            depends: { jquery: "jQuery" },
            exports: null,
          },
          VisView: {
            path: 'js/lib/VisView',
            depends: { d3tooltips: 'foo' }, // added to d3 object as d3.tip
            exports: 'VisView',
          },
        },
      }))
      .pipe(concat('polis.js'))
  // TODO      .pipe(header("copyright Polis... (except that libs are mixed in)
      if (!devMode) {
        s = s
          .pipe(uglify())
          .pipe(gzip())
      }
      s = s.pipe(rename('polis.js'));
      return s.pipe(gulp.dest(destRoot + "/js"));
});

// for big infrequently changing scripts that we don't want to concatenate
// on each dev build.
gulp.task("scriptsOther", function() {

  var files = [];
  if (devMode) {
    files.push('bower_components/d3/d3.js');
  } else {
    files.push('bower_components/d3/d3.min.js');
  }
  var s = gulp.src(files);
  if (!devMode) {
    s = s
      .pipe(uglify())
      .pipe(gzip())
      .pipe(rename(function (path) {
        // path.dirname += "/ciao";
        // path.basename += "-goodbye";
        // path.extname = ".md"

        // remove .gz extension
        var ext = path.extname;
        path.extname = ext.substr(0, ext.length- ".gz".length);
      }));
  }
  return s.pipe(gulp.dest(destRoot + "/js"));
});

gulp.task("configureForProduction", function() {
  destRoot = "./dist";
  devMode = false;
});

gulp.task('common', [
  "scriptsOther",
  "scripts",
  "less",
  "fontawesome",
  "index",
  ], function() {
    // if (devMode) {
    //   connect.reload();
    // }
});

gulp.task('dev', [
  "common",
  ], function(){
});

gulp.task('dist', [
  "configureForProduction",
  "common",
  ], function(){
});

gulp.task("watchForDev", [
  "connect",
  ], function() {
    gulp.watch([
      "js/**",
      "!js/tmpl/**", // These are genterated, so don't watch!
      "css/**",
      "*.html",
    ], function(e) {
      console.log("watch saw: " + e.path + " " + e.type);
      gulp.run("dev");
    });
});

gulp.task('deploy', [
  // "dist"
  "configureForProduction",
  ], function() {

    var creds = JSON.parse(fs.readFileSync('.polis_s3_creds_client.json'));
    creds.bucket = "www.polis.io";

    // Files without Gzip
    gulp.src([
      destRoot + '/**',
      '!' + destRoot + '/js/**',
      ], {read: false}).pipe(s3(creds, {
        delay: 1000,
        headers: {
          'x-amz-acl': 'public-read',
        }
      }));

    // Gzipped Files
    gulp.src([
      destRoot + '/**/js/**', // simply saying "/js/**" causes the 'js' prefix to be stripped, and the files end up in the root of the bucket.
      ], {read: false}).pipe(s3(creds, {
        delay: 1000,
        headers: {
          'x-amz-acl': 'public-read',
          'Content-Encoding': 'gzip',
        }
      }));

});

// For now, you'll have to copy the assets from the other repo into the "about" directory
gulp.task('deployAboutPage', [
  ], function() {

    var creds = JSON.parse(fs.readFileSync('.polis_s3_creds_client.json'));
    creds.bucket = "about.polis.io";

    var root = "../about-polis";
    gulp.src([
      root + "/index.html",
      root + "/**/bower_components/bootstrap/dist/css/bootstrap.css", // ** to preserve path
      root + "/snowcity*.jpg",
      ], {read: false}).pipe(s3(creds, {
        delay: 1000,
        headers: {
          'x-amz-acl': 'public-read',
        }
      }));
});

gulp.task('default', [
  "dev",
  "watchForDev",
  ], function() {
});

<|endoftext|>


<commit_before>//plugins
var autoprefixer = require('autoprefixer'),
    browserSync = require('browser-sync'),
    cache = require('gulp-cache'),
    concat = require('gulp-concat'),
    cssnano = require('gulp-cssnano'),
    del = require('del'),
    gulp = require('gulp'),
    gulpif = require('gulp-if'),
    gulpSequence = require('gulp-sequence'),
    htmlmin = require('gulp-htmlmin'),
    imagemin = require('gulp-imagemin'),
    jshint = require('gulp-jshint'),
    lazypipe = require('lazypipe'),
    mainBowerFiles = require('main-bower-files'),
    mergequeries = require('gulp-merge-media-queries'),
    plumber = require('gulp-plumber'),
    postcss = require('gulp-postcss'),
    purgecss = require('gulp-purgecss'),
    sass = require('gulp-sass'),
    size = require('gulp-size'),
    sourcemaps = require('gulp-sourcemaps'),
    uglify = require('gulp-uglify'),
    useref = require('gulp-useref'),
    wiredep = require('wiredep').stream;

//things we don't want to type every time
var reload = browserSync.reload,
    autoprefixList = ['last 2 versions', 'safari 5', 'ie 8', 'ie 9', 'opera 12.1', 'ios 6', 'android 4'];

//project paths
var config = {
    global: { //main folders
        input: 'src',
        output: 'dist',
        tmp: '.tmp'
    },
    bower: { //bower.json file
        input: 'bower.json'
    },
    clean: { //generated directories
        tmp: '.tmp/*',
        dist: 'dist/*'
    },
    fonts: { //font paths
        input: 'src/fonts/**/*',
        output: 'dist/fonts',
        bower: '**/*.{eot,svg,ttf,woff,woff2}', //we only need these file types for the 'fonts' task
        tmp: '.tmp/fonts'
    },
    html: { //html paths
        input: 'src/**/*.html',
    },
    images: { //image paths
        input: 'src/images/**/*',
        output: 'dist/images'
    },
    scripts: { //script paths
        input: 'src/scripts/**/*.js',
        output: 'dist/js',
        tmp: '.tmp/js'
    },
    size: { //dislays size of the folder below it's build
        output: 'dist/**/*'
    },
    static: { //static files --> everything except html files
        input: ['src/*.*', '!src/*.html']
    },
    styles: { //style paths
        all: 'src/scss/**/*.{scss,sass}',
        input: 'src/scss/main.{scss,sass}',
        output: 'dist/css',
        bower: 'src/scss',
        tmp: '.tmp/css'
    }
}

//set up local server with browsersync - http://localhost:3000
gulp.task('serve', ['styles', 'scripts', 'fonts'], function() {
    browserSync({
        server: {
            baseDir:[config.global.tmp, config.global.input], //.tmp and src as base directories
            routes: {
                '/bower_components': 'bower_components' //serve bower_components as if it's located in 'src'
            }
        },
        notify: false,
        port: 3000 //configure port to your liking
    });

    //watch html, js and images and reload browser when something changes
    gulp.watch([
        config.html.input,
        config.scripts.input,
        config.images.input
    ]).on('change', reload);

    //watch scss, js, fonts and bower.js and execute task accordingly
    gulp.watch(config.styles.all, ['styles']);
    gulp.watch(config.scripts.input, ['scripts']);
    gulp.watch(config.fonts.input, ['fonts']);
    gulp.watch(config.bower.input, ['wiredep', 'fonts']); //execute wiredep when bower.json changes; this will automatically inject assets from bower_components in our HTML or SCSS
});

//compile scss, autoprefix with postcss and make sourcemap
gulp.task('styles', function() {
    return gulp.src(config.styles.all)
        .pipe(plumber()) //plumber prevents pipe breaking caused by errors from gulp plugins
        .pipe(sourcemaps.init())
        .pipe(sass({
            outputStyle: 'nested', //output values: nested, expanded, compact and compressed
            includePaths: ['.'] //resolves Sass @imports for external libraries
        }).on('error', sass.logError))
        .pipe(postcss([autoprefixer({ browsers: autoprefixList })])) //autoprefix css with earlier specified list
        .pipe(sourcemaps.write()) //write sourcemap
        .pipe(gulp.dest(config.styles.tmp)) //output to tmp/css folder (dev only)
        .pipe(reload({ stream: true }));
});

//lints js files
gulp.task('scripts', function() {
    return gulp.src([config.scripts.input])
        .pipe(plumber())
        .pipe(jshint())
        .pipe(jshint.reporter('default')) //lint js
        .pipe(gulp.dest(config.scripts.tmp)) //output to tmp/js folder (dev only)
        .pipe(reload({ stream:true, once: true }));
});

//move fonts to dist and tmp
//also moves used fonts from bower_components (like fontawesome) --> override main option in bower.json
gulp.task('fonts', function() {
    return gulp.src(mainBowerFiles(config.fonts.bower, function(err){}) // get the main bower files, filter by ext: eot,svg,ttf,woff or woff2 for this task
        .concat(config.fonts.input)) //concatenate our own fonts to the array
        .pipe(gulp.dest(config.fonts.tmp))
        .pipe(gulp.dest(config.fonts.output));
});

//compress images - PNG, JPG, GIF, SVG
gulp.task('images', function() {
    return gulp.src([config.images.input])
        .pipe(plumber())
        .pipe(cache(imagemin({
                optimizationLevel: 6,
                progressive: true,
                interlaced: true,
                svgoPlugins: [{ cleanupIDs: false }] //don't remove IDs from SVG files
            })))
        .pipe(gulp.dest(config.images.output));
});

//make js, css and html files ready for deployment:
//js files between 'build:js' blocks in our HTML will be concatenated and uglified
//css files between 'build:css' in our HTML will be concatenated, minified and unused css will be removed; matching media queries will be merged
//html files will be minified
gulp.task('build', function() {
    return gulp.src(config.html.input)
        .pipe(plumber())
        .pipe(useref({ searchPath: ['.tmp', 'src', '.'] }))
        .pipe(gulpif('*.js', uglify()))
        .pipe(gulpif('*.css', optimizeCss())) //optimize css by calling a lazily-initialized pipeline
        .pipe(gulpif('*.html', htmlmin({ collapseWhitespace: true, removeComments: true })))
        .pipe(gulp.dest(config.global.output))
});

//with lazypipe we can create a chain of events in our gulpif condition
//purgecss will remove unused css and cssnano will minify it
var optimizeCss = lazypipe()
    .pipe(purgecss, {
        content: [config.html.input], //which html files should be purged
        whitelist: ['is--active'] //leave certain selectors untouched in our css (selectors that are added via JS)
    })
    .pipe(mergequeries, {
        log: false //merge media queries - put true if you wanna see which media queries were processed
    })
    .pipe(cssnano, {
        safe: true, // http://cssnano.co/options/#optionssafe-bool
        autoprefixer: false, //don't autoprefix - our styles task already took care of that
        discardComments: {
            removeAll: true //remove all comments
        }
    });

//inject bower components in SCSS and HTML files
gulp.task('wiredep', function() {
    gulp.src(config.styles.input) //inject between 'bower' blocks in main.scss file
        .pipe(plumber())
        .pipe(wiredep({
            ignorePath: /^(\.\.\/)+/
        }))
        .pipe(gulp.dest(config.styles.bower)); //injects them in src/scss folder

    gulp.src(config.html.input) //inject between 'bower' blocks in .html files
        .pipe(plumber())
        .pipe(wiredep({
            ignorePath: /^(\.\.\/)*\.\./
        }))
        .pipe(gulp.dest(config.global.input)); //injects them in src/ folder
});

//move all static files to dist folder (robots.txt, humans.txt, favicon,...)
//hidden (.git for example) folders will be ignored
gulp.task('static', function() {
    return gulp.src(config.static.input, {
        dot: true
    }).pipe(gulp.dest(config.global.output));
});

//clean dist and .tmp folder (you can also use rm -rf <folder_name> if you don't want to use a plugin)
gulp.task('clean:dist', function() {
    return del(config.clean.dist);
});
gulp.task('clean:tmp', function() {
    return del(config.clean.tmp);
});

//clear cache if needed
gulp.task('clear', function(done) {
    return cache.clearAll(done);
});

//display the size of dist files
gulp.task('size', function() {
    return gulp.src(config.size.output)
        .pipe(size({title: 'Deployment build:', gzip: true}));
});

//development task; tasks in [] run parallel, the rest will follow the order specified in the sequence
gulp.task('dev', gulpSequence('clean:tmp', ['serve']));

//task for building production ready files
gulp.task('default', gulpSequence('clean:dist', ['build', 'images', 'fonts', 'static'], 'size'));

<commit_msg>added comments in gulpfile<commit_after>/* GULPFILE SETUP
 * ==================================================
 *  Live reloading and sync with browsersync, linting
 *  and minifies JS, compiles Sass, autoprefixes and
 *  minifies css, media query optimization, image
 *  optimization and file concatenation.
 * ==================================================
 */

/* PLUGINS
 * --------------------------------------------------
 *  Load Gulp plugins
 * -------------------------------------------------- */
var autoprefixer = require('autoprefixer'),
    browserSync = require('browser-sync'),
    cache = require('gulp-cache'),
    concat = require('gulp-concat'),
    cssnano = require('gulp-cssnano'),
    del = require('del'),
    gulp = require('gulp'),
    gulpif = require('gulp-if'),
    gulpSequence = require('gulp-sequence'),
    htmlmin = require('gulp-htmlmin'),
    imagemin = require('gulp-imagemin'),
    jshint = require('gulp-jshint'),
    lazypipe = require('lazypipe'),
    mainBowerFiles = require('main-bower-files'),
    mergequeries = require('gulp-merge-media-queries'),
    plumber = require('gulp-plumber'),
    postcss = require('gulp-postcss'),
    purgecss = require('gulp-purgecss'),
    sass = require('gulp-sass'),
    size = require('gulp-size'),
    sourcemaps = require('gulp-sourcemaps'),
    uglify = require('gulp-uglify'),
    useref = require('gulp-useref'),
    wiredep = require('wiredep').stream;

/* VARS
 * --------------------------------------------------
 *  Variables and project paths
 * -------------------------------------------------- */
var reload = browserSync.reload,
    autoprefixList = ['last 2 versions', 'safari 5', 'ie 8', 'ie 9', 'opera 12.1', 'ios 6', 'android 4'];

var config = {
    global: { // main folders
        input: 'src',
        output: 'dist',
        tmp: '.tmp'
    },
    bower: { // bower.json file
        input: 'bower.json'
    },
    clean: { // generated directories
        tmp: '.tmp/*',
        dist: 'dist/*'
    },
    fonts: { // font paths
        input: 'src/fonts/**/*',
        output: 'dist/fonts',
        bower: '**/*.{eot,svg,ttf,woff,woff2}', // we only need these file types for the 'fonts' task
        tmp: '.tmp/fonts'
    },
    html: { // html paths
        input: 'src/**/*.html',
    },
    images: { // image paths
        input: 'src/images/**/*',
        output: 'dist/images'
    },
    scripts: { // script paths
        input: 'src/scripts/**/*.js',
        output: 'dist/js',
        tmp: '.tmp/js'
    },
    size: { // dislays size of the folder below it's build
        output: 'dist/**/*'
    },
    static: { // static files --> everything except html files
        input: ['src/*.*', '!src/*.html']
    },
    styles: { // style paths
        all: 'src/scss/**/*.{scss,sass}',
        input: 'src/scss/main.{scss,sass}',
        output: 'dist/css',
        bower: 'src/scss',
        tmp: '.tmp/css'
    }
}

/* SERVE TASK
 * --------------------------------------------------
 *  Livereload with browserSync, watch files on
 *  change and execute tasks accordingly
 *
 *  http://localhost:3000
 * -------------------------------------------------- */
gulp.task('serve', ['styles', 'scripts', 'fonts'], function() {
    browserSync({
        server: {
            baseDir:[config.global.tmp, config.global.input], // .tmp and src as base directories
            routes: {
                '/bower_components': 'bower_components' // serve bower_components as if it's located in 'src'
            }
        },
        notify: false,
        port: 3000 // configure port to your liking
    });

    // watch html, js and images and reload browser when something changes
    gulp.watch([
        config.html.input,
        config.scripts.input,
        config.images.input
    ]).on('change', reload);

    // watch scss, js, fonts and bower.js and execute task accordingly
    gulp.watch(config.styles.all, ['styles']);
    gulp.watch(config.scripts.input, ['scripts']);
    gulp.watch(config.fonts.input, ['fonts']);
    gulp.watch(config.bower.input, ['wiredep', 'fonts']); // execute wiredep when bower.json changes; this will automatically inject assets from bower_components in our HTML or SCSS
});

/* STYLES TASK
 * --------------------------------------------------
 *  Compile SCSS, autoprefix and make sourcemap
 * -------------------------------------------------- */
gulp.task('styles', function() {
    return gulp.src(config.styles.all)
        .pipe(plumber()) // plumber prevents pipe breaking caused by errors from gulp plugins
        .pipe(sourcemaps.init())
        .pipe(sass({
            outputStyle: 'nested', // output values: nested, expanded, compact and compressed
            includePaths: ['.'] // resolves Sass @imports for external libraries
        }).on('error', sass.logError))
        .pipe(postcss([autoprefixer({ browsers: autoprefixList })])) // autoprefix css with earlier specified list
        .pipe(sourcemaps.write()) // write sourcemap
        .pipe(gulp.dest(config.styles.tmp)) // output to tmp/css folder (dev only)
        .pipe(reload({ stream: true }));
});

/* SCRIPTS TASK
 * --------------------------------------------------
 *  Lint JS file(s) and report errors in console
 * -------------------------------------------------- */
gulp.task('scripts', function() {
    return gulp.src([config.scripts.input])
        .pipe(plumber())
        .pipe(jshint())
        .pipe(jshint.reporter('default')) // lint js
        .pipe(gulp.dest(config.scripts.tmp)) // output to tmp/js folder (dev only)
        .pipe(reload({ stream:true, once: true }));
});

/* FONTS TASK
 * --------------------------------------------------
 *  Get fonts for bower dependencies that need them
 *  and move them to dist and .tmp folder. Concat
 *  own fonts to mainBowerFiles array if needed
 * -------------------------------------------------- */
gulp.task('fonts', function() {
    return gulp.src(mainBowerFiles(config.fonts.bower, function(err){}) // get the main bower files, filter by ext: eot,svg,ttf,woff or woff2 for this task
        .concat(config.fonts.input)) // concatenate our own fonts to the array
        .pipe(gulp.dest(config.fonts.tmp))
        .pipe(gulp.dest(config.fonts.output))
        .pipe(reload({ stream: true }));
});

/* IMAGES TASK
 * --------------------------------------------------
 *  Compress images - PNG, JPG, GIF and SVG
 *  Doesn't remove IDs from SVG files
 * -------------------------------------------------- */
gulp.task('images', function() {
    return gulp.src([config.images.input])
        .pipe(plumber())
        .pipe(cache(imagemin([
            imagemin.optipng({ optimizationLevel: 6 }),
            imagemin.jpegtran({ progressive: true }),
            imagemin.gifsicle({ interlaced: true }),
            imagemin.svgo({
                plugins: [{ cleanupIDs: false }]
            })
        ])))
        .pipe(gulp.dest(config.images.output))
        .pipe(reload({ stream: true }));
});

/* BUILD TASK
 * --------------------------------------------------
 *  Make all of our src/ files ready for deployment:
 *   - Concatenate same type of files with useref
 *     between build blocks; 'build:{js,css}'
 *   - Uglify JS
 *   - Optimize CSS
 *   - Minify HTML
 * -------------------------------------------------- */
gulp.task('build', function() {
    return gulp.src(config.html.input)
        .pipe(plumber())
        .pipe(useref({ searchPath: ['.tmp', 'src', '.'] }))
        .pipe(gulpif('*.js', uglify()))
        .pipe(gulpif('*.css', optimizeCss())) // optimize css by calling a lazily-initialized pipeline
        .pipe(gulpif('*.html', htmlmin({ collapseWhitespace: true, removeComments: true })))
        .pipe(gulp.dest(config.global.output))
});

// optimizeCss function - with lazypipe we can create a chain of events in our gulpif condition
var optimizeCss = lazypipe()
    .pipe(purgecss, {
        content: [config.html.input], // which html files should be purged
        whitelist: ['is--active'] // leave certain selectors untouched in our css (selectors that are added via JS)
    })
    .pipe(mergequeries, {
        log: false // merge media queries - put true if you wanna see which media queries were processed
    })
    .pipe(cssnano, {
        safe: true, // http://cssnano.co/options/#optionssafe-bool
        autoprefixer: false, // don't autoprefix - our styles task already took care of that
        discardComments: {
            removeAll: true // remove all comments
        }
    });

/* WIREDEP TASK
 * --------------------------------------------------
 *  Inject bower dependencies in SCSS and NJK files
 * -------------------------------------------------- */
gulp.task('wiredep', function() {
    gulp.src(config.styles.input) // inject between 'bower' blocks in main.scss file
        .pipe(plumber())
        .pipe(wiredep({
            ignorePath: /^(\.\.\/)+/
        }))
        .pipe(gulp.dest(config.styles.bower)); // injects them in src/scss folder

    gulp.src(config.html.input) // inject between 'bower' blocks in .html files
        .pipe(plumber())
        .pipe(wiredep({
            ignorePath: /^(\.\.\/)*\.\./
        }))
        .pipe(gulp.dest(config.global.input)); // injects them in src/ folder
});

/* STATIC TASK
 * --------------------------------------------------
 *  Move static files to dist/ folder (robots.txt,
 *  humans.txt, favicon). Hidden files will be
 *  ignored (.git for example)
 * -------------------------------------------------- */
gulp.task('static', function() {
    return gulp.src(config.static.input, {
        dot: true
    }).pipe(gulp.dest(config.global.output));
});

/* CLEAN TASK
 * --------------------------------------------------
 *  Deletes dist/ and .tmp/ folder
 * -------------------------------------------------- */
gulp.task('clean:dist', del.bind(null, config.clean.dist));
gulp.task('clean:tmp', del.bind(null, config.clean.tmp));

/* CLEAR TASK
 * --------------------------------------------------
 *  Clear cache if needed
 * -------------------------------------------------- */
gulp.task('clear', function(done) {
    return cache.clearAll(done);
});

/* SIZE TASK
 * --------------------------------------------------
 *  Display size of dist folder
 * -------------------------------------------------- */
gulp.task('size', function() {
    return gulp.src(config.size.output)
        .pipe(size({title: 'Deployment build:', gzip: true}));
});

/* DEV TASK
 * --------------------------------------------------
 *  Start developing by running this task. Builds
 *  our project from the source files (src/) into a
 *  temporary folder (.tmp/) while watching files
 *  for changes
 * -------------------------------------------------- */
gulp.task('dev', gulpSequence('clean:tmp', ['serve']));

/* DEFAULT TASK
 * --------------------------------------------------
 *  Creates a production-ready build
 *  located in the dist/ folder
 * -------------------------------------------------- */
gulp.task('default', gulpSequence('clean:dist', ['build', 'images', 'fonts', 'static'], 'size'));

<|endoftext|>


<commit_before>pencilBoxApp.controller('CreateQuizController', ['$scope', '$routeParams', 'CreateQuiz', '$http', '$q',
    function ($scope, $routeParams, CreateQuiz, $http, $q) {
        $scope.hasChange = false;
        $scope.hasError = false;
        $scope.current_grade = $routeParams.gradeId;
        $scope.current_subject = $routeParams.subjectId;
        $scope.current_chapter = $routeParams.chapterId;

        CreateQuiz.query(function (response) {
            console.log("Query is Initialized");
            $scope.quizJson = {questions: response.questions};
            $scope.highlightDefault();
        });

        $scope.init = function (mode) {
            $scope.mode = mode;
        };

        $scope.questionTypes = [
            {
                "name": "Fill the blanks",
                "image": "fill-the-blanks.png",
                "type": "fill-the-blanks"
            },
            {
                "name": "Multiple options",
                "image": "multiple-options.png",
                "type": "multiple-options"
            },
            {
                "name": "Match the following",
                "image": "match-the-following.png",
                "type": "match-the-following"
            }
        ];

        $scope.showOverlay = function (element) {
            $scope.overlay = new Overlay(element, {closeHandler: true});
        };

        $scope.closeOverlay = function ($event) {
            if ($scope.hasChange) {
                $event.preventDefault();
                var r = confirm("You have unsaved changes in the quiz. Do you want to save it?");
                if (r) return;
            }

            $scope.overlay.disposeOverlay();
            var url = window.location.href;
            url = url.replace("update-quiz/", "");
            url = url.replace("create-quiz/", "");
            window.location.href = url.replace("update-quiz/", "");
        };

        $scope.highlightDefault = function () {
            $scope.selectedQuestion = null;
            if ($scope.mode === "create" || $scope.quizJson.questions.length === 0) {
                $scope.quizJson = {questions: []}
                $scope.currentQuestionType = "fill-the-blanks";
                $scope.currentQuestion = $scope.getCurrentQuestionObject();
            } else {
                $scope.selectedQuestion = 0;
                $scope.currentQuestion = $scope.getCurrentQuestionObject($scope.selectedQuestion);
                $scope.currentQuestionType = $scope.currentQuestion.type;
            }
        }

        $scope.overlayLoaded = function () {
            $scope.showOverlay(document.getElementById("create-quiz-mask"));
        }

        $scope.selectQuestionType = function (type) {
            if ($scope.selectedQuestion != null) {
                return;
            }
            $scope.currentQuestionType = type;
            $scope.currentQuestion = $scope.getCurrentQuestionObject();
            $scope.isValidMultipleOption();
        }

        $scope.selectQuestion = function (index) {
            $scope.selectedQuestion = index;
            $scope.currentQuestion = $scope.getCurrentQuestionObject($scope.selectedQuestion);
            $scope.currentQuestionType = $scope.currentQuestion.type;
            $scope.isValidMultipleOption();
        }

        $scope.isCurrentSubject = function (subject) {
            return $scope.current_subject.toLowerCase() === subject.toLowerCase();
        }

        $scope.isNotCurrentSubject = function (subject) {
            return $scope.current_subject.toLowerCase() !== subject.toLowerCase();
        };

        $scope.isCurrentChapter = function (chapter) {
            return $scope.current_chapter.toLowerCase() === chapter.toLowerCase();
        };

        $scope.isApplication = function (type) {
            return type === "apps";
        };

        $scope.isVideo = function (type) {
            return type === "videos";
        };

        $scope.isQuiz = function (type) {
            return type === "quiz";
        };

        $scope.getCurrentQuestionObject = function (index) {
            if (typeof index != "undefined") {
                return $scope.quizJson.questions[index];
            }
            if ($scope.currentQuestionType === "fill-the-blanks") {
                return $scope.getFIBQuestion();
            } else if ($scope.currentQuestionType === "multiple-options") {
                return $scope.getMultipleOption();
            } else if ($scope.currentQuestionType === "match-the-following") {
                return $scope.getMatchTheFollowing();
            }
        };

        $scope.getFIBQuestion = function () {

            return {
                "type": "fill-the-blanks",
                "question": "",
                "questionCollection": [],
                "marks": 1
            }

        };

        $scope.getMultipleOption = function () {

            return {
                "type": "multiple-options",
                "question": "",
                "options": [{"value": "", "answer": false}, {"value": "", "answer": false}],
                "marks": 1
            }

        };

        $scope.getMatchTheFollowing = function () {

            return {
                "type": "match-the-following",
                "questions": [{"question": "", "answer": ""}, {"question": "", "answer": ""}, {"question": "", "answer": ""}],
                "marks": 1
            }

        };

        $scope.addRow = function () {
            if ($scope.currentQuestionType === "multiple-options") {
                if ($scope.currentQuestion.options.length < 5) {
                    $scope.currentQuestion.options.push({"value": "", "answer": false});
                } else {
                    var options = {
                        title: "Alert",
                        description: "Cannot add more than 5 options",
                        buttons: ["ok"]
                    }
                    new CustomDialog(options);
                }
            } else {
                if ($scope.currentQuestion.questions.length < 10) {
                    $scope.currentQuestion.questions.push({"question": "", "answer": ""});
                } else {
                    var options = {
                        title: "Alert",
                        description: "Cannot add more than 10 options",
                        buttons: ["ok"]
                    }
                    new CustomDialog(options);
                }
            }
            $scope.isValidMultipleOption();
        }
        $scope.deleteRow = function (index) {
            var key = $scope.currentQuestionType === "multiple-options" ? "options" : "questions";
            if ($scope.currentQuestion[key].length > 2) {
                $scope.currentQuestion[key].splice(index, 1);
            }
            $scope.isValidMultipleOption();
        }

        $scope.addQuestion = function () {
            if ($scope.hasError()) {
                var options = {
                    title: "Alert",
                    description: "Please fill the red field",
                    buttons: ["ok"]
                }
                new CustomDialog(options);
                return;
            }

            if ($scope.currentQuestionType === "fill-the-blanks") {
                // Converting into question collection

                var questArr = $scope.currentQuestion.question.replace(/__.*?__/g, '_______').split(" ");
                var answerArr = $scope.currentQuestion.question.match(/__.*?__/g).join("").split("_").filter(Boolean);

                for (var i = 0, length = questArr.length; i < length; i++) {
                    if (questArr[i] === "_______") {
                        $scope.currentQuestion.questionCollection.push({type: "answer", value: answerArr.shift()});
                    } else if (questArr[i].trim().length > 0) {
                        $scope.currentQuestion.questionCollection.push({type: "question", value: questArr[i].trim()});
                    }
                }
            }

            if ($scope.selectedQuestion == null) {
                $scope.quizJson.questions.push(angular.copy($scope.currentQuestion));
            }

            $scope.selectedQuestion = null;
            $scope.currentQuestion = $scope.getCurrentQuestionObject();
            $scope.hasChange = true;
            $scope.isValidMultipleOption();
        }

        $scope.hasError = function () {
            return document.querySelectorAll(".question-box .error, .question-box .ng-invalid").length > 0;
        }

        $scope.addBlank = function () {
            var answer = prompt("Enter the answer for blank:", '');
            if (!answer || !answer.trim()) return;
            var caretPosition = document.getElementById('fill-in-the-blank-question').selectionStart;
            var insertAt = function (mainString, insertString, index) {
                var leftPart = mainString.substr(0, index);
                var rightPart = mainString.substr(index, mainString.length);
                return leftPart + insertString + rightPart;
            };
            $scope.currentQuestion.question = insertAt($scope.currentQuestion.question, '__' + answer + '__', caretPosition);
        };

        $scope.fillInTheBlankPreview = function () {
            if (!$scope.currentQuestion) return '';
            return $scope.currentQuestion.question ? $scope.currentQuestion.question.replace(/__.*?__/g, '_______') : "";
        };
        $scope.download = function () {
            document.getElementById("download-json").href = window.URL.createObjectURL(new Blob([JSON.stringify($scope.quizJson)], {type: "application/json"}));
        };

        $scope.isFirstQuestion = function () {
            return $scope.quizJson.questions.indexOf($scope.currentQuestion) <= 0;
        };

        $scope.previousQuestion = function () {
            var index = $scope.selectedQuestion;
            if($scope.selectedQuestion == null) {
                index = 0;
            } else {
                if($scope.selectedQuestion !== 0) {
                    index = $scope.selectedQuestion - 1;
                }
            }
            $scope.selectQuestion(index);
        };
        $scope.nextQuestion = function () {
            var index = $scope.selectedQuestion;
            if($scope.selectedQuestion == null) {
                index = $scope.quizJson.questions.length - 1;
            } else {
                if($scope.selectedQuestion !== $scope.quizJson.questions.length - 1) {
                    index = $scope.selectedQuestion + 1;
                }
            }
            $scope.selectQuestion(index);
        };

        $scope.isValidMultipleOption = function () {
            if ($scope.currentQuestionType === "multiple-options") {
                $scope.mcHasError = true;
                for (var i = 0, length = $scope.currentQuestion.options.length; i < length; i++) {
                    if ($scope.currentQuestion.options[i].answer) {
                        $scope.mcHasError = false;
                        break;
                    }
                }
            }
        };

        $scope.cancelQuestion = function () {
          console.log('here');
             var dialogInstance = new CustomDialog($q, {
                title: "Alert",
                description: "Are you sure you want to delete this question?",
                buttons: ["ok", "cancel"]
            }).show();
            dialogInstance
              .then(function(result){
                $scope.quizJson.questions.splice($scope.selectedQuestion, 1);
              })
              .catch(function(e){});
            $scope.selectQuestion();
        };

        $scope.isExistingQuestion = function () {
            return $scope.selectedQuestion;
        };

        $scope.getErrorMessage = function () {
            var msg = "";
            if($scope.currentQuestionType === "fill-the-blanks") {
                msg = $scope.currentQuestion.question.trim().length === 0 ? "Please fill the question" : !$scope.currentQuestion.question.trim().match(/__[^_]+__/) ? "Please provide answer" : "";
            } else if($scope.currentQuestionType === "match-the-following") {
                msg = "Please fill the questions and answers";
            } else {
                msg = $scope.currentQuestion.question.trim().length === 0 ? "Please fill the question" :
                        $scope.currentQuestion.question.trim().length < 10 ? "Please provide atleast 10 characters" :
                            $scope.currentQuestion.options.map(function(item) {return item.value.trim() === ""}) ? "Please fill the answer options" :
                                $scope.currentQuestion.options.map(function(item) {return item.answer === false}) ? "Please set answer to the question" : "";
            }
            return msg;
        }

        $scope.saveQuiz = function () {
            if ($scope.hasError()) {
                var errorMsg = $scope.getErrorMessage();
                var options = {
                    title: "Alert",
                    description: errorMsg || "Please fill the red field",
                    buttons: ["ok"]
                }
                new CustomDialog(options);
            } else {
                var data = {
                    grade: $scope.current_grade,
                    subject: $scope.current_subject,
                    chapter: $scope.current_chapter,
                    quiz: $scope.quizJson
                };
                $http.post('/save.php', data, {headers: {'Content-Type': 'application/json'}}).
                    then(function (response) {
                        $scope.hasChange = false;
                        $scope.closeOverlay({
                            preventDefault: function () {
                            }
                        })
                    }, function () {
                        console.log(arguments);
                    });
            }
        };

        $scope.validateInput = function(obj, limit) {
            if(obj.length >= limit) {
                var options = {
                    title: "Alert",
                    description: "You cannot add more than " + limit + " characters",
                    buttons: ["ok"]
                }
                new CustomDialog(options);
            }
        }
    }

]);
<commit_msg>Reformat<commit_after>pencilBoxApp.controller('CreateQuizController', ['$scope', '$routeParams', 'CreateQuiz', '$http', '$q',
    function ($scope, $routeParams, CreateQuiz, $http, $q) {
        $scope.hasChange = false;
        $scope.hasError = false;
        $scope.current_grade = $routeParams.gradeId;
        $scope.current_subject = $routeParams.subjectId;
        $scope.current_chapter = $routeParams.chapterId;

        CreateQuiz.query(function (response) {
            console.log("Query is Initialized");
            $scope.quizJson = {questions: response.questions};
            $scope.highlightDefault();
        });

        $scope.init = function (mode) {
            $scope.mode = mode;
        };

        $scope.questionTypes = [
            {
                "name": "Fill the blanks",
                "image": "fill-the-blanks.png",
                "type": "fill-the-blanks"
            },
            {
                "name": "Multiple options",
                "image": "multiple-options.png",
                "type": "multiple-options"
            },
            {
                "name": "Match the following",
                "image": "match-the-following.png",
                "type": "match-the-following"
            }
        ];

        $scope.showOverlay = function (element) {
            $scope.overlay = new Overlay(element, {closeHandler: true});
        };

        $scope.closeOverlay = function ($event) {
            if ($scope.hasChange) {
                $event.preventDefault();
                var r = confirm("You have unsaved changes in the quiz. Do you want to save it?");
                if (r) return;
            }

            $scope.overlay.disposeOverlay();
            var url = window.location.href;
            url = url.replace("update-quiz/", "");
            url = url.replace("create-quiz/", "");
            window.location.href = url.replace("update-quiz/", "");
        };

        $scope.highlightDefault = function () {
            $scope.selectedQuestion = null;
            if ($scope.mode === "create" || $scope.quizJson.questions.length === 0) {
                $scope.quizJson = {questions: []}
                $scope.currentQuestionType = "fill-the-blanks";
                $scope.currentQuestion = $scope.getCurrentQuestionObject();
            } else {
                $scope.selectedQuestion = 0;
                $scope.currentQuestion = $scope.getCurrentQuestionObject($scope.selectedQuestion);
                $scope.currentQuestionType = $scope.currentQuestion.type;
            }
        }

        $scope.overlayLoaded = function () {
            $scope.showOverlay(document.getElementById("create-quiz-mask"));
        }

        $scope.selectQuestionType = function (type) {
            if ($scope.selectedQuestion != null) {
                return;
            }
            $scope.currentQuestionType = type;
            $scope.currentQuestion = $scope.getCurrentQuestionObject();
            $scope.isValidMultipleOption();
        }

        $scope.selectQuestion = function (index) {
            $scope.selectedQuestion = index;
            $scope.currentQuestion = $scope.getCurrentQuestionObject($scope.selectedQuestion);
            $scope.currentQuestionType = $scope.currentQuestion.type;
            $scope.isValidMultipleOption();
        }

        $scope.isCurrentSubject = function (subject) {
            return $scope.current_subject.toLowerCase() === subject.toLowerCase();
        }

        $scope.isNotCurrentSubject = function (subject) {
            return $scope.current_subject.toLowerCase() !== subject.toLowerCase();
        };

        $scope.isCurrentChapter = function (chapter) {
            return $scope.current_chapter.toLowerCase() === chapter.toLowerCase();
        };

        $scope.isApplication = function (type) {
            return type === "apps";
        };

        $scope.isVideo = function (type) {
            return type === "videos";
        };

        $scope.isQuiz = function (type) {
            return type === "quiz";
        };

        $scope.getCurrentQuestionObject = function (index) {
            if (typeof index != "undefined") {
                return $scope.quizJson.questions[index];
            }
            if ($scope.currentQuestionType === "fill-the-blanks") {
                return $scope.getFIBQuestion();
            } else if ($scope.currentQuestionType === "multiple-options") {
                return $scope.getMultipleOption();
            } else if ($scope.currentQuestionType === "match-the-following") {
                return $scope.getMatchTheFollowing();
            }
        };

        $scope.getFIBQuestion = function () {

            return {
                "type": "fill-the-blanks",
                "question": "",
                "questionCollection": [],
                "marks": 1
            }

        };

        $scope.getMultipleOption = function () {

            return {
                "type": "multiple-options",
                "question": "",
                "options": [{"value": "", "answer": false}, {"value": "", "answer": false}],
                "marks": 1
            }

        };

        $scope.getMatchTheFollowing = function () {

            return {
                "type": "match-the-following",
                "questions": [{"question": "", "answer": ""}, {"question": "", "answer": ""}, {
                    "question": "",
                    "answer": ""
                }],
                "marks": 1
            }

        };

        $scope.addRow = function () {
            if ($scope.currentQuestionType === "multiple-options") {
                if ($scope.currentQuestion.options.length < 5) {
                    $scope.currentQuestion.options.push({"value": "", "answer": false});
                } else {
                    var options = {
                        title: "Alert",
                        description: "Cannot add more than 5 options",
                        buttons: ["ok"]
                    }
                    new CustomDialog(options);
                }
            } else {
                if ($scope.currentQuestion.questions.length < 10) {
                    $scope.currentQuestion.questions.push({"question": "", "answer": ""});
                } else {
                    var options = {
                        title: "Alert",
                        description: "Cannot add more than 10 options",
                        buttons: ["ok"]
                    }
                    new CustomDialog(options);
                }
            }
            $scope.isValidMultipleOption();
        }
        $scope.deleteRow = function (index) {
            var key = $scope.currentQuestionType === "multiple-options" ? "options" : "questions";
            if ($scope.currentQuestion[key].length > 2) {
                $scope.currentQuestion[key].splice(index, 1);
            }
            $scope.isValidMultipleOption();
        }

        $scope.addQuestion = function () {
            if ($scope.hasError()) {
                var options = {
                    title: "Alert",
                    description: "Please fill the red field",
                    buttons: ["ok"]
                }
                new CustomDialog(options);
                return;
            }

            if ($scope.currentQuestionType === "fill-the-blanks") {
                // Converting into question collection

                var questArr = $scope.currentQuestion.question.replace(/__.*?__/g, '_______').split(" ");
                var answerArr = $scope.currentQuestion.question.match(/__.*?__/g).join("").split("_").filter(Boolean);

                for (var i = 0, length = questArr.length; i < length; i++) {
                    if (questArr[i] === "_______") {
                        $scope.currentQuestion.questionCollection.push({type: "answer", value: answerArr.shift()});
                    } else if (questArr[i].trim().length > 0) {
                        $scope.currentQuestion.questionCollection.push({type: "question", value: questArr[i].trim()});
                    }
                }
            }

            if ($scope.selectedQuestion == null) {
                $scope.quizJson.questions.push(angular.copy($scope.currentQuestion));
            }

            $scope.selectedQuestion = null;
            $scope.currentQuestion = $scope.getCurrentQuestionObject();
            $scope.hasChange = true;
            $scope.isValidMultipleOption();
        }

        $scope.hasError = function () {
            return document.querySelectorAll(".question-box .error, .question-box .ng-invalid").length > 0;
        }

        $scope.addBlank = function () {
            var answer = prompt("Enter the answer for blank:", '');
            if (!answer || !answer.trim()) return;
            var caretPosition = document.getElementById('fill-in-the-blank-question').selectionStart;
            var insertAt = function (mainString, insertString, index) {
                var leftPart = mainString.substr(0, index);
                var rightPart = mainString.substr(index, mainString.length);
                return leftPart + insertString + rightPart;
            };
            $scope.currentQuestion.question = insertAt($scope.currentQuestion.question, '__' + answer + '__', caretPosition);
        };

        $scope.fillInTheBlankPreview = function () {
            if (!$scope.currentQuestion) return '';
            return $scope.currentQuestion.question ? $scope.currentQuestion.question.replace(/__.*?__/g, '_______') : "";
        };
        $scope.download = function () {
            document.getElementById("download-json").href = window.URL.createObjectURL(new Blob([JSON.stringify($scope.quizJson)], {type: "application/json"}));
        };

        $scope.isFirstQuestion = function () {
            return $scope.quizJson.questions.indexOf($scope.currentQuestion) <= 0;
        };

        $scope.previousQuestion = function () {
            var index = $scope.selectedQuestion;
            if ($scope.selectedQuestion == null) {
                index = 0;
            } else {
                if ($scope.selectedQuestion !== 0) {
                    index = $scope.selectedQuestion - 1;
                }
            }
            $scope.selectQuestion(index);
        };
        $scope.nextQuestion = function () {
            var index = $scope.selectedQuestion;
            if ($scope.selectedQuestion == null) {
                index = $scope.quizJson.questions.length - 1;
            } else {
                if ($scope.selectedQuestion !== $scope.quizJson.questions.length - 1) {
                    index = $scope.selectedQuestion + 1;
                }
            }
            $scope.selectQuestion(index);
        };

        $scope.isValidMultipleOption = function () {
            if ($scope.currentQuestionType === "multiple-options") {
                $scope.mcHasError = true;
                for (var i = 0, length = $scope.currentQuestion.options.length; i < length; i++) {
                    if ($scope.currentQuestion.options[i].answer) {
                        $scope.mcHasError = false;
                        break;
                    }
                }
            }
        };

        $scope.cancelQuestion = function () {
            console.log('here');
            var dialogInstance = new CustomDialog($q, {
                title: "Alert",
                description: "Are you sure you want to delete this question?",
                buttons: ["ok", "cancel"]
            }).show();
            dialogInstance
                    .then(function (result) {
                        $scope.quizJson.questions.splice($scope.selectedQuestion, 1);
                    })
                    .catch(function (e) {
                    });
            $scope.selectQuestion();
        };

        $scope.isExistingQuestion = function () {
            return $scope.selectedQuestion;
        };

        $scope.getErrorMessage = function () {
            var msg = "";
            if ($scope.currentQuestionType === "fill-the-blanks") {
                msg = $scope.currentQuestion.question.trim().length === 0 ? "Please fill the question" : !$scope.currentQuestion.question.trim().match(/__[^_]+__/) ? "Please provide answer" : "";
            } else if ($scope.currentQuestionType === "match-the-following") {
                msg = "Please fill the questions and answers";
            } else {
                msg = $scope.currentQuestion.question.trim().length === 0 ? "Please fill the question" :
                        $scope.currentQuestion.question.trim().length < 10 ? "Please provide atleast 10 characters" :
                                $scope.currentQuestion.options.map(function (item) {
                                    return item.value.trim() === ""
                                }) ? "Please fill the answer options" :
                                        $scope.currentQuestion.options.map(function (item) {
                                            return item.answer === false
                                        }) ? "Please set answer to the question" : "";
            }
            return msg;
        }

        $scope.saveQuiz = function () {
            if ($scope.hasError()) {
                var errorMsg = $scope.getErrorMessage();
                var options = {
                    title: "Alert",
                    description: errorMsg || "Please fill the red field",
                    buttons: ["ok"]
                }
                new CustomDialog(options);
            } else {
                var data = {
                    grade: $scope.current_grade,
                    subject: $scope.current_subject,
                    chapter: $scope.current_chapter,
                    quiz: $scope.quizJson
                };
                $http.post('/save.php', data, {headers: {'Content-Type': 'application/json'}}).
                        then(function (response) {
                            $scope.hasChange = false;
                            $scope.closeOverlay({
                                preventDefault: function () {
                                }
                            })
                        }, function () {
                            console.log(arguments);
                        });
            }
        };

        $scope.validateInput = function (obj, limit) {
            if (obj.length >= limit) {
                var options = {
                    title: "Alert",
                    description: "You cannot add more than " + limit + " characters",
                    buttons: ["ok"]
                }
                new CustomDialog(options);
            }
        }
    }

]);
<|endoftext|>


<commit_before>

var restify = require('restify');
var url = require('url');
var _ = require('underscore');
var async = require('async');
const qs = require('querystring');
const got = require('got');

function configureServer (opts, ctx) {
  var server = restify.createServer(opts);
  var consul = ctx.consul;

  function get_candidates (service, next) {
    return consul.catalog.service.nodes({service: service}, next);
  }

  function apply_policies (services) {
    function byTotalActive (x) { return x.HealthStatus.total.active; }
    function byCapacityQuotient (x) { return x.HealthStatus.total.expected / x.HealthStatus.total.max; }
    function hasHealth (x) { return x && x.HealthStatus; }
    return _.sortBy(services.filter(hasHealth), byCapacityQuotient);
  }

  function require_health (services, next) {
    async.each(services, get_health, function finish (err) {
      next(err, services);
    });
  }

  function fetch_services (service, tenants, next) {
    if (tenants.call) {
      next = tenants;
      tenants = [ ];
    }
    return get_candidates(service, function (err, clusters) {
      function filter_runners (tenant) {
        var runners = _.where(clusters, { ServiceAddress : tenant.ServiceAddress });
        return runners;
      }
      var candidates = [ ];
      var orig_len = clusters.length;
      if (1 == tenants.length) {
        candidates = filter_runners(tenants[0]);
        console.log('tenants', tenants.length, 'orig', orig_len, 'filtered', candidates.length);
        if (candidates.length == 1) {
          return next(err, candidates);
        }
      } else if (tenants.length > 1) {
        candidates = _.flatten(_.map(tenants, filter_runners));
        console.log('tenants', tenants.length, 'orig', orig_len, 'filtered', candidates.length);
        if (candidates.length > 0) {
          return next(err, candidates);
        }
      }
      // return require_health(clusters, next);
      return require_health(clusters, function (err, services) {
        if (err) { throw err; }
        var ordered = apply_policies(services);
        console.log('ordered policies, no matching candidates for tenant');
        return next(err, ordered);
      });
    });
  }

  function fetch_elected_services (service, tenants, next) {
    if (tenants.call) {
      next = tenants;
      tenants = [ ];
    }
    return fetch_services(service, tenants, function (err, services) {
      if (err) throw err;
      if (tenants.length > 0) {
        return next(services);
      }
      return next(apply_policies(services));
    });
  }

  function get_best_service (service, next) {
    return fetch_elected_services(service, function (services) {
      // if (err) throw err;
      return next(elect(services));
    });
  }

  function elect (services) {
    return services[0];
  }

  function suggest (elected) {
    var candidate = { };
    _.extend(candidate, elected);
    return candidate;
  }

  function ratify (elected, next) {
  }

  function get_health (runner, done) {
    var uri = url.format({ hostname: runner.ServiceAddress, port: runner.ServicePort, pathname: '/stats/active', protocol: 'http' });
    console.log("fetching", uri);
    got(uri).json( ).then(function (body) {
      runner.HealthStatus = body;
      done( );
    }).catch(function (err) {
      console.log("ERROR skipping", arguments);
      done( );
    });
  }

  server.use(function (req, res, next) {
    res.locals = { };
    next( );
  });

  function format_locals (req, res, next) {
    res.send(res.locals);
    next( );
  }

  server.get('/suggested/consul/:service', function (req, res, next) {
  });
  server.get('/available/consul/:service', function (req, res, next) {
    fetch_services(req.params.service, function (err, services) {
      res.locals.services = services;
      next( );
    });
  }, format_locals);

  server.get('/elected/consul/:service', function (req, res, next) {
    get_best_service(req.params.service, function (clusters) {
      res.locals.service = clusters;
      console.log('BEST SERVICE', clusters);
      next( );
    })
  },
  format_locals
  );

  server.get('/scheduled/consul/:service/:tenant/:suffix',
  function (req, res, next) {
    var suffix = req.params.suffix || 'backends';
    var domain = [ req.params.tenant, suffix ].join('.');
    var search  = {service: suffix, tag: req.params.tenant };
    console.log('SEARCH TENANT', search);
    consul.catalog.service.nodes(search, function (err, tenants) {
      if (err) {
        console.log(err);
        return next(err);
      }

      var orig_num_tenants = tenants ? tenants.length : 0;
      tenants = _.uniq(tenants, _.iteratee('ServiceAddress'));
      console.log("SEARCH TENANT RESULTS", err, orig_num_tenants, tenants ? tenants.length : err);

      res.locals.tenants = tenants;
      next( );

    });
  },
  function (req, res, next) {
    var search = {service: req.params.service };
    if (res.locals.tenants.length >= 1) {
      // res.locals.services = res.locals.tenants;
      // res.locals.preferred = res.locals.tenants[0];
      // console.log("skipping CLUSTER search", res.locals.tenants.length);
      // next( );
      // return;
    }
    fetch_elected_services(req.params.service, res.locals.tenants, function (services) {
      console.log("SEARCH CLUSTER RESULTS", services.length);
      res.locals.services = services;
      res.locals.preferred = elect(services);
      next( );
    });
  },
  function (req, res, next) {
    var tenants = res.locals.tenants;
    function hostsTenant  ( ) { }
    function electRunners (tenant) {
      var runners = _.where(res.locals.services, { ServiceAddress : tenant.ServiceAddress });
      return runners;
    }
    var candidates = _.flatten(_.map(tenants, electRunners));
    console.log("candidates", candidates.length);
    res.locals.candidates = candidates;
    next( );
  },
  function (req, res, next) {
    if (_.isEmpty(res.locals.candidates)) {
      // this is a new/non-existing tenant
      res.locals.elected = res.locals.preferred;
    } else {
      //pre-existing, use a candidate
      res.locals.elected = res.locals.candidates[0];
    }
    if (!res.locals.elected) {
      var fail = { tenant: res.locals.tenants[0], candidates: res.locals.candidates };
      console.log("FAILED TENANT", fail.tenant);
      console.log("FAIL NO SUITABLE CANDIDATE", fail);
      res.send(404, fail);
      return next(fail);
    }
    var urlish = { hostname: res.locals.elected.ServiceAddress
    , port: res.locals.elected.ServicePort
    , pathname: '/environs/' + req.params.tenant
    , protocol: 'http'
    };
    var uri = url.format(urlish);
    console.log('elected', uri);
    res.locals.endpoint = uri;
    res.header('X-ELECTED-RUNNER', uri);
    next( );
  },
  format_locals
  );

  server.get('/consul/:service', function (req, res, next) {
    consul.catalog.service.nodes({service: req.params.service}, function (err, clusters) {
      res.locals.services = clusters;
      next( );
    });
  },
  function (req, res, next) {
    async.each(res.locals.services, get_health, function finish ( ) {
      res.locals.services = _.sortBy(res.locals.services.filter(function (x) { return x && x.HealthStatus; }), function (x) { return x.HealthStatus.total.active; });
      next( );
    });
  },
  function (req, res, next) {
    res.send(res.locals);
    next( );
  });
  return server;
}

function consul_url_to_opts (input) {
  var u = url.parse(input);
  var q = qs.parse(u.query);
  var protos = { consul: 'http', consuls: 'https' };
  var scheme = protos[u.protocol] || u.protocol;
  var o = {
    host: u.hostname,
    port: u.port,
    url: url.format({protocol: scheme, host: u.host})
  };
  if (u.search && 'https' == scheme) {
    if (q.promisify) {
      o.promisify = true;
    }
    // TODO: security
  }
  return o;
}

function configureWatcher (opts) {
  var config = {

    consul: consul_url_to_opts(options.url)
  };
  console.log('connecting to consul', opts);
  var client = require('consul')(opts);
  return client;
  client.agent.self( function (err, data, res) {
    // client.agent.self(console.log.bind(console, "DEBUG CONSUL"));
    // console.log("CONNECTED TO CONSUL", client, arguments);
    if (err) throw err;
    fn(err, client, data, res);
  });
  // client.agent.self(console.log.bind(console, "DEBUG CONSUL"));
  return client;
}

if (!module.parent) {
  var port = parseInt(process.env.PORT || '2829')
  var boot = require('bootevent')( );
  var env = require('./lib/env');
  boot.acquire(function consul_env (ctx, next) {
    var opts = consul_url_to_opts(env.consul.url);
    ctx.consul = require('consul')(opts);
    ctx.consul.agent.self( function (err, data, res) {
      if (err) throw err;
      console.log('connected to consul');
      next( );
    });
  }).acquire(function server_env (ctx, next) {
    ctx.server = configureServer({ }, ctx);
    next( );
  }).boot(function (ctx) {
    ctx.server.listen(port, function ( ) {
      console.log('listening on', this.address( ));
    });
  });
}

<commit_msg>feature toggle SEARCH_TENANT_CLUSTER_TAG<commit_after>

var restify = require('restify');
var url = require('url');
var _ = require('underscore');
var async = require('async');
const qs = require('querystring');
const got = require('got');

function configureServer (opts, ctx) {
  var server = restify.createServer(opts);
  var consul = ctx.consul;

  function get_candidates (service, next) {
    return consul.catalog.service.nodes({service: service}, next);
  }

  function apply_policies (services) {
    function byTotalActive (x) { return x.HealthStatus.total.active; }
    function byCapacityQuotient (x) { return x.HealthStatus.total.expected / x.HealthStatus.total.max; }
    function hasHealth (x) { return x && x.HealthStatus; }
    return _.sortBy(services.filter(hasHealth), byCapacityQuotient);
  }

  function require_health (services, next) {
    async.each(services, get_health, function finish (err) {
      next(err, services);
    });
  }

  function fetch_services (service, tenants, next) {
    if (tenants.call) {
      next = tenants;
      tenants = [ ];
    }
    return get_candidates(service, function (err, clusters) {
      function filter_runners (tenant) {
        var runners = _.where(clusters, { ServiceAddress : tenant.ServiceAddress });
        return runners;
      }
      var candidates = [ ];
      var orig_len = clusters.length;
      if (1 == tenants.length) {
        candidates = filter_runners(tenants[0]);
        console.log('tenants', tenants.length, 'orig', orig_len, 'filtered', candidates.length);
        if (candidates.length == 1) {
          return next(err, candidates);
        }
      } else if (tenants.length > 1) {
        candidates = _.flatten(_.map(tenants, filter_runners));
        console.log('tenants', tenants.length, 'orig', orig_len, 'filtered', candidates.length);
        if (candidates.length > 0) {
          return next(err, candidates);
        }
      }
      // return require_health(clusters, next);
      return require_health(clusters, function (err, services) {
        if (err) { throw err; }
        var ordered = apply_policies(services);
        console.log('ordered policies, no matching candidates for tenant');
        return next(err, ordered);
      });
    });
  }

  function fetch_elected_services (service, tenants, next) {
    if (tenants.call) {
      next = tenants;
      tenants = [ ];
    }
    return fetch_services(service, tenants, function (err, services) {
      if (err) throw err;
      if (tenants.length > 0) {
        return next(services);
      }
      return next(apply_policies(services));
    });
  }

  function get_best_service (service, next) {
    return fetch_elected_services(service, function (services) {
      // if (err) throw err;
      return next(elect(services));
    });
  }

  function elect (services) {
    return services[0];
  }

  function suggest (elected) {
    var candidate = { };
    _.extend(candidate, elected);
    return candidate;
  }

  function ratify (elected, next) {
  }

  function get_health (runner, done) {
    var uri = url.format({ hostname: runner.ServiceAddress, port: runner.ServicePort, pathname: '/stats/active', protocol: 'http' });
    console.log("fetching", uri);
    got(uri).json( ).then(function (body) {
      runner.HealthStatus = body;
      done( );
    }).catch(function (err) {
      console.log("ERROR skipping", arguments);
      done( );
    });
  }

  server.use(function (req, res, next) {
    res.locals = { };
    next( );
  });

  function format_locals (req, res, next) {
    res.send(res.locals);
    next( );
  }

  server.get('/suggested/consul/:service', function (req, res, next) {
  });
  server.get('/available/consul/:service', function (req, res, next) {
    fetch_services(req.params.service, function (err, services) {
      res.locals.services = services;
      next( );
    });
  }, format_locals);

  server.get('/elected/consul/:service', function (req, res, next) {
    get_best_service(req.params.service, function (clusters) {
      res.locals.service = clusters;
      console.log('BEST SERVICE', clusters);
      next( );
    })
  },
  format_locals
  );

  server.get('/scheduled/consul/:service/:tenant/:suffix',
  function (req, res, next) {
    var suffix = req.params.suffix || 'backends';
    var domain = [ req.params.tenant, suffix ].join('.');
    // var search  = {service: suffix, tag: req.params.tenant };
    var additional_tag = opts.SEARCH_TENANT_CLUSTER_TAG ? req.params.service : 'tenant';
    var required_tag = [ req.params.tenant, additional_tag ];
    var search  = {service: suffix, tag: required_tag };
    console.log('SEARCH TENANT', search);
    consul.catalog.service.nodes(search, function (err, tenants) {
      if (err) {
        console.log(err);
        return next(err);
      }

      var orig_num_tenants = tenants ? tenants.length : 0;
      tenants = _.uniq(tenants, _.iteratee('ServiceAddress'));
      console.log("SEARCH TENANT RESULTS", err, orig_num_tenants, tenants ? tenants.length : err, tenants);

      res.locals.tenants = tenants;
      next( );

    });
  },
  function (req, res, next) {
    var search = {service: req.params.service };
    if (res.locals.tenants.length >= 1) {
      // res.locals.services = res.locals.tenants;
      // res.locals.preferred = res.locals.tenants[0];
      // console.log("skipping CLUSTER search", res.locals.tenants.length);
      // next( );
      // return;
    }
    fetch_elected_services(req.params.service, res.locals.tenants, function (services) {
      console.log("SEARCH CLUSTER RESULTS", services.length);
      res.locals.services = services;
      res.locals.preferred = elect(services);
      next( );
    });
  },
  function (req, res, next) {
    var tenants = res.locals.tenants;
    function hostsTenant  ( ) { }
    function electRunners (tenant) {
      var runners = _.where(res.locals.services, { ServiceAddress : tenant.ServiceAddress });
      return runners;
    }
    var candidates = _.flatten(_.map(tenants, electRunners));
    console.log("candidates", candidates.length);
    res.locals.candidates = candidates;
    next( );
  },
  function (req, res, next) {
    if (_.isEmpty(res.locals.candidates)) {
      // this is a new/non-existing tenant
      res.locals.elected = res.locals.preferred;
    } else {
      //pre-existing, use a candidate
      res.locals.elected = res.locals.candidates[0];
    }
    if (!res.locals.elected) {
      var fail = { tenant: res.locals.tenants[0], candidates: res.locals.candidates };
      console.log("FAILED TENANT", fail.tenant);
      console.log("FAIL NO SUITABLE CANDIDATE", fail);
      res.send(404, fail);
      return next(fail);
    }
    var urlish = { hostname: res.locals.elected.ServiceAddress
    , port: res.locals.elected.ServicePort
    , pathname: '/environs/' + req.params.tenant
    , protocol: 'http'
    };
    var uri = url.format(urlish);
    console.log('elected', uri);
    res.locals.endpoint = uri;
    res.header('X-ELECTED-RUNNER', uri);
    next( );
  },
  format_locals
  );

  server.get('/consul/:service', function (req, res, next) {
    consul.catalog.service.nodes({service: req.params.service}, function (err, clusters) {
      res.locals.services = clusters;
      next( );
    });
  },
  function (req, res, next) {
    async.each(res.locals.services, get_health, function finish ( ) {
      res.locals.services = _.sortBy(res.locals.services.filter(function (x) { return x && x.HealthStatus; }), function (x) { return x.HealthStatus.total.active; });
      next( );
    });
  },
  function (req, res, next) {
    res.send(res.locals);
    next( );
  });
  return server;
}

function consul_url_to_opts (input) {
  var u = url.parse(input);
  var q = qs.parse(u.query);
  var protos = { consul: 'http', consuls: 'https' };
  var scheme = protos[u.protocol] || u.protocol;
  var o = {
    host: u.hostname,
    port: u.port,
    url: url.format({protocol: scheme, host: u.host})
  };
  if (u.search && 'https' == scheme) {
    if (q.promisify) {
      o.promisify = true;
    }
    // TODO: security
  }
  return o;
}

function configureWatcher (opts) {
  var config = {

    consul: consul_url_to_opts(options.url)
  };
  console.log('connecting to consul', opts);
  var client = require('consul')(opts);
  return client;
  client.agent.self( function (err, data, res) {
    // client.agent.self(console.log.bind(console, "DEBUG CONSUL"));
    // console.log("CONNECTED TO CONSUL", client, arguments);
    if (err) throw err;
    fn(err, client, data, res);
  });
  // client.agent.self(console.log.bind(console, "DEBUG CONSUL"));
  return client;
}

if (!module.parent) {
  var port = parseInt(process.env.PORT || '2829')
  var SEARCH_TENANT_CLUSTER_TAG = process.env.SEARCH_TENANT_CLUSTER_TAG == '1'
  var boot = require('bootevent')( );
  var env = require('./lib/env');
  boot.acquire(function consul_env (ctx, next) {
    var opts = consul_url_to_opts(env.consul.url);
    ctx.consul = require('consul')(opts);
    ctx.consul.agent.self( function (err, data, res) {
      if (err) throw err;
      console.log('connected to consul');
      next( );
    });
  }).acquire(function server_env (ctx, next) {
    ctx.server = configureServer({SEARCH_TENANT_CLUSTER_TAG: SEARCH_TENANT_CLUSTER_TAG }, ctx);
    next( );
  }).boot(function (ctx) {
    ctx.server.listen(port, function ( ) {
      console.log('listening on', this.address( ));
    });
  });
}

<|endoftext|>