<commit_before>//#!/usr/bin/env node

global.Snakeskin = require('./snakeskin');

var program = require('commander');
var beautify = require('js-beautify');

var path = require('path');
var fs = require('fs'),
	exists = fs.existsSync || path.existsSync;

program
	.version(Snakeskin.VERSION.join('.'))

	.usage('[options] [text ...]')
	.option('-p, --params', 'javascript options object')

	.option('-s, --source [src]', 'path to the template file')
	.option('-o, --output [src]', 'path to the file to save')
	.option('-n, --common-js', 'common.js export (for node.js)')

	.option('-d, --data [src]', 'path to the data file (JSON) or data JSON')
	.option('-t, --tpl [name]', 'name of the main template')

	.option('--disable-localization', 'disable support for localization')
	.option('--i18n-fn', 'i18n function name')
	.option('--language [src]', 'path to the localization file (JSON) or localization JSON')
	.option('--words [src]', 'path to the localization file to save')

	.option('--disable-xml', 'disable default xml validation')
	.option('--interface', 'render all templates as interface')
	.option('--string-buffer', 'use StringBuffer for concatenate strings')
	.option('--inline-iterators', 'inline forEach and forIn')
	.option('--disable-escape-output', 'disable default "html" filter')
	.option('--pretty-print', 'formatting output')

	.parse(process.argv);

var params = program['params'];

if (params) {
	params = parse(params);

} else {
	params = {
		xml: !program['disableXml'],
		commonJS: program['commonJs'],
		localization: !program['disableLocalization'],
		i18nFn: program['i18nFn'],
		language: program['language'],
		words: program['words'],
		interface: program['interface'],
		stringBuffer: program['stringBuffer'],
		inlineIterators: program['inlineIterators'],
		escapeOutput: !program['disableEscapeOutput'],
		prettyPrint: program['prettyPrint']
	};
}

var prettyPrint = params.prettyPrint;

if (params.language) {
	params.language = parse(params.language);
}

var tplData = program['data'],
	mainTpl = program['tpl'];

var words = params.words;

if (words) {
	params.words = {};
}

var args = program['args'],
	input;

var file = program['source'],
	newFile = program['output'];

if (!file && args.length) {
	input = args.join(' ');

	if (exists(input)) {
		file = input;
		input = false;
	}
}

function parse(val) {
	if (exists(val)) {
		return JSON.parse(fs.readFileSync(val));
	}

	return eval(`(${val})`);
}

function action(data) {
	if (!data && !file) {
		program['help']();
	}

	var tpls = {};

	if (tplData || mainTpl) {
		params.commonJS = true;
		params.context = tpls;
		params.prettyPrint = false;
	}

	var str = String(data),
		res = Snakeskin.compile(str, params, {file: file});

	var toConsole = input && !program['output'] ||
		!newFile;

	if (res !== false) {
		if (tplData || mainTpl) {
			let tpl;

			if (mainTpl && mainTpl !== true) {
				tpl = tpls[mainTpl];

			} else {
				if (file) {
					tpl = tpls[file.split('.').slice(0, -1).join('.')] || tpls.main || tpls[Object.keys(tpls)[0]];

				} else {
					tpl = tpls.main || tpls[Object.keys(tpls)[0]];
				}
			}

			if (!tpl) {
				console.error('Template is not defined');
				process.exit(1);
			}

			if (tplData && tplData !== true) {
				tplData = parse(tplData);

			} else {
				tplData = void 0;
			}

			res = tpl(tplData);

			if (prettyPrint) {
				if (toConsole) {
					res = beautify['html'](res);

				} else {
					res = beautify[newFile.split('.').slice(-1)](res);
				}
			}
		}

		if (toConsole) {
			console.log(res);

		} else {
			fs.writeFileSync(newFile, res);
			console.log(`File "${file}" has been successfully compiled "${newFile}".`);
		}

	} else {
		process.exit(1);
	}

	if (words) {
		fs.writeFileSync(words, JSON.stringify(params.words, null, '\t'));
	}

	process.exit(0);
}

if (input == null) {
	let buf = '';
	let stdin = process.stdin,
		stdout = process.stdout;

	stdin.setEncoding('utf8');
	stdin.on('data', (chunk) => {
		buf += chunk;
	});

	stdin.on('end', () => {
		action(buf);
	}).resume();

	process.on('SIGINT', () => {
		stdout.write('\n');
		stdin.emit('end');
		stdout.write('\n');
		process.exit();
	});

} else {
	action(file ? fs.readFileSync(file).toString() : input);
}

<commit_msg>Доработка консольного API<commit_after>//#!/usr/bin/env node

global.Snakeskin = require('./snakeskin');

var program = require('commander');
var beautify = require('js-beautify');

var path = require('path');
var fs = require('fs'),
	exists = fs.existsSync || path.existsSync;

program
	.version(Snakeskin.VERSION.join('.'))

	.usage('[options] [dir|file ...]')
	.option('-p, --params[options]', 'javascript options object')

	.option('-s, --source [src]', 'path to the template file')
	.option('-o, --output [src]', 'path to the file to save')
	.option('-n, --common-js', 'common.js export (for node.js)')

	.option('-e, --exec', 'execute compiled template')
	.option('-d, --data [src]', 'path to the data file (JSON) or data JSON')
	.option('-t, --tpl [name]', 'name of the main template')

	.option('--disable-localization', 'disable support for localization')
	.option('--i18n-fn', 'i18n function name')
	.option('--language [src]', 'path to the localization file (JSON) or localization JSON')
	.option('--words [src]', 'path to the localization file to save')

	.option('--disable-xml', 'disable default xml validation')
	.option('--interface', 'render all templates as interface')
	.option('--string-buffer', 'use StringBuffer for concatenate strings')
	.option('--inline-iterators', 'inline forEach and forIn')
	.option('--disable-escape-output', 'disable default "html" filter')
	.option('--pretty-print', 'formatting output')

	.parse(process.argv);

var params = program['params'] ?
	parse(params) : {};

params.xml = 'disableXml' in program ?
	!program['disableXml'] : params.xml;

params.commonJS = 'commonJs' in program ?
	program['commonJs'] : params.commonJS;

params.localization = 'disableLocalization' in program ?
	!program['disableLocalization'] : params.localization;

params.i18nFn = 'i18nFn' in program ?
	program['i18nFn'] : params.i18nFn;

params.language = 'language' in program ?
	program['language'] : params.language;

params.words = 'words' in program ?
	program['words'] : params.words;

params.interface = 'interface' in program ?
	program['interface'] : params.interface;

params.stringBuffer = 'stringBuffer' in program ?
	program['stringBuffer'] : params.stringBuffer;

params.inlineIterators = 'inlineIterators' in program ?
	program['inlineIterators'] : params.inlineIterators;

params.escapeOutput = 'disableEscapeOutput' in program ?
	!program['disableEscapeOutput'] : params.escapeOutput;

params.prettyPrint = 'prettyPrint' in program ?
	program['prettyPrint'] : params.prettyPrint;

var prettyPrint = params.prettyPrint;

if (params.language) {
	params.language = parse(params.language);
}

var exec = program['exec'];
var tplData = program['data'],
	mainTpl = program['tpl'];

var words = params.words;

if (words) {
	params.words = {};
}

var args = program['args'],
	input;

var file = program['source'],
	newFile = program['output'];

if (!file && args.length) {
	input = args.join(' ');

	if (exists(input)) {
		file = input;
		input = false;
	}
}

function parse(val) {
	if (exists(val)) {
		return JSON.parse(fs.readFileSync(val));
	}

	var res;

	try {
		res =  eval(`(${val})`);

	} catch (ignore) {
		res = {};
	}

	return res || {};
}

function action(data) {
	if (!data && !file) {
		program['help']();
	}

	var tpls = {};

	if (tplData || mainTpl || exec) {
		params.commonJS = true;
		params.context = tpls;
		params.prettyPrint = false;
	}

	var str = String(data),
		res = Snakeskin.compile(str, params, {file: file});

	var toConsole = input && !program['output'] ||
		!newFile;

	if (res !== false) {
		if (tplData || mainTpl || exec) {
			let tpl;

			if (mainTpl && mainTpl !== true) {
				tpl = tpls[mainTpl];

			} else {
				if (file) {
					tpl = tpls[file.split('.').slice(0, -1).join('.')] || tpls.main || tpls[Object.keys(tpls)[0]];

				} else {
					tpl = tpls.main || tpls[Object.keys(tpls)[0]];
				}
			}

			if (!tpl) {
				console.error('Template is not defined');
				process.exit(1);
			}

			if (tplData && tplData !== true) {
				tplData = parse(tplData);

			} else {
				tplData = void 0;
			}

			res = tpl(tplData);

			if (prettyPrint) {
				if (toConsole) {
					res = beautify['html'](res);

				} else {
					res = beautify[newFile.split('.').slice(-1)](res);
				}
			}
		}

		if (toConsole) {
			console.log(res);

		} else {
			fs.writeFileSync(newFile, res);
			console.log(`File "${file}" has been successfully compiled "${newFile}".`);
		}

	} else {
		process.exit(1);
	}

	if (words) {
		fs.writeFileSync(words, JSON.stringify(params.words, null, '\t'));
	}

	process.exit(0);
}

if (input == null) {
	let buf = '';
	let stdin = process.stdin,
		stdout = process.stdout;

	stdin.setEncoding('utf8');
	stdin.on('data', (chunk) => {
		buf += chunk;
	});

	stdin.on('end', () => {
		action(buf);
	}).resume();

	process.on('SIGINT', () => {
		stdout.write('\n');
		stdin.emit('end');
		stdout.write('\n');
		process.exit();
	});

} else {
	action(file ? fs.readFileSync(file).toString() : input);
}

<|endoftext|>


<commit_before>/*!
 *
 *  Tap Tempo
 *  Copyright 2015 Justin Varghese John
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *    https://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License
 *
 */

'use strict';

// This gulpfile makes use of new JavaScript features.
// Babel handles this without us having to do anything. It just works.
// You can read more about the new JavaScript features here:
// https://babeljs.io/docs/learn-es2015/

import fs from 'fs';
import path from 'path';
import gulp from 'gulp';
import del from 'del';
import runSequence from 'run-sequence';
import browserSync from 'browser-sync';
import swPrecache from 'sw-precache';
import gulpLoadPlugins from 'gulp-load-plugins';
import {output as pagespeed} from 'psi';
import pkg from './package.json';

const $ = gulpLoadPlugins();
const reload = browserSync.reload;

// Lint JavaScript
gulp.task('jshint', () =>
  gulp.src('app/scripts/**/*.js')
    .pipe($.jshint())
    .pipe($.jshint.reporter('jshint-stylish'))
    .pipe($.if(!browserSync.active, $.jshint.reporter('fail')))
);

// Optimize images
gulp.task('images', () =>
  gulp.src('app/images/**/*')
    .pipe($.cache($.imagemin({
      progressive: true,
      interlaced: true
    })))
    .pipe(gulp.dest('dist/images'))
    .pipe($.size({title: 'images'}))
);

// Copy all files at the root level (app)
gulp.task('copy', () =>
  gulp.src([
    'app/*',
    '!app/*.html',
    'node_modules/apache-server-configs/dist/.htaccess'
  ], {
    dot: true
  }).pipe(gulp.dest('dist'))
    .pipe($.size({title: 'copy'}))
);

// Compile and automatically prefix stylesheets
gulp.task('styles', () => {
  const AUTOPREFIXER_BROWSERS = [
    'ie >= 10',
    'ie_mob >= 10',
    'ff >= 30',
    'chrome >= 34',
    'safari >= 7',
    'opera >= 23',
    'ios >= 7',
    'android >= 4.4',
    'bb >= 10'
  ];

  // For best performance, don't add Sass partials to `gulp.src`
  return gulp.src([
    'app/styles/**/*.scss',
    'app/styles/**/*.css'
  ])
    .pipe($.newer('.tmp/styles'))
    .pipe($.sourcemaps.init())
    .pipe($.sass({
      precision: 10
    }).on('error', $.sass.logError))
    .pipe($.autoprefixer(AUTOPREFIXER_BROWSERS))
    .pipe($.sourcemaps.write())
    .pipe(gulp.dest('.tmp/styles'))
    // Concatenate and minify styles
    .pipe($.if('*.css', $.minifyCss()))
    .pipe($.size({title: 'styles'}))
    .pipe($.sourcemaps.write('.'))
    .pipe(gulp.dest('dist/styles'));
});

