<commit_before>#!/usr/bin/env node
/**
 * Build script for F2
 *
 * This script requires that the following node packages be installed:
 *   - markitdown (npm install -g markitdown)
 *   - optimist (npm install optimist)
 *   - uglify-js (npm install uglify-js)
 *   - wrench-js (npm install wrench)
 *   - yuidocjs (npm install yuidocjs)
 *     also requires pandoc: http://johnmacfarlane.net/pandoc/installing.html
 */
var exec = require('child_process').exec;
var fs = require('fs');
var jsp = require('uglify-js').parser;
var pro = require('uglify-js').uglify;
var wrench = require('wrench');
var Y = require('yuidocjs');
var optimist = require('optimist');
var argv = optimist
	.usage('Build script for F2\nUsage: $0 [options]')
	.boolean('a').alias('a', 'all').describe('a', 'Build all')
	.boolean('d').alias('d', 'docs').describe('d', 'Build the docs')
	.boolean('g').alias('g', 'gh-pages').describe('g', 'Copy docs to gh-pages folder. Must have the gh-pages branch cloned to ../gh-pages')
	.boolean('h').alias('h', 'help').describe('h', 'Display this help information')
	.boolean('j').alias('j', 'js-sdk').describe('j', 'Build just the JS SDK')
	.boolean('y').alias('y', 'yuidoc').describe('y', 'Build the YUIDoc for the SDK')
	.argv;

// constants
var ENCODING = 'utf-8';
var EOL = '\n';

// files to be packaged
var packageFiles = [
	'sdk/src/third-party/json3.js',
	'sdk/src/third-party/eventemitter2.js',
	'sdk/src/third-party/easyXDM/easyXDM.min.js',
	'sdk/f2.no-third-party.js' // this file is created by the build process
];

// only the files that represent f2
var coreFiles = [
	'sdk/src/preamble.js',
	'sdk/src/classes.js',
	'sdk/src/constants.js',
	'sdk/src/container.js',
	'sdk/src/ui.js'
];

// a list of options that maps an argument to a function
var options = [
	{ arg: 'h', f: help },
	{ arg: 'j', f: js },
	{ arg: 'd', f: docs },
	{ arg: 'y', f: yuidoc },
	{ arg: 'g', f: ghp }
];

// process the list of options
function processNext() {
	var option = options.shift();

	if (!option) { return; }

	if (argv[option.arg] || argv.a) {
		option.f();
	} else {
		processNext();
	}
}
processNext();

/**
 * Build the documentation for GitHub Pages. The documentation generation is
 * asynchronous, so if anything needs to execute after the generation is
 * complete, those functions can be passed as parameters.
 * @method docs
 */
function docs() {
	console.log('Generating Docs...');
	var callbacks = arguments;

	exec(
		'markitdown ./ --output-path ../html --header ./template/header.html --footer ./template/footer.html --head ./template/style.html --title "F2 Documentation"',
		{ cwd:'./docs/src' },
		function(error, stdout, stderr) {
			if (error) {
				console.log(stderr);
			} else {
				//console.log(stdout);
				console.log('COMPLETE');
				processNext();
			}
		}
	);
};

/**
 * Copies all documentation to the gh-pages folder
 * @method ghp
 */
function ghp() {
	console.log('Copying documentation to gh-pages...');
	// temporary - do not overwrite the gh-pages index.html until launch
	fs.renameSync('./docs/html/index.html', './docs/html/index-temp.html');
	wrench.copyDirSyncRecursive('./docs/html', '../gh-pages', { preserve:true });
	// temporary - put index.html back to normal
	fs.renameSync('./docs/html/index-temp.html', './docs/html/index.html');
	wrench.copyDirSyncRecursive('./sdk/docs', '../gh-pages/sdk');
	console.log('COMPLETE');

	processNext();
};

/**
 * Display the help information
 * @method help
 */
function help() {
	optimist.wrap(80).showHelp();
};

/**
 * Build the debug, minified, and no-third-party sdk files
 * @method js
 */
function js() {
	console.log('Building f2.no-third-party.js...');
	var contents = coreFiles.map(function(f) {
		return fs.readFileSync(f, ENCODING);
	});
	fs.writeFileSync('./sdk/f2.no-third-party.js', contents.join(EOL), ENCODING);
	console.log('COMPLETE');

	console.log('Building Debug Package...');
	var contents = packageFiles.map(function(f) {
		return fs.readFileSync(f, ENCODING);
	});
	fs.writeFileSync('./sdk/f2.debug.js', contents.join(EOL), ENCODING);
	console.log('COMPLETE');

	console.log('Building Minified Package...');
	var contents = packageFiles.map(function(f) {

		var code = fs.readFileSync(f, ENCODING);
		var comments = [];
		var token = '"F2: preserved commment block"';

		// borrowed from enter-js
		code = code.replace(/\/\*![\s\S]*?\*\//g, function(comment) {
			comments.push(comment)
			return ';' + token + ';';
		});

		var ast = jsp.parse(code); // parse code and get the initial AST
		ast = pro.ast_mangle(ast); // get a new AST with mangled names
		ast = pro.ast_squeeze(ast); // get an AST with compression optimizations
		code = pro.gen_code(ast); // compressed code here

		code = code.replace(RegExp(token, 'g'), function() {
			return EOL + comments.shift() + EOL;
		});

		return code;
	});
	fs.writeFileSync('./sdk/f2.min.js', contents.join(EOL), ENCODING);
	console.log('COMPLETE');

	processNext();
};

/**
 * Build the YUIDoc for the sdk
 * @method yuidoc
 */
function yuidoc() {
	var callbacks = arguments;
	var docOptions = {
		quiet:true,
		norecurse:true,
		paths:['./sdk/src'],
		outdir:'./sdk/docs',
		themedir:'./sdk/doc-theme'
	};

	console.log('Generating YUIDoc...');
	var json = (new Y.YUIDoc(docOptions)).run();
	docOptions = Y.Project.mix(json, docOptions);
	(new Y.DocBuilder(docOptions, json)).compile(function() {
		console.log('COMPLETE');
		processNext();
	});
};<commit_msg>build script updates for compiling LESS<commit_after>#!/usr/bin/env node
/**
 * Build script for F2
 *
 * This script requires that the following node packages be installed:
 *   - markitdown (npm install -g markitdown)
 *   - optimist (npm install optimist)
 *   - uglify-js (npm install uglify-js)
 *   - wrench-js (npm install wrench)
 *   - yuidocjs (npm install yuidocjs)
 *     also requires pandoc: http://johnmacfarlane.net/pandoc/installing.html
 */
var exec = require('child_process').exec;
var fs = require('fs');
var jsp = require('uglify-js').parser;
var pro = require('uglify-js').uglify;
var wrench = require('wrench');
var Y = require('yuidocjs');
var optimist = require('optimist');
var argv = optimist
	.usage('Build script for F2\nUsage: $0 [options]')
	.boolean('a').alias('a', 'all').describe('a', 'Build all')
	.boolean('d').alias('d', 'docs').describe('d', 'Build the docs')
	.boolean('l').alias('l', 'less').describe('l', 'Compile LESS')
	.boolean('g').alias('g', 'gh-pages').describe('g', 'Copy docs to gh-pages folder. Must have the gh-pages branch cloned to ../gh-pages')
	.boolean('h').alias('h', 'help').describe('h', 'Display this help information')
	.boolean('j').alias('j', 'js-sdk').describe('j', 'Build just the JS SDK')
	.boolean('y').alias('y', 'yuidoc').describe('y', 'Build the YUIDoc for the SDK')
	.argv;

// constants
var ENCODING = 'utf-8';
var EOL = '\n';

// files to be packaged
var packageFiles = [
	'sdk/src/third-party/json3.js',
	'sdk/src/third-party/eventemitter2.js',
	'sdk/src/third-party/easyXDM/easyXDM.min.js',
	'sdk/f2.no-third-party.js' // this file is created by the build process
];

// only the files that represent f2
var coreFiles = [
	'sdk/src/preamble.js',
	'sdk/src/classes.js',
	'sdk/src/constants.js',
	'sdk/src/container.js',
	'sdk/src/ui.js'
];

// a list of options that maps an argument to a function
var options = [
	{ arg: 'h', f: help },
	{ arg: 'j', f: js },
	{ arg: 'd', f: docs },
	{ arg: 'l', f: less },
	{ arg: 'y', f: yuidoc },
	{ arg: 'g', f: ghp }
];

// process the list of options
function processNext() {
	var option = options.shift();

	if (!option) { return; }

	if (argv[option.arg] || argv.a) {
		option.f();
	} else {
		processNext();
	}
}
processNext();

/**
 * Build the documentation for GitHub Pages. The documentation generation is
 * asynchronous, so if anything needs to execute after the generation is
 * complete, those functions can be passed as parameters.
 * @method docs
 */
function docs() {
	console.log('Generating Docs...');
	var callbacks = arguments;

	exec(
		'markitdown ./ --output-path ../html --header ./template/header.html --footer ./template/footer.html --head ./template/style.html --title "F2 Documentation"',
		{ cwd:'./docs/src' },
		function(error, stdout, stderr) {
			if (error) {
				console.log(stderr);
			} else {
				console.log('COMPLETE');
				less();//compile LESS before we're done.
				processNext();
			}
		}
	);
};

/**
 * Compile LESS into f2.css
 * @method less
 */
function less(){
	console.log('Compiling LESS...');
	exec(
		'lessc ./template/less/bootstrap.less > ../html/css/F2.css', //--compress
		{ cwd: './docs/src' },
		function(error, stdout, stderr){
			if (error){
				console.log(stderr);
			} else {
				console.log("COMPLETE");
				processNext();
			}
		}
	);
}

/**
 * Copies all documentation to the gh-pages folder
 * @method ghp
 */
function ghp() {
	console.log('Copying documentation to gh-pages...');
	// temporary - do not overwrite the gh-pages index.html until launch
	fs.renameSync('./docs/html/index.html', './docs/html/index-temp.html');
	wrench.copyDirSyncRecursive('./docs/html', '../gh-pages', { preserve:true });
	// temporary - put index.html back to normal
	fs.renameSync('./docs/html/index-temp.html', './docs/html/index.html');
	wrench.copyDirSyncRecursive('./sdk/docs', '../gh-pages/sdk');
	console.log('COMPLETE');

	processNext();
};

/**
 * Display the help information
 * @method help
 */
function help() {
	optimist.wrap(80).showHelp();
};

/**
 * Build the debug, minified, and no-third-party sdk files
 * @method js
 */
function js() {
	console.log('Building f2.no-third-party.js...');
	var contents = coreFiles.map(function(f) {
		return fs.readFileSync(f, ENCODING);
	});
	fs.writeFileSync('./sdk/f2.no-third-party.js', contents.join(EOL), ENCODING);
	console.log('COMPLETE');

	console.log('Building Debug Package...');
	var contents = packageFiles.map(function(f) {
		return fs.readFileSync(f, ENCODING);
	});
	fs.writeFileSync('./sdk/f2.debug.js', contents.join(EOL), ENCODING);
	console.log('COMPLETE');

	console.log('Building Minified Package...');
	var contents = packageFiles.map(function(f) {

		var code = fs.readFileSync(f, ENCODING);
		var comments = [];
		var token = '"F2: preserved commment block"';

		// borrowed from enter-js
		code = code.replace(/\/\*![\s\S]*?\*\//g, function(comment) {
			comments.push(comment)
			return ';' + token + ';';
		});

		var ast = jsp.parse(code); // parse code and get the initial AST
		ast = pro.ast_mangle(ast); // get a new AST with mangled names
		ast = pro.ast_squeeze(ast); // get an AST with compression optimizations
		code = pro.gen_code(ast); // compressed code here

		code = code.replace(RegExp(token, 'g'), function() {
			return EOL + comments.shift() + EOL;
		});

		return code;
	});
	fs.writeFileSync('./sdk/f2.min.js', contents.join(EOL), ENCODING);
	console.log('COMPLETE');

	processNext();
};