// Concatenate and minify JavaScript. Optionally transpiles ES2015 code to ES5.
// to enables ES2015 support remove the line `"only": "gulpfile.babel.js",` in the
// `.babelrc` file.
gulp.task('scripts', () =>
    gulp.src([
      // Note: Since we are not using useref in the scripts build pipeline,
      //       you need to explicitly list your scripts here in the right order
      //       to be correctly concatenated
      './app/scripts/jquery-2.1.3.min.js',
      './app/scripts/main.js',
      './app/scripts/BufferLoader.js'
    ])
      .pipe($.newer('.tmp/scripts'))
      .pipe($.sourcemaps.init())
      .pipe($.babel())
      .pipe($.sourcemaps.write())
      .pipe(gulp.dest('.tmp/scripts'))
      .pipe($.concat('main.min.js'))
      .pipe($.uglify({preserveComments: 'some'}))
      // Output files
      .pipe($.size({title: 'scripts'}))
      .pipe($.sourcemaps.write('.'))
      .pipe(gulp.dest('dist/scripts'))
);

// Scan your HTML for assets & optimize them
gulp.task('html', () => {
  const assets = $.useref.assets({searchPath: '{.tmp,app}'});

  return gulp.src('app/**/*.html')
    .pipe(assets)
    // Remove any unused CSS
    // Note: If not using the Style Guide, you can delete it from
    //       the next line to only include styles your project uses.
    .pipe($.if('*.css', $.uncss({
      html: [
        'app/index.html'
      ],
      // CSS Selectors for UnCSS to ignore
      ignore: [
        /.navdrawer-container.open/,
        /.app-bar.open/
      ]
    })))

    // Concatenate and minify styles
    // In case you are still using useref build blocks
    .pipe($.if('*.css', $.minifyCss()))
    .pipe(assets.restore())
    .pipe($.useref())

    // Minify any HTML
    .pipe($.if('*.html', $.minifyHtml()))
    // Output files
    .pipe($.if('*.html', $.size({title: 'html', showFiles: true})))
    .pipe(gulp.dest('dist'));
});

// Clean output directory
gulp.task('clean', cb => del(['.tmp', 'dist/*', '!dist/.git'], {dot: true},
  cb));

// Watch files for changes & reload
gulp.task('serve', ['scripts', 'styles'], () => {
  browserSync({
    notify: false,
    // Customize the Browsersync console logging prefix
    logPrefix: 'JVJ',
    // Allow scroll syncing across breakpoints
    scrollElementMapping: ['main', '.mdl-layout'],
    // Run as an https by uncommenting 'https: true'
    // Note: this uses an unsigned certificate which on first access
    //       will present a certificate warning in the browser.
    // https: true,
    server: ['.tmp', 'app']
  });

  gulp.watch(['app/**/*.html'], reload);
  gulp.watch(['app/styles/**/*.{scss,css}'], ['styles', reload]);
  gulp.watch(['app/scripts/**/*.js'], ['jshint', 'scripts']);
  gulp.watch(['app/images/**/*'], reload);
});

// Build and serve the output from the dist build
gulp.task('serve:dist', ['default'], () =>
  browserSync({
    notify: false,
    logPrefix: 'JVJ',
    // Allow scroll syncing across breakpoints
    scrollElementMapping: ['main', '.mdl-layout'],
    // Run as an https by uncommenting 'https: true'
    // Note: this uses an unsigned certificate which on first access
    //       will present a certificate warning in the browser.
    // https: true,
    server: 'dist'
  })
);

// Build production files, the default task
gulp.task('default', ['clean'], cb =>
  runSequence(
    'styles',
    ['jshint', 'html', 'scripts', 'images', 'copy'],
    'generate-service-worker',
    cb
  )
);

// Run PageSpeed Insights
gulp.task('pagespeed', cb =>
  // Update the below URL to the public URL of your site
  pagespeed('example.com', {
    strategy: 'mobile'
    // By default we use the PageSpeed Insights free (no API key) tier.
    // Use a Google Developer API key if you have one: http://goo.gl/RkN0vE
    // key: 'YOUR_API_KEY'
  }, cb)
);

// See http://www.html5rocks.com/en/tutorials/service-worker/introduction/ for
// an in-depth explanation of what service workers are and why you should care.
// Generate a service worker file that will provide offline functionality for
// local resources. This should only be done for the 'dist' directory, to allow
// live reload to work as expected when serving from the 'app' directory.
gulp.task('generate-service-worker', cb => {
  const rootDir = 'dist';

  swPrecache({
    // Used to avoid cache conflicts when serving on localhost.
    cacheId: pkg.name || 'web-starter-kit',
    staticFileGlobs: [
      // Add/remove glob patterns to match your directory setup.
      `${rootDir}/images/**/*`,
      `${rootDir}/scripts/**/*.js`,
      `${rootDir}/styles/**/*.css`,
      `${rootDir}/*.{html,json}`
    ],
    // Translates a static file path to the relative URL that it's served from.
    stripPrefix: path.join(rootDir, path.sep)
  }, (err, swFileContents) => {
    if (err) {
      cb(err);
      return;
    }

    const filepath = path.join(rootDir, 'service-worker.js');

    fs.writeFile(filepath, swFileContents, err => {
      if (err) {
        cb(err);
        return;
      }

      cb();
    });
  });
});

// Load custom tasks from the `tasks` directory
// try { require('require-dir')('tasks'); } catch (err) { console.error(err); }
<commit_msg>Update gulp build<commit_after>/*!
 *
 *  Tap Tempo
 *  Copyright 2015 Justin Varghese John
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *    https://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License
 *
 */

'use strict';

// This gulpfile makes use of new JavaScript features.
// Babel handles this without us having to do anything. It just works.
// You can read more about the new JavaScript features here:
// https://babeljs.io/docs/learn-es2015/

import fs from 'fs';
import path from 'path';
import gulp from 'gulp';
import del from 'del';
import runSequence from 'run-sequence';
import browserSync from 'browser-sync';
import swPrecache from 'sw-precache';
import gulpLoadPlugins from 'gulp-load-plugins';
import {output as pagespeed} from 'psi';
import pkg from './package.json';

const $ = gulpLoadPlugins();
const reload = browserSync.reload;

// Lint JavaScript
gulp.task('jshint', () =>
  gulp.src('app/scripts/**/*.js')
    .pipe($.jshint())
    .pipe($.jshint.reporter('jshint-stylish'))
    .pipe($.if(!browserSync.active, $.jshint.reporter('fail')))
);

// Optimize images
gulp.task('images', () =>
  gulp.src('app/images/**/*')
    .pipe($.cache($.imagemin({
      progressive: true,
      interlaced: true
    })))
    .pipe(gulp.dest('dist/images'))
    .pipe($.size({title: 'images'}))
);

// Copy all files at the root level (app)
gulp.task('copy', () =>
  gulp.src([
    'app/*',
    '!app/*.html',
    'node_modules/apache-server-configs/dist/.htaccess'
  ], {
    dot: true
  }).pipe(gulp.dest('dist'))
    .pipe($.size({title: 'copy'}))
);

// Compile and automatically prefix stylesheets
gulp.task('styles', () => {
  const AUTOPREFIXER_BROWSERS = [
    'ie >= 10',
    'ie_mob >= 10',
    'ff >= 30',
    'chrome >= 34',
    'safari >= 7',
    'opera >= 23',
    'ios >= 7',
    'android >= 4.4',
    'bb >= 10'
  ];

  // For best performance, don't add Sass partials to `gulp.src`
  return gulp.src([
    'app/styles/**/*.scss',
    'app/styles/**/*.css'
  ])
    .pipe($.newer('.tmp/styles'))
    .pipe($.sourcemaps.init())
    .pipe($.sass({
      precision: 10
    }).on('error', $.sass.logError))
    .pipe($.autoprefixer(AUTOPREFIXER_BROWSERS))
    .pipe($.sourcemaps.write())
    .pipe(gulp.dest('.tmp/styles'))
    // Concatenate and minify styles
    .pipe($.if('*.css', $.minifyCss()))
    .pipe($.size({title: 'styles'}))
    .pipe($.sourcemaps.write('.'))
    .pipe(gulp.dest('dist/styles'));
});

// Concatenate and minify JavaScript. Optionally transpiles ES2015 code to ES5.
// to enables ES2015 support remove the line `"only": "gulpfile.babel.js",` in the
// `.babelrc` file.
gulp.task('scripts', () =>
    gulp.src([
      // Note: Since we are not using useref in the scripts build pipeline,
      //       you need to explicitly list your scripts here in the right order
      //       to be correctly concatenated
      './app/scripts/jquery-2.1.3.min.js',
      './app/scripts/BufferLoader.js',
      './app/scripts/metro.js',
      './app/scripts/main.js'
    ])
      .pipe($.newer('.tmp/scripts'))
      .pipe($.sourcemaps.init())
      .pipe($.babel())
      .pipe($.sourcemaps.write())
      .pipe(gulp.dest('.tmp/scripts'))
      .pipe($.concat('main.min.js'))
      .pipe($.uglify({preserveComments: 'some'}))
      // Output files
      .pipe($.size({title: 'scripts'}))
      .pipe($.sourcemaps.write('.'))
      .pipe(gulp.dest('dist/scripts'))
);

// Scan your HTML for assets & optimize them
gulp.task('html', () => {
  const assets = $.useref.assets({searchPath: '{.tmp,app}'});

  return gulp.src('app/**/*.html')
    .pipe(assets)
    // Remove any unused CSS
    // Note: If not using the Style Guide, you can delete it from
    //       the next line to only include styles your project uses.
    .pipe($.if('*.css', $.uncss({
      html: [
        'app/index.html'
      ],
      // CSS Selectors for UnCSS to ignore
      ignore: [
        /.navdrawer-container.open/,
        /.app-bar.open/
      ]
    })))

    // Concatenate and minify styles
    // In case you are still using useref build blocks
    .pipe($.if('*.css', $.minifyCss()))
    .pipe(assets.restore())
    .pipe($.useref())

    // Minify any HTML
    .pipe($.if('*.html', $.minifyHtml()))
    // Output files
    .pipe($.if('*.html', $.size({title: 'html', showFiles: true})))
    .pipe(gulp.dest('dist'));
});

// Clean output directory
gulp.task('clean', cb => del(['.tmp', 'dist/*', '!dist/.git'], {dot: true},
  cb));

// Watch files for changes & reload
gulp.task('serve', ['scripts', 'styles'], () => {
  browserSync({
    notify: false,
    // Customize the Browsersync console logging prefix
    logPrefix: 'JVJ',
    // Allow scroll syncing across breakpoints
    scrollElementMapping: ['main', '.mdl-layout'],
    // Run as an https by uncommenting 'https: true'
    // Note: this uses an unsigned certificate which on first access
    //       will present a certificate warning in the browser.
    // https: true,
    server: ['.tmp', 'app']
  });

  gulp.watch(['app/**/*.html'], reload);
  gulp.watch(['app/styles/**/*.{scss,css}'], ['styles', reload]);
  gulp.watch(['app/scripts/**/*.js'], ['jshint', 'scripts']);
  gulp.watch(['app/images/**/*'], reload);
});

// Build and serve the output from the dist build
gulp.task('serve:dist', ['default'], () =>
  browserSync({
    notify: false,
    logPrefix: 'JVJ',
    // Allow scroll syncing across breakpoints
    scrollElementMapping: ['main', '.mdl-layout'],
    // Run as an https by uncommenting 'https: true'
    // Note: this uses an unsigned certificate which on first access
    //       will present a certificate warning in the browser.
    // https: true,
    server: 'dist'
  })
);

// Build production files, the default task
gulp.task('default', ['clean'], cb =>
  runSequence(
    'styles',
    ['jshint', 'html', 'scripts', 'images', 'copy'],
    'generate-service-worker',
    cb
  )
);

// Run PageSpeed Insights
gulp.task('pagespeed', cb =>
  // Update the below URL to the public URL of your site
  pagespeed('example.com', {
    strategy: 'mobile'
    // By default we use the PageSpeed Insights free (no API key) tier.
    // Use a Google Developer API key if you have one: http://goo.gl/RkN0vE
    // key: 'YOUR_API_KEY'
  }, cb)
);

// See http://www.html5rocks.com/en/tutorials/service-worker/introduction/ for
// an in-depth explanation of what service workers are and why you should care.
// Generate a service worker file that will provide offline functionality for
// local resources. This should only be done for the 'dist' directory, to allow
// live reload to work as expected when serving from the 'app' directory.
gulp.task('generate-service-worker', cb => {
  const rootDir = 'dist';

  swPrecache({
    // Used to avoid cache conflicts when serving on localhost.
    cacheId: pkg.name || 'web-starter-kit',
    staticFileGlobs: [
      // Add/remove glob patterns to match your directory setup.
      `${rootDir}/images/**/*`,
      `${rootDir}/scripts/**/*.js`,
      `${rootDir}/styles/**/*.css`,
      `${rootDir}/*.{html,json}`
    ],
    // Translates a static file path to the relative URL that it's served from.
    stripPrefix: path.join(rootDir, path.sep)
  }, (err, swFileContents) => {
    if (err) {
      cb(err);
      return;
    }

    const filepath = path.join(rootDir, 'service-worker.js');

    fs.writeFile(filepath, swFileContents, err => {
      if (err) {
        cb(err);
        return;
      }

      cb();
    });
  });
});

// Load custom tasks from the `tasks` directory
// try { require('require-dir')('tasks'); } catch (err) { console.error(err); }
<|endoftext|>


<commit_before>import webpack from 'webpack'
import merge from 'webpack-merge'
import baseConfig from './base'

import pkg from '../package.json'

const port = process.env.PORT || 3000

export default merge(baseConfig, {
  devtool: 'eval',

  entry: [
    `webpack-hot-middleware/client?path=http://localhost:${port}/__webpack_hmr`,
    'babel-polyfill',
    './app/index',
  ],

  output: {
    publicPath: `http://localhost:${port}/dist/`,
  },

  module: {
    rules: [
      {
        test: /\.scss$/,
        loaders: ['style-loader', 'css-loader', 'sass-loader'],
      },

      {
        test: /\.woff(\?v=\d+\.\d+\.\d+)?$/,
        loader: 'url?limit=10000&mimetype=application/font-woff',
      },
      {
        test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/,
        loader: 'url?limit=10000&mimetype=application/font-woff',
      },
      {
        test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
        loader: 'url?limit=10000&mimetype=application/octet-stream',
      },
      { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: 'file' },
      { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: 'url?limit=10000&mimetype=image/svg+xml' },
    ],
  },

  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoEmitOnErrorsPlugin(),
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify('development'),
      __MJML_APP_VERSION__: JSON.stringify(pkg.version),
      __MJML_VERSION__: JSON.stringify(pkg.dependencies.mjml),
    }),
    new webpack.LoaderOptionsPlugin({
      debug: true,
    }),
  ],

  target: 'electron-renderer',
  node: {
    __dirname: true,
    __filename: true,
  },
  externals: ['mjml-core', 'node-mailjet'],
})
<commit_msg>dev build config<commit_after>import webpack from 'webpack'
import merge from 'webpack-merge'
import baseConfig from './base'

import pkg from '../package.json'

const port = process.env.PORT || 3000

export default merge(baseConfig, {
  devtool: 'eval',

  entry: [
    `webpack-hot-middleware/client?path=http://localhost:${port}/__webpack_hmr`,
    'babel-polyfill',
    './app/index',
  ],

  output: {
    publicPath: `http://localhost:${port}/dist/`,
  },

  module: {
    rules: [
      {
        test: /\.scss$/,
        loaders: ['style-loader', 'css-loader', 'sass-loader'],
      },

      {
        test: /\.woff(\?v=\d+\.\d+\.\d+)?$/,
        loader: 'url?limit=10000&mimetype=application/font-woff',
      },
      {
        test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/,
        loader: 'url?limit=10000&mimetype=application/font-woff',
      },
      {
        test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
        loader: 'url?limit=10000&mimetype=application/octet-stream',
      },
      { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: 'file' },
      { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: 'url?limit=10000&mimetype=image/svg+xml' },
    ],
  },

  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoEmitOnErrorsPlugin(),
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify('development'),
      __MJML_APP_VERSION__: JSON.stringify(pkg.version),
      __MJML_VERSION__: JSON.stringify(pkg.dependencies.mjml),
    }),
    new webpack.LoaderOptionsPlugin({
      debug: true,
    }),
  ],

  target: 'electron-renderer',
  node: {
    __dirname: true,
    __filename: true,
  },
  externals: ['mjml', 'node-mailjet'],
})
<|endoftext|>


<commit_before>/*
    Copyright (C) 2014 Härnösands kommun

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as
    published by the Free Software Foundation, either version 3 of the
    License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
/**
 *
 */
Ext.define('OpenEMap.view.layer.Tree' ,{
    extend: 'Ext.tree.Panel',
    requires: [
        'OpenEMap.data.GroupedLayerTree',
        'GeoExt.tree.Column'
    ],

    rootVisible: false,
    hideHeaders: true,

    initComponent: function() {
        if(!this.store && this.mapPanel) {
            this.store = Ext.create('OpenEMap.data.GroupedLayerTree', {
                root: {
                    text: (this.mapPanel.config && this.mapPanel.config.name ? this.mapPanel.config.name : 'Lager'),
                    expanded: true,
                    isGroupLayer: true,
                    layers: this.mapPanel.map.layerSwitcherLayerTree
                },
                map: this.mapPanel.map
            });
        }

        this.on('checkchange', function(node, checked, eOpts) {
            // Loop this node and children
            node.cascadeBy(function(n){
                n.set('checked', checked);
                var olLayerRef = n.get('layer');
                // Change layer visibility (Layer groups have no layer reference)
                if(olLayerRef) {
                    olLayerRef.setVisibility(checked);
                  	olLayerRef.options.visibility = checked;
                }
                var wms = n.get('wms');
                if (wms) {
                    wms.options.visibility = checked;
                }

           	});

           	function checkParent(n, checked) {
	            var parent = n.parentNode;
	            if (parent.get("checked") != n.get("checked")) {
		           	if (checked) {
		           	    // check parent if not root
		            	if (!parent.isRoot()) {
		                	parent.set("checked", checked);
		                	checkParent(parent, checked);
		               	}
		           	} else {
		               	if (!parent.isRoot() && !parent.childNodes.some(function(node) { return node.get('checked'); })) {
		                   	parent.set("checked", checked);
		                   	checkParent(parent, checked);
		               	}
		           	}
	            }
	       	}
           	// Checking/unchecking parent node
			checkParent(node, checked);

        });

        this.on('cellclick', function(tree, td, cellIndex, node, el, columnIndex, e) {

            // function to create legend tooltip
            var createLegend = function(url) {
                var img = Ext.create('Ext.Img', {
                    src: url
                });
                var tip = Ext.create('Ext.tip.ToolTip', {
                    title: 'Legend ' + node.raw.name,
                    closable: true,
                    items: img
                });
                tip.showBy(el);
                img.getEl().on('load', function() {
                    tip.doLayout();
                });
            };

            // function to get legend url
            // TODO: could share code with inline legend creation in GroupedLayerTree
            var getLegendUrl = function(node) {
                var layer = node.raw.layer;
                var url;
                if (node.raw.legendURL !== undefined) {
                    url = layer.legendURL;
                } else if (node.raw.wms && node.raw.wms.params.LAYERS) {
                    var layerRecord = GeoExt.data.LayerModel.createFromLayer(layer);
                    var legend = Ext.create('GeoExt.container.WmsLegend', {
                        layerRecord: layerRecord
                    });
                    url = legend.getLegendUrl(node.raw.wms.params.LAYERS);
                }
                return url;
            };

            // Get target element in an IE9 compatible way
            var target = e.browserEvent.target || e.browserEvent.srcElement;

            // check if target is the inline legend image
            if (Ext.get(target).hasCls('legendimg') && node.raw.layer) {
                var url = getLegendUrl(node);
                if (url && url.length > 0) {
                    createLegend(url);
                }
            }
        });

        this.callParent(arguments);
    },

    getConfig: function(includeLayerRef) {
    	// Start with initial config to get a complete config object
        var config = Ext.clone(this.client.initialConfig);

        // Get layers config from layer tree, to reflect changes made by user
       	var layers = this.getStore().getLayerConfiguration(includeLayerRef);

        // layer tree does not include base layers so extract them from initial config
    	var baseLayers = config.layers.filter(function(layer) {
    		return (layer.wms && layer.wms.options.isBaseLayer) ? layer : false;
    	});
    	config.layers = baseLayers.concat(layers);

    	return config;
    }
});
<commit_msg>Fix for wms.params.layers/wms.params.LAYERS in capital/small letters<commit_after>/*
    Copyright (C) 2014 Härnösands kommun

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as
    published by the Free Software Foundation, either version 3 of the
    License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
/**
 *
 */
Ext.define('OpenEMap.view.layer.Tree' ,{
    extend: 'Ext.tree.Panel',
    requires: [
        'OpenEMap.data.GroupedLayerTree',
        'GeoExt.tree.Column'
    ],

    rootVisible: false,
    hideHeaders: true,

    initComponent: function() {
        if(!this.store && this.mapPanel) {
            this.store = Ext.create('OpenEMap.data.GroupedLayerTree', {
                root: {
                    text: (this.mapPanel.config && this.mapPanel.config.name ? this.mapPanel.config.name : 'Lager'),
                    expanded: true,
                    isGroupLayer: true,
                    layers: this.mapPanel.map.layerSwitcherLayerTree
                },
                map: this.mapPanel.map
            });
        }

        this.on('checkchange', function(node, checked, eOpts) {
            // Loop this node and children
            node.cascadeBy(function(n){
                n.set('checked', checked);
                var olLayerRef = n.get('layer');
                // Change layer visibility (Layer groups have no layer reference)
                if(olLayerRef) {
                    olLayerRef.setVisibility(checked);
                  	olLayerRef.options.visibility = checked;
                }
                var wms = n.get('wms');
                if (wms) {
                    wms.options.visibility = checked;
                }

           	});

           	function checkParent(n, checked) {
	            var parent = n.parentNode;
	            if (parent.get("checked") != n.get("checked")) {
		           	if (checked) {
		           	    // check parent if not root
		            	if (!parent.isRoot()) {
		                	parent.set("checked", checked);
		                	checkParent(parent, checked);
		               	}
		           	} else {
		               	if (!parent.isRoot() && !parent.childNodes.some(function(node) { return node.get('checked'); })) {
		                   	parent.set("checked", checked);
		                   	checkParent(parent, checked);
		               	}
		           	}
	            }
	       	}
           	// Checking/unchecking parent node
			checkParent(node, checked);

        });

        this.on('cellclick', function(tree, td, cellIndex, node, el, columnIndex, e) {

            // function to create legend tooltip
            var createLegend = function(url) {
                var img = Ext.create('Ext.Img', {
                    src: url
                });
                var tip = Ext.create('Ext.tip.ToolTip', {
                    title: 'Legend ' + node.raw.name,
                    closable: true,
                    items: img
                });
                tip.showBy(el);
                img.getEl().on('load', function() {
                    tip.doLayout();
                });
            };

            // function to get legend url
            // TODO: could share code with inline legend creation in GroupedLayerTree
            var getLegendUrl = function(node) {
                var layer = node.raw.layer;
                var url;
                if (node.raw.legendURL !== undefined) {
                    url = layer.legendURL;
                } else if (node.raw.wms && (node.raw.wms.params.LAYERS || node.raw.wms.params.layers)) {
                    var layerRecord = GeoExt.data.LayerModel.createFromLayer(layer);
                    var legend = Ext.create('GeoExt.container.WmsLegend', {
                        layerRecord: layerRecord
                    });
                    url = legend.getLegendUrl(node.raw.wms.params.LAYERS || node.raw.wms.params.layers);
                }
                return url;
            };

            // Get target element in an IE9 compatible way
            var target = e.browserEvent.target || e.browserEvent.srcElement;

            // check if target is the inline legend image
            if (Ext.get(target).hasCls('legendimg') && node.raw.layer) {
                var url = getLegendUrl(node);
                if (url && url.length > 0) {
                    createLegend(url);
                }
            }
        });

        this.callParent(arguments);
    },

    getConfig: function(includeLayerRef) {
    	// Start with initial config to get a complete config object
        var config = Ext.clone(this.client.initialConfig);

        // Get layers config from layer tree, to reflect changes made by user
       	var layers = this.getStore().getLayerConfiguration(includeLayerRef);

        // layer tree does not include base layers so extract them from initial config
    	var baseLayers = config.layers.filter(function(layer) {
    		return (layer.wms && layer.wms.options.isBaseLayer) ? layer : false;
    	});
    	config.layers = baseLayers.concat(layers);

    	return config;
    }
});
<|endoftext|>