/**
 * Build the YUIDoc for the sdk
 * @method yuidoc
 */
function yuidoc() {
	var callbacks = arguments;
	var docOptions = {
		quiet:true,
		norecurse:true,
		paths:['./sdk/src'],
		outdir:'./sdk/docs',
		themedir:'./sdk/doc-theme'
	};

	console.log('Generating YUIDoc...');
	var json = (new Y.YUIDoc(docOptions)).run();
	docOptions = Y.Project.mix(json, docOptions);
	(new Y.DocBuilder(docOptions, json)).compile(function() {
		console.log('COMPLETE');
		processNext();
	});
};<|endoftext|>


<commit_before>// Get JSON from source file
var source = require('./simple-icons.json');

// Loop through icons
for (var i = 0; i < source.icons.length; i++) {

    var hex = source.icons[i].hex;

    // Add red, green and blue values to the JSON object
    var red   = parseInt(hex.substr(0,2), 16) / 255;
    var green = parseInt(hex.substr(2,2), 16) / 255;
    var blue  = parseInt(hex.substr(4,2), 16) / 255;

    // Add hue to the JSON object
    var max = Math.max(red, green, blue);
    var min = Math.min(red, green, blue);
    var delta = max - min;
    source.icons[i].luminance = 100 * (max + min) / 2;
    if (source.icons[i].luminance < 50) {
        source.icons[i].saturation = 100 * (max - min) / (max + min);
    } else {
        source.icons[i].saturation = 100 * (max - min) / (2 - max - min);
    }
    if (delta === 0) {
        var hue = 0;
    } else {
        if (max === red) {
            var hue = ((green - blue) / delta) * 60;
            if (hue < 0) {
                hue += 360;
            }
        } else if (max === green) {
            var hue = (((blue - red) / delta) + 2) * 60;
        } else {
            var hue = (((red - green) / delta) + 4) * 60;
        }
    }
    source.icons[i].hue = hue;
}

// Sort icons by hue

// Ensures blue icons appear first in the last, purple ones last
for (var i = 0; i < source.icons.length; i++) {
    source.icons[i].hue += 90;
    source.icons[i].hue = source.icons[i].hue % 360;
}
source.icons.sort(function(a, b) {
    return parseFloat(b.hue) - parseFloat(a.hue);
});
var tmp = [];
for (var i = 0; i < source.icons.length; i++) {
    if (source.icons[i].luminance < 10) {
        tmp.push(source.icons[i]);
        source.icons.splice(i,1);
        i--;
    }
}
for (var i = 0; i < source.icons.length; i++) {
    if (source.icons[i].saturation < 5) {
        tmp.push(source.icons[i]);
        source.icons.splice(i,1);
        i--;
    }
}
tmp.sort(function(a, b) {
    return parseFloat(b.luminance) - parseFloat(a.luminance);
});
for (var i = 0; i < tmp.length; i++) {
    source.icons.push(tmp[i]);
}
console.log(tmp);

// Read header and footer content into variables
var fs = require('fs');
function readFile(path, callback) {
    try {
        var filename = require.resolve(path);
        fs.readFile(filename, 'utf8', callback);
    } catch (e) {
        callback(e);
    }
}
var fs = require('fs');
var header = fs.readFileSync('./src/header.html', 'utf8');
var footer = fs.readFileSync('./src/footer.html', 'utf8');

// Build content
var main = "
" + source.icons.length + " SVG icons for popular brands Download them from GitHub
\n
";

for (var i = 0; i < source.icons.length; i++) {
    var filePath = source.icons[i].title.toLowerCase();
    filePath = filePath.replace(' ', '');
    filePath = filePath.replace('.', '');
    filePath = filePath.replace('+', 'plus');
    filePath = "./icons/" + filePath + ".svg";
    console.log(source.icons[i].title + ", sat = " + source.icons[i].saturation);
    var fs = require('fs');
    var svg = fs.readFileSync(filePath, 'utf8');
    main += "\t\t\t
" + svg + source.icons[i].title + "
#" + source.icons[i].hex + "
\n";
}

// Put all content together and export to index.html
var htmlOutput = header + main + footer;
fs.writeFile("./index.html", htmlOutput, function(err) {
    if(err) {
        return console.log(err);
    }
    console.log("The file was saved!");
});<commit_msg>Fixed divide by zero issue<commit_after>// Get JSON from source file
var source = require('./simple-icons.json');

// Loop through icons
for (var i = 0; i < source.icons.length; i++) {

    var hex = source.icons[i].hex;

    // Add red, green and blue values to the JSON object
    var red   = parseInt(hex.substr(0,2), 16) / 255;
    var green = parseInt(hex.substr(2,2), 16) / 255;
    var blue  = parseInt(hex.substr(4,2), 16) / 255;

    // Add hue to the JSON object
    var max = Math.max(red, green, blue);
    var min = Math.min(red, green, blue);
    var delta = max - min;
    source.icons[i].luminance = 100 * (max + min) / 2;
    if (delta === 0) {
        var hue = 0;
        source.icons[i].saturation = 0;
    } else {
        if (source.icons[i].luminance < 50) {
            source.icons[i].saturation = 100 * (max - min) / (max + min);
        } else {
            source.icons[i].saturation = 100 * (max - min) / (2 - max - min);
        }
        if (max === red) {
            var hue = ((green - blue) / delta) * 60;
            if (hue < 0) {
                hue += 360;
            }
        } else if (max === green) {
            var hue = (((blue - red) / delta) + 2) * 60;
        } else {
            var hue = (((red - green) / delta) + 4) * 60;
        }
    }
    source.icons[i].hue = hue;
}

// Sort icons by hue

// Ensures blue icons appear first in the last, purple ones last
for (var i = 0; i < source.icons.length; i++) {
    source.icons[i].hue += 90;
    source.icons[i].hue = source.icons[i].hue % 360;
}
source.icons.sort(function(a, b) {
    return parseFloat(b.hue) - parseFloat(a.hue);
});
var tmp = [];
for (var i = 0; i < source.icons.length; i++) {
    if (source.icons[i].luminance < 10) {
        tmp.push(source.icons[i]);
        source.icons.splice(i,1);
        i--;
    }
}
for (var i = 0; i < source.icons.length; i++) {
    if (source.icons[i].saturation < 5) {
        tmp.push(source.icons[i]);
        source.icons.splice(i,1);
        i--;
    }
}
tmp.sort(function(a, b) {
    return parseFloat(b.luminance) - parseFloat(a.luminance);
});
for (var i = 0; i < tmp.length; i++) {
    source.icons.push(tmp[i]);
}
console.log(tmp);

// Read header and footer content into variables
var fs = require('fs');
function readFile(path, callback) {
    try {
        var filename = require.resolve(path);
        fs.readFile(filename, 'utf8', callback);
    } catch (e) {
        callback(e);
    }
}
var fs = require('fs');
var header = fs.readFileSync('./src/header.html', 'utf8');
var footer = fs.readFileSync('./src/footer.html', 'utf8');

// Build content
var main = "
" + source.icons.length + " SVG icons for popular brands Download them from GitHub
\n
";

for (var i = 0; i < source.icons.length; i++) {
    var filePath = source.icons[i].title.toLowerCase();
    filePath = filePath.replace(' ', '');
    filePath = filePath.replace('.', '');
    filePath = filePath.replace('+', 'plus');
    filePath = "./icons/" + filePath + ".svg";
    console.log(source.icons[i].title + ", sat = " + source.icons[i].saturation);
    var fs = require('fs');
    var svg = fs.readFileSync(filePath, 'utf8');
    main += "\t\t\t
" + svg + source.icons[i].title + "
#" + source.icons[i].hex + "
\n";
}

// Put all content together and export to index.html
var htmlOutput = header + main + footer;
fs.writeFile("./index.html", htmlOutput, function(err) {
    if(err) {
        return console.log(err);
    }
    console.log("The file was saved!");
});<|endoftext|>


<commit_before>'use strict';

const

/* node modules */

path = require( 'path' ),
_ = require( 'lodash' ),
chalk = require( 'chalk' ),
webpack = require( 'webpack' ),

/* local files */

index = require( './index.js' ),

webpackConfigGenerator = require( './webpackConfigGenerator.js' ),

parseFlags = flags => {

  if ( _.isString( flags ) ) {

    flags = flags.split( ' ' );

  }

  if ( !flags ) {
    flags = [];
  }

  flags = _.zipObject( flags, _.fill( Array( flags.length ), true ) );

  if ( !!flags.test ) {
    delete flags.test;
    flags.unit = true;
    flags.integration = true;
  }

  return _.defaults( flags, {
    server: false,
    sourcemaps: false,
    minify: false,
    integration: false,
    unit: false,
    watch: false,
    full: false,
    ignore: false,
    verbose: false,
  } );

},

handlewebpackErrors = errors => {
  console.log(chalk.red.bold('webpack has returned errors.'));
  _.forEach( errors, error => console.log( error ) );
},

handlewebpackWarnings = errors => {
  console.log(chalk.yellow.bold('webpack has returned warnings.'));
  _.forEach( errors, error => console.log( error ) );
},

webpackResponseHandler = ( skedge_pack_error, stats ) => {

  const jsonStats = stats.toJson();

  if ( jsonStats.errors.length > 0 ) {
    return handlewebpackErrors(jsonStats.errors);
  }

  if ( jsonStats.warnings.length > 0 ) {
    handlewebpackWarnings(jsonStats.warnings);
  }

  console.log(_.keys(jsonStats));
  let modules = _.chain( jsonStats.modules )
    .map( module => module.name )
    .groupBy( moduleName => path.dirname( moduleName ) )
    .value();

  //modules = _.mapValues( modules, module => _.map( module, filePath => path.basename( filePath ) ) )
  console.log('webpack Modules')
  _.forEach( modules, ( module, modulePath ) => {
    console.log( '  ' + chalk.magenta( modulePath ) );
    const moduleLog = ['   '];
    let logLine = 0;
    _.forEach( module, filePath => {
      filePath = path.basename( filePath );
      if ( moduleLog[logLine].length + filePath.length <= 120 ) {

        moduleLog[logLine] += ' ' + filePath;

      } else {

        moduleLog[++logLine] = '\t' + filePath;

      }

    } );
    console.log( chalk.cyan( moduleLog.join( '\n' ) ) );
  } );

  //console.log(_.map( jsonStats.modules, module => module.name ) );
  //console.log(jsonStats.assets);

  console.log( chalk.green( 'webpack completed' ) );
  console.log( );

};

module.exports = function skedge_pack ( options ) {

  const
    source = path.join( process.cwd() , options.source || 'source' ),
    destination = path.join( process.cwd() , options.destination || 'app' ),
    main = path.join( source , options.main || 'main.js' ),
    configPath = path.join( source , options.config || 'config.js' ),
    config = require( configPath ),
    flags = parseFlags( options.flags ),
    compiler = webpack( webpackConfigGenerator( source, destination, main, flags ) );

  console.log( );
  console.log( chalk.green.bold( `Deadalus > ${config.title}` ) );
  console.log( '\tflags  : ' + _.map( flags, ( status, flag ) => !status ? chalk.gray( flag ) : chalk.green( flag ) ).join( ' ' ) );
  console.log( '\tsource : ' + chalk.cyan( source ) );
  console.log( '\tdest   : ' + chalk.cyan( destination ) );
  console.log( '\tmain   : ' + chalk.cyan( main ) );
  console.log( '\tconfig : ' + chalk.cyan( configPath ) );
  console.log( )

  index( flags, configPath, destination );

  console.log( );
  console.log( 'Running ' + chalk.green('webpack') );

  if ( !flags.watch ) {

    console.log( '\tversion : compiler.run' );
    console.log();
    compiler.run( webpackResponseHandler );

  } else if ( !flags.server ) {

    console.log( '\tversion : compiler.watch' );
    console.log();

    compiler.watch({ // watch options:
      aggregateTimeout: 300, // wait so long for more changes
      poll: true // use polling instead of native watchers
    }, webpackResponseHandler );

  } else if ( flags.watch && flags.server ) {

    console.log( '\tversion : webpackDevServer' );
    console.log( '\tport    : 8060' );
    console.log();

    const server = new webpackDevServer( compiler, {
      contentBase: destination,
      hot: true,
      port: 8060,
      colors: true,
      filename: "bundle.js",
    } );

    server.listen(8060);

  }

}