<commit_before>var
  _ = require('lodash'),
  q = require('q'),
  stringify = require('json-stable-stringify'),
  Router = require('router'),
  bodyParser = require('body-parser'),
  finalhandler = require('finalhandler'),
  RequestObject = require('../core/models/requestObject'),
  ResponseObject = require('../core/models/responseObject'),
  PluginImplementationError = require('../core/errors/pluginImplementationError'),
  BadRequestError = require('../core/errors/badRequestError');

module.exports = function RouterController (kuzzle) {
  this.router = null;
  this.pluginRouter = null;
  this.routename = 'kuzzle';
  this.kuzzle = kuzzle;
  this.connections = {};

  /**
   * Declares a new connection on a given protocol. Called by protocol plugins.
   * Returns a context object to be used with router.execute() and router.removeConnection()
   *
   * @param {string} protocol - protocol name
   * @param {string} connectionId - unique connection identifier
   * @return {promise} connection context, to be used with other router functions
   */
  this.newConnection = function (protocol, connectionId) {
    var error;

    if (!connectionId || !protocol || typeof connectionId !== 'string' || typeof protocol !== 'string') {
      error = new PluginImplementationError('Rejected new connection declaration: invalid arguments');
      kuzzle.pluginsManager.trigger('log:error', error);
      return q.reject(error);
    }

    if (!this.connections[connectionId]) {
      this.connections[connectionId] = {
        connection: {type: protocol, id: connectionId},
        user: null
      };
    }

    kuzzle.statistics.newConnection(this.connections[connectionId]);

    return q(this.connections[connectionId]);
  };

  /**
   * Called by protocol plugins: forward a received request to Kuzzle.
   * Return a promise resolved or rejected with the corresponding ResponseObject
   *
   * A note about the JWT headers: if this value is falsey, if no "authorization" field is found, if the token is not
   * properly formatted or if the token itself is invalid, then the corresponding user will automatically
   * be set to "anonymous".
   *
   * @param {Object} jwtHeaders - Optional JWT headers
   * @param {Object} requestObject - the request to execute
   * @param {String} context - connection context, obtained using the newConnection() method
   * @return {Promise} ResponseObject
   */
  this.execute = function (jwtHeaders, requestObject, context) {
    var error;

    if (!requestObject) {
      error = new PluginImplementationError('Request execution error: no provided request');
      kuzzle.pluginsManager.trigger('log:error', error);
      return q.reject(new ResponseObject({}, error));
    }

    if (!context || !context.connection) {
      error = new PluginImplementationError('Unable to execute request: ' + requestObject +
        '\nReason: invalid context. Use context.getRouter().newConnection() to get a valid context.');
      kuzzle.pluginsManager.trigger('log:error', error);
      return q.reject(new ResponseObject({}, error));
    }

    if (!context.connection.id || !this.connections[context.connection.id]) {
      error = new PluginImplementationError('Unable to execute request: unknown context. ' +
        'Has context.getRouter().newConnection() been called before executing requests?');
      kuzzle.pluginsManager.trigger('log:error', error);
      return q.reject(new ResponseObject({}, error));
    }

    return kuzzle.repositories.user.loadFromToken(getBearerTokenFromHeaders(jwtHeaders))
      .then(user => {
        context.user = user;

        return kuzzle.funnel.execute(requestObject, context);
      })
      .then(response => {
        return response;
      })
      .catch(e => {
        kuzzle.pluginsManager.trigger('log:error', e);
        return q.reject(new ResponseObject({}, e));
      });
  };

  /**
   * Called by protocol plugins: removes a connection from the connection pool.
   * @param {object} context - connection context, obtained using the newConnection() method
   */
  this.removeConnection = function (context) {
    if (context.connection.id && this.connections[context.connection.id]) {
      delete this.connections[context.connection.id];
      kuzzle.hotelClerk.removeCustomerFromAllRooms(context.connection);
      kuzzle.statistics.dropConnection(context.connection);
    }
    else {
      kuzzle.pluginsManager.trigger('log:error', new PluginImplementationError('Unable to remove connection: ' +
        context + '.\nReason: unknown context'));
    }
  };

  /**
   * Initializes the HTTP routes for the Kuzzle REST API.
   */
  this.initRouterHttp = function () {
    var
      apiBase = new Router(),
      api = new Router(),
      coverage,
      routes = [
        {verb: 'get', url: '/_getLastStats', controller: 'admin', action: 'getLastStats'},
        {verb: 'get', url: '/_getAllStats', controller: 'admin', action: 'getAllStats'},
        {verb: 'get', url: '/_now', controller: 'read', action: 'now'},
        {verb: 'get', url: '/_listIndexes', controller: 'read', action: 'listIndexes'},
        {verb: 'get', url: '/_listSubscriptions', controller: 'subscribe', action: 'list'},
        {verb: 'get', url: '/:index/_listCollections', controller: 'read', action: 'listCollections'},
        {verb: 'get', url: '/:index/_listCollections/:type', controller: 'read', action: 'listCollections'},
        {verb: 'get', url: '/:index/:collection/_mapping', controller: 'admin', action: 'getMapping'},
        {verb: 'get', url: '/:index/:collection/:id', controller: 'read', action: 'get'},

        {verb: 'post', url: '/_bulk', controller: 'bulk', action: 'import'},
        {verb: 'post', url: '/_getStats', controller: 'admin', action: 'getStats'},
        {verb: 'post', url: '/_login', controller: 'auth', action: 'login'},
        {verb: 'post', url: '/:index/_bulk', controller: 'bulk', action: 'import'},
        {verb: 'post', url: '/:index/:collection/_bulk', controller: 'bulk', action: 'import'},
        {verb: 'post', url: '/:index/:collection/_search', controller: 'read', action: 'search'},
        {verb: 'post', url: '/:index/:collection/_count', controller: 'read', action: 'count'},
        {verb: 'post', url: '/:index/:collection/_create', controller: 'write', action: 'create'},
        {verb: 'post', url: '/:index/:collection', controller: 'write', action: 'publish'},

        {verb: 'delete', url: '/_deleteIndexes', controller: 'admin', action: 'deleteIndexes'},
        {verb: 'delete', url: '/:index', controller: 'admin', action: 'deleteIndex'},
        {verb: 'delete', url: '/:index/:collection/_query', controller: 'write', action: 'deleteByQuery'},
        {verb: 'delete', url: '/:index/:collection/_truncate', controller: 'admin', action: 'truncateCollection'},
        {verb: 'delete', url: '/:index/:collection/:id', controller: 'write', action: 'delete'},
        {verb: 'delete', url: '/:index/:collection', controller: 'admin', action: 'deleteCollection'},

        {verb: 'put', url: '/_role/:id', controller: 'admin', action: 'putRole'},
        {verb: 'put', url: '/:index', controller: 'admin', action: 'createIndex'},
        {verb: 'put', url: '/:index/:collection', controller: 'write', action: 'createCollection'},
        {verb: 'put', url: '/:index/:collection/_mapping', controller: 'admin', action: 'putMapping'},
        {verb: 'put', url: '/:index/:collection/:id/_:action', controller: 'write'},
        {verb: 'put', url: '/:index/:collection/:id', controller: 'write', action: 'createOrUpdate'}
      ];
    routes = routes.concat(kuzzle.pluginsManager.routes);

    this.router = new Router();

    // create and mount a new router for the coverage API
    if (process.env.FEATURE_COVERAGE === '1') {
      coverage = require('istanbul-middleware');
      this.router.use('/coverage', coverage.createHandler({ resetOnGet: true }));
    }

    // create and mount a new router for our API
    this.router.use('/api', apiBase);
    this.router.use('/api/' + kuzzle.config.apiVersion, api);

    /*
     Registering the basic _serverInfo route
     This route is also used to get Kuzzle API Version, so it isn't registered under api/<version> but
     directly under api/
     */
    apiBase.get('/_serverInfo', (request, response) => {
      executeFromRest.call(kuzzle, {controller: 'read', action: 'serverInfo'}, request, response);
    });

    // create and mount a new router for plugins
    this.pluginRouter = new Router();
    api.use('/_plugin', this.pluginRouter);

    // add a body parsing middleware to our API
    api.use(bodyParser.json());

    // Simple hello world to let know to the user that kuzzle is running
    api.get('/', (request, response) => {
      response.writeHead('Access-Control-Allow-Origin', '*');
      response.writeHead('Access-Control-Allow-Headers', 'X-Requested-With');
      response.writeHead(200, {'Content-Type': 'application/json'});
      response.end(stringify({status: 200, error: null, result: 'Hello from Kuzzle :)'}));
    });

    // Register API routes
    routes.forEach(route => {
      api[route.verb](route.url, function (request, response) {
        var params = {
          controller: route.controller
        };

        if (route.action) {
          params.action = route.action;
        }

        executeFromRest.call(kuzzle, params, request, response);
      });
    });
  };

  /**
   * Forward incoming REST requests to the HTTP routes created by the
   * initRouterHttp function.
   *
   * @param request transmitted through the REST API
   * @param response is the HTTP connection handler
   */
  this.routeHttp = function (request, response) {
    kuzzle.pluginsManager.trigger('log:silly', 'Handle HTTP request');

    this.router(request, response, finalhandler(request, response));
  };

  /**
   * Handles requests coming from MQ protocols: AMQP, MQTT & STOMP
   *
   */
  this.routeMQListener = function () {
    kuzzle.services.list.mqBroker.listenExchange(this.routename, function handleMQMessage(msg) {
      var
        context = {
          connection: null,
          user: null
        },
        data,
        requestObject,
        rawContent;

      if (!(msg.content instanceof Buffer)) {
        rawContent = msg.content.toString();
      }
      else {
        rawContent = (new Buffer(msg.content)).toString();
      }

      try {
        data = JSON.parse(rawContent);
      }
      catch (e) {
        kuzzle.pluginsManager.trigger('log:error', {message: 'Parse error', error: e});
        return false;
      }

      kuzzle.pluginsManager.trigger('log:silly', 'Handle MQ input' + msg.fields.routingKey);

      // For MQTT messages, we do not have a replyTo header like with AMQP or STOMP
      // => MQTT client has to send its mqtt client id and subscribe to the topic exchange mqtt.<clientId>
      //    to get feedback from Kuzzle.
      if (msg.properties && msg.properties.replyTo) {
        context.connection = {type: 'amq', id: data.clientId};
      }
      else {
        context.connection = {type: 'mqtt', id: data.clientId};
      }

      requestObject = new RequestObject(data, {}, 'mq');

      kuzzle.repositories.user.loadFromToken(getBearerTokenFromHeaders(data.headers))
        .then(function (user) {
          context.user = user;
          return kuzzle.funnel.execute(requestObject, context);
        })
        .then(function (responseObject) {
          if (context.connection.type === 'amq') {
            kuzzle.services.list.mqBroker.replyTo(msg.properties.replyTo, responseObject.toJson());
          }
          else {
            kuzzle.services.list.mqBroker.addExchange('mqtt.' + context.connection.id, responseObject.toJson());
          }
        })
        .catch(function (error) {
          var errorObject = new ResponseObject({}, error);
          kuzzle.pluginsManager.trigger('log:error', error);
          if (context.connection.type === 'amq') {
            kuzzle.services.list.mqBroker.replyTo(msg.properties.replyTo, errorObject.toJson());
          }
          else {
            kuzzle.services.list.mqBroker.addExchange('mqtt.' + context.connection.id, errorObject.toJson());
          }
        });
    });
  };

};

/**
 * Transmit HTTP requests to the funnel controller and forward its response back to
 * the client
 *
 * @param params contains the request metadata
 * @param request is the original request from the client
 * @param response is the HTTP connection handler
 */
function executeFromRest(params, request, response) {
  var
    requestObject,
    errorObject,
    data,
    context = {
      connection: {type: 'rest'},
      user: null
    };

  if (!params.controller) {
    errorObject = new ResponseObject({}, new BadRequestError('The "controller" argument is missing'));
    response.writeHead(errorObject.status, {'Content-Type': 'application/json'});
    response.end(stringify(errorObject.toJson()));
    return false;
  }

  if (request.headers['content-type'] && request.headers['content-type'] !== 'application/json') {
    errorObject = new ResponseObject({}, new BadRequestError('Invalid request content-type. Expected "application/json", got: "' + request.headers['content-type'] +'"'));
    response.writeHead(errorObject.status, {'Content-Type': 'application/json'});
    response.end(stringify(errorObject.toJson()));
    return false;
  }

  data = {
    controller: params.controller,
    action: params.action || request.params.action,
    collection: request.params.collection
  };

  if (request.params.action) {
    delete request.params.action;
  }
  if (request.params.collection) {
    delete request.params.collection;
  }
  if (request.params.id) {
    data._id = request.params.id;
    delete request.params.id;
  }

  if (request.params.index) {
    data.index = request.params.index;
    delete request.params.index;
  }

  _.forEach(request.params, function (value, param) {
    request.body[param] = value;
  });

  requestObject = new RequestObject(data, request.body, 'rest');

  response.setHeader('Access-Control-Allow-Origin', '*');
  response.setHeader('Access-Control-Allow-Headers', 'X-Requested-With');

  // Just in case the user make a GET on url /mainindex/test/_search
  // Without this test we return something weird: a result.hits.hits with all document without filter because the body is empty in REST by default
  if (data.controller === 'read' && data.action === 'get') {
    if (['_search', '_bulk', '_count', '_create', '_query', '_truncate'].indexOf(data._id) !== -1) {
      errorObject = new ResponseObject({}, new BadRequestError('The action ' + requestObject.data._id + ' can\'t be done with a GET'));
      response.writeHead(errorObject.status, {'Content-Type': 'application/json'});
      response.end(stringify(errorObject.toJson()));
      return false;
    }
  }

  this.repositories.user.loadFromToken(getBearerTokenFromHeaders(request.headers))
    .then(function (user) {
      context.user = user;

      return this.funnel.execute(requestObject, context);
    }.bind(this))
    .then(function (responseObject) {
      response.writeHead(responseObject.status, {'Content-Type': 'application/json'});
      response.end(stringify(responseObject.toJson()));
    })
    .catch(function (error) {
      errorObject = new ResponseObject(requestObject, error);
      this.emit(requestObject.controller + ':websocket:funnel:reject', error);
      this.pluginsManager.trigger('log:error', error);
      response.writeHead(errorObject.status, {'Content-Type': 'application/json'});
      response.end(stringify(errorObject.toJson()));
    }.bind(this));
}

/**
 * Extract the Bearer token from the given headers
 * @param {Object} headers
 * @returns {*}
 */
function getBearerTokenFromHeaders (headers) {
  var
    r;

  if (headers !== undefined && headers.authorization !== undefined) {
    r = /^Bearer (.+)$/.exec(headers.authorization);
    if (r !== null && r[1].trim() !== '') {
      return r[1].trim();
    }
  }

  return null;
}
<commit_msg>nitpick<commit_after>var
  _ = require('lodash'),
  q = require('q'),
  stringify = require('json-stable-stringify'),
  Router = require('router'),
  bodyParser = require('body-parser'),
  finalhandler = require('finalhandler'),
  RequestObject = require('../core/models/requestObject'),
  ResponseObject = require('../core/models/responseObject'),
  PluginImplementationError = require('../core/errors/pluginImplementationError'),
  BadRequestError = require('../core/errors/badRequestError');

module.exports = function RouterController (kuzzle) {
  this.router = null;
  this.pluginRouter = null;
  this.routename = 'kuzzle';
  this.kuzzle = kuzzle;
  this.connections = {};

  /**
   * Declares a new connection on a given protocol. Called by protocol plugins.
   * Returns a context object to be used with router.execute() and router.removeConnection()
   *
   * @param {string} protocol - protocol name
   * @param {string} connectionId - unique connection identifier
   * @return {promise} connection context, to be used with other router functions
   */
  this.newConnection = function (protocol, connectionId) {
    var error;

    if (!connectionId || !protocol || typeof connectionId !== 'string' || typeof protocol !== 'string') {
      error = new PluginImplementationError('Rejected new connection declaration: invalid arguments');
      kuzzle.pluginsManager.trigger('log:error', error);
      return q.reject(error);
    }

    if (!this.connections[connectionId]) {
      this.connections[connectionId] = {
        connection: {type: protocol, id: connectionId},
        user: null
      };
    }

    kuzzle.statistics.newConnection(this.connections[connectionId]);

    return q(this.connections[connectionId]);
  };

  /**
   * Called by protocol plugins: forward a received request to Kuzzle.
   * Return a promise resolved or rejected with the corresponding ResponseObject
   *
   * A note about the JWT headers: if this value is falsey, if no "authorization" field is found, if the token is not
   * properly formatted or if the token itself is invalid, then the corresponding user will automatically
   * be set to "anonymous".
   *
   * @param {Object} jwtHeaders - Optional JWT headers
   * @param {Object} requestObject - the request to execute
   * @param {String} context - connection context, obtained using the newConnection() method
   * @return {Promise} ResponseObject
   */
  this.execute = function (jwtHeaders, requestObject, context) {
    var error;

    if (!requestObject) {
      error = new PluginImplementationError('Request execution error: no provided request');
      kuzzle.pluginsManager.trigger('log:error', error);
      return q.reject(new ResponseObject({}, error));
    }

    if (!context || !context.connection) {
      error = new PluginImplementationError('Unable to execute request: ' + requestObject +
        '\nReason: invalid context. Use context.getRouter().newConnection() to get a valid context.');
      kuzzle.pluginsManager.trigger('log:error', error);
      return q.reject(new ResponseObject({}, error));
    }

    if (!context.connection.id || !this.connections[context.connection.id]) {
      error = new PluginImplementationError('Unable to execute request: unknown context. ' +
        'Has context.getRouter().newConnection() been called before executing requests?');
      kuzzle.pluginsManager.trigger('log:error', error);
      return q.reject(new ResponseObject({}, error));
    }

    return kuzzle.repositories.user.loadFromToken(getBearerTokenFromHeaders(jwtHeaders))
      .then(user => {
        context.user = user;

        return kuzzle.funnel.execute(requestObject, context);
      })
      .then(response => {
        return response;
      })
      .catch(e => {
        kuzzle.pluginsManager.trigger('log:error', e);
        return q.reject(new ResponseObject({}, e));
      });
  };

  /**
   * Called by protocol plugins: removes a connection from the connection pool.
   * @param {object} context - connection context, obtained using the newConnection() method
   */
  this.removeConnection = function (context) {
    if (context.connection.id && this.connections[context.connection.id]) {
      delete this.connections[context.connection.id];
      kuzzle.hotelClerk.removeCustomerFromAllRooms(context.connection);
      kuzzle.statistics.dropConnection(context.connection);
    }
    else {
      kuzzle.pluginsManager.trigger('log:error', new PluginImplementationError('Unable to remove connection: ' +
        context + '.\nReason: unknown context'));
    }
  };

  /**
   * Initializes the HTTP routes for the Kuzzle REST API.
   */
  this.initRouterHttp = function () {
    var
      apiBase = new Router(),
      api = new Router(),
      coverage,
      routes = [
        {verb: 'get', url: '/_getLastStats', controller: 'admin', action: 'getLastStats'},
        {verb: 'get', url: '/_getAllStats', controller: 'admin', action: 'getAllStats'},
        {verb: 'get', url: '/_now', controller: 'read', action: 'now'},
        {verb: 'get', url: '/_listIndexes', controller: 'read', action: 'listIndexes'},
        {verb: 'get', url: '/_listSubscriptions', controller: 'subscribe', action: 'list'},
        {verb: 'get', url: '/:index/_listCollections', controller: 'read', action: 'listCollections'},
        {verb: 'get', url: '/:index/_listCollections/:type', controller: 'read', action: 'listCollections'},
        {verb: 'get', url: '/:index/:collection/_mapping', controller: 'admin', action: 'getMapping'},
        {verb: 'get', url: '/:index/:collection/:id', controller: 'read', action: 'get'},

        {verb: 'post', url: '/_bulk', controller: 'bulk', action: 'import'},
        {verb: 'post', url: '/_getStats', controller: 'admin', action: 'getStats'},
        {verb: 'post', url: '/_login', controller: 'auth', action: 'login'},
        {verb: 'post', url: '/:index/_bulk', controller: 'bulk', action: 'import'},
        {verb: 'post', url: '/:index/:collection/_bulk', controller: 'bulk', action: 'import'},
        {verb: 'post', url: '/:index/:collection/_search', controller: 'read', action: 'search'},
        {verb: 'post', url: '/:index/:collection/_count', controller: 'read', action: 'count'},
        {verb: 'post', url: '/:index/:collection/_create', controller: 'write', action: 'create'},
        {verb: 'post', url: '/:index/:collection', controller: 'write', action: 'publish'},

        {verb: 'delete', url: '/_deleteIndexes', controller: 'admin', action: 'deleteIndexes'},
        {verb: 'delete', url: '/:index', controller: 'admin', action: 'deleteIndex'},
        {verb: 'delete', url: '/:index/:collection/_query', controller: 'write', action: 'deleteByQuery'},
        {verb: 'delete', url: '/:index/:collection/_truncate', controller: 'admin', action: 'truncateCollection'},
        {verb: 'delete', url: '/:index/:collection/:id', controller: 'write', action: 'delete'},
        {verb: 'delete', url: '/:index/:collection', controller: 'admin', action: 'deleteCollection'},

        {verb: 'put', url: '/_role/:id', controller: 'admin', action: 'putRole'},
        {verb: 'put', url: '/:index', controller: 'admin', action: 'createIndex'},
        {verb: 'put', url: '/:index/:collection', controller: 'write', action: 'createCollection'},
        {verb: 'put', url: '/:index/:collection/_mapping', controller: 'admin', action: 'putMapping'},
        {verb: 'put', url: '/:index/:collection/:id/_:action', controller: 'write'},
        {verb: 'put', url: '/:index/:collection/:id', controller: 'write', action: 'createOrUpdate'}
      ];
    routes = routes.concat(kuzzle.pluginsManager.routes);

    this.router = new Router();

    // create and mount a new router for the coverage API
    if (process.env.FEATURE_COVERAGE === '1') {
      coverage = require('istanbul-middleware');
      this.router.use('/coverage', coverage.createHandler({ resetOnGet: true }));
    }

    // create and mount a new router for our API
    this.router.use('/api', apiBase);
    this.router.use('/api/' + kuzzle.config.apiVersion, api);

    /*
     Registering the basic _serverInfo route
     This route is also used to get Kuzzle API Version, so it isn't registered under api/<version> but
     directly under api/
     */
    apiBase.get('/_serverInfo', (request, response) => {
      executeFromRest.call(kuzzle, {controller: 'read', action: 'serverInfo'}, request, response);
    });

    // create and mount a new router for plugins
    this.pluginRouter = new Router();
    api.use('/_plugin', this.pluginRouter);

    // add a body parsing middleware to our API
    api.use(bodyParser.json());

    // Simple hello world to let know to the user that kuzzle is running
    api.get('/', (request, response) => {
      response.writeHead('Access-Control-Allow-Origin', '*');
      response.writeHead('Access-Control-Allow-Headers', 'X-Requested-With');
      response.writeHead(200, {'Content-Type': 'application/json'});
      response.end(stringify({status: 200, error: null, result: 'Hello from Kuzzle :)'}));
    });

    // Register API routes
    routes.forEach(route => {
      api[route.verb](route.url, function (request, response) {
        var params = {
          controller: route.controller
        };

        if (route.action) {
          params.action = route.action;
        }

        executeFromRest.call(kuzzle, params, request, response);
      });
    });
  };

  /**
   * Forward incoming REST requests to the HTTP routes created by the
   * initRouterHttp function.
   *
   * @param request transmitted through the REST API
   * @param response is the HTTP connection handler
   */
  this.routeHttp = function (request, response) {
    kuzzle.pluginsManager.trigger('log:silly', 'Handle HTTP request');

    this.router(request, response, finalhandler(request, response));
  };

  /**
   * Handles requests coming from MQ protocols: AMQP, MQTT & STOMP
   *
   */
  this.routeMQListener = function () {
    kuzzle.services.list.mqBroker.listenExchange(this.routename, function handleMQMessage(msg) {
      var
        context = {
          connection: null,
          user: null
        },
        data,
        requestObject,
        rawContent;

      if (!(msg.content instanceof Buffer)) {
        rawContent = msg.content.toString();
      }
      else {
        rawContent = (new Buffer(msg.content)).toString();
      }

      try {
        data = JSON.parse(rawContent);
      }
      catch (e) {
        kuzzle.pluginsManager.trigger('log:error', {message: 'Parse error', error: e});
        return false;
      }

      kuzzle.pluginsManager.trigger('log:silly', 'Handle MQ input' + msg.fields.routingKey);

      // For MQTT messages, we do not have a replyTo header like with AMQP or STOMP
      // => MQTT client has to send its mqtt client id and subscribe to the topic exchange mqtt.<clientId>
      //    to get feedback from Kuzzle.
      if (msg.properties && msg.properties.replyTo) {
        context.connection = {type: 'amq', id: data.clientId};
      }
      else {
        context.connection = {type: 'mqtt', id: data.clientId};
      }

      requestObject = new RequestObject(data, {}, 'mq');

      kuzzle.repositories.user.loadFromToken(getBearerTokenFromHeaders(data.headers))
        .then(function (user) {
          context.user = user;
          return kuzzle.funnel.execute(requestObject, context);
        })
        .then(function (responseObject) {
          if (context.connection.type === 'amq') {
            kuzzle.services.list.mqBroker.replyTo(msg.properties.replyTo, responseObject.toJson());
          }
          else {
            kuzzle.services.list.mqBroker.addExchange('mqtt.' + context.connection.id, responseObject.toJson());
          }
        })
        .catch(function (error) {
          var errorObject = new ResponseObject({}, error);
          kuzzle.pluginsManager.trigger('log:error', error);
          if (context.connection.type === 'amq') {
            kuzzle.services.list.mqBroker.replyTo(msg.properties.replyTo, errorObject.toJson());
          }
          else {
            kuzzle.services.list.mqBroker.addExchange('mqtt.' + context.connection.id, errorObject.toJson());
          }
        });
    });
  };

};