/* index.html file */

//webpack-dev-server --content-base source --inline --hot
<commit_msg>fixed webpackDevServer<commit_after>'use strict';

const

/* node modules */

path = require( 'path' ),
_ = require( 'lodash' ),
chalk = require( 'chalk' ),
webpack = require( 'webpack' ),
webpackDevServer = require( 'webpack-dev-server' ),

/* local files */

index = require( './index.js' ),

webpackConfigGenerator = require( './webpackConfigGenerator.js' ),

parseFlags = flags => {

  if ( _.isString( flags ) ) {

    flags = flags.split( ' ' );

  }

  if ( !flags ) {
    flags = [];
  }

  flags = _.zipObject( flags, _.fill( Array( flags.length ), true ) );

  if ( !!flags.test ) {
    delete flags.test;
    flags.unit = true;
    flags.integration = true;
  }

  return _.defaults( flags, {
    server: false,
    sourcemaps: false,
    minify: false,
    integration: false,
    unit: false,
    watch: false,
    full: false,
    ignore: false,
    verbose: false,
  } );

},

handlewebpackErrors = errors => {
  console.log(chalk.red.bold('webpack has returned errors.'));
  _.forEach( errors, error => console.log( error ) );
},

handlewebpackWarnings = errors => {
  console.log(chalk.yellow.bold('webpack has returned warnings.'));
  _.forEach( errors, error => console.log( error ) );
},

webpackResponseHandler = ( skedge_pack_error, stats ) => {

  const jsonStats = stats.toJson();

  if ( jsonStats.errors.length > 0 ) {
    return handlewebpackErrors(jsonStats.errors);
  }

  if ( jsonStats.warnings.length > 0 ) {
    handlewebpackWarnings(jsonStats.warnings);
  }

  console.log(_.keys(jsonStats));
  let modules = _.chain( jsonStats.modules )
    .map( module => module.name )
    .groupBy( moduleName => path.dirname( moduleName ) )
    .value();

  //modules = _.mapValues( modules, module => _.map( module, filePath => path.basename( filePath ) ) )
  console.log('webpack Modules')
  _.forEach( modules, ( module, modulePath ) => {
    console.log( '  ' + chalk.magenta( modulePath ) );
    const moduleLog = ['   '];
    let logLine = 0;
    _.forEach( module, filePath => {
      filePath = path.basename( filePath );
      if ( moduleLog[logLine].length + filePath.length <= 120 ) {

        moduleLog[logLine] += ' ' + filePath;

      } else {

        moduleLog[++logLine] = '\t' + filePath;

      }

    } );
    console.log( chalk.cyan( moduleLog.join( '\n' ) ) );
  } );

  //console.log(_.map( jsonStats.modules, module => module.name ) );
  //console.log(jsonStats.assets);

  console.log( chalk.green( 'webpack completed' ) );
  console.log( );

};

module.exports = function skedge_pack ( options ) {

  const
    source = path.join( process.cwd() , options.source || 'source' ),
    destination = path.join( process.cwd() , options.destination || 'app' ),
    main = path.join( source , options.main || 'main.js' ),
    configPath = path.join( source , options.config || 'config.js' ),
    config = require( configPath ),
    flags = parseFlags( options.flags ),
    compiler = webpack( webpackConfigGenerator( source, destination, main, flags ) );

  console.log( );
  console.log( chalk.green.bold( `Deadalus > ${config.title}` ) );
  console.log( '\tflags  : ' + _.map( flags, ( status, flag ) => !status ? chalk.gray( flag ) : chalk.green( flag ) ).join( ' ' ) );
  console.log( '\tsource : ' + chalk.cyan( source ) );
  console.log( '\tdest   : ' + chalk.cyan( destination ) );
  console.log( '\tmain   : ' + chalk.cyan( main ) );
  console.log( '\tconfig : ' + chalk.cyan( configPath ) );
  console.log( )

  index( flags, configPath, destination );

  console.log( );
  console.log( 'Running ' + chalk.green('webpack') );

  if ( !flags.watch ) {

    console.log( '\tversion : compiler.run' );
    console.log();
    compiler.run( webpackResponseHandler );

  } else if ( !flags.server ) {

    console.log( '\tversion : compiler.watch' );
    console.log();

    compiler.watch({ // watch options:
      aggregateTimeout: 300, // wait so long for more changes
      poll: true // use polling instead of native watchers
    }, webpackResponseHandler );

  } else if ( flags.watch && flags.server ) {

    console.log( '\tversion : webpackDevServer' );
    console.log( '\tport    : 8060' );
    console.log();

    const server = new webpackDevServer( compiler, {
      contentBase: destination,
      hot: true,
      port: 8060,
      colors: true,
      filename: main,
      watchOptions: {
        aggregateTimeout: 300,
        poll: 1000
      },
      publicPath: '/',
      stats: { colors: true }
    } );

    server.listen(8060);

  }

}

/* index.html file */

//webpack-dev-server --content-base source --inline --hot
<|endoftext|>


<commit_before>({
    baseUrl: "src",
    out: "bundle.js",
    name: "almond",
    include: "patterns",

    wrap: {
        endFile: "src/wrap-end.js"
    },

    paths: {
        // Externals
        "almond": "bower_components/almond/almond",
        "jquery": "bower_components/jquery/jquery",
        "logging": "bower_components/logging/src/logging",
        "jquery.form": "bower_components/jquery-form/jquery.form",
        "jquery.anythingslider": "bower_components/AnythingSlider/js/jquery.anythingslider",
        "jcrop": "bower_components/jcrop/js/jquery.Jcrop",
        "klass": "bower_components/klass/src/klass",
        "photoswipe": "legacy/photoswipe",
        "parsley": "bower_components/parsleyjs/parsley",
        "parsley.extend": "bower_components/parsleyjs/parsley.extend",
        "patternslib.slides": "bower_components/slides/src/slides",
        "modernizr": "bower_components/modernizr/modernizr",
        "less": "bower_components/less.js/dist/less-1.6.2",
        "prefixfree": "bower_components/prefixfree/prefixfree.min",
        "Markdown.Converter": "legacy/Markdown.Converter",
        "Markdown.Extra": "legacy/Markdown.Extra",
        "Markdown.Sanitizer": "legacy/Markdown.Sanitizer",
        "imagesloaded":  "bower_components/imagesloaded/imagesloaded",
        "eventEmitter/EventEmitter": "bower_components/eventEmitter/EventEmitter",
        "eventie/eventie": "bower_components/eventie/eventie",
        "jquery.chosen": "bower_components/chosen/chosen/chosen.jquery",
        "jquery.fullcalendar": "bower_components/fullcalendar/fullcalendar.min",
        "jquery.fullcalendar.dnd": "bower_components/fullcalendar/lib/jquery-ui.custom.min",
        "jquery.placeholder": "bower_components/jquery-placeholder/jquery.placeholder.min",
        "jquery.textchange": "bower_components/jquery-textchange/jquery.textchange",
        "select2": "bower_components/select2/select2.min",
        "tinymce": "bower_components/jquery.tinymce/jscripts/tiny_mce/jquery.tinymce",
        "spectrum": "bower_components/spectrum/spectrum",

        // Core
        "pat-utils": "core/utils",
        "pat-pluggable": "core/pluggable",
        "pat-compat": "core/compat",
        "pat-jquery-ext": "core/jquery-ext",
        "pat-logger": "core/logger",
        "pat-parser": "core/parser",
        "pat-remove": "core/remove",
        "pat-url": "core/url",
        "pat-store": "core/store",
        "pat-registry": "core/registry",
        "pat-htmlparser": "lib/htmlparser",
        "pat-depends_parse": "lib/depends_parse",
        "pat-dependshandler": "lib/dependshandler",
        "pat-input-change-events": "lib/input-change-events",

        // Patterns
        "patterns": "patterns",
        "pat-ajax": "pat/ajax",
        "pat-autofocus": "pat/autofocus",
        "pat-autoscale": "pat/autoscale",
        "pat-autosubmit": "pat/autosubmit",
        "pat-autosuggest": "pat/autosuggest",
        "pat-breadcrumbs": "pat/breadcrumbs",
        "pat-bumper": "pat/bumper",
        "pat-carousel": "pat/carousel",
        "pat-checkedflag": "pat/checkedflag",
        "pat-checklist": "pat/checklist",
        "pat-chosen": "pat/chosen",
        "pat-collapsible": "pat/collapsible",
        "pat-colour-picket": "pat/colour-picker",
        "pat-depends": "pat/depends",
        "pat-edit-tinymce": "pat/edit-tinymce",
        "pat-equaliser": "pat/equaliser",
        "pat-expandable": "pat/expandable",
        "pat-focus": "pat/focus",
        "pat-formstate": "pat/form-state",
        "pat-forward": "pat/forward",
        "pat-fullcalendar": "pat/fullcalendar",
        "pat-gallery": "pat/gallery",
        "pat-image-crop": "pat/image-crop",
        "pat-inject": "pat/inject",
        "pat-legend": "pat/legend",
        "pat-markdown": "pat/markdown",
        "pat-menu": "pat/menu",
        "pat-modal": "pat/modal",
        "pat-navigation": "pat/navigation",
        "pat-notification": "pat/notification",
        "pat-placeholder": "pat/placeholder",
        "pat-selectbox": "pat/selectbox",
        "pat-skeleton": "pat/skeleton",
        "pat-slides": "pat/slides",
        "pat-slideshow-builder": "pat/slideshow-builder",
        "pat-sortable": "pat/sortable",
        "pat-stacks": "pat/stacks",
        "pat-subform": "pat/subform",
        "pat-switch": "pat/switch",
        "pat-toggle": "pat/toggle",
        "pat-tooltip": "pat/tooltip",
        "pat-validate": "pat/validate",
        "pat-zoom": "pat/zoom",

        // Calendar pattern
        "moment": "bower_components/moment/moment",
        "moment-timezone": "bower_components/moment-timezone/moment-timezone",
        "pat-calendar": "pat/calendar/calendar",
        "pat-calendar-dnd": "pat/calendar/dnd",
        "pat-calendar-moment-timezone-data": "pat/calendar/moment-timezone-data"
    },

    shim: {
        "jquery": {
            exports: "jQuery"
        },
        "jquery.fullcalendar.dnd": {
            depends: "jQuery",
        },
        "tinymce": {
            depends: "jquery"
        },
        "photoswipe": {
            depends: "klass",
        }
    },

    optimize: "none"
})
<commit_msg>fixed build.js<commit_after>({
    baseUrl: "src",
    out: "bundle.js",
    name: "almond",
    include: "patterns",

    wrap: {
        endFile: "src/wrap-end.js"
    },

    paths: {
        // Externals
        "almond": "bower_components/almond/almond",
        "jquery": "bower_components/jquery/jquery",
        "logging": "bower_components/logging/src/logging",
        "jquery.form": "bower_components/jquery-form/jquery.form",
        "jquery.anythingslider": "bower_components/AnythingSlider/js/jquery.anythingslider",
        "jcrop": "bower_components/jcrop/js/jquery.Jcrop",
        "klass": "bower_components/klass/src/klass",
        "photoswipe": "legacy/photoswipe",
        "parsley": "bower_components/parsleyjs/parsley",
        "parsley.extend": "bower_components/parsleyjs/parsley.extend",
        "patternslib.slides": "bower_components/slides/src/slides",
        "modernizr": "bower_components/modernizr/modernizr",
        "less": "bower_components/less.js/dist/less-1.6.2",
        "prefixfree": "bower_components/prefixfree/prefixfree.min",
        "Markdown.Converter": "legacy/Markdown.Converter",
        "Markdown.Extra": "legacy/Markdown.Extra",
        "Markdown.Sanitizer": "legacy/Markdown.Sanitizer",
        "imagesloaded":  "bower_components/imagesloaded/imagesloaded",
        "eventEmitter/EventEmitter": "bower_components/eventEmitter/EventEmitter",
        "eventie/eventie": "bower_components/eventie/eventie",
        "jquery.chosen": "bower_components/chosen/chosen/chosen.jquery",
        "jquery.fullcalendar": "bower_components/fullcalendar/fullcalendar.min",
        "jquery.fullcalendar.dnd": "bower_components/fullcalendar/lib/jquery-ui.custom.min",
        "jquery.placeholder": "bower_components/jquery-placeholder/jquery.placeholder.min",
        "jquery.textchange": "bower_components/jquery-textchange/jquery.textchange",
        "select2": "bower_components/select2/select2.min",
        "tinymce": "bower_components/jquery.tinymce/jscripts/tiny_mce/jquery.tinymce",
        "spectrum": "bower_components/spectrum/spectrum",
        "masonry": "bower_components/masonry/masonry",
        "outlayer": "bower_components/outlayer",
        "doc-ready": "bower_components/doc-ready",
        "get-size": "bower_components/get-size",
        "get-style-property": "bower_components/get-style-property",
        "matches-selector": "bower_components/matches-selector",

        // Core
        "pat-utils": "core/utils",
        "pat-pluggable": "core/pluggable",
        "pat-compat": "core/compat",
        "pat-jquery-ext": "core/jquery-ext",
        "pat-logger": "core/logger",
        "pat-parser": "core/parser",
        "pat-remove": "core/remove",
        "pat-url": "core/url",
        "pat-store": "core/store",
        "pat-registry": "core/registry",
        "pat-htmlparser": "lib/htmlparser",
        "pat-depends_parse": "lib/depends_parse",
        "pat-dependshandler": "lib/dependshandler",
        "pat-input-change-events": "lib/input-change-events",

        // Patterns
        "patterns": "patterns",
        "pat-ajax": "pat/ajax",
        "pat-autofocus": "pat/autofocus",
        "pat-autoscale": "pat/autoscale",
        "pat-autosubmit": "pat/autosubmit",
        "pat-autosuggest": "pat/autosuggest",
        "pat-breadcrumbs": "pat/breadcrumbs",
        "pat-bumper": "pat/bumper",
        "pat-carousel": "pat/carousel",
        "pat-checkedflag": "pat/checkedflag",
        "pat-checklist": "pat/checklist",
        "pat-chosen": "pat/chosen",
        "pat-collapsible": "pat/collapsible",
        "pat-colour-picket": "pat/colour-picker",
        "pat-depends": "pat/depends",
        "pat-edit-tinymce": "pat/edit-tinymce",
        "pat-equaliser": "pat/equaliser",
        "pat-expandable": "pat/expandable",
        "pat-focus": "pat/focus",
        "pat-formstate": "pat/form-state",
        "pat-forward": "pat/forward",
        "pat-fullcalendar": "pat/fullcalendar",
        "pat-gallery": "pat/gallery",
        "pat-image-crop": "pat/image-crop",
        "pat-inject": "pat/inject",
        "pat-legend": "pat/legend",
        "pat-markdown": "pat/markdown",
        "pat-masonry": "pat/masonry",
        "pat-menu": "pat/menu",
        "pat-modal": "pat/modal",
        "pat-navigation": "pat/navigation",
        "pat-notification": "pat/notification",
        "pat-placeholder": "pat/placeholder",
        "pat-selectbox": "pat/selectbox",
        "pat-skeleton": "pat/skeleton",
        "pat-slides": "pat/slides",
        "pat-slideshow-builder": "pat/slideshow-builder",
        "pat-sortable": "pat/sortable",
        "pat-stacks": "pat/stacks",
        "pat-subform": "pat/subform",
        "pat-switch": "pat/switch",
        "pat-toggle": "pat/toggle",
        "pat-tooltip": "pat/tooltip",
        "pat-validate": "pat/validate",
        "pat-zoom": "pat/zoom",

        // Calendar pattern
        "moment": "bower_components/moment/moment",
        "moment-timezone": "bower_components/moment-timezone/moment-timezone",
        "pat-calendar": "pat/calendar/calendar",
        "pat-calendar-dnd": "pat/calendar/dnd",
        "pat-calendar-moment-timezone-data": "pat/calendar/moment-timezone-data"
    },

    shim: {
        "jquery": {
            exports: "jQuery"
        },
        "jquery.fullcalendar.dnd": {
            depends: "jQuery",
        },
        "tinymce": {
            depends: "jquery"
        },
        "photoswipe": {
            depends: "klass",
        }
    },

    optimize: "none"
})
<|endoftext|>