/**
 * Transmit HTTP requests to the funnel controller and forward its response back to
 * the client
 *
 * @param params contains the request metadata
 * @param request is the original request from the client
 * @param response is the HTTP connection handler
 */
function executeFromRest(params, request, response) {
  var
    requestObject,
    errorObject,
    data,
    context = {
      connection: {type: 'rest'},
      user: null
    };

  if (!params.controller) {
    errorObject = new ResponseObject({}, new BadRequestError('The "controller" argument is missing'));
    response.writeHead(errorObject.status, {'Content-Type': 'application/json'});
    response.end(stringify(errorObject.toJson()));
    return false;
  }

  if (request.headers['content-type'] && request.headers['content-type'] !== 'application/json') {
    errorObject = new ResponseObject({}, new BadRequestError('Invalid request content-type. Expected "application/json", got: "' + request.headers['content-type'] +'"'));
    response.writeHead(errorObject.status, {'Content-Type': 'application/json'});
    response.end(stringify(errorObject.toJson()));
    return false;
  }

  data = {
    controller: params.controller,
    action: params.action || request.params.action,
    collection: request.params.collection
  };

  if (request.params.action) {
    delete request.params.action;
  }
  if (request.params.collection) {
    delete request.params.collection;
  }
  if (request.params.id) {
    data._id = request.params.id;
    delete request.params.id;
  }

  if (request.params.index) {
    data.index = request.params.index;
    delete request.params.index;
  }

  _.forEach(request.params, function (value, param) {
    request.body[param] = value;
  });

  requestObject = new RequestObject(data, request.body, 'rest');

  response.setHeader('Access-Control-Allow-Origin', '*');
  response.setHeader('Access-Control-Allow-Headers', 'X-Requested-With');

  // Just in case the user make a GET on url /mainindex/test/_search
  // Without this test we return something weird: a result.hits.hits with all documents without filter because the body is empty in REST by default
  if (data.controller === 'read' && data.action === 'get') {
    if (['_search', '_bulk', '_count', '_create', '_query', '_truncate'].indexOf(data._id) !== -1) {
      errorObject = new ResponseObject({}, new BadRequestError('The action ' + requestObject.data._id + ' can\'t be done with a GET'));
      response.writeHead(errorObject.status, {'Content-Type': 'application/json'});
      response.end(stringify(errorObject.toJson()));
      return false;
    }
  }

  this.repositories.user.loadFromToken(getBearerTokenFromHeaders(request.headers))
    .then(function (user) {
      context.user = user;

      return this.funnel.execute(requestObject, context);
    }.bind(this))
    .then(function (responseObject) {
      response.writeHead(responseObject.status, {'Content-Type': 'application/json'});
      response.end(stringify(responseObject.toJson()));
    })
    .catch(function (error) {
      errorObject = new ResponseObject(requestObject, error);
      this.emit(requestObject.controller + ':websocket:funnel:reject', error);
      this.pluginsManager.trigger('log:error', error);
      response.writeHead(errorObject.status, {'Content-Type': 'application/json'});
      response.end(stringify(errorObject.toJson()));
    }.bind(this));
}

/**
 * Extract the Bearer token from the given headers
 * @param {Object} headers
 * @returns {*}
 */
function getBearerTokenFromHeaders (headers) {
  var
    r;

  if (headers !== undefined && headers.authorization !== undefined) {
    r = /^Bearer (.+)$/.exec(headers.authorization);
    if (r !== null && r[1].trim() !== '') {
      return r[1].trim();
    }
  }

  return null;
}
<|endoftext|>


<commit_before>// Global jQuery references
var $body = null;
var $shareModal = null;
var $commentCount = null;
var $goButton = null;
var $audioPlayer = null;

// Global state
var firstShareLoad = true;
var playedSongs = [];
var playlist = [];

/*
 * Run on page load.
 */
var onDocumentLoad = function(e) {
    // Cache jQuery references
    $body = $('body');
    $shareModal = $('#share-modal');
    $commentCount = $('.comment-count');
    $goButton = $('.js-go');
    $audioPlayer = $('#audio-player');

    // Bind events
    $shareModal.on('shown.bs.modal', onShareModalShown);
    $shareModal.on('hidden.bs.modal', onShareModalHidden);
    $goButton.on('click', onGoButtonClick);
    $body.on('click', '.tags li a', onTagClick);
    $(window).on('resize', onWindowResize);

    // configure ZeroClipboard on share panel
    ZeroClipboard.config({ swfPath: 'js/lib/ZeroClipboard.swf' });
    var clippy = new ZeroClipboard($(".clippy"));

    clippy.on('ready', function(readyEvent) {
        clippy.on('aftercopy', onClippyCopy);
    });

    onWindowResize();

    SONG_DATA = _.shuffle(SONG_DATA);
    setupAudio();
    loadPlayedSongs();
    buildPlaylist();
}

var setupAudio = function() {
    for (i=0; i<SONG_DATA.length; i++) {
        if (SONG_DATA[i].mp3_file == 'folk_song.mp3') {
            var song = SONG_DATA[i];
        }
    }
    markSongPlayed(song);

    $audioPlayer.jPlayer({
        ready: function () {
            $(this).jPlayer('setMedia', {
                mp3: '/assets/songs/folk_song.mp3'
            });

            $(this).jPlayer('play');
        },
        ended: playNextSong,
        supplied: 'mp3',
        loop: false,
    });
}

/*
 * Play the next song in the playlist.
 */
var playNextSong = function() {
    for (i=0; i<SONG_DATA.length; i++) {
        for (j=0; j<playedSongs.length; j++) {
            if (SONG_DATA[i].unique_id === playedSongs[j].id) {
                continue;
            } else {
                var nextSong = SONG_DATA[i];
                break;
            }
        }
        break;
    }
    var nextsongURL = APP_CONFIG.S3_BASE_URL + "/assets/songs/" + nextSong.mp3_file

    $audioPlayer.jPlayer('setMedia', {
        mp3: nextsongURL
    }).jPlayer('play');

    // TODO
    // What do we do if we don't find one? (we've played them all)

    // render "last played" JST of current song
    // render "currently playing" JST of next song
    // replace current song with the next song
    // add last song to stack of played songs (history)
    // rebind events inside the two JST's (or have use jquery's live(), maybe)

    markSongPlayed(nextSong);
}

/*
 * Load previously played songs from browser state (cookie, whatever)
 */
var loadPlayedSongs = function() {
    playedSongs = simpleStorage.get('playedSongs');
    console.log(playedSongs);
}

/*
 * Mark the current song as played and save state.
 */
var markSongPlayed = function(song) {
    playedSongs.push(song.unique_id)

    simpleStorage.set('playedSongs', playedSongs);
}

/*
 * Build a playlist from a set of tags.
 */
var buildPlaylist = function(tags) {
    var newPlaylist = [];

    // TODO
    // filter SONG_DATA, probably with _.filter()

    return newPlaylist;
}

/*
 * Handle clicks on tags.
 */
var onTagClick = function(e) {
    e.preventDefault();

    playlist = buildPlaylist();

    // TODO
    // update display of songs in queue
    // if current song has this tag, stop it and playNextSong();
}

/*
 * Skip the current song.
 */
var onSkipClick = function(e) {
    e.preventDefault();

    playNextSong();
}

/*
 * Basic templating example.
 */
var renderExampleTemplate = function() {
    var context = $.extend(APP_CONFIG, {
        'template_path': 'jst/example.html',
        'config': JSON.stringify(APP_CONFIG, null, 4),
        'copy': JSON.stringify(COPY, null, 4)
    });

    var html = JST.example(context);

    $('#template-example').html(html);
}

/*
 * Display the comment count.
 */
var showCommentCount = function(count) {
    $commentCount.text(count);

    if (count > 0) {
        $commentCount.addClass('has-comments');
    }

    if (count > 1) {
        $commentCount.next('.comment-label').text('Comments');
    }
}

/*
 * Share modal opened.
 */
var onShareModalShown = function(e) {
    _gaq.push(['_trackEvent', APP_CONFIG.PROJECT_SLUG, 'open-share-discuss']);

    if (firstShareLoad) {
        loadComments();

        firstShareLoad = false;
    }
}

/*
 * Share modal closed.
 */
var onShareModalHidden = function(e) {
    _gaq.push(['_trackEvent', APP_CONFIG.PROJECT_SLUG, 'close-share-discuss']);
}

/*
 * Text copied to clipboard.
 */
var onClippyCopy = function(e) {
    alert('Copied to your clipboard!');

    _gaq.push(['_trackEvent', APP_CONFIG.PROJECT_SLUG, 'summary-copied']);
}

/*
 * Fade in the next song of the playlist
 */
var showNewSong = function(e) {
    // $('.played-song').slideDown();
    $('.new-song').fadeIn();
    _.delay(function(){
        $('html, body').animate({
            scrollTop: $(".new-song").offset().top
        }, 500);
    }, 200);
}

/*
 * Fade in the next song of the playlist
 */
var onGoButtonClick = function(e) {
    $('.current-song, .player, .playlist-filters, .filter-head').fadeIn();

    $(this).fadeOut();

    // _.delay(function(){
    //         $('html, body').animate({
    //             scrollTop: $(".current-song").offset().top
    //         }, 500);
    //     }, 200);

    $('.current-song, .player, .playlist-filters').fadeIn();

    $('html, body').animate({
        scrollTop: $('.current-song').offset().top
    }, 1000);

    playNextSong();
}

/*
 * Fade in the next song of the playlist
 */
var onWindowResize = function(e) {
    $('.landing').css('height', $(window).height());
}

$(onDocumentLoad);
<commit_msg>Slight code refactor.<commit_after>// Global jQuery references
var $body = null;
var $shareModal = null;
var $commentCount = null;
var $goButton = null;
var $audioPlayer = null;

// Global state
var firstShareLoad = true;
var playedSongs = [];
var playlist = [];

/*
 * Run on page load.
 */
var onDocumentLoad = function(e) {
    // Cache jQuery references
    $body = $('body');
    $shareModal = $('#share-modal');
    $commentCount = $('.comment-count');
    $goButton = $('.js-go');
    $audioPlayer = $('#audio-player');

    // Bind events
    $shareModal.on('shown.bs.modal', onShareModalShown);
    $shareModal.on('hidden.bs.modal', onShareModalHidden);
    $goButton.on('click', onGoButtonClick);
    $body.on('click', '.tags li a', onTagClick);
    $(window).on('resize', onWindowResize);

    // configure ZeroClipboard on share panel
    ZeroClipboard.config({ swfPath: 'js/lib/ZeroClipboard.swf' });
    var clippy = new ZeroClipboard($(".clippy"));

    clippy.on('ready', function(readyEvent) {
        clippy.on('aftercopy', onClippyCopy);
    });

    onWindowResize();

    SONG_DATA = _.shuffle(SONG_DATA);
    setupAudio();
    loadPlayedSongs();
    buildPlaylist();
}

var setupAudio = function() {
    $audioPlayer.jPlayer({
        ended: playNextSong,
        supplied: 'mp3',
        loop: false,
    });
}

/*
 * Play the next song in the playlist.
 */
var playNextSong = function() {
    var nextSong = _.find(SONG_DATA, function(song) {
        return !_.contains(playedSongs, song['unique_id']);
    })

    var nextsongURL = APP_CONFIG.S3_BASE_URL + "/assets/songs/" + nextSong['mp3_file'];

    $audioPlayer.jPlayer('setMedia', {
        mp3: nextsongURL
    }).jPlayer('play');

    // TODO
    // What do we do if we don't find one? (we've played them all)

    // render "last played" JST of current song
    // render "currently playing" JST of next song
    // replace current song with the next song
    // add last song to stack of played songs (history)
    // rebind events inside the two JST's (or have use jquery's live(), maybe)

    markSongPlayed(nextSong);
}

/*
 * Load previously played songs from browser state (cookie, whatever)
 */
var loadPlayedSongs = function() {
    playedSongs = simpleStorage.get('playedSongs');
}

/*
 * Mark the current song as played and save state.
 */
var markSongPlayed = function(song) {
    playedSongs.push(song['unique_id'])

    simpleStorage.set('playedSongs', playedSongs);
}

/*
 * Build a playlist from a set of tags.
 */
var buildPlaylist = function(tags) {
    var newPlaylist = [];

    // TODO
    // filter SONG_DATA, probably with _.filter()

    return newPlaylist;
}

/*
 * Handle clicks on tags.
 */
var onTagClick = function(e) {
    e.preventDefault();

    playlist = buildPlaylist();

    // TODO
    // update display of songs in queue
    // if current song has this tag, stop it and playNextSong();
}

/*
 * Skip the current song.
 */
var onSkipClick = function(e) {
    e.preventDefault();

    playNextSong();
}

/*
 * Basic templating example.
 */
var renderExampleTemplate = function() {
    var context = $.extend(APP_CONFIG, {
        'template_path': 'jst/example.html',
        'config': JSON.stringify(APP_CONFIG, null, 4),
        'copy': JSON.stringify(COPY, null, 4)
    });

    var html = JST.example(context);

    $('#template-example').html(html);
}

/*
 * Display the comment count.
 */
var showCommentCount = function(count) {
    $commentCount.text(count);

    if (count > 0) {
        $commentCount.addClass('has-comments');
    }

    if (count > 1) {
        $commentCount.next('.comment-label').text('Comments');
    }
}

/*
 * Share modal opened.
 */
var onShareModalShown = function(e) {
    _gaq.push(['_trackEvent', APP_CONFIG.PROJECT_SLUG, 'open-share-discuss']);

    if (firstShareLoad) {
        loadComments();

        firstShareLoad = false;
    }
}

/*
 * Share modal closed.
 */
var onShareModalHidden = function(e) {
    _gaq.push(['_trackEvent', APP_CONFIG.PROJECT_SLUG, 'close-share-discuss']);
}

/*
 * Text copied to clipboard.
 */
var onClippyCopy = function(e) {
    alert('Copied to your clipboard!');

    _gaq.push(['_trackEvent', APP_CONFIG.PROJECT_SLUG, 'summary-copied']);
}

/*
 * Fade in the next song of the playlist
 */
var showNewSong = function(e) {
    // $('.played-song').slideDown();
    $('.new-song').fadeIn();
    _.delay(function(){
        $('html, body').animate({
            scrollTop: $(".new-song").offset().top
        }, 500);
    }, 200);
}

/*
 * Fade in the next song of the playlist
 */
var onGoButtonClick = function(e) {
    $('.current-song, .player, .playlist-filters, .filter-head').fadeIn();

    $(this).fadeOut();

    // _.delay(function(){
    //         $('html, body').animate({
    //             scrollTop: $(".current-song").offset().top
    //         }, 500);
    //     }, 200);

    $('.current-song, .player, .playlist-filters').fadeIn();

    $('html, body').animate({
        scrollTop: $('.current-song').offset().top
    }, 1000);

    playNextSong();
}

/*
 * Fade in the next song of the playlist
 */
var onWindowResize = function(e) {
    $('.landing').css('height', $(window).height());
}

$(onDocumentLoad);
<|endoftext|>


<commit_before>import browserify from 'browserify';
import babelify from 'babelify';
import fs from 'fs';
import path from 'path';
import del from 'del';
import source from 'vinyl-source-stream';
import buffer from 'vinyl-buffer';
import gulp from 'gulp';
import debounce from 'lodash.debounce';
import oghliner from 'oghliner';
import loadPlugins from 'gulp-load-plugins';
const plugins = loadPlugins({
  lazy: false,
});
import cssnext from 'postcss-cssnext';
import cssImport from 'postcss-import';
import cssNested from 'postcss-nested';
import cssExtend from 'postcss-simple-extend';
import cssSimpleVars from 'postcss-simple-vars';
import cssMqpacker from 'css-mqpacker';
import mkdirp from 'mkdirp';

import babelRegister from 'babel-core/register';
babelRegister();
import sourceMapSupport from 'source-map-support';
sourceMapSupport.install();

const develop = process.env.NODE_ENV !== 'production';
console.log(`Building for ${develop ? 'development' : 'production'}`);

import browserSyncCreator from 'browser-sync';
const browserSync = browserSyncCreator.create();
const statusFilename = './dist/status.json';

import engine from './engine/index.js';

gulp.task('clean', () => {
  return del(['./dist']);
});

gulp.task('lint', () => {
  return gulp
    .src(['./*.js', './engine/*.js', './src/*.js', './tests/**/*.js'])
    .pipe(plugins.eslint())
    .pipe(plugins.eslint.format())
    .pipe(plugins.eslint.failOnError());
});

gulp.task('deploy', ['build'], () => {
  return oghliner.deploy({
    rootDir: 'dist',
  });
});

gulp.task('build:status', () => {
  const cacheDir = path.join('./dist', 'cache');
  mkdirp.sync(cacheDir);
  const options = {
    cacheDir: cacheDir,
  };
  return engine.buildStatus(options).then((status) => {
    fs.writeFileSync(statusFilename, JSON.stringify(status, null, 2));
  });
});

gulp.task('build:index', ['build:status'], () => {
  const status = JSON.parse(fs.readFileSync(statusFilename));
  return engine.buildIndex(status).then((contents) => {
    fs.writeFileSync(path.join('./dist', 'index.html'), contents);
  });
});

gulp.task('build:html', ['build:index', 'build:css'], () => {
  gulp
    .src('./dist/*.html')
    // TODO: Uncomment when compression works
    // .pipe(plugins.if(!develop, plugins.inlineSource({
    //   compress: false,
    // })))
    .pipe(plugins.if(!develop, plugins.minifyHtml()))
    .pipe(gulp.dest('./dist/'));
});

gulp.task('build:tabzilla', () => {
  return gulp
    .src(['./node_modules/mozilla-tabzilla/media/img/*.png'])
    .pipe(gulp.dest('./dist/images'));
});

gulp.task('build:root', () => {
  return gulp
    .src(
      ['./src/*.*', './src/fonts/*.*', './src/images/*.*'],
      {base: './src'}
    )
    .pipe(gulp.dest('./dist'));
});

gulp.task('build:js', () => {
  return browserify({
    entries: './src/js/index.js',
    debug: true,
  })
  .transform(babelify.configure())
  .bundle()
  .pipe(source('bundle.js'))
  .pipe(buffer())
  .pipe(plugins.sourcemaps.init({
    loadMaps: true,
  }))
  .pipe(plugins.if(!develop, plugins.uglify()))
  .pipe(plugins.sourcemaps.write('.'))
  .pipe(gulp.dest('./dist'));
});

gulp.task('build:css', () => {
  const processors = [
    cssImport(),
    cssExtend(),
    cssNested(),
    cssSimpleVars(),
    cssMqpacker(),
    cssnext({
      browers: ['last 1 version'],
      compress: true,
      messages: {
        console: true,
      },
    }),
  ];
  return gulp
    .src('./src/css/index.css')
    .pipe(plugins.sourcemaps.init())
    .pipe(plugins.postcss(processors))
    .pipe(plugins.concat('bundle.css'))
    .pipe(plugins.replace('../media/img/', 'images/'))
    .pipe(plugins.sourcemaps.write('.'))
    .pipe(gulp.dest('./dist'));
});

gulp.task('build:dist', ['build:root', 'build:tabzilla', 'build:status', 'build:html', 'build:js', 'build:css']);

function offline() {
  return oghliner.offline({
    rootDir: 'dist/',
    fileGlobs: [
      'index.html',
      'bundle.js',
      'bundle.css',
      'images/*.png',
    ],
  });
}

gulp.task('build', ['build:dist'], offline);

gulp.task('watch', ['build'], () => {
  browserSync.init({
    open: false,
    server: {
      baseDir: './dist',
      ghostMode: false,
      notify: false,
    },
  });
  gulp.watch(['./src/*.*'], ['build:root']);
  gulp.watch(['./src/css/**/*.css'], ['build:css']);
  gulp.watch(['./src/js/*.js'], ['build:js']);
  gulp.watch(['./engine/*.js', './features/*.md', './src/tpl/*.html'], ['build:html']);
  gulp.watch(['./dist/**/*.*', '!./dist/offline-worker.js'], debounce(offline, 200));
  gulp.watch(['./dist/offline-worker.js'], browserSync.reload);
});

gulp.task('default', ['build']);
<commit_msg>browser-sync as dev dev, speeding up install<commit_after>import browserify from 'browserify';
import babelify from 'babelify';
import fs from 'fs';
import path from 'path';
import del from 'del';
import source from 'vinyl-source-stream';
import buffer from 'vinyl-buffer';
import gulp from 'gulp';
import debounce from 'lodash.debounce';
import oghliner from 'oghliner';
import loadPlugins from 'gulp-load-plugins';
const plugins = loadPlugins({
  lazy: false,
});
import cssnext from 'postcss-cssnext';
import cssImport from 'postcss-import';
import cssNested from 'postcss-nested';
import cssExtend from 'postcss-simple-extend';
import cssSimpleVars from 'postcss-simple-vars';
import cssMqpacker from 'css-mqpacker';
import mkdirp from 'mkdirp';

import babelRegister from 'babel-core/register';
babelRegister();
import sourceMapSupport from 'source-map-support';
sourceMapSupport.install();

const develop = process.env.NODE_ENV !== 'production';
console.log(`Building for ${develop ? 'development' : 'production'}`);

const statusFilename = './dist/status.json';