<commit_before>const metalsmith = require('metalsmith');
const collections = require('metalsmith-collections');
const excerpts = require('metalsmith-excerpts');
const layouts = require('metalsmith-layouts');
const markdown = require('metalsmith-markdown');
const marked = require('marked');
const metalsmithDebug = require('metalsmith-debug');
const moment = require('metalsmith-moment');
const syntaxHighlighting = require('metalsmith-prism');

const metadata = require('./data/metadata.json');

const filesToIgnore = [
  '.DS_Store'
];

const mdRenderer = new marked.Renderer();

mdRenderer.image = (href, title, alt) => {
  if (title) {
    return `<figure><img src="${href}" alt="${alt}" title="${title}" /><figcaption>${title}</figcaption></figure>`;
  }
  else {
    return `<img src="${href}" alt="${alt}" />`;
  }
}
// https://github.com/markedjs/marked/issues/773
mdRenderer.paragraph = (text) => {
  if (text.startsWith('<figure') && text.endsWith('
')) {
    return text;
  }
  else {
    return `<p>${text}</p>`;
  }
}

metalsmith(__dirname)
.metadata(metadata)
.source('./src')
.destination('./build')
.clean(false)
.ignore(filesToIgnore)
.use(markdown({
  renderer: mdRenderer
}))
.use(collections({
  writing: {
    pattern: ['writing/**/*.html'],
    sortBy: 'published',
    reverse: true
  }
}))
.use(syntaxHighlighting())
.use(excerpts())
.use(moment([
  'published',
  'updated'
]))
.use(layouts({
  engine: 'ejs',
  directory: 'layouts',
  default: 'article.ejs',
  pattern: ['**/*.html', '*.html']
}))
.use(metalsmithDebug())
.build(err => {
  if (err) {
    console.log('❌ Build failed:', err)
  }
  else {
    console.log('✅ Build complete.')
  }
});
<commit_msg>Remove forward slash on void elements as it is no longer needed (https://html.spec.whatwg.org/multipage/syntax.html#syntax-start-tag)<commit_after>const metalsmith = require('metalsmith');
const collections = require('metalsmith-collections');
const excerpts = require('metalsmith-excerpts');
const layouts = require('metalsmith-layouts');
const markdown = require('metalsmith-markdown');
const marked = require('marked');
const metalsmithDebug = require('metalsmith-debug');
const moment = require('metalsmith-moment');
const syntaxHighlighting = require('metalsmith-prism');

const metadata = require('./data/metadata.json');

const filesToIgnore = [
  '.DS_Store'
];

const mdRenderer = new marked.Renderer();

mdRenderer.image = (href, title, alt) => {
  if (title) {
    return `<figure><img src="${href}" alt="${alt}" title="${title}"><figcaption>${title}</figcaption></figure>`;
  }
  else {
    return `<img src="${href}" alt="${alt}">`;
  }
}
// https://github.com/markedjs/marked/issues/773
mdRenderer.paragraph = (text) => {
  if (text.startsWith('<figure') && text.endsWith('
')) {
    return text;
  }
  else {
    return `<p>${text}</p>`;
  }
}

metalsmith(__dirname)
.metadata(metadata)
.source('./src')
.destination('./build')
.clean(false)
.ignore(filesToIgnore)
.use(markdown({
  renderer: mdRenderer
}))
.use(collections({
  writing: {
    pattern: ['writing/**/*.html'],
    sortBy: 'published',
    reverse: true
  }
}))
.use(syntaxHighlighting())
.use(excerpts())
.use(moment([
  'published',
  'updated'
]))
.use(layouts({
  engine: 'ejs',
  directory: 'layouts',
  default: 'article.ejs',
  pattern: ['**/*.html', '*.html']
}))
.use(metalsmithDebug())
.build(err => {
  if (err) {
    console.log('❌ Build failed:', err)
  }
  else {
    console.log('✅ Build complete.')
  }
});
<|endoftext|>


<commit_before>var fs = require('fs'),
    CleanCSS = require('clean-css'),
    UglifyJS = require("uglify-js"),
    Mustache = require('mustache'),
    cheerio = require('cheerio'),
    chokidar = require('chokidar');

var sourceDir = __dirname + '/source/',
    iconDir = __dirname + '/icons/',
    distDir = __dirname + '/public/';

// If called from command line then
// compile and watch the
// directory for changes
if (require.main === module) {

  // Make the output dir if it doesn't exist
  try {fs.mkdirSync('public');} catch (e) {}

  compile();

  watcher = chokidar.watch(sourceDir, {ignored: /^\./, persistent: true});
  watcher.add(iconDir);
  watcher.on('change', function(path) {compile();});
}

function compile (callback) {

  empty(distDir);

  moveImages('favicon.png');
  compressCSS('style.css');
  concatJS('lib/lunr.js','app.js');

  if (callback) compressJS();

  var metadata = JSON.parse(fs.readFileSync(iconDir + '_metadata.json', 'utf8')),
      icons = extractIcons(metadata);

  var iconTemplate = fs.readFileSync(sourceDir + '/icon.html', 'utf8'),
      homepageTemplate = fs.readFileSync(sourceDir + '/index.html', 'utf8'),

      partials = {
        header: fs.readFileSync(sourceDir + 'header.html', 'utf8'),
        head: fs.readFileSync(sourceDir + 'head.html', 'utf8'),
        footer: fs.readFileSync(sourceDir + 'footer.html', 'utf8')
      };

  createIconPages(iconTemplate, partials, icons);
  createHomepage(homepageTemplate, partials, icons);

  makeStaticPages('license');

  if (callback) return callback();

  console.log('Built site...');
}

function extractIcons (metadata) {

  var icons = [],
      index = 0;

  for (var fileName in metadata) {

    var iconData = metadata[fileName],
        svg = fs.readFileSync(iconDir + fileName, 'utf8'),
        slug = makeSlug(iconData.title, fileName),
        svgString = manipSVG(svg, slug);

    var icon = {
      title: iconData.title,
      tags: iconData.tags.join(', '),
      index: ++index,
      slug: slug,
      svg: svg,
      base64: new Buffer(svgString).toString('base64'),
      fileName: fileName,
      svgString: svgString
    };

    icons.push(icon);
  }

  return icons;
}

function manipSVG (svgString, slug) {

  var $ = cheerio.load(svgString, { xmlMode: true});

  $('svg')
    .removeAttr('version')
    .removeAttr('width')
    .removeAttr('height')
    .removeAttr('enable-background')
    .attr('id', 'icon-' + slug)

  // this gets the outerhtml of the svg el
  return $.xml($('svg'));
}

function createIconPages (template, partials, icons) {

  for (var i in icons) {

    var icon = icons[i],
        slug = icon.slug,

        pagePath = distDir + '/' + slug + '-icon/';

    fs.mkdirSync(pagePath);

    var renderedTemplate = Mustache.render(template, icon, partials);

    fs.writeFileSync(pagePath + 'index.html', renderedTemplate);
    fs.writeFileSync(pagePath + icon.fileName, icon.svg);
  }
}

function createHomepage (template, partials, icons) {

  var html = Mustache.render(
        template,
        {icons: icons},
        partials
      );

  fs.writeFileSync(distDir + '/index.html', html);
}

function makeStaticPages() {

  for (var i in arguments) {

    fileName = arguments[i];

    fs.mkdirSync(distDir + fileName);

    fs.writeFileSync(distDir + fileName + '/index.html', fs.readFileSync(sourceDir + fileName + '.html'));
  }

}

function empty (dirPath, removeSelf) {

  if (removeSelf === undefined)
    removeSelf = false;

  try {
    var files = fs.readdirSync(dirPath);
  } catch(e) {
    return;
  }

  if (files.length > 0) {
    for (var i = 0; i < files.length; i++) {
      var filePath = dirPath + '/' + files[i];
      if (fs.statSync(filePath).isFile())
        fs.unlinkSync(filePath);
      else
        empty(filePath, true);
    }
  }

  if (removeSelf)
    fs.rmdirSync(dirPath);
};

function makeSlug (string, fileName) {
  string = string.replace('/',' ').replace(/[\.,\/#\'!$%\^&\*;:{}=_`~()]/g,"").trim().replace(/ +/g,'-').toLowerCase();

  try {
    string = encodeURIComponent(string).split('%').join('-');
  } catch (err) {
    string = makeFromFile(fileName);
  }
  // ensure slug is less than 1k chars long
  return string.slice(0, 1000);
}

function moveImages () {

  for (var i in arguments) {

    fileName = arguments[i];

    fs.writeFileSync(distDir + fileName, fs.readFileSync(sourceDir + fileName));
  }
}

function concatJS () {

  var js = '', fileName, uncompressedFile;

  for (var i in arguments) {

    fileName = arguments[i];

    uncompressedFile = fs.readFileSync(sourceDir + fileName, 'utf8');

    js += uncompressedFile;
  }

  fs.writeFileSync(distDir + fileName, js);
}

function  compressJS () {

  var compressedFile = UglifyJS.minify(distDir + 'app.js').code;

  fs.writeFileSync(distDir + 'app.js', compressedFile);

}

function compressCSS () {

  var css = '', fileName, uncompressedCSS;

  for (var i in arguments) {

    fileName = arguments[i];

    uncompressedCSS = fs.readFileSync(sourceDir + fileName, 'utf8');

    css += new CleanCSS().minify(uncompressedCSS);
  }

  fs.writeFileSync(distDir + fileName, css);

  return
}

module.exports = compile;<commit_msg>refactored build file<commit_after>var fs = require('fs');
var clean = require('clean-css');
var uglify = require("uglify-js");
var cheerio = require('cheerio');
var empty = require('rimraf').sync;
var render = require('mustache').render;
var dirname = require('path').dirname;

compile();

// watch icon directory for any changes
// and rebuild when we spot one.
fs.watch('source', compile);

function compile () {

  // Make the output dir if it doesn't exist
  try {fs.mkdirSync('public');} catch (e) {}

  // Ensure the output dir is empty
  empty('public/*');

  var icons = extract('icons.json');
  var partials = {
    header: read('header.html'),
    head: read('head.html'),
    footer: read('footer.html')
  };

  // Build the homepage & icon pages
  homepage(icons, partials, 'index.html');
  permalinks(icons, partials, 'icon.html');

  copy('favicon.png');
  copy('bull.jpg');

  static(['about', 'license'], partials);

  compressCSS('style.css');
  compressJS('lib/lunr.js','app.js');

  console.log('Built site...');
}

function output (name, contents) {

  if (name && name.indexOf('/') > -1) {
    var parent = dirname(name);
    try {fs.mkdirSync('public/' + parent);} catch (e) {}
  }

  fs.writeFileSync('public/'+name, contents, 'utf8');
}

function read (name) {
  return fs.readFileSync('source/'+ name, 'utf8');
}

function copy (from, to) {

  // make parent folder exist
  if (to && to.indexOf('/') > -1) {
    var parent = dirname(to);
    try {fs.mkdirSync('public/' + parent);} catch (e) {}
  }

  // make parent folder exist
  fs.writeFileSync('public/'+ (to || from), fs.readFileSync('source/' + from));
}

function homepage (icons, partials, name) {
  output(name, render(read(name), {icons: icons}, partials));
}

function static (pages, partials) {

  for (var i in pages)
    output(pages[i] + '/index.html', render(read(pages[i]+'.html'), {}, partials));

}

function extract (metadata) {

  metadata = JSON.parse(read(metadata));

  var icons = [];

  for (var name in metadata) {

    if (name.indexOf('.svg') === -1) continue;

    var svg = read('icons/' + name);
    var tags = metadata[name].join(', ');
    var svgEl = el(svg, slug(name));
    var base64 = new Buffer(svgEl).toString('base64');

    var icon = {
      svg: svg,
      tags: tags,
      name: name,
      slug: slug(name),
      title: title(name),
      index: icons.length,
      svgEl: svgEl,
      base64: base64
    };

    icons.push(icon);
  }

  return icons;
}

function title (str) {
  str = str.slice(0, str.lastIndexOf('.svg'));
  str = str[0].toUpperCase() + str.slice(1);
  str = str.split('_').join(' ');
  return str;
}

function slug (str) {
  return str.slice(0, str.lastIndexOf('.svg')).trim().toLowerCase().split('_').join('-');
}

function el (svg, slug) {

  var $ = cheerio.load(svg, {xmlMode: true});

  $('svg')
    .removeAttr('version')
    .removeAttr('width')
    .removeAttr('height')
    .removeAttr('enable-background')
    .attr('id', 'icon-' + slug);

  // this gets the outerhtml of the svg el
  return $.xml($('svg'));
}

function permalinks (icons, partials, name) {

  var template = read(name);

  for (var i in icons) {

    var icon = icons[i];
    var slug = icon.slug;
    var pagePath = slug + '-icon/';

    fs.mkdirSync('public/' + pagePath);

    var path = pagePath + 'index.html';

    output(path, render(template, icon, partials));
    output(pagePath + icon.name, icon.svg);
  }
}

function compressJS () {

  var js = '';

  // concatenate passed files
  for (var i in arguments)
    js += read(arguments[i]);

  // write concatenated file
  output('app.js', js);

  // then minify it since we can't pass a string to uglify
  output('app.js', uglify.minify('public/app.js').code);
}

function compressCSS () {

  var css = '';

  for (var i in arguments)
    css += new clean().minify(read(arguments[i]));

  output('style.css', css);
}

module.exports = compile;<|endoftext|>


<commit_before>#!/usr/bin/env node
var marked = require('marked'),
	fs = require('fs'),
	multiline = require('multiline'),
	Handlebars = require('handlebars'),
	jsdom = require("jsdom");

require('es6-shim')

var DEBUG = false

var log;
if ( DEBUG ) {
	log = console.log.bind(console);
} else {
	log = function(){}
}

var source = fs.readFileSync('rosetta-stone.md', 'utf8')
var template = Handlebars.compile(fs.readFileSync('template.mustache', 'utf8'));

var originalHTML = marked(source)

var htmlToJson = function(window){
	var $ = window.$;
	var data = {}
	var firstSection = $('h2').each(function(sectionIndex, sectionElement){
		var $sectionElement = $(sectionElement)
		var sectionTitle = $sectionElement.text()
		log('\n\nMAKING SECTION AND TABLE FOR', sectionTitle)
		data[sectionTitle] = {}
		var currentRow = 0
		var currentCell = 0
		var rowName
		var previousCellContents;
		$sectionElement.nextUntil('h2').each(function(nonSectionIndex, element){
			var $element = $(element)
			var elementText = $element.text()
			if ( $element.is('h3') ) {
				// We're done with previous row, so add it
				rowName = elementText
				data[sectionTitle][rowName] = {}
				log('  MAKING ROW FOR', rowName)
				currentRow++
			} else {
				if ( $element.is('h4') ) {
					cellName = elementText
					data[sectionTitle][rowName][cellName] = []
					currentCell++
				} else {
					if ( elementText.toLowerCase().includes('as above') ) {
						data[sectionTitle][rowName][cellName] = previousCellContents
						return
					}
					log('      ADDING TO CELL', currentCell, ':', elementText)
					data[sectionTitle][rowName][cellName].push('
'+$element.html()+'
')
					// Commands might be the same, but don't copy references
					if ( ! $element.is('a') ) {
						previousCellContents = data[sectionTitle][rowName][cellName]
					}
				}
			}
		})
	})
	return data
}

jsdom.env(originalHTML, ["http://code.jquery.com/jquery.js"], function (errors, window) {
	var data = htmlToJson(window)
	fs.writeFileSync('json/rosetta-stone.json', JSON.stringify({sections: data}, null, 2));
});
<commit_msg>Remove unused dependency<commit_after>#!/usr/bin/env node
var marked = require('marked'),
	fs = require('fs'),
	multiline = require('multiline'),
	jsdom = require("jsdom");

require('es6-shim')

var DEBUG = false

var log;
if ( DEBUG ) {
	log = console.log.bind(console);
} else {
	log = function(){}
}

var source = fs.readFileSync('rosetta-stone.md', 'utf8')

var originalHTML = marked(source)

var htmlToJson = function(window){
	var $ = window.$;
	var data = {}
	var firstSection = $('h2').each(function(sectionIndex, sectionElement){
		var $sectionElement = $(sectionElement)
		var sectionTitle = $sectionElement.text()
		log('\n\nMAKING SECTION AND TABLE FOR', sectionTitle)
		data[sectionTitle] = {}
		var currentRow = 0
		var currentCell = 0
		var rowName
		var previousCellContents;
		$sectionElement.nextUntil('h2').each(function(nonSectionIndex, element){
			var $element = $(element)
			var elementText = $element.text()
			if ( $element.is('h3') ) {
				// We're done with previous row, so add it
				rowName = elementText
				data[sectionTitle][rowName] = {}
				log('  MAKING ROW FOR', rowName)
				currentRow++
			} else {
				if ( $element.is('h4') ) {
					cellName = elementText
					data[sectionTitle][rowName][cellName] = []
					currentCell++
				} else {
					if ( elementText.toLowerCase().includes('as above') ) {
						data[sectionTitle][rowName][cellName] = previousCellContents
						return
					}
					log('      ADDING TO CELL', currentCell, ':', elementText)
					data[sectionTitle][rowName][cellName].push('
'+$element.html()+'
')
					// Commands might be the same, but don't copy references
					if ( ! $element.is('a') ) {
						previousCellContents = data[sectionTitle][rowName][cellName]
					}
				}
			}
		})
	})
	return data
}

jsdom.env(originalHTML, ["http://code.jquery.com/jquery.js"], function (errors, window) {
	var data = htmlToJson(window)
	fs.writeFileSync('json/rosetta-stone.json', JSON.stringify({sections: data}, null, 2));
});
<|endoftext|>


<commit_before>function run(firstTime) {

    var branch = require("metalsmith-branch");
    var cleancss = require("metalsmith-clean-css");
    var collections = require("metalsmith-collections");
    var concat = require("metalsmith-concat");
    var crypto = require("crypto");
    var define = require("metalsmith-define");
    var entities = require("entities");
    var fingerprint = require("metalsmith-fingerprint");
    var fs = require("fs");
    var Gaze = require("gaze").Gaze;
    var layouts = require("metalsmith-layouts");
    var markdown = require("metalsmith-markdown-remarkable");
    var metalsmith = require("metalsmith");
    var moment = require("moment");
    var notebookjs = require("notebookjs");
    var path = require("path");
    var remarkable = require("remarkable");
    var rss = require("metalsmith-rss");
    var serve = require("metalsmith-serve");
    var tags = require("metalsmith-tags");
    var uglify = require("metalsmith-uglify");
    var srcset = require("./srcset");
    var home = process.env["HOME"];

    var argv = require("yargs")
        .option("p", {
            alias: "prod",
            default: false,
            describe: "running in production",
            type: "boolean"})
        .option("n", {
            alias: "noserve",
            default: false,
            describe: "do not run web server after building",
            type: "boolean"})
        .argv;

    var isProd = argv.p;

    /*
     * Metalsmith plugin that does nothing.
     */
    var noop = function(files, metalsmith, done) { return process.nextTick(done); };

    /*
     * Metalsmith plugin that executs a proc if a give test value evaluates to true.
     */
    var maybe = function(test, proc) { return test ? proc : noop; };

    /*
     * Metalsmith plugin that executes a proc only if `firsttime` is true.
     */
    var ifFirstTime = function(proc) { return maybe(firstTime && !argv.n, proc); };

    /*
     * Meta data for the templates.
     */
    var site = {
        isProd: isProd,
        url: "http://keystrokecountdown.com",
        title: "Keystroke Countdown",
        description: "Sporadic musings on software, algorithms, platforms",
        navigation: null,
        integrations: {
            verification: "5iLi_clt29n1AVjPn8ELBcDwVQn4RZgG20-Cxs1Vcrw",
            analytics: "UA-77645652-1"
        },
        author: {
            name: "Brad Howes",
            bio: "Programmer in C++, Python, Swift, Javascript, Elisp. Started out doing punch cards in FORTRAN.",
            image: "/images/HarrisonsLaugh.jpg",
            location: "Prague, Czech Republic",
            website: "http://linkedin.com/in/bradhowes"
        },
        images: {
            sizes: [300, 400, 500, 650, 750, 1000, 1500],
            defaultSize: 650
        },
        snippet: {
            maxLength: 280,
            suffix: "..."       // Will be replaced by elipses character (…) during Markdown processing
        }
    };

    /*
     * Settings for the Remarkable Markdown processor. Declared here since we render in two places: normal *.md
     * processing; snippet generation.
     */
    var markdownOptions = {
        html: true,             // Allow and pass inline HTML
        sup: true,              // Accept '^' as a superscript operator
        breaks: false,          // Require two new lines to end a paragraph
        typographer: true,      // Allow substitutions for nicer looking text
        smartypants: true,      // Allow substitutions for nicer looking text
        gfm: true,              // Allow GitHub Flavored Markdown (GFM) constructs
        footnote: true,         // Allow footnotes
        tables: true,           // Allow table constructs
        langPrefix: "language-" // Prefix to use for <code> language designation (set to match Prism setting)
    };

    /*
     * Create a Markdown converter that we will use when converting Markdown text for the snippet.
     */
    var md = new remarkable("full", markdownOptions);

    /*
     * Convert a relative directory to an absolute one.
     */
    function absPath(p) { return path.join(__dirname, p); }

    /*
     * Obtain a string representation of a date in a particular format. The sole (optional) parameter `date`
     * can be a timestamp OR an object. If the former, then convert the date into the format "Month Day, Year".
     * If the latter, then take the format from the object and use "now" as the timestamp to convert.
     */
    function formatDate(date) {
        var format = "MMM Do, YYYY", tmp;
        if (typeof date === "object") {
            format = date.hash.format;
            date = moment.now();
        }
        return moment(date).format(format);
    };

    /*
     * Obtain a relative URL from the given argument.
     */
    function relativeUrl(url) {
        var dir, ext;
        dir = path.dirname(url);
        ext = path.extname(url);
        if (ext == ".md" || ext == ".ipynb") url = url.replace(ext, ".html");
        url = path.join("/", url);
        return url;
    }

    function asset(url) {
        return relativeUrl(url);
    }

    /**
     * Set various metadata elements for a given build file.
     * @param file the relative path of the file being processed
     * @param data the build object for the file
     */
    function updateMetadata(file, data) {
        var url = relativeUrl(file);

        data.relativeUrl = url;
        data.absoluteUrl = path.join(site.url, url);
        data.url = data.relativeUrl; // !!! This is for the RSS generator

        if (typeof data["author"] === "undefined") data["author"] = site.author.name;

        if (typeof data["date"] === "undefined") {
            data.date = "";
            data.formattedDate = "";
        }
        else {
            data.formattedDate = formatDate(data.date);
        }

        data.postDate = data.date;

        if (data.image) {
            var prefix = path.dirname(url);
            data.image = path.join("/", prefix, data.image).replace(/ /g, "%20");
        }
    }

    /**
     * Generate a "snippet" of text from Markdown material.
     * @param contents the Markdown text to use as the source material.
     * @return HTML code containing the snippet text between <p> tags.
     */
    function createSnippet(contents) {

        // Strategy:
        // - get first Markdown paragraph
        // - replace any inline images with their alt text values
        // - remove any inline Katex (MathJax) operators
        // - split the paragraph into words
        // - accumulate words until all of the words are used or the snippet reaches the max length limit
        //
        var para = contents.toString().split("\n\n")[0].replace(/\[(.*)\]\(.*\)/g, "$1");
        var bits = para.replace(/\\\\\((.*)\\\\\)/g, "$1").split(/[ \n]+/);
        var index = 0;
        var maxLength = site.snippet.maxLength;
        var snippet = "";
        while (index < bits.length && (snippet.length + bits[index].length + 1) < maxLength) {
            if (bits[index].length > 0) {
                snippet += " " + bits[index];
            }
            index += 1;
        }

        // If there are still some words remaining in the paragraph, add an elipses
        //
        if (index < bits.length) snippet += site.snippet.suffix;

        // Convert the Markdown into HTML and return.
        //
        return md.render(snippet + "\n\n");
    }

    // --- Start of Metalsmith processing ---

    if (firstTime) console.log("-- isProd:", isProd, "noserve:", argv.n);

    metalsmith(absPath(""))
        .clean(false)           // !!! Necessary to keep our .git directory at the destination
        .source(absPath("./src"))
        .destination(home + "/Sites/keystrokecountdown")
        .ignore([".~/*", "**/*~", "**/.~/*"]) // Ignore Emacs backup files
        .use(define({site: site}))            // Pass in `site` definitions from above
        .use(srcset({                         // Generate images for various screen sizes
            rule: "(min-width: 768px) 625px, calc(100vw-6rem)",
            sizes: site.images,
            attribution: true,
            fileExtension: ".md"
        }))
        .use(branch("**/*.md")
            .use(function(files, metalsmith, done) {

                // Generate metadata for each Markdown file.
                //
                Object.keys(files).forEach(function(file) {
                    var data = files[file];
                    updateMetadata(file, data);
                    if (typeof data["description"] === "undefined" || data.description === '') {
                        data.description = createSnippet(data.contents);
                    }
                });
                return process.nextTick(done);
            })
            .use(markdown("full", markdownOptions)) // Generate HTML from Markdown
        )
        .use(branch("**/*.ipynb")
            .use(function(files, metalsmith, done) {

                // Convert preprocessed IPython files into HTML.
                //
                Object.keys(files).forEach(function(file) {
                    var data = files[file];
                    var html = file.replace(".ipynb", ".html");
                    var ipynb, notebook, str;
                    var tmp;

                    ipynb = JSON.parse(fs.readFileSync(path.join(metalsmith.source(), file)));
                    tmp = ipynb["metadata"]["blog"];

                    if (typeof tmp === "undefined") {
                        console.log("** skipping IPython file", file, "-- missing 'blog' contents");
                        return;
                    }

                    // Parse the notebook and generate HTML from it
                    //
                    notebook = notebookjs.parse(ipynb);
                    str = notebook.render().outerHTML;
                    data.contents = new Buffer(str);

                    // Set metadata
                    //
                    data.title = tmp["title"] || path.basename(path.dirname(file));
                    if (tmp["image"]) {
                        data.image = tmp["image"];
                    }

                    data.author = tmp["author"] || site.author.name;
                    data.layout = "post.hbs";
                    data.tags = tmp["tags"] || "";
                    data.description = tmp["description"] || "";
                    data.date = tmp["date"] || "";

                    updateMetadata(file, data);

                    delete files[file];
                    files[html] = data;
                });

                return process.nextTick(done);
        }))
        .use(tags({             // Generate tag pages
            handle: "tags",
            path: "topics/:tag.html",
            layout: "tag.hbs",
            sortBy: "date",
            reverse: true

        }))
        .use(function(files, metalsmith, done) {

            // Generate an array of tag objects alphabetically ordered in case-insensitive manner. Also, add to
            // each tag object an `articleCount` with the number of articles containing the tag, and a `tag`
            // attribute containing the tag value.
            //
            var sortedTags = [];
            var tags = metalsmith.metadata()["tags"];
            Object.keys(tags).forEach(function(tag) {
                var count = tags[tag].length;
                tags[tag].articleCount = count;
                tags[tag].tag = tag;
                sortedTags.push([tag.toLowerCase(), tags[tag]]);
            });

            // Sort the lower-case tags
            //
            sortedTags.sort(function(a, b) {return a[0].localeCompare(b[0]);});

            // Save the array of tag objects that are properly ordered
            //
            metalsmith.metadata()["sortedTags"] = sortedTags.map(function(a) {return a[1];});

            // Revise article metadata so that each tag is the tag object, and if there is no image, use
            // a default one from the home page.
            //
            Object.keys(files).forEach(function(file) {
                var data = files[file];
                if (! data["image"]) {
                    data["image"] = "/computer-keyboard-stones-on-grass-background-header.jpg";
                }

                if (data["tags"] && data["tags"].length) {
                    var tmp = data["tags"];
                    tmp.sort(function(a, b) {return a.toLowerCase().localeCompare(b.toLowerCase());});
                    data["tags"] = tmp.map(function(a) {return tags[a];});
                }
                else {
                    data["tags"] = sortedTags.map(function(a) { return a[1]; });
                }
            });

            return process.nextTick(done);
        })
        .use(concat({           // Generate one CSS file
            files: "css/*.css",
            output: "css/all.css"
        }))
        .use(cleancss({         // Compress the CSS file
            files: "css/*.css"
        }))
        .use(uglify({           // Generate one Javascript file and compress it
            order: ["js/katex-0.6.0.min.js", "js/prism.min.js", "js/index.js"],
            filter: "js/*.js",
            concat: "js/all.js"
        }))
        .use(function(files, metalsmith, done) { // Fingerprint the "all" JS and CSS files
            Object.keys(files).forEach(function(filePath) {
                if (filePath === "css/all.css" || filePath === "js/all.js") {
                    var data = files[filePath];
                    var hash = crypto.createHmac('md5', 'metalsmith').update(data.contents).digest('hex');
                    var ext = path.extname(filePath);
                    var fingerprint = [filePath.substring(0, filePath.lastIndexOf(ext)), '-', hash, ext]
                        .join('').replace(/\\/g, '/');
                    metalsmith.metadata()[filePath] = relativeUrl(fingerprint);
                }
            });
            return process.nextTick(done);
        })
       .use(collections({      // Generate a collection of all of the articles
            articles: {
                pattern: "articles/**/*.html",
                sortBy: "date",
                reverse: true
            }
        }))
        .use(layouts({          // Generate HTML pages from Handlebar templates
            engine: "handlebars",
            directory: "templates",
            partials: "templates/partials",
            pattern: "**/*.html",
            cache: false,
            helpers: {
                encode: encodeURIComponent,
                date: formatDate,
                asset: asset
            }
        }))
        .use(rss({              // Generate an `rss.xml` file for all of the articles
            feedOptions: {
                title: site.title,
                description: site.description,
                site_url: site.url,
                feed_url: site.url + "/rss.xml",
                managingEditor: site.author.name,
                copyright: "Copyright © 2016, Brad Howes",
                language: "en"
            },
            collection: "articles",
            limit: 50,
            destination: "rss.xml"
        }))
        .use(function(files, metalsmith, done) {

            // The stock RSS generator wraps many of the text values in CDATA. Undo that since it seems to break
            // some RSS readers when they try to follow a URL from the CDATA.
            //
            var data = files["rss.xml"];
            var content = data.contents.toString();
            content = content.replace(/<!\[CDATA\[/g, '');
            content = content.replace(/]]>/g, '');
            data.contents = content;
            return process.nextTick(done);
        })
        .use(ifFirstTime(function(files, metalsmith, done) {

            // Watch for changes in the source files.
            //
            var paths = [
                "src/**/*.+(ipynb|md)", // HTML source files
                "src/css/**/*",         // CSS and font files
                "src/js/**/*",          // Javascript files
                "templates/**/*"        // Handlebar templates and partials
            ];

            if (typeof metalsmith["__gazer"] == "undefined") {

                // Need to create a new file watcher
                //
                var pendingUpdate = false;
                var updateDelay = 100; // msecs

                console.log("-- watcher: starting");
                metalsmith.__gazer = new Gaze(paths);
                metalsmith.__gazer.on("all", function(event, path) {
                    console.log("-- watcher:", path, event);
                    if (pendingUpdate) {
                        clearTimeout(pendingUpdate);
                    }
                    pendingUpdate = setTimeout(function() {
                        console.log("-- watcher: rebuilding");
                        run(false);
                        console.log("-- watcher: done");
                    }, updateDelay);
                });
            }

            return process.nextTick(done);
        }))
        .use(ifFirstTime(serve({ // Start a simple HTTP server to serve the generated HTML files.
            port: 7000,
            http_error_files: {
                404: "/404.html"
            }
        })))
        .build(function (err) { // Execute all of the above.
            if (err) {
                console.log(err);
                throw err;
            }
        });
}

run(true);
<commit_msg>More cleanup of css/js all file generation<commit_after>function run(firstTime) {

    var branch = require("metalsmith-branch");
    var cleancss = require("metalsmith-clean-css");
    var collections = require("metalsmith-collections");
    var concat = require("metalsmith-concat");
    var crypto = require("crypto");
    var define = require("metalsmith-define");
    var entities = require("entities");
    var fingerprint = require("metalsmith-fingerprint");
    var fs = require("fs");
    var Gaze = require("gaze").Gaze;
    var layouts = require("metalsmith-layouts");
    var markdown = require("metalsmith-markdown-remarkable");
    var metalsmith = require("metalsmith");
    var moment = require("moment");
    var notebookjs = require("notebookjs");
    var path = require("path");
    var remarkable = require("remarkable");
    var rss = require("metalsmith-rss");
    var serve = require("metalsmith-serve");
    var tags = require("metalsmith-tags");
    var uglify = require("metalsmith-uglify");
    var srcset = require("./srcset");
    var home = process.env["HOME"];

    var argv = require("yargs")
        .option("p", {
            alias: "prod",
            default: false,
            describe: "running in production",
            type: "boolean"})
        .option("n", {
            alias: "noserve",
            default: false,
            describe: "do not run web server after building",
            type: "boolean"})
        .argv;

    var isProd = argv.p;

    /*
     * Metalsmith plugin that does nothing.
     */
    var noop = function(files, metalsmith, done) { return process.nextTick(done); };

    /*
     * Metalsmith plugin that executs a proc if a give test value evaluates to true.
     */
    var maybe = function(test, proc) { return test ? proc : noop; };

    /*
     * Metalsmith plugin that executes a proc only if `firsttime` is true.
     */
    var ifFirstTime = function(proc) { return maybe(firstTime && !argv.n, proc); };

    /*
     * Meta data for the templates.
     */
    var site = {
        isProd: isProd,
        url: "http://keystrokecountdown.com",
        title: "Keystroke Countdown",
        description: "Sporadic musings on software, algorithms, platforms",
        navigation: null,
        integrations: {
            verification: "5iLi_clt29n1AVjPn8ELBcDwVQn4RZgG20-Cxs1Vcrw",
            analytics: "UA-77645652-1"
        },
        author: {
            name: "Brad Howes",
            bio: "Programmer in C++, Python, Swift, Javascript, Elisp. Started out doing punch cards in FORTRAN.",
            image: "/images/HarrisonsLaugh.jpg",
            location: "Prague, Czech Republic",
            website: "http://linkedin.com/in/bradhowes"
        },
        images: {
            sizes: [300, 400, 500, 650, 750, 1000, 1500],
            defaultSize: 650
        },
        snippet: {
            maxLength: 280,
            suffix: "..."       // Will be replaced by elipses character (…) during Markdown processing
        }
    };

    /*
     * Settings for the Remarkable Markdown processor. Declared here since we render in two places: normal *.md
     * processing; snippet generation.
     */
    var markdownOptions = {
        html: true,             // Allow and pass inline HTML
        sup: true,              // Accept '^' as a superscript operator
        breaks: false,          // Require two new lines to end a paragraph
        typographer: true,      // Allow substitutions for nicer looking text
        smartypants: true,      // Allow substitutions for nicer looking text
        gfm: true,              // Allow GitHub Flavored Markdown (GFM) constructs
        footnote: true,         // Allow footnotes
        tables: true,           // Allow table constructs
        langPrefix: "language-" // Prefix to use for <code> language designation (set to match Prism setting)
    };

    /*
     * Create a Markdown converter that we will use when converting Markdown text for the snippet.
     */
    var md = new remarkable("full", markdownOptions);

    /*
     * Convert a relative directory to an absolute one.
     */
    function absPath(p) { return path.join(__dirname, p); }

    /*
     * Obtain a string representation of a date in a particular format. The sole (optional) parameter `date`
     * can be a timestamp OR an object. If the former, then convert the date into the format "Month Day, Year".
     * If the latter, then take the format from the object and use "now" as the timestamp to convert.
     */
    function formatDate(date) {
        var format = "MMM Do, YYYY", tmp;
        if (typeof date === "object") {
            format = date.hash.format;
            date = moment.now();
        }
        return moment(date).format(format);
    };

    /*
     * Obtain a relative URL from the given argument.
     */
    function relativeUrl(url) {
        var dir, ext;
        dir = path.dirname(url);
        ext = path.extname(url);
        if (ext == ".md" || ext == ".ipynb") url = url.replace(ext, ".html");
        url = path.join("/", url);
        return url;
    }

    function asset(url) {
        return relativeUrl(url);
    }

    /**
     * Set various metadata elements for a given build file.
     * @param file the relative path of the file being processed
     * @param data the build object for the file
     */
    function updateMetadata(file, data) {
        var url = relativeUrl(file);

        data.relativeUrl = url;
        data.absoluteUrl = path.join(site.url, url);
        data.url = data.relativeUrl; // !!! This is for the RSS generator

        if (typeof data["author"] === "undefined") data["author"] = site.author.name;

        if (typeof data["date"] === "undefined") {
            data.date = "";
            data.formattedDate = "";
        }
        else {
            data.formattedDate = formatDate(data.date);
        }

        data.postDate = data.date;

        if (data.image) {
            var prefix = path.dirname(url);
            data.image = path.join("/", prefix, data.image).replace(/ /g, "%20");
        }
    }

    /**
     * Generate a "snippet" of text from Markdown material.
     * @param contents the Markdown text to use as the source material.
     * @return HTML code containing the snippet text between <p> tags.
     */
    function createSnippet(contents) {

        // Strategy:
        // - get first Markdown paragraph
        // - replace any inline images with their alt text values
        // - remove any inline Katex (MathJax) operators
        // - split the paragraph into words
        // - accumulate words until all of the words are used or the snippet reaches the max length limit
        //
        var para = contents.toString().split("\n\n")[0].replace(/\[(.*)\]\(.*\)/g, "$1");
        var bits = para.replace(/\\\\\((.*)\\\\\)/g, "$1").split(/[ \n]+/);
        var index = 0;
        var maxLength = site.snippet.maxLength;
        var snippet = "";
        while (index < bits.length && (snippet.length + bits[index].length + 1) < maxLength) {
            if (bits[index].length > 0) {
                snippet += " " + bits[index];
            }
            index += 1;
        }

        // If there are still some words remaining in the paragraph, add an elipses
        //
        if (index < bits.length) snippet += site.snippet.suffix;

        // Convert the Markdown into HTML and return.
        //
        return md.render(snippet + "\n\n");
    }

    // --- Start of Metalsmith processing ---

    if (firstTime) console.log("-- isProd:", isProd, "noserve:", argv.n);

    metalsmith(absPath(""))
        .clean(false)           // !!! Necessary to keep our .git directory at the destination
        .source(absPath("./src"))
        .destination(home + "/Sites/keystrokecountdown")
        .ignore([".~/*", "**/*~", "**/.~/*"]) // Ignore Emacs backup files
        .use(define({site: site}))            // Pass in `site` definitions from above
        .use(srcset({                         // Generate images for various screen sizes
            rule: "(min-width: 768px) 625px, calc(100vw-6rem)",
            sizes: site.images,
            attribution: true,
            fileExtension: ".md"
        }))
        .use(branch("**/*.md")
            .use(function(files, metalsmith, done) {

                // Generate metadata for each Markdown file.
                //
                Object.keys(files).forEach(function(file) {
                    var data = files[file];
                    updateMetadata(file, data);
                    if (typeof data["description"] === "undefined" || data.description === '') {
                        data.description = createSnippet(data.contents);
                    }
                });
                return process.nextTick(done);
            })
            .use(markdown("full", markdownOptions)) // Generate HTML from Markdown
        )
        .use(branch("**/*.ipynb")
            .use(function(files, metalsmith, done) {

                // Convert preprocessed IPython files into HTML.
                //
                Object.keys(files).forEach(function(file) {
                    var data = files[file];
                    var html = file.replace(".ipynb", ".html");
                    var ipynb, notebook, str;
                    var tmp;

                    ipynb = JSON.parse(fs.readFileSync(path.join(metalsmith.source(), file)));
                    tmp = ipynb["metadata"]["blog"];

                    if (typeof tmp === "undefined") {
                        console.log("** skipping IPython file", file, "-- missing 'blog' contents");
                        return;
                    }

                    // Parse the notebook and generate HTML from it
                    //
                    notebook = notebookjs.parse(ipynb);
                    str = notebook.render().outerHTML;
                    data.contents = new Buffer(str);

                    // Set metadata
                    //
                    data.title = tmp["title"] || path.basename(path.dirname(file));
                    if (tmp["image"]) {
                        data.image = tmp["image"];
                    }

                    data.author = tmp["author"] || site.author.name;
                    data.layout = "post.hbs";
                    data.tags = tmp["tags"] || "";
                    data.description = tmp["description"] || "";
                    data.date = tmp["date"] || "";

                    updateMetadata(file, data);

                    delete files[file];
                    files[html] = data;
                });

                return process.nextTick(done);
        }))
        .use(tags({             // Generate tag pages
            handle: "tags",
            path: "topics/:tag.html",
            layout: "tag.hbs",
            sortBy: "date",
            reverse: true

        }))
        .use(function(files, metalsmith, done) {

            // Generate an array of tag objects alphabetically ordered in case-insensitive manner. Also, add to
            // each tag object an `articleCount` with the number of articles containing the tag, and a `tag`
            // attribute containing the tag value.
            //
            var sortedTags = [];
            var tags = metalsmith.metadata()["tags"];
            Object.keys(tags).forEach(function(tag) {
                var count = tags[tag].length;
                tags[tag].articleCount = count;
                tags[tag].tag = tag;
                sortedTags.push([tag.toLowerCase(), tags[tag]]);
            });

            // Sort the lower-case tags
            //
            sortedTags.sort(function(a, b) {return a[0].localeCompare(b[0]);});

            // Save the array of tag objects that are properly ordered
            //
            metalsmith.metadata()["sortedTags"] = sortedTags.map(function(a) {return a[1];});

            // Revise article metadata so that each tag is the tag object, and if there is no image, use
            // a default one from the home page.
            //
            Object.keys(files).forEach(function(file) {
                var data = files[file];
                if (! data["image"]) {
                    data["image"] = "/computer-keyboard-stones-on-grass-background-header.jpg";
                }

                if (data["tags"] && data["tags"].length) {
                    var tmp = data["tags"];
                    tmp.sort(function(a, b) {return a.toLowerCase().localeCompare(b.toLowerCase());});
                    data["tags"] = tmp.map(function(a) {return tags[a];});
                }
                else {
                    data["tags"] = sortedTags.map(function(a) { return a[1]; });
                }
            });

            return process.nextTick(done);
        })
        .use(function(files, metalsmith, done) { // Generate one CSS file
            var filePaths = ["css/font-awesome.css",
                             "css/katex-0.6.0.min.css",
                             "css/merriweather.css",
                             "css/notebook.css",
                             "css/prism.css",
                             "css/screen.css"];
            var outputPath = "css/all.css";
            var contents = filePaths.map(function(filePath) {return files[filePath].contents;});
            filePaths.map(function(filePath) {delete files[filePath];});
            files[outputPath] = {contents: contents.join("\n")};
            return process.nextTick(done);
        })
        .use(cleancss({         // Compress the CSS file
            files: "css/all.css"
        }))
        .use(uglify({           // Generate one Javascript file and compress it
            order: ["js/katex-0.6.0.min.js", "js/prism.min.js", "js/index.js"],
            filter: "js/*.js",
            concat: "js/all.js",
            removeOriginal: true
        }))
        .use(function(files, metalsmith, done) { // Fingerprint the "all" JS and CSS files
            Object.keys(files).forEach(function(filePath) {
                if (filePath === "css/all.css" || filePath === "js/all.js") {
                    var data = files[filePath];
                    var hash = crypto.createHmac('md5', 'metalsmith').update(data.contents).digest('hex');
                    var ext = path.extname(filePath);
                    var fingerprint = [filePath.substring(0, filePath.lastIndexOf(ext)), '-', hash, ext]
                        .join('').replace(/\\/g, '/');
                    files[fingerprint] = files[filePath];
                    delete files[filePath];
                    metalsmith.metadata()[filePath] = relativeUrl(fingerprint);
                }
            });
            return process.nextTick(done);
        })
       .use(collections({      // Generate a collection of all of the articles
            articles: {
                pattern: "articles/**/*.html",
                sortBy: "date",
                reverse: true
            }
        }))
        .use(layouts({          // Generate HTML pages from Handlebar templates
            engine: "handlebars",
            directory: "templates",
            partials: "templates/partials",
            pattern: "**/*.html",
            cache: false,
            helpers: {
                encode: encodeURIComponent,
                date: formatDate,
                asset: asset
            }
        }))
        .use(rss({              // Generate an `rss.xml` file for all of the articles
            feedOptions: {
                title: site.title,
                description: site.description,
                site_url: site.url,
                feed_url: site.url + "/rss.xml",
                managingEditor: site.author.name,
                copyright: "Copyright © 2016, Brad Howes",
                language: "en"
            },
            collection: "articles",
            limit: 50,
            destination: "rss.xml"
        }))
        .use(function(files, metalsmith, done) {

            // The stock RSS generator wraps many of the text values in CDATA. Undo that since it seems to break
            // some RSS readers when they try to follow a URL from the CDATA.
            //
            var data = files["rss.xml"];
            var content = data.contents.toString();
            content = content.replace(/<!\[CDATA\[/g, '');
            content = content.replace(/]]>/g, '');
            data.contents = content;
            return process.nextTick(done);
        })
        .use(ifFirstTime(function(files, metalsmith, done) {

            // Watch for changes in the source files.
            //
            var paths = [
                "src/**/*.+(ipynb|md)", // HTML source files
                "src/css/**/*",         // CSS and font files
                "src/js/**/*",          // Javascript files
                "templates/**/*"        // Handlebar templates and partials
            ];

            if (typeof metalsmith["__gazer"] == "undefined") {

                // Need to create a new file watcher
                //
                var pendingUpdate = false;
                var updateDelay = 100; // msecs

                console.log("-- watcher: starting");
                metalsmith.__gazer = new Gaze(paths);
                metalsmith.__gazer.on("all", function(event, path) {
                    console.log("-- watcher:", path, event);
                    if (pendingUpdate) {
                        clearTimeout(pendingUpdate);
                    }
                    pendingUpdate = setTimeout(function() {
                        console.log("-- watcher: rebuilding");
                        run(false);
                        console.log("-- watcher: done");
                    }, updateDelay);
                });
            }

            return process.nextTick(done);
        }))
        .use(ifFirstTime(serve({ // Start a simple HTTP server to serve the generated HTML files.
            port: 7000,
            http_error_files: {
                404: "/404.html"
            }
        })))
        .build(function (err) { // Execute all of the above.
            if (err) {
                console.log(err);
                throw err;
            }
        });
}

run(true);
<|endoftext|>


<commit_before>// `styles` section processor
// Make sure to run `bin` processor BEFORE styles if your main style files are
// using asset_path helpers.

'use strict';

/*global N*/

// stdlib
var fs    = require('fs');
var path  = require('path');

// 3rd-party
var _       = require('underscore');
var async   = require('nlib').Vendor.Async;
var JASON   = require('nlib').Vendor.JASON;
var fstools = require('nlib').Vendor.FsTools;
var treeGet = require('nlib').Support.tree.get;
var Mincer  = require('mincer');
var nib     = require('nib');

// internal
var findPaths = require('./utils').findPaths;
var RENDERERS = require('./styles/renderers');

////////////////////////////////////////////////////////////////////////////////

Mincer.StylusEngine.registerConfigurator(function (style) {
  style.use(nib());
});

////////////////////////////////////////////////////////////////////////////////

// Combines all styles from a lookup path
function renderStyles(options, callback) {
  findPaths(options, function (err, pathnames) {
    if (err) {
      callback(err);
      return;
    }

    async.mapSeries(pathnames, function (pathname, next) {
      var render = RENDERERS[pathname.extname];

      if (!render) {
        next("Don't know how to compile " + pathname);
        return;
      }

      render(pathname, next);
    }, function (err, chunks) {
      callback(err, chunks.join('\n'));
    });
  });
}

// Write styles.css gathered from all lookup sources
function writeRenderedStyles(destination, config, callback) {
  fstools.mkdir(path.dirname(destination), function (err) {
    if (err) {
      callback(err);
      return;
    }

    async.mapSeries(config.lookup, function (options, next) {
      renderStyles(_.extend({ main: config.main }, options), next);
    }, function (err, chunks) {
      if (err) {
        callback(err);
        return;
      }

      fs.writeFile(destination, chunks.join('\n'), 'utf8', callback);
    });
  });
}

function compileStyles(pkgName, tmpdir, config, callback) {
  var
  assetsOutdir = path.join(tmpdir, pkgName, 'bin'),
  stylesOutdir = path.join(tmpdir, pkgName, 'styles'),
  stylesFile   = path.join(stylesOutdir, 'styles.css'),
  mainFile     = stylesFile;

  writeRenderedStyles(stylesFile, config, function (err) {
    var environment = new Mincer.Environment(tmpdir);

    if (err) {
      callback(err);
      return;
    }

    environment.appendPath('.');
    environment.appendPath(stylesOutdir);
    environment.appendPath(assetsOutdir);

    if (config.main) {
      mainFile = path.resolve(config.main.root, config.main.file);
      environment.appendPath(config.main.appRoot);
    }

    //
    // Provide some helpers to EJS and Stylus
    //

    environment.registerHelper({
      asset_path: function (pathname) {
        var asset = environment.findAsset(pathname);
        return !asset ? "file-not-found" : ("/assets/" + asset.digestPath);
      },
      N: function (path) {
        return treeGet(N, path);
      },
      jason: JASON.stringify
    });

    //
    // compile and write main file
    //

    environment.findAsset(mainFile).compile(function (err, data) {
      var outfile = path.join(tmpdir, pkgName, pkgName + '.css');

      if (err) {
        callback(err);
        return;
      }

      fs.writeFile(outfile, data, 'utf8', callback);
    });
  });
}

////////////////////////////////////////////////////////////////////////////////

module.exports = function (tmpdir, config, callback) {
  async.forEachSeries(_.keys(config.packages), function (pkgName, next) {
    var stylesConfig = config.packages[pkgName].styles;

    if (!stylesConfig) {
      next();
      return;
    }

    compileStyles(pkgName, tmpdir, stylesConfig, next);
  }, callback);
};
<commit_msg>Refactor styles processor<commit_after>// `styles` section processor
// Make sure to run `bin` processor BEFORE styles if your main style files are
// using asset_path helpers.
//
//      .
//      |- /styles/
//      |   |- /<package>/
//      |   |   |- **/*.*
//      |   |   `- ...
//      |   `- ...
//      `- ...
//
//      ||
//      \/
//
//      .
//      |- /styles/
//      |   |- <package>.css
//      |   `- ...
//      `- ...
//

'use strict';

/*global N*/

// stdlib
var fs    = require('fs');
var path  = require('path');

// 3rd-party
var _       = require('underscore');
var async   = require('nlib').Vendor.Async;
var JASON   = require('nlib').Vendor.JASON;
var fstools = require('nlib').Vendor.FsTools;
var treeGet = require('nlib').Support.tree.get;
var Mincer  = require('mincer');
var nib     = require('nib');

// internal
var findPaths = require('./utils').findPaths;
var RENDERERS = require('./styles/renderers');

////////////////////////////////////////////////////////////////////////////////

Mincer.StylusEngine.registerConfigurator(function (style) {
  style.use(nib());
});

////////////////////////////////////////////////////////////////////////////////

// Combines all styles from a lookup path
function renderStyles(options, callback) {
  findPaths(options, function (err, pathnames) {
    if (err) {
      callback(err);
      return;
    }

    async.mapSeries(pathnames, function (pathname, next) {
      var render = RENDERERS[pathname.extname];

      if (!render) {
        next("Don't know how to compile " + pathname);
        return;
      }

      render(pathname, next);
    }, function (err, chunks) {
      callback(err, chunks.join('\n'));
    });
  });
}

// Write styles.css gathered from all lookup sources
function writeRenderedStyles(destination, config, callback) {
  fstools.mkdir(path.dirname(destination), function (err) {
    if (err) {
      callback(err);
      return;
    }

    async.mapSeries(config.lookup, function (options, next) {
      renderStyles(_.extend({ main: config.main }, options), next);
    }, function (err, chunks) {
      if (err) {
        callback(err);
        return;
      }

      fs.writeFile(destination, chunks.join('\n'), 'utf8', callback);
    });
  });
}

function compileStyles(pkgName, tmpdir, config, callback) {
  var
  assetsOutdir    = path.join(tmpdir, 'assets'),
  stylesOutfile   = path.join(tmpdir, 'styles', pkgName + '.css'),
  stylesTempdir   = path.join(tmpdir, 'styles', pkgName),
  stylesTempfile  = path.join(stylesTempdir, 'styles.css'),
  mainFile        = stylesTempfile;

  writeRenderedStyles(stylesTempfile, config, function (err) {
    var environment = new Mincer.Environment(tmpdir);

    if (err) {
      callback(err);
      return;
    }

    environment.appendPath(stylesTempdir);
    environment.appendPath(assetsOutdir);

    if (config.main) {
      // FIXME: main file path resolvement seems strange
      mainFile = path.resolve(config.main.root, config.main.file);
      environment.appendPath(config.main.appRoot);
    }

    //
    // Provide some helpers to EJS and Stylus
    //

    environment.registerHelper({
      asset_path: function (pathname) {
        var asset = environment.findAsset(pathname);
        return !asset ? "file-not-found" : ("/assets/" + asset.digestPath);
      },
      N: function (path) {
        return treeGet(N, path);
      },
      jason: JASON.stringify
    });

    //
    // compile and write main file
    //

    environment.findAsset(mainFile).compile(function (err, data) {
      if (err) {
        callback(err);
        return;
      }

      fs.writeFile(stylesOutfile, data, 'utf8', function (err) {
        if (err) {
          callback(err);
          return;
        }

        fstools.remove(stylesTempdir, callback);
      });
    });
  });
}

////////////////////////////////////////////////////////////////////////////////

module.exports = function (tmpdir, config, callback) {
  async.forEachSeries(_.keys(config.packages), function (pkgName, next) {
    var stylesConfig = config.packages[pkgName].styles;

    if (!stylesConfig) {
      next();
      return;
    }

    compileStyles(pkgName, tmpdir, stylesConfig, next);
  }, callback);
};
<|endoftext|>