import engine from './engine/index.js';

gulp.task('clean', () => {
  return del(['./dist']);
});

gulp.task('lint', () => {
  return gulp
    .src(['./*.js', './engine/*.js', './src/*.js', './tests/**/*.js'])
    .pipe(plugins.eslint())
    .pipe(plugins.eslint.format())
    .pipe(plugins.eslint.failOnError());
});

gulp.task('deploy', ['build'], () => {
  return oghliner.deploy({
    rootDir: 'dist',
  });
});

gulp.task('build:status', () => {
  const cacheDir = path.join('./dist', 'cache');
  mkdirp.sync(cacheDir);
  const options = {
    cacheDir: cacheDir,
  };
  return engine.buildStatus(options).then((status) => {
    fs.writeFileSync(statusFilename, JSON.stringify(status, null, 2));
  });
});

gulp.task('build:index', ['build:status'], () => {
  const status = JSON.parse(fs.readFileSync(statusFilename));
  return engine.buildIndex(status).then((contents) => {
    fs.writeFileSync(path.join('./dist', 'index.html'), contents);
  });
});

gulp.task('build:html', ['build:index', 'build:css'], () => {
  gulp
    .src('./dist/*.html')
    // TODO: Uncomment when compression works
    // .pipe(plugins.if(!develop, plugins.inlineSource({
    //   compress: false,
    // })))
    .pipe(plugins.if(!develop, plugins.minifyHtml()))
    .pipe(gulp.dest('./dist/'));
});

gulp.task('build:tabzilla', () => {
  return gulp
    .src(['./node_modules/mozilla-tabzilla/media/img/*.png'])
    .pipe(gulp.dest('./dist/images'));
});

gulp.task('build:root', () => {
  return gulp
    .src(
      ['./src/*.*', './src/fonts/*.*', './src/images/*.*'],
      {base: './src'}
    )
    .pipe(gulp.dest('./dist'));
});

gulp.task('build:js', () => {
  return browserify({
    entries: './src/js/index.js',
    debug: true,
  })
  .transform(babelify.configure())
  .bundle()
  .pipe(source('bundle.js'))
  .pipe(buffer())
  .pipe(plugins.sourcemaps.init({
    loadMaps: true,
  }))
  .pipe(plugins.if(!develop, plugins.uglify()))
  .pipe(plugins.sourcemaps.write('.'))
  .pipe(gulp.dest('./dist'));
});

gulp.task('build:css', () => {
  const processors = [
    cssImport(),
    cssExtend(),
    cssNested(),
    cssSimpleVars(),
    cssMqpacker(),
    cssnext({
      browers: ['last 1 version'],
      compress: true,
      messages: {
        console: true,
      },
    }),
  ];
  return gulp
    .src('./src/css/index.css')
    .pipe(plugins.sourcemaps.init())
    .pipe(plugins.postcss(processors))
    .pipe(plugins.concat('bundle.css'))
    .pipe(plugins.replace('../media/img/', 'images/'))
    .pipe(plugins.sourcemaps.write('.'))
    .pipe(gulp.dest('./dist'));
});

gulp.task('build:dist', ['build:root', 'build:tabzilla', 'build:status', 'build:html', 'build:js', 'build:css']);

function offline() {
  return oghliner.offline({
    rootDir: 'dist/',
    fileGlobs: [
      'index.html',
      'bundle.js',
      'bundle.css',
      'images/*.png',
    ],
  });
}

gulp.task('build', ['build:dist'], offline);

gulp.task('watch', ['build'], () => {
  const browserSyncCreator = require('browser-sync');
  const browserSync = browserSyncCreator.create();
  browserSync.init({
    open: false,
    server: {
      baseDir: './dist',
      ghostMode: false,
      notify: false,
    },
  });
  gulp.watch(['./src/*.*'], ['build:root']);
  gulp.watch(['./src/css/**/*.css'], ['build:css']);
  gulp.watch(['./src/js/*.js'], ['build:js']);
  gulp.watch(['./engine/*.js', './features/*.md', './src/tpl/*.html'], ['build:html']);
  gulp.watch(['./dist/**/*.*', '!./dist/offline-worker.js'], debounce(offline, 200));
  gulp.watch(['./dist/offline-worker.js'], browserSync.reload);
});

gulp.task('default', ['build']);
<|endoftext|>


<commit_before>// generated on 2016-01-09 using generator-chrome-extension 0.4.4
import gulp from 'gulp';
import gulpLoadPlugins from 'gulp-load-plugins';
import del from 'del';
import runSequence from 'run-sequence';
import {stream as wiredep} from 'wiredep';

const $ = gulpLoadPlugins();

gulp.task('extras', () => {
  return gulp.src([
    'app/*.*',
    'app/_locales/**',
    '!app/scripts.babel',
    '!app/*.json',
    '!app/*.html',
  ], {
    base: 'app',
    dot: true
  }).pipe(gulp.dest('dist'));
});

function lint(files, options) {
  return () => {
    return gulp.src(files)
      .pipe($.eslint())
      .pipe($.eslint.format());
  };
}

gulp.task('lint', lint('app/scripts.babel/**/*.js'));

gulp.task('images', () => {
  return gulp.src('app/images/**/*')
    .pipe($.if($.if.isFile, $.cache($.imagemin({
      progressive: true,
      interlaced: true,
      // don't remove IDs from SVGs, they are often used
      // as hooks for embedding and styling
      svgoPlugins: [{cleanupIDs: false}]
    }))
    .on('error', function (err) {
      console.log(err);
      this.end();
    })))
    .pipe(gulp.dest('dist/images'));
});

gulp.task('html',  () => {
  return gulp.src('app/*.html')
    .pipe($.useref({searchPath: ['.tmp', 'app', '.']}))
    .pipe($.sourcemaps.init())
    .pipe($.if('*.js', $.uglify()))
    .pipe($.if('*.css', $.minifyCss({compatibility: '*'})))
    .pipe($.sourcemaps.write())
    .pipe($.if('*.html', $.minifyHtml({conditionals: true, loose: true})))
    .pipe(gulp.dest('dist'));
});

gulp.task('chromeManifest', () => {
  return gulp.src('app/manifest.json')
    .pipe($.chromeManifest({
      buildnumber: true,
      background: {
        target: 'scripts/background.js',
        exclude: [
          'scripts/chromereload.js'
        ]
      }
  }))
  .pipe($.if('*.css', $.minifyCss({compatibility: '*'})))
  .pipe($.if('*.js', $.sourcemaps.init()))
  .pipe($.if('*.js', $.uglify()))
  .pipe($.if('*.js', $.sourcemaps.write('.')))
  .pipe(gulp.dest('dist'));
});

gulp.task('babel', () => {
  return gulp.src('app/scripts.babel/**/*.js')
      .pipe($.babel())
      .pipe(gulp.dest('app/scripts'));
});

gulp.task('clean', del.bind(null, ['.tmp', 'dist']));

gulp.task('watch', ['lint', 'babel', 'html'], () => {
  $.livereload.listen();

  gulp.watch([
    'app/*.html',
    'app/scripts/**/*.js',
    'app/images/**/*',
    'app/styles/**/*',
    'app/_locales/**/*.json'
  ]).on('change', $.livereload.reload);

  gulp.watch('app/scripts.babel/**/*.js', ['lint', 'babel']);
  gulp.watch('bower.json', ['wiredep']);
});

gulp.task('size', () => {
  return gulp.src('dist/**/*').pipe($.size({title: 'build', gzip: true}));
});

gulp.task('wiredep', () => {
  gulp.src('app/*.html')
    .pipe(wiredep({
      ignorePath: /^(\.\.\/)*\.\./
    }))
    .pipe(gulp.dest('app'));
});

gulp.task('package', function () {
  var manifest = require('./dist/manifest.json');
  return gulp.src('dist/*')
      .pipe($.zip('Timus Friends-' + manifest.version + '.zip'))
      .pipe(gulp.dest('package'));
});

gulp.task('build', (cb) => {
  runSequence(
    'lint', 'babel', 'chromeManifest',
    ['html', 'images', 'extras'],
    'size', cb);
});

gulp.task('default', ['clean'], cb => {
  runSequence('build', cb);
});
<commit_msg>Add switch to disable sourcemaps<commit_after>// generated on 2016-01-09 using generator-chrome-extension 0.4.4
import gulp from 'gulp';
import gulpLoadPlugins from 'gulp-load-plugins';
import del from 'del';
import runSequence from 'run-sequence';
import {stream as wiredep} from 'wiredep';

const $ = gulpLoadPlugins();

const DEBUG = false;

gulp.task('extras', () => {
  return gulp.src([
    'app/*.*',
    'app/_locales/**',
    '!app/scripts.babel',
    '!app/*.json',
    '!app/*.html',
  ], {
    base: 'app',
    dot: true
  }).pipe(gulp.dest('dist'));
});

function lint(files, options) {
  return () => {
    return gulp.src(files)
      .pipe($.eslint())
      .pipe($.eslint.format());
  };
}

gulp.task('lint', lint('app/scripts.babel/**/*.js'));

gulp.task('images', () => {
  return gulp.src('app/images/**/*')
    .pipe($.if($.if.isFile, $.cache($.imagemin({
      progressive: true,
      interlaced: true,
      // don't remove IDs from SVGs, they are often used
      // as hooks for embedding and styling
      svgoPlugins: [{cleanupIDs: false}]
    }))
    .on('error', function (err) {
      console.log(err);
      this.end();
    })))
    .pipe(gulp.dest('dist/images'));
});

gulp.task('html',  () => {
  return gulp.src('app/*.html')
    .pipe($.useref({searchPath: ['.tmp', 'app', '.']}))
    .pipe($.if(DEBUG, $.sourcemaps.init()))
    .pipe($.if('*.js', $.uglify()))
    .pipe($.if('*.css', $.minifyCss({compatibility: '*'})))
    .pipe($.if(DEBUG, $.sourcemaps.write()))
    .pipe($.if('*.html', $.minifyHtml({conditionals: true, loose: true})))
    .pipe(gulp.dest('dist'));
});

gulp.task('chromeManifest', () => {
  return gulp.src('app/manifest.json')
    .pipe($.chromeManifest({
      buildnumber: true,
      background: {
        target: 'scripts/background.js',
        exclude: [
          'scripts/chromereload.js'
        ]
      }
  }))
  .pipe($.if('*.css', $.minifyCss({compatibility: '*'})))
  .pipe($.if('*.js', $.if(DEBUG, $.sourcemaps.init())))
  .pipe($.if('*.js', $.uglify()))
  .pipe($.if('*.js', $.if(DEBUG, $.sourcemaps.write('.'))))
  .pipe(gulp.dest('dist'));
});

gulp.task('babel', () => {
  return gulp.src('app/scripts.babel/**/*.js')
      .pipe($.babel())
      .pipe(gulp.dest('app/scripts'));
});

gulp.task('clean', del.bind(null, ['.tmp', 'dist']));

gulp.task('watch', ['lint', 'babel', 'html'], () => {
  $.livereload.listen();

  gulp.watch([
    'app/*.html',
    'app/scripts/**/*.js',
    'app/images/**/*',
    'app/styles/**/*',
    'app/_locales/**/*.json'
  ]).on('change', $.livereload.reload);

  gulp.watch('app/scripts.babel/**/*.js', ['lint', 'babel']);
  gulp.watch('bower.json', ['wiredep']);
});

gulp.task('size', () => {
  return gulp.src('dist/**/*').pipe($.size({title: 'build', gzip: true}));
});

gulp.task('wiredep', () => {
  gulp.src('app/*.html')
    .pipe(wiredep({
      ignorePath: /^(\.\.\/)*\.\./
    }))
    .pipe(gulp.dest('app'));
});

gulp.task('package', function () {
  var manifest = require('./dist/manifest.json');
  return gulp.src('dist/*')
      .pipe($.zip('Timus Friends-' + manifest.version + '.zip'))
      .pipe(gulp.dest('package'));
});

gulp.task('build', (cb) => {
  runSequence(
    'lint', 'babel', 'chromeManifest',
    ['html', 'images', 'extras'],
    'size', cb);
});

gulp.task('default', ['clean'], cb => {
  runSequence('build', cb);
});
<|endoftext|>