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

/**
* DiscordForge - a plugin system for Discord.
* Copyright (C) 2017 DiscordForge Development
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

// add a title to make it look nice in cmd (win) and any other process manager (mac/linux)
process.title = "DiscordForge";

// parse commands
var command, argv = null;
try {
    var {command, argv} = require('command-line-commands')([null, 'help', 'inject', 'uninject', 'plugin', 'repo', 'update']);
} catch (err) {
    console.log('error: unknown command - try \'discordforge help\'');
    return;
}
// require deps
const options = require('minimist')(argv);
const readline = require('readline');
const usage = require('command-line-usage');
const ps = require('ps-node');
const asar = require('asar');
const path = require('path');
const fs = require('fs');
const os = require('os');
const {spawn} = require('child_process');
const https = require('https');
const ora = require('ora');

// constant variables
const dforgeDir = path.join(os.homedir(), '.discordforge');
const temp = path.join(dforgeDir, '_temp');
const toSplice1 = 'mainWindow.webContents.on(\'dom-ready\', function () {});';
const splice1 = 'mainWindow.webContents.on(\'dom-ready\', function () {mainWindow.webContents.executeJavaScript(\'require(require(\\\'path\\\').join(\\\'../../app.asar\\\', \\\'discordforge\\\'))();\');});';
const configPath = path.join(__dirname, 'config.json');
const gSpin = ora({
	color: 'cyan',
	stream: process.stdout,
	spinner: {
		"interval": 80,
		"frames": [
			"[    ]",
			"[   =]",
			"[  ==]",
			"[ ===]",
			"[====]",
			"[=== ]",
			"[==  ]",
			"[=   ]"
		]
	}
});

// recursive deletion for directory.
const rmdir = function(dir, cb) {
	var list = fs.readdirSync(dir);
	for(var i = 0; i < list.length; i++) {
		var filename = path.join(dir, list[i]);
		var stat = fs.statSync(filename);

		if(filename == "." || filename == "..") {
			// pass these files
		} else if(stat.isDirectory()) {
			// rmdir recursively
			rmdir(filename, () => {});
		} else {
			// rm fiilename
			fs.unlinkSync(filename);
		}
	}
	fs.rmdirSync(dir);
	cb();
};

// help page
const help = [
    {
        header: 'DiscordForge',
        content: 'A toolkit that allows you to create and install your own plugins into the Discord client.'
    },
    {
        header: 'Synopsis',
        content: [
            '$ discordforge [[bold]{command}] [...]'
        ]
    },
    {
        header: 'Commands',
        content: [
            {
                name: 'help',
                summary: 'Display help information about DiscordForge.'
            },
            {
                name: 'inject',
                summary: 'Injects DiscordForge into the running Discord process\'s app.asar.'
            },
            {
                name: 'uninject',
                summary: 'Reverses the operation of inject.'
            },
            {
                name: 'plugin [[bold]{command}] ...',
                summary: 'Plugin management.'
            },
            {
                name: 'repo [[bold]{command}] ...',
                summary: 'Repository management.'
            },
            {
                name: 'update',
                summary: 'Updates DiscordForge.'
            },
        ]
    },
    {
        header: 'Plugin Management',
        content: [
            {
                name: 'These commands allow you to manage plugins.\n'
            },
            {
                name: 'plugin install [[bold]{name}]',
                summary: 'Installs a plugin.'
            },
            {
                name: 'plugin uninstall [[bold]{name}]',
                summary: 'Uninstalls a plugin.'
            },
            {
                name: 'plugin list',
                summary: 'Lists all installed plugins.'
            }
        ]
    },
    {
        header: 'Repository Management',
        content: [
            {
                name: 'These commands allow you to manage repositories.\n'
            },
            {
                name: 'repo add [[bold]{name}]',
                summary: 'Adds a repository to search plugins for.'
            },
            {
                name: 'repo remove [[bold]{name}]',
                summary: 'Removes a repository.'
            },
            {
                name: 'repo list',
                summary: 'Lists all repositories.'
            }
        ]
    }
];
var defaultConfig = {
		repositories: [
			"DiscordForge/Plugins"
		]
};
var config = {};
var _ = options._;

// config
if (!fs.existsSync(configPath)) {
	fs.writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2), 'utf8');
	config = defaultConfig;
} else {
	config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
}

// no args specified or command "help"
if (command == null || command == 'help') {
    console.log(usage(help));
} else if (command == 'inject') {
    // find discord application path
    var discordPath = null;
	var discordBin = null;
	var discordPids;
    ps.lookup({}, (err, res) => {
        if (err) throw err;
        else {
            var raw = res.filter(proc => proc.command.includes('Discord'));
            var procs = {};
            raw.forEach(proc => {
                if (!procs[proc.command])
                    procs[proc.command] = {command: proc.command, pid: []};

                procs[proc.command].pid.push(proc.pid);
            });

            if (Object.keys(procs).length == 0) {
                console.log('No processes were found.');
                return;
            } else if (Object.keys(procs).length == 1) {
				let proc = procs[Object.keys(procs)[0]];
                discordPath = proc.command.substr(0, proc.command.lastIndexOf('\\'));
				discordBin = proc.command;
				discordPids = proc.pid;
            } else {
                let k = Object.keys(procs);
                for (let i = 0; i < k.length; i++) {
                    console.log(`${i}: ${k[i]} [${procs[k[i]].pid.join(', ')}]`);
                }
                console.log('Please select your process from above.');
				var rl = readline.createInterface({
					input: process.stdin,
					output: process.stdout
				});
				rl.question('> ', answer => {
					if (typeof parseInt(answer) === 'number') {
						let proc = procs[Object.keys(procs)[answer]];
						discordPath = proc.command.substr(0, proc.command.lastIndexOf('\\'));
						discordBin = proc.command;
						discordPids = proc.pid;
					} else {
						console.log('You didn\'t input a number. Re-run the command and try again.');
					}
					rl.close();
				});
            }
        }
		var spinner = gSpin;
		console.log(`Process found. Using path '${discordPath}'`);
		spinner.start('Extracting...');
		fs.mkdirSync(temp);
		asar.extractAll(path.join(discordPath, 'resources', 'app.asar'), temp);
		if (fs.existsSync(path.join(temp, 'index.js.bak')) != false) {
			spinner.fail('DiscordForge is already injected.');
			rmdir(temp, () => {});
		} else {
			spinner.text = "Splicing...";
			fs.readFile(path.join(temp, 'index.js'), 'utf8', (e, d) => {
				if (e) throw e;
				var inject = d.replace(toSplice1, splice1);
				fs.writeFileSync(path.join(temp, 'index.js.bak'), fs.readFileSync(path.join(temp, 'index.js')));
				fs.writeFile(path.join(temp, 'index.js'), inject, (e1) => {
					if (e1) throw e1;
					fs.writeFileSync(path.join(temp, 'discordforge.js'), fs.readFileSync(path.join(__dirname, 'bootstrap.js')));
					spinner.text = 'Archiving...';
					discordPids.forEach(o => {
						try {
							process.kill(o);
						} catch (e) {
								// ESRCH => killed the main process first, meaning the other processes died. doesn't matter.
						}
					});
					asar.createPackage(temp, path.join(discordPath, 'resources', 'app.asar'), () => {
						spinner.text = 'Cleaning up...';
						rmdir(temp, () => {
							spinner.text = 'Starting Discord...';
							spawn(discordBin, {
								detached: true
							}).unref();
							spinner.succeed('Injected.');
							process.exit(0);
						});
					});
				});
			});
		}
    });
} else if (command == 'uninject') {
    // find discord application path
    var discordPath = null;
	var discordBin = null;
	var discordPids;
    ps.lookup({}, (err, res) => {
        if (err) throw err;
        else {
            var raw = res.filter(proc => proc.command.includes('Discord'));
            var procs = {};
            raw.forEach(proc => {
                if (!procs[proc.command])
                    procs[proc.command] = {command: proc.command, pid: []};

                procs[proc.command].pid.push(proc.pid);
            });

            if (Object.keys(procs).length == 0) {
                console.log('No processes were found.');
                return;
            } else if (Object.keys(procs).length == 1) {
				let proc = procs[Object.keys(procs)[0]];
                discordPath = proc.command.substr(0, proc.command.lastIndexOf('\\'));
				discordBin = proc.command;
				discordPids = proc.pid;
            } else {
                let k = Object.keys(procs);
                for (let i = 0; i < keys.length; i++) {
                    console.log(`${i}: ${k[i]} [${procs[k[i]].pid.join(', ')}]`);
                }
                console.log('Please select your process from above.');
				var rl = readline.createInterface({
					input: process.stdin,
					output: process.stdout
				});
				rl.question('> ', answer => {
					if (typeof parseInt(answer) === 'number') {
						let proc = procs[Object.keys(procs)[answer]];
						discordPath = proc.command.substr(0, proc.command.lastIndexOf('\\'));
						discordBin = proc.command;
						discordPids = proc.pid;
					} else {
						console.log('You didn\'t input a number. Re-run the command and try again.');
					}
					rl.close();
				});
            }
        }
		var spinner = gSpin;
		console.log(`Process found. Using path '${discordPath}'`);
		spinner.start('Extracting...');
		fs.mkdirSync(temp);
		asar.extractAll(path.join(discordPath, 'resources', 'app.asar'), temp);
		if (fs.existsSync(path.join(temp, 'index.js.bak')) == false) {
			spinner.fail('Not injected or corrupt. If you injected DiscordForge and you are getting this message, reinstall Discord.');
			rmdir(temp, () => {});
		} else {
			spinner.text = 'Removing modification...';
			fs.writeFile(path.join(temp, 'index.js'), fs.readFileSync(path.join(temp, 'index.js.bak')), (e) => {
				if (e) throw e;
				fs.unlinkSync(path.join(temp, 'index.js.bak'));
				fs.unlinkSync(path.join(temp, 'discordforge.js'));
				spinner.text = 'Archiving...';
				discordPids.forEach(o => {
					try {
						process.kill(o);
					} catch (e) {
							// ESRCH => killed the main process first, meaning the other processes died. doesn't matter.
					}
				});
				asar.createPackage(temp, path.join(discordPath, 'resources', 'app.asar'), () => {
					spinner.text = 'Cleaning up...';
					rmdir(temp, () => {
						spinner.text = 'Starting Discord...';
						spawn(discordBin, {
							detached: true
						}).unref();
						spinner.succeed('Uninjected.');
						process.exit(0);
					});
				});
			});
		}
	});
} else if (command == 'repo') {
	let subcommand = _[0];
	let repoName = _[1];
	if (subcommand == 'list') {
		console.log('Repositories:');
		config.repositories.forEach(r => {
			console.log(r);
		});
	} else if (subcommand == 'add') {
		if (repoName != null) {
			let found = false;
			config.repositories.forEach(r => {
				if (repoName.toLowerCase() == r.toLowerCase()) {
					console.log('This repository has already been added.');
					found = true;
					return;
				}
			});
			if (!found) {
				https.get(`https://raw.githubusercontent.com/${repoName}/master/repository.json`, res => {
					let status = res.statusCode;
					if (status == 404) {
						console.log('Repository either does not exist or doesn\'t have a `repository.json` on the master branch.');
						return;
					} else {
						var repoJson = '';
						res.setEncoding('utf8');
						res.on('data', d => {
							repoJson = JSON.parse(d);
						});
						res.on('end', () => {
							if (repoJson.description == undefined || repoJson.plugins == undefined) {
								console.log('Invalid repository metadata.');
								return;
							}
							console.log(`Adding repository '${repoName}'`);
							console.log(`Description: ${repoJson.description}`);
							config.repositories.push(repoName);
							console.log('Added.');
							fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8');
						});
					}
				});
			}
		} else {
			console.log('No repository passed.');
		}
	} else if (subcommand == 'remove') {
		if (repoName != null) {
			let found = false;
			config.repositories.forEach(r => {
				if (repoName.toLowerCase() == r.toLowerCase()) {
					found = true;
					return;
				}
			});
			if (found) {
				console.log(`Removing repository '${repoName}'`);
				config.repositories.splice(config.repositories.indexOf(repoName), 1);
				console.log('Removed.');
				fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8');
			} else {
				console.log('Repository was not added.');
			}
		} else {
			console.log('No repository passed.');
		}
	} else {
		console.log('error: no subcommand - try \'discordforge help\'');
	}
}<commit_msg>DiscordForge 1.0<commit_after>#!/usr/bin/env node

/**
* DiscordForge - a plugin system for Discord.
* Copyright (C) 2017 DiscordForge Development
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

// DiscordForge details
const VERSION = '1.0';
process.title = `DiscordForge ${VERSION}`;

// parse commands
var command, argv = null;
try {
    var {command, argv} = require('command-line-commands')([null, 'help', 'inject', 'uninject', 'plugin', 'repo', 'update']);
} catch (err) {
    console.log('error: unknown command - try \'discordforge help\'');
    return;
}
// require deps
const options = require('minimist')(argv);
const readline = require('readline');
const usage = require('command-line-usage');
const ps = require('ps-node');
const asar = require('asar');
const path = require('path');
const fs = require('fs');
const os = require('os');
const {spawn} = require('child_process');
const https = require('https');
const ora = require('ora');

// constant variables
const dforgeDir = path.join(os.homedir(), '.discordforge');
const temp = path.join(dforgeDir, '_temp');
const toSplice1 = 'mainWindow.webContents.on(\'dom-ready\', function () {});';
const splice1 = 'mainWindow.webContents.on(\'dom-ready\', function () {mainWindow.webContents.executeJavaScript(\'require(require(\\\'path\\\').join(\\\'../../app.asar\\\', \\\'discordforge\\\'))();\');});';
const configPath = path.join(__dirname, 'config.json');
const gSpin = ora({
	color: 'cyan',
	stream: process.stdout,
	spinner: {
		"interval": 80,
		"frames": [
			"[    ]",
			"[   =]",
			"[  ==]",
			"[ ===]",
			"[====]",
			"[=== ]",
			"[==  ]",
			"[=   ]"
		]
	}
});

// recursive deletion for directory.
const rmdir = function(dir, cb) {
	var list = fs.readdirSync(dir);
	for(var i = 0; i < list.length; i++) {
		var filename = path.join(dir, list[i]);
		var stat = fs.statSync(filename);

		if(filename == "." || filename == "..") {
			// pass these files
		} else if(stat.isDirectory()) {
			// rmdir recursively
			rmdir(filename, () => {});
		} else {
			// rm fiilename
			fs.unlinkSync(filename);
		}
	}
	fs.rmdirSync(dir);
	cb();
};

// help page
const help = [
    {
        header: `DiscordForge ${VERSION}`,
        content: 'A toolkit that allows you to create and install your own plugins into the Discord client.'
    },
    {
        header: 'Synopsis',
        content: [
            '$ discordforge [[bold]{command}] [...]'
        ]
    },
    {
        header: 'Commands',
        content: [
            {
                name: 'help',
                summary: 'Display help information about DiscordForge.'
            },
            {
                name: 'inject',
                summary: 'Injects DiscordForge into the running Discord process\'s app.asar.'
            },
            {
                name: 'uninject',
                summary: 'Reverses the operation of inject.'
            },
            {
                name: 'plugin [[bold]{command}] ...',
                summary: 'Plugin management.'
            },
            {
                name: 'repo [[bold]{command}] ...',
                summary: 'Repository management.'
            },
            {
                name: 'update',
                summary: 'Updates DiscordForge.'
            },
        ]
    },
    {
        header: 'Plugin Management',
        content: [
            {
                name: 'These commands allow you to manage plugins.\n'
            },
            {
                name: 'plugin install [[bold]{name}]',
                summary: 'Installs a plugin.'
            },
            {
                name: 'plugin uninstall [[bold]{name}]',
                summary: 'Uninstalls a plugin.'
            },
            {
                name: 'plugin list',
                summary: 'Lists all installed plugins.'
            }
        ]
    },
    {
        header: 'Repository Management',
        content: [
            {
                name: 'These commands allow you to manage repositories.\n'
            },
            {
                name: 'repo add [[bold]{name}]',
                summary: 'Adds a repository to search plugins for.'
            },
            {
                name: 'repo remove [[bold]{name}]',
                summary: 'Removes a repository.'
            },
            {
                name: 'repo list',
                summary: 'Lists all repositories.'
            }
        ]
    }
];
var defaultConfig = {
		repositories: [
			"DiscordForge/Plugins"
		]
};
var config = {};
var _ = options._;

// config
if (!fs.existsSync(configPath)) {
	fs.writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2), 'utf8');
	config = defaultConfig;
} else {
	config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
}

// no args specified or command "help"
if (command == null || command == 'help') {
    console.log(usage(help));
} else if (command == 'inject') {
    // find discord application path
    var discordPath = null;
	var discordBin = null;
	var discordPids;
    ps.lookup({}, (err, res) => {
        if (err) throw err;
        else {
            var raw = res.filter(proc => proc.command.includes('Discord'));
            var procs = {};
            raw.forEach(proc => {
                if (!procs[proc.command])
                    procs[proc.command] = {command: proc.command, pid: []};

                procs[proc.command].pid.push(proc.pid);
            });

            if (Object.keys(procs).length == 0) {
                console.log('No processes were found.');
                return;
            } else if (Object.keys(procs).length == 1) {
				let proc = procs[Object.keys(procs)[0]];
                discordPath = proc.command.substr(0, proc.command.lastIndexOf('\\'));
				discordBin = proc.command;
				discordPids = proc.pid;
            } else {
                let k = Object.keys(procs);
                for (let i = 0; i < k.length; i++) {
                    console.log(`${i}: ${k[i]} [${procs[k[i]].pid.join(', ')}]`);
                }
                console.log('Please select your process from above.');
				var rl = readline.createInterface({
					input: process.stdin,
					output: process.stdout
				});
				rl.question('> ', answer => {
					if (typeof parseInt(answer) === 'number') {
						let proc = procs[Object.keys(procs)[answer]];
						discordPath = proc.command.substr(0, proc.command.lastIndexOf('\\'));
						discordBin = proc.command;
						discordPids = proc.pid;
					} else {
						console.log('You didn\'t input a number. Re-run the command and try again.');
					}
					rl.close();
				});
            }
        }
		var spinner = gSpin;
		console.log(`Process found. Using path '${discordPath}'`);
		spinner.start('Extracting...');
		fs.mkdirSync(temp);
		asar.extractAll(path.join(discordPath, 'resources', 'app.asar'), temp);
		if (fs.existsSync(path.join(temp, 'index.js.bak')) != false) {
			spinner.fail('DiscordForge is already injected.');
			rmdir(temp, () => {});
		} else {
			spinner.text = "Splicing...";
			fs.readFile(path.join(temp, 'index.js'), 'utf8', (e, d) => {
				if (e) throw e;
				var inject = d.replace(toSplice1, splice1);
				fs.writeFileSync(path.join(temp, 'index.js.bak'), fs.readFileSync(path.join(temp, 'index.js')));
				fs.writeFile(path.join(temp, 'index.js'), inject, (e1) => {
					if (e1) throw e1;
					fs.writeFileSync(path.join(temp, 'discordforge.js'), fs.readFileSync(path.join(__dirname, 'bootstrap.js')));
					spinner.text = 'Archiving...';
					discordPids.forEach(o => {
						try {
							process.kill(o);
						} catch (e) {
								// ESRCH => killed the main process first, meaning the other processes died. doesn't matter.
						}
					});
					asar.createPackage(temp, path.join(discordPath, 'resources', 'app.asar'), () => {
						spinner.text = 'Cleaning up...';
						rmdir(temp, () => {
							spinner.text = 'Starting Discord...';
							spawn(discordBin, {
								detached: true
							}).unref();
							spinner.succeed('Injected.');
							process.exit(0);
						});
					});
				});
			});
		}
    });
} else if (command == 'uninject') {
    // find discord application path
    var discordPath = null;
	var discordBin = null;
	var discordPids;
    ps.lookup({}, (err, res) => {
        if (err) throw err;
        else {
            var raw = res.filter(proc => proc.command.includes('Discord'));
            var procs = {};
            raw.forEach(proc => {
                if (!procs[proc.command])
                    procs[proc.command] = {command: proc.command, pid: []};

                procs[proc.command].pid.push(proc.pid);
            });

            if (Object.keys(procs).length == 0) {
                console.log('No processes were found.');
                return;
            } else if (Object.keys(procs).length == 1) {
				let proc = procs[Object.keys(procs)[0]];
                discordPath = proc.command.substr(0, proc.command.lastIndexOf('\\'));
				discordBin = proc.command;
				discordPids = proc.pid;
            } else {
                let k = Object.keys(procs);
                for (let i = 0; i < keys.length; i++) {
                    console.log(`${i}: ${k[i]} [${procs[k[i]].pid.join(', ')}]`);
                }
                console.log('Please select your process from above.');
				var rl = readline.createInterface({
					input: process.stdin,
					output: process.stdout
				});
				rl.question('> ', answer => {
					if (typeof parseInt(answer) === 'number') {
						let proc = procs[Object.keys(procs)[answer]];
						discordPath = proc.command.substr(0, proc.command.lastIndexOf('\\'));
						discordBin = proc.command;
						discordPids = proc.pid;
					} else {
						console.log('You didn\'t input a number. Re-run the command and try again.');
					}
					rl.close();
				});
            }
        }
		var spinner = gSpin;
		console.log(`Process found. Using path '${discordPath}'`);
		spinner.start('Extracting...');
		fs.mkdirSync(temp);
		asar.extractAll(path.join(discordPath, 'resources', 'app.asar'), temp);
		if (fs.existsSync(path.join(temp, 'index.js.bak')) == false) {
			spinner.fail('Not injected or corrupt. If you injected DiscordForge and you are getting this message, reinstall Discord.');
			rmdir(temp, () => {});
		} else {
			spinner.text = 'Removing modification...';
			fs.writeFile(path.join(temp, 'index.js'), fs.readFileSync(path.join(temp, 'index.js.bak')), (e) => {
				if (e) throw e;
				fs.unlinkSync(path.join(temp, 'index.js.bak'));
				fs.unlinkSync(path.join(temp, 'discordforge.js'));
				spinner.text = 'Archiving...';
				discordPids.forEach(o => {
					try {
						process.kill(o);
					} catch (e) {
							// ESRCH => killed the main process first, meaning the other processes died. doesn't matter.
					}
				});
				asar.createPackage(temp, path.join(discordPath, 'resources', 'app.asar'), () => {
					spinner.text = 'Cleaning up...';
					rmdir(temp, () => {
						spinner.text = 'Starting Discord...';
						spawn(discordBin, {
							detached: true
						}).unref();
						spinner.succeed('Uninjected.');
						process.exit(0);
					});
				});
			});
		}
	});
} else if (command == 'repo') {
	let subcommand = _[0];
	let repoName = _[1];
	if (subcommand == 'list') {
		console.log('Repositories:');
		config.repositories.forEach(r => {
			console.log(r);
		});
	} else if (subcommand == 'add') {
		if (repoName != null) {
			let found = false;
			config.repositories.forEach(r => {
				if (repoName.toLowerCase() == r.toLowerCase()) {
					console.log('This repository has already been added.');
					found = true;
					return;
				}
			});
			if (!found) {
				https.get(`https://raw.githubusercontent.com/${repoName}/master/repository.json`, res => {
					let status = res.statusCode;
					if (status == 404) {
						console.log('Repository either does not exist or doesn\'t have a `repository.json` on the master branch.');
						return;
					} else {
						var repoJson = '';
						res.setEncoding('utf8');
						res.on('data', d => {
							repoJson = JSON.parse(d);
						});
						res.on('end', () => {
							if (repoJson.description == undefined || repoJson.plugins == undefined) {
								console.log('Invalid repository metadata.');
								return;
							}
							console.log(`Adding repository '${repoName}'`);
							console.log(`Description: ${repoJson.description}`);
							config.repositories.push(repoName);
							console.log('Added.');
							fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8');
						});
					}
				});
			}
		} else {
			console.log('No repository passed.');
		}
	} else if (subcommand == 'remove') {
		if (repoName != null) {
			let found = false;
			config.repositories.forEach(r => {
				if (repoName.toLowerCase() == r.toLowerCase()) {
					found = true;
					return;
				}
			});
			if (found) {
				console.log(`Removing repository '${repoName}'`);
				config.repositories.splice(config.repositories.indexOf(repoName), 1);
				console.log('Removed.');
				fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8');
			} else {
				console.log('Repository was not added.');
			}
		} else {
			console.log('No repository passed.');
		}
	} else {
		console.log('error: no subcommand - try \'discordforge help\'');
	}
} else if (command == 'plugin') {
	let subcommand = _[0];
	let plugin = _[1];
	/**if (subcommand == 'install') {

	} else {
		console.log('error: no subcommand - try \'discordforge help\'');
	}*/
	console.log('Plugin command has not yet been implemented, as there are no plugins to test with. In DiscordForge 1.0.1, this will mean something.\n\nDevelopers: Prepare your plugin for the 1.0.1 update as well. As well as creating an `init` function, create an `uninit` funtion.');
}<|endoftext|>


<commit_before>import path from 'path'
import fs from 'fs'

import _ from 'lodash'
import postcss from 'postcss'
import perfectionist from 'perfectionist'

import registerConfigAsDependency from './lib/registerConfigAsDependency'
import processTailwindFeatures from './processTailwindFeatures'
import resolveConfig from './util/resolveConfig'

function resolveConfigPath(filePath) {
  if (_.isObject(filePath)) {
    return undefined
  }

  if (!_.isUndefined(filePath)) {
    return path.resolve(filePath)
  }

  try {
    const defaultConfigPath = path.resolve('./tailwind.config.js')
    fs.accessSync(defaultConfigPath)
    return defaultConfigPath
  } catch (err) {
    return undefined
  }
}

const getConfigFunction = config => () => {
  if (_.isUndefined(config) && !_.isObject(config)) {
    return resolveConfig([require('../defaultConfig')()])
  }

  if (!_.isObject(config)) {
    delete require.cache[require.resolve(config)]
  }

  return resolveConfig([
    _.isObject(config) ? config : require(config),
    require('../defaultConfig')(),
  ])
}

const plugin = postcss.plugin('tailwind', config => {
  const plugins = []
  const resolvedConfigPath = resolveConfigPath(config)

  if (!_.isUndefined(resolvedConfigPath)) {
    plugins.push(registerConfigAsDependency(resolvedConfigPath))
  }

  return postcss([
    ...plugins,
    processTailwindFeatures(getConfigFunction(resolvedConfigPath ? resolvedConfigPath : config)),
    perfectionist({
      cascade: true,
      colorShorthand: true,
      indentSize: 2,
      maxSelectorLength: 1,
      maxValueLength: false,
      trimLeadingZero: true,
      trimTrailingZeros: true,
      zeroLengthNoUnit: false,
    }),
  ])
})

plugin.defaultConfig = function() {
  // prettier-ignore
  throw new Error("`require('tailwindcss').defaultConfig()` is no longer a function, access it instead as `require('tailwindcss/defaultConfig')()`.")
}

module.exports = plugin
<commit_msg>Simplify ternary to or statement<commit_after>import path from 'path'
import fs from 'fs'

import _ from 'lodash'
import postcss from 'postcss'
import perfectionist from 'perfectionist'

import registerConfigAsDependency from './lib/registerConfigAsDependency'
import processTailwindFeatures from './processTailwindFeatures'
import resolveConfig from './util/resolveConfig'

function resolveConfigPath(filePath) {
  if (_.isObject(filePath)) {
    return undefined
  }

  if (!_.isUndefined(filePath)) {
    return path.resolve(filePath)
  }

  try {
    const defaultConfigPath = path.resolve('./tailwind.config.js')
    fs.accessSync(defaultConfigPath)
    return defaultConfigPath
  } catch (err) {
    return undefined
  }
}

const getConfigFunction = config => () => {
  if (_.isUndefined(config) && !_.isObject(config)) {
    return resolveConfig([require('../defaultConfig')()])
  }

  if (!_.isObject(config)) {
    delete require.cache[require.resolve(config)]
  }

  return resolveConfig([
    _.isObject(config) ? config : require(config),
    require('../defaultConfig')(),
  ])
}

const plugin = postcss.plugin('tailwind', config => {
  const plugins = []
  const resolvedConfigPath = resolveConfigPath(config)

  if (!_.isUndefined(resolvedConfigPath)) {
    plugins.push(registerConfigAsDependency(resolvedConfigPath))
  }

  return postcss([
    ...plugins,
    processTailwindFeatures(getConfigFunction(resolvedConfigPath || config)),
    perfectionist({
      cascade: true,
      colorShorthand: true,
      indentSize: 2,
      maxSelectorLength: 1,
      maxValueLength: false,
      trimLeadingZero: true,
      trimTrailingZeros: true,
      zeroLengthNoUnit: false,
    }),
  ])
})

plugin.defaultConfig = function() {
  // prettier-ignore
  throw new Error("`require('tailwindcss').defaultConfig()` is no longer a function, access it instead as `require('tailwindcss/defaultConfig')()`.")
}

module.exports = plugin
<|endoftext|>


<commit_before>var type = require("type"),
    createMap = require("create_map");

var NativeWeakMap = global.WeakMap,
    WeakMapShim;

if (type.isNative(NativeWeakMap)) {
    WeakMapShim = NativeWeakMap;

    WeakMapShim.prototype.count = function() {
        return this.size;
    };
} else {
    WeakMapShim = function Map() {
        if (!(this instanceof Map)) {
            throw new TypeError("Constructor WeakMap requires 'new'");
        }

        this._map = createMap();
    };
    WeakMapShim.prototype.constructor = WeakMapShim;

    WeakMapShim.prototype.get = function(key) {

        return this._map.get(key);
    };

    WeakMapShim.prototype.set = function(key, value) {
        if (key == null || typeof(key) !== "object") {
            throw new TypeError("Invalid value used as key");
        }

        this._map.set(key, value);
    };

    WeakMapShim.prototype.has = function(key) {

        return this._map.has(key);
    };

    WeakMapShim.prototype["delete"] = function(key) {

        return this._map.remove(key);
    };

    WeakMapShim.prototype.clear = function() {

        this._map.clear();
    };

    if (Object.defineProperty) {
        Object.defineProperty(WeakMapShim.prototype, "size", {
            get: function() {
                return this._map.size();
            }
        });
    }

    WeakMapShim.prototype.count = function() {
        return this._map.size();
    };

    WeakMapShim.prototype.length = 1;
}

WeakMapShim.prototype.remove = WeakMapShim.prototype["delete"];

module.exports = WeakMapShim;
<commit_msg>updates<commit_after>var type = require("type"),
    createMap = require("create_map");

var NativeWeakMap = global.WeakMap,
    WeakMapShim;

if (type.isNative(NativeWeakMap)) {
    WeakMapShim = NativeWeakMap;

    WeakMapShim.prototype.count = function() {
        return this.size;
    };
} else {
    WeakMapShim = function WeakMap() {
        if (!(this instanceof WeakMap)) {
            throw new TypeError("Constructor WeakMap requires 'new'");
        }

        this._map = createMap();
    };
    WeakMapShim.prototype.constructor = WeakMapShim;

    WeakMapShim.prototype.get = function(key) {

        return this._map.get(key);
    };

    WeakMapShim.prototype.set = function(key, value) {
        if (type.isPrimitive(key)) {
            throw new TypeError("Invalid value used as key");
        }

        this._map.set(key, value);
    };

    WeakMapShim.prototype.has = function(key) {

        return this._map.has(key);
    };

    WeakMapShim.prototype["delete"] = function(key) {

        return this._map.remove(key);
    };

    WeakMapShim.prototype.clear = function() {

        this._map.clear();
    };

    if (Object.defineProperty) {
        Object.defineProperty(WeakMapShim.prototype, "size", {
            get: function() {
                return this._map.size();
            }
        });
    }

    WeakMapShim.prototype.count = function() {
        return this._map.size();
    };

    WeakMapShim.prototype.length = 1;
}

WeakMapShim.prototype.remove = WeakMapShim.prototype["delete"];

module.exports = WeakMapShim;
<|endoftext|>


<commit_before>const E_END = 'Iterator exhausted before seek ended.'
const E_SHOW = 'Argument 1 of seek must use filter NodeFilter.SHOW_TEXT.'
const E_WHERE = 'Argument 2 of seek must be an integer or a Text Node.'

const DOCUMENT_POSITION_PRECEDING = 2
const SHOW_TEXT = 4
const TEXT_NODE = 3

export default function seek(iter, where) {
  if (iter.whatToShow !== SHOW_TEXT) {
    let error

    // istanbul ignore next
    try {
      error = new DOMException(E_SHOW, 'InvalidStateError')
    } catch {
      error = new Error(E_SHOW);
      error.code = 11
      error.name = 'InvalidStateError'
      error.toString = () => `InvalidStateError: ${E_SHOW}`
    }

    throw error
  }

  let count = 0
  let node = iter.referenceNode
  let predicates = null

  if (isInteger(where)) {
    predicates = {
      forward: () => count < where,
      backward: () => count > where || !iter.pointerBeforeReferenceNode,
    }
  } else if (isText(where)) {
    let forward = before(node, where) ? () => false : () => node !== where
    let backward = () => node !== where || !iter.pointerBeforeReferenceNode
    predicates = {forward, backward}
  } else {
    throw new TypeError(E_WHERE)
  }

  while (predicates.forward()) {
    node = iter.nextNode()

    if (node === null) {
      throw new RangeError(E_END)
    }

    count += node.nodeValue.length
  }

  if (iter.nextNode()) {
    node = iter.previousNode()
  }

  while (predicates.backward()) {
    node = iter.previousNode()

    if (node === null) {
      throw new RangeError(E_END)
    }

    count -= node.nodeValue.length
  }

  return count
}

function isInteger(n) {
  if (typeof n !== 'number') return false;
  return isFinite(n) && Math.floor(n) === n;
}

function isText(node) {
  return node.nodeType === TEXT_NODE
}

function before(ref, node) {
  return ref.compareDocumentPosition(node) & DOCUMENT_POSITION_PRECEDING
}
<commit_msg>Ensure error is thrown for empty text<commit_after>const E_END = 'Iterator exhausted before seek ended.'
const E_SHOW = 'Argument 1 of seek must use filter NodeFilter.SHOW_TEXT.'
const E_WHERE = 'Argument 2 of seek must be an integer or a Text Node.'

const DOCUMENT_POSITION_PRECEDING = 2
const SHOW_TEXT = 4
const TEXT_NODE = 3

export default function seek(iter, where) {
  if (iter.whatToShow !== SHOW_TEXT) {
    let error

    // istanbul ignore next
    try {
      error = new DOMException(E_SHOW, 'InvalidStateError')
    } catch {
      error = new Error(E_SHOW);
      error.code = 11
      error.name = 'InvalidStateError'
      error.toString = () => `InvalidStateError: ${E_SHOW}`
    }

    throw error
  }

  let count = 0
  let node = iter.referenceNode
  let predicates = null

  if (isInteger(where)) {
    predicates = {
      forward: () => count < where,
      backward: () => count > where || !iter.pointerBeforeReferenceNode,
    }
  } else if (isText(where)) {
    let forward = before(node, where) ? () => false : () => node !== where
    let backward = () => node !== where || !iter.pointerBeforeReferenceNode
    predicates = {forward, backward}
  } else {
    throw new TypeError(E_WHERE)
  }

  while (predicates.forward()) {
    node = iter.nextNode()

    if (node === null) {
      throw new RangeError(E_END)
    }

    count += node.nodeValue.length
  }

  if (iter.nextNode()) {
    node = iter.previousNode()
  }

  while (predicates.backward()) {
    node = iter.previousNode()

    if (node === null) {
      throw new RangeError(E_END)
    }

    count -= node.nodeValue.length
  }

  if (!isText(iter.referenceNode)) {
    throw new RangeError(E_END);
  }

  return count
}

function isInteger(n) {
  if (typeof n !== 'number') return false;
  return isFinite(n) && Math.floor(n) === n;
}

function isText(node) {
  return node.nodeType === TEXT_NODE
}

function before(ref, node) {
  return ref.compareDocumentPosition(node) & DOCUMENT_POSITION_PRECEDING
}
<|endoftext|>


<commit_before>import createHistory from 'history/createBrowserHistory';
import React from 'react';
import ReactDOM from 'react-dom';
import { AppContainer } from 'react-hot-loader';

import Root from 'Root';
import buildStore from 'store';

const history = createHistory();
const store = buildStore(history);

const domElement = document.getElementById('react');

const render = () => {
  ReactDOM.render(
    <AppContainer>
      <Root history={history} store={store} />
    </AppContainer>,
    domElement
  );
}

render();
if (module.hot) {
  module.hot.accept('./Root', () => { render(); });
}
<commit_msg>Fix linting issues<commit_after>import createHistory from 'history/createBrowserHistory';
import React from 'react';
import ReactDOM from 'react-dom';
import { AppContainer } from 'react-hot-loader';

import Root from 'Root';
import buildStore from 'store';

const history = createHistory();
const store = buildStore(history);

const domElement = document.getElementById('react');

const render = () => {
  ReactDOM.render(
    <AppContainer>
      <Root history={history} store={store} />
    </AppContainer>,
    domElement
  );
}

render();
if (module.hot) {
  module.hot.accept('./Root', () => {
    render();
  });
}
<|endoftext|>


<commit_before>const browserify = require('browserify');
const budo = require('budo');
const babelify = require('babelify');
const path = require('path');
const {
  dirname,
  isAbsolute,
  join,
  parse,
} = path;
const compile = require('idyll-compiler');
const fs = require('fs');
const chokidar = require('chokidar');
const changeCase = require('change-case');
const brfs = require('brfs');
const reactPreset = require('babel-preset-react');
const es2015Preset = require('babel-preset-es2015');
const compression = require('compression');
const resolve = require('resolve');
const Mustache = require('mustache');
const ReactDOMServer = require('react-dom/server');
const React = require('react');
const Baby = require('babyparse');
const UglifyJS = require("uglify-js");
const htmlTags = require('html-tags');

const css = require('./assets/css');
const filterAST = require('./assets/ast');

require('babel-core/register')({
    presets: ['react']
});

const getPathFactory = (basedir) => (p) => {
  if (!p) return undefined;
  if (isAbsolute(p)) return p;
  return join(basedir, p);
}

const idyll = (options = {}, cb) => {
  const inputDir = options.inputFile
    ? isAbsolute(options.inputFile)
      ? dirname(options.inputFile)
      : dirname(join(process.cwd(), options.inputFile))
    : process.cwd();
  const getPath = getPathFactory(inputDir);
  const inputPkg = require(getPath('package.json'));
  const inputConfig = (inputPkg.idyll || {components: {}});
  for (let key in inputConfig.components) {
    inputConfig.components[changeCase.paramCase(key)] = inputConfig.components[key];
    delete inputConfig.components[key];
  };

  const opts = Object.assign(
    {},
    {
      watch: false,
      components: 'components',
      datasets: 'data',
      minify: true,
      defaultComponents: path.join('components', 'default'),
      layout: 'blog',
      output: 'build',
      template: '_index.html',
      theme: 'idyll',
      compilerOptions: {
        spellcheck: true
      },
    },
    options
  );

  // input paths
  const IDL_FILE = getPath(opts.inputFile);
  const HTML_TEMPLATE = getPath(opts.template);
  const CSS_INPUT = getPath(opts.css);
  const CUSTOM_COMPONENTS_FOLDER = getPath(opts.components);
  const DEFAULT_COMPONENTS_FOLDER = getPath(opts.defaultComponents);
  const DATA_FOLDER = getPath(opts.datasets);

  // output paths
  const BUILD_PATH = getPath(opts.output);
  if (!fs.existsSync(BUILD_PATH)) fs.mkdirSync(BUILD_PATH);
  const HTML_OUTPUT = join(BUILD_PATH, 'index.html');
  const CSS_OUTPUT = join(BUILD_PATH, 'styles.css');
  const JAVASCRIPT_OUTPUT = join(BUILD_PATH, 'index.js');

  const TMP_PATH = getPath('.idyll');
  if (!fs.existsSync(TMP_PATH)) fs.mkdirSync(TMP_PATH);
  const AST_FILE = join(TMP_PATH, 'ast.json');
  const COMPONENTS_FILE = join(TMP_PATH, 'components.js');
  const DATA_FILE = join(TMP_PATH, 'data.js');

  let componentFiles = [];
  let customComponentFiles = [];
  const readComponents = () => {
    componentFiles = fs.readdirSync(DEFAULT_COMPONENTS_FOLDER);
    customComponentFiles = CUSTOM_COMPONENTS_FOLDER
      ? fs.readdirSync(CUSTOM_COMPONENTS_FOLDER)
      : [];
  }

  const writeAST = (ast) => {
    fs.writeFileSync(AST_FILE, JSON.stringify(filterAST(ast)));
  };

  const writeCSS = (css) => {
    fs.writeFileSync(CSS_OUTPUT, css);
  };

  const writeData = (data) => {
    fs.writeFileSync(DATA_FILE, `module.exports = ${JSON.stringify(data)}`);
  };

  const writeComponents = (components) => {
    const src = Object.keys(components)
      .map((key) => {
        return `'${key}': require('${components[key]}')`;
      })
      .join(',\n\t');
    fs.writeFileSync(COMPONENTS_FILE, `module.exports = {\n\t${src}\n}\n`);
  };

  const writeHTML = (meta) => {
    // const InteractiveDocument = require('./client/component');
    // tree.meta.idyllContent = ReactDOMServer.renderToString(React.createElement(InteractiveDocument));
    const output = Mustache.render(fs.readFileSync(HTML_TEMPLATE, 'utf8'), meta);
    fs.writeFileSync(HTML_OUTPUT, output);
  };

  const interpretAST = (ast) => {
    const getNodesByName = (name, tree) => {
      const predicate = typeof name === 'string' ? (s) => s === name : name;

      const byName = (acc, val) => {
        if (typeof val === 'string') return acc;
        if (predicate(val[0])) acc.push(val);

        if (val[2] && typeof val[2] !== 'string') acc = val[2].reduce(byName, acc);

        return acc;
      }

      return tree.reduce(
        byName,
        []
      )
    }

    const getMeta = (ast) => {
      // there should only be one meta node
      const metaNodes = getNodesByName('meta', ast);

      if (!metaNodes.length) {
        return {};
      }
      // data is stored in props, hence [1]
      return metaNodes[0][1].reduce(
        (acc, prop) => {
          acc[prop[0]] = prop[1][1];
          return acc;
        },
        {}
      )
    }

    const getData = (ast) => {
      // can be multiple data nodes
      const dataNodes = getNodesByName('data', ast);

      // turn each data node into a field on an object
      // whose key is the name prop
      // and whose value is the parsed data
      return dataNodes.reduce(
        (acc, dataNode) => {
          const props = dataNode[1];
          const { name, source } = props.reduce(
            (hash, val) => {
              hash[val[0]] = val[1][1];
              return hash;
            },
            {}
          );

          if (source.endsWith('.csv')) {
            acc[name] = Baby.parseFiles(path.join(DATA_FOLDER, source), { header: true }).data;
          } else {
            acc[name] = require(path.join(DATA_FOLDER, source));
          }

          return acc;
        },
        {}
      );
    }

    const getComponents = (ast) => {
      const ignoreNames = ['var', 'data', 'meta', 'derived'];
      const componentNodes = getNodesByName(s => !ignoreNames.includes(s), ast);

      return componentNodes.reduce(
        (acc, node) => {
          const name = changeCase.paramCase(node[0].split('.')[0]);

          if (!acc[name]) {
            if (inputConfig.components[name]) {
              const compPath = path.parse(path.join(inputDir, inputConfig.components[name]));
              acc[name] = path.join(path.relative(TMP_PATH, compPath.dir), compPath.base).replace(/\\/g, '/');
            } else if (customComponentFiles.indexOf(name + '.js') > -1) {
              acc[name] = path.relative(TMP_PATH, path.join(CUSTOM_COMPONENTS_FOLDER, name)).replace(/\\/g, '/');
            } else if (componentFiles.indexOf(name + '.js') > -1) {
              acc[name] = path.relative(TMP_PATH, path.join(DEFAULT_COMPONENTS_FOLDER, name)).replace(/\\/g, '/');
            } else {
              try {
                // npm modules are required via relative paths to support working with a locally linked idyll
                const compPath = path.parse(resolve.sync(name, {basedir: inputDir}));
                acc[name] = path.join(path.relative(TMP_PATH, compPath.dir), compPath.base).replace(/\\/g, '/');
              } catch (err) {
                if (htmlTags.indexOf(node[0].toLowerCase()) === -1) {
                  throw new Error(`Component named ${node[0]} could not be found.`)
                }
              }
            }
          }

          return acc;
        },
        {}
      )
    }

    return {
      ast,
      components: getComponents(ast),
      data: getData(ast),
      meta: getMeta(ast)
    }
  };

  const browserifyOpts = {
    transform: [
      [ babelify, { presets: [ reactPreset, es2015Preset ] } ],
      [ brfs ]
    ],
    plugin: [
      (b) => b.require([
        {file: AST_FILE, expose: '__IDYLL_AST__'},
        {file: COMPONENTS_FILE, expose: '__IDYLL_COMPONENTS__'},
        {file: DATA_FILE, expose: '__IDYLL_DATA__'}
      ])
    ]
  };

  const build = (callback) => {
    process.env['NODE_ENV'] = 'production';
    var b = browserify(path.join(__dirname, 'client', 'build.js'), browserifyOpts);
    b.bundle(function(err, buff) {
      if (err) {
        console.error('Error creating index.js bundle:');
        console.error(err);
        process.exit(1);
      }
      callback(buff.toString('utf8'));
    });
  }

  const start = () => {
    let watchedFiles = [];
    if (CSS_INPUT) watchedFiles.push(CSS_INPUT);
    if (IDL_FILE) watchedFiles.push(IDL_FILE);

    chokidar.watch(watchedFiles).on('all', (event, path) => {
      if (path.indexOf('.css') !== -1) {
        writeCSS(css(options));
      } else {
        compileAndWriteFiles();
      }
    });
    const watchedComponentFolders = [DEFAULT_COMPONENTS_FOLDER, CUSTOM_COMPONENTS_FOLDER];
    const handleComponentAddRemove = () => {
      readComponents();
      compileAndWriteFiles();
    };
    chokidar.watch(watchedComponentFolders).on('add', handleComponentAddRemove).on('unlink', handleComponentAddRemove);

    budo(path.resolve(path.join(__dirname, 'client', 'live.js')), {
      live: true,
      open: true,
      forceDefaultIndex: true,
      css: path.parse(CSS_OUTPUT).base,
      middleware: compression(),
      watchGlob: '**/*.{html,css,json,js}',
      browserify: browserifyOpts
    });
    compileAndWriteFiles();
  }

  const compileAndWriteFiles = () => {
    const idlInput = IDL_FILE
      ? fs.readFileSync(IDL_FILE, 'utf8')
      : opts.inputString;

    try {
      const ast = compile(idlInput, opts.compilerOptions);
      const tree = interpretAST(ast);
      tree.css = css(opts);

      writeAST(ast);
      writeComponents(tree.components);
      writeData(tree.data);
      writeHTML(tree.meta);
      writeCSS(tree.css);
      return tree;
    } catch(err) {
      console.error('Error writing artifacts to disk:');
      console.error(err.message);
    }
  }

  readComponents();
  if (opts.watch) {
    start();
  } else {
    const tree = compileAndWriteFiles();
    build(function (js) {
      tree.js = opts.minify ? UglifyJS.minify(js, {fromString: true}).code : js;
      fs.writeFileSync(JAVASCRIPT_OUTPUT, tree.js);
      if (cb) cb(tree);
    });
  }
};

module.exports = idyll;
<commit_msg>Make file writing functions do nothing but write the contents they are passed<commit_after>const browserify = require('browserify');
const budo = require('budo');
const babelify = require('babelify');
const path = require('path');
const {
  dirname,
  isAbsolute,
  join,
  parse,
} = path;
const compile = require('idyll-compiler');
const fs = require('fs');
const chokidar = require('chokidar');
const changeCase = require('change-case');
const brfs = require('brfs');
const reactPreset = require('babel-preset-react');
const es2015Preset = require('babel-preset-es2015');
const compression = require('compression');
const resolve = require('resolve');
const Mustache = require('mustache');
const ReactDOMServer = require('react-dom/server');
const React = require('react');
const Baby = require('babyparse');
const UglifyJS = require("uglify-js");
const htmlTags = require('html-tags');

const css = require('./assets/css');
const filterAST = require('./assets/ast');

require('babel-core/register')({
    presets: ['react']
});

const getPathFactory = (basedir) => (p) => {
  if (!p) return undefined;
  if (isAbsolute(p)) return p;
  return join(basedir, p);
}

const idyll = (options = {}, cb) => {
  const inputDir = options.inputFile
    ? isAbsolute(options.inputFile)
      ? dirname(options.inputFile)
      : dirname(join(process.cwd(), options.inputFile))
    : process.cwd();
  const getPath = getPathFactory(inputDir);
  const inputPkg = require(getPath('package.json'));
  const inputConfig = (inputPkg.idyll || {components: {}});
  for (let key in inputConfig.components) {
    inputConfig.components[changeCase.paramCase(key)] = inputConfig.components[key];
    delete inputConfig.components[key];
  };

  const opts = Object.assign(
    {},
    {
      watch: false,
      components: 'components',
      datasets: 'data',
      minify: true,
      defaultComponents: path.join('components', 'default'),
      layout: 'blog',
      output: 'build',
      template: '_index.html',
      theme: 'idyll',
      compilerOptions: {
        spellcheck: true
      },
    },
    options
  );

  // input paths
  const IDL_FILE = getPath(opts.inputFile);
  const HTML_TEMPLATE = getPath(opts.template);
  const CSS_INPUT = getPath(opts.css);
  const CUSTOM_COMPONENTS_FOLDER = getPath(opts.components);
  const DEFAULT_COMPONENTS_FOLDER = getPath(opts.defaultComponents);
  const DATA_FOLDER = getPath(opts.datasets);

  // output paths
  const BUILD_PATH = getPath(opts.output);
  if (!fs.existsSync(BUILD_PATH)) fs.mkdirSync(BUILD_PATH);
  const HTML_OUTPUT = join(BUILD_PATH, 'index.html');
  const CSS_OUTPUT = join(BUILD_PATH, 'styles.css');
  const JAVASCRIPT_OUTPUT = join(BUILD_PATH, 'index.js');

  const TMP_PATH = getPath('.idyll');
  if (!fs.existsSync(TMP_PATH)) fs.mkdirSync(TMP_PATH);
  const AST_FILE = join(TMP_PATH, 'ast.json');
  const COMPONENTS_FILE = join(TMP_PATH, 'components.js');
  const DATA_FILE = join(TMP_PATH, 'data.js');

  let componentFiles = [];
  let customComponentFiles = [];
  const readComponents = () => {
    componentFiles = fs.readdirSync(DEFAULT_COMPONENTS_FOLDER);
    customComponentFiles = CUSTOM_COMPONENTS_FOLDER
      ? fs.readdirSync(CUSTOM_COMPONENTS_FOLDER)
      : [];
  }

  const writeAST = (ast) => {
    fs.writeFileSync(AST_FILE, ast);
  };

  const writeCSS = (css) => {
    fs.writeFileSync(CSS_OUTPUT, css);
  };

  const writeData = (data) => {
    fs.writeFileSync(DATA_FILE, data);
  };

  const writeComponents = (components) => {
    fs.writeFileSync(COMPONENTS_FILE, components);
  };

  const writeHTML = (html) => {
    fs.writeFileSync(HTML_OUTPUT, html);
  };

  const interpretAST = (ast) => {
    const getNodesByName = (name, tree) => {
      const predicate = typeof name === 'string' ? (s) => s === name : name;

      const byName = (acc, val) => {
        if (typeof val === 'string') return acc;
        if (predicate(val[0])) acc.push(val);

        if (val[2] && typeof val[2] !== 'string') acc = val[2].reduce(byName, acc);

        return acc;
      }

      return tree.reduce(
        byName,
        []
      )
    }

    const getHTML = (ast) => {
      // there should only be one meta node
      const metaNodes = getNodesByName('meta', ast);

      // data is stored in props, hence [1]
      const meta = metaNodes.length && metaNodes[0][1].reduce(
        (acc, prop) => {
          acc[prop[0]] = prop[1][1];
          return acc;
        },
        {}
      )

      // const InteractiveDocument = require('./client/component');
      // meta.idyllContent = ReactDOMServer.renderToString(React.createElement(InteractiveDocument));
      return Mustache.render(fs.readFileSync(HTML_TEMPLATE, 'utf8'), meta || {});
    }

    const getData = (ast) => {
      // can be multiple data nodes
      const dataNodes = getNodesByName('data', ast);

      // turn each data node into a field on an object
      // whose key is the name prop
      // and whose value is the parsed data
      const data = dataNodes.reduce(
        (acc, dataNode) => {
          const props = dataNode[1];
          const { name, source } = props.reduce(
            (hash, val) => {
              hash[val[0]] = val[1][1];
              return hash;
            },
            {}
          );

          if (source.endsWith('.csv')) {
            acc[name] = Baby.parseFiles(path.join(DATA_FOLDER, source), { header: true }).data;
          } else {
            acc[name] = require(path.join(DATA_FOLDER, source));
          }

          return acc;
        },
        {}
      );

      return `module.exports = ${JSON.stringify(data)}`;
    }

    const getComponents = (ast) => {
      const ignoreNames = ['var', 'data', 'meta', 'derived'];
      const componentNodes = getNodesByName(s => !ignoreNames.includes(s), ast);

      const components = componentNodes.reduce(
        (acc, node) => {
          const name = changeCase.paramCase(node[0].split('.')[0]);

          if (!acc[name]) {
            if (inputConfig.components[name]) {
              const compPath = path.parse(path.join(inputDir, inputConfig.components[name]));
              acc[name] = path.join(path.relative(TMP_PATH, compPath.dir), compPath.base).replace(/\\/g, '/');
            } else if (customComponentFiles.indexOf(name + '.js') > -1) {
              acc[name] = path.relative(TMP_PATH, path.join(CUSTOM_COMPONENTS_FOLDER, name)).replace(/\\/g, '/');
            } else if (componentFiles.indexOf(name + '.js') > -1) {
              acc[name] = path.relative(TMP_PATH, path.join(DEFAULT_COMPONENTS_FOLDER, name)).replace(/\\/g, '/');
            } else {
              try {
                // npm modules are required via relative paths to support working with a locally linked idyll
                const compPath = path.parse(resolve.sync(name, {basedir: inputDir}));
                acc[name] = path.join(path.relative(TMP_PATH, compPath.dir), compPath.base).replace(/\\/g, '/');
              } catch (err) {
                if (htmlTags.indexOf(node[0].toLowerCase()) === -1) {
                  throw new Error(`Component named ${node[0]} could not be found.`)
                }
              }
            }
          }

          return acc;
        },
        {}
      );

      const src = Object.keys(components)
        .map((key) => {
          return `'${key}': require('${components[key]}')`;
        })
        .join(',\n\t');

      return `module.exports = {\n\t${src}\n}\n`;
    }

    return {
      ast: filterAST(ast),
      components: getComponents(ast),
      data: getData(ast),
      html: getHTML(ast)
    }
  };

  const browserifyOpts = {
    transform: [
      [ babelify, { presets: [ reactPreset, es2015Preset ] } ],
      [ brfs ]
    ],
    plugin: [
      (b) => b.require([
        {file: AST_FILE, expose: '__IDYLL_AST__'},
        {file: COMPONENTS_FILE, expose: '__IDYLL_COMPONENTS__'},
        {file: DATA_FILE, expose: '__IDYLL_DATA__'}
      ])
    ]
  };

  const build = (callback) => {
    process.env['NODE_ENV'] = 'production';
    var b = browserify(path.join(__dirname, 'client', 'build.js'), browserifyOpts);
    b.bundle(function(err, buff) {
      if (err) {
        console.error('Error creating index.js bundle:');
        console.error(err);
        process.exit(1);
      }
      callback(buff.toString('utf8'));
    });
  }

  const start = () => {
    let watchedFiles = [];
    if (CSS_INPUT) watchedFiles.push(CSS_INPUT);
    if (IDL_FILE) watchedFiles.push(IDL_FILE);

    chokidar.watch(watchedFiles).on('all', (event, path) => {
      if (path.indexOf('.css') !== -1) {
        writeCSS(css(options));
      } else {
        compileAndWriteFiles();
      }
    });
    const watchedComponentFolders = [DEFAULT_COMPONENTS_FOLDER, CUSTOM_COMPONENTS_FOLDER];
    const handleComponentAddRemove = () => {
      readComponents();
      compileAndWriteFiles();
    };
    chokidar.watch(watchedComponentFolders).on('add', handleComponentAddRemove).on('unlink', handleComponentAddRemove);

    budo(path.resolve(path.join(__dirname, 'client', 'live.js')), {
      live: true,
      open: true,
      forceDefaultIndex: true,
      css: path.parse(CSS_OUTPUT).base,
      middleware: compression(),
      watchGlob: '**/*.{html,css,json,js}',
      browserify: browserifyOpts
    });
    compileAndWriteFiles();
  }

  const compileAndWriteFiles = () => {
    const idlInput = IDL_FILE
      ? fs.readFileSync(IDL_FILE, 'utf8')
      : opts.inputString;

    try {
      const ast = compile(idlInput, opts.compilerOptions);
      const tree = interpretAST(ast);
      tree.css = css(opts);

      writeAST(JSON.stringify(tree.ast));
      writeComponents(tree.components);
      writeData(tree.data);
      writeHTML(tree.html);
      writeCSS(tree.css);
      return tree;
    } catch(err) {
      console.error('Error writing artifacts to disk:');
      console.error(err.message);
    }
  }

  readComponents();
  if (opts.watch) {
    start();
  } else {
    const tree = compileAndWriteFiles();
    build(function (js) {
      tree.js = opts.minify ? UglifyJS.minify(js, {fromString: true}).code : js;
      fs.writeFileSync(JAVASCRIPT_OUTPUT, tree.js);
      if (cb) cb(tree);
    });
  }
};

module.exports = idyll;
<|endoftext|>


<commit_before>import React from 'react';
import { render } from 'react-dom';
import { createStore, combineReducers, compose, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import { Provider as TranslationProvider } from 'retranslate';
import { Provider as ReduxProvider } from 'react-redux';
import { Router, Route, browserHistory } from 'react-router';
import { syncHistoryWithStore, routerReducer, routerMiddleware } from 'react-router-redux';

import translations from './translations';
import './index.scss';

import requireAuthentication from './requireAuthentication';
import LoginPage, { reducer as loginReducer, actions as loginActions } from './login';
import { reducer as exchangeReducer, actions as exchangeActions } from './exchange';
import App from './app';
import Steps, {
  SelectSources,
  SelectTargetFund,
  TransferFutureCapital,
  ConfirmApplication,
} from './steps';

const rootReducer = combineReducers({
  routing: routerReducer,
  login: loginReducer,
  exchange: exchangeReducer, // exchage of funds
});

const composeEnhancers = (process.env.NODE_ENV === 'development' &&
  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) || compose; // eslint-disable-line

const routingMiddleware = routerMiddleware(browserHistory);

const store = createStore(rootReducer, composeEnhancers(applyMiddleware(thunk, routingMiddleware)));

const history = syncHistoryWithStore(browserHistory, store);

// TODO: figure out a place where to put these two
function getUserIfNecessary() {
  const { login } = store.getState();
  if (login.token && !(login.user || login.loadingUser)) {
    store.dispatch(loginActions.getUser());
  }
}

function getSourceFundsIfNecessary() {
  const { login, exchange } = store.getState();
  if (login.token && !(exchange.pensionFunds || exchange.loadingPensionFunds)) {
    store.dispatch(exchangeActions.getSourceFunds());
  }
}

render((
  <TranslationProvider messages={translations} language="et" fallbackLanguage="et">
    <ReduxProvider store={store}>
      <Router history={history}>
        <Route path="/login" component={LoginPage} />
        <Route path="/" component={requireAuthentication(App)} onEnter={getUserIfNecessary}>
          <Route path="/steps" component={Steps} onEnter={getSourceFundsIfNecessary}>
            <Route path="select-sources" component={SelectSources} />
            <Route path="select-target-fund" component={SelectTargetFund} />
            <Route path="transfer-future-capital" component={TransferFutureCapital} />
            <Route path="confirm-application" component={ConfirmApplication} />
          </Route>
        </Route>
      </Router>
    </ReduxProvider>
  </TranslationProvider>
), document.getElementById('root'));
<commit_msg>get both source and target funds on enter<commit_after>import React from 'react';
import { render } from 'react-dom';
import { createStore, combineReducers, compose, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import { Provider as TranslationProvider } from 'retranslate';
import { Provider as ReduxProvider } from 'react-redux';
import { Router, Route, browserHistory } from 'react-router';
import { syncHistoryWithStore, routerReducer, routerMiddleware } from 'react-router-redux';

import translations from './translations';
import './index.scss';

import requireAuthentication from './requireAuthentication';
import LoginPage, { reducer as loginReducer, actions as loginActions } from './login';
import { reducer as exchangeReducer, actions as exchangeActions } from './exchange';
import App from './app';
import Steps, {
  SelectSources,
  SelectTargetFund,
  TransferFutureCapital,
  ConfirmApplication,
} from './steps';

const rootReducer = combineReducers({
  routing: routerReducer,
  login: loginReducer,
  exchange: exchangeReducer, // exchage of funds
});

const composeEnhancers = (process.env.NODE_ENV === 'development' &&
  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) || compose; // eslint-disable-line

const routingMiddleware = routerMiddleware(browserHistory);

const store = createStore(rootReducer, composeEnhancers(applyMiddleware(thunk, routingMiddleware)));

const history = syncHistoryWithStore(browserHistory, store);

// TODO: figure out a place where to put these two
function getUserIfNecessary() {
  const { login } = store.getState();
  if (login.token && !(login.user || login.loadingUser)) {
    store.dispatch(loginActions.getUser());
  }
}

function getFundsIfNecessary() {
  const { login, exchange } = store.getState();
  if (login.token && !(exchange.sourceFunds || exchange.loadingSourceFunds ||
    exchange.targetFunds || exchange.loadingTargetFunds)) {
    store.dispatch(exchangeActions.getSourceFunds());
    store.dispatch(exchangeActions.getTargetFunds());
  }
}

render((
  <TranslationProvider messages={translations} language="et" fallbackLanguage="et">
    <ReduxProvider store={store}>
      <Router history={history}>
        <Route path="/login" component={LoginPage} />
        <Route path="/" component={requireAuthentication(App)} onEnter={getUserIfNecessary}>
          <Route path="/steps" component={Steps} onEnter={getFundsIfNecessary}>
            <Route path="select-sources" component={SelectSources} />
            <Route path="select-target-fund" component={SelectTargetFund} />
            <Route path="transfer-future-capital" component={TransferFutureCapital} />
            <Route path="confirm-application" component={ConfirmApplication} />
          </Route>
        </Route>
      </Router>
    </ReduxProvider>
  </TranslationProvider>
), document.getElementById('root'));
<|endoftext|>


<commit_before>var http = require('http')
var url = require('url')
var request = require('request')

const PORT = 3000

var params = process.argv
var destUrl = ''
var token = ''
var readOnly = false

// Parse CLI parameters
params.forEach(function (val, index, array) {
  switch (val) {
    // URL to proxy to
    case '--url':
      destUrl = params[index + 1]
    // Token for request
    case '--token':
      token = params[index + 1]
    // Read only flag
    case '--read-only':
      readOnly = true
  }
})

const server = http.createServer(function(req, res) {
  if (req.method == 'GET') {
    proxyRequest(destUrl, req, function(err, body) {
      if (!err) {
        res.setHeader('Content-Type', 'application/json')
        res.write(body)
        res.end()
      }
    })
  }
}).listen(PORT)

function proxyRequest(dest, req, callback) {
  return request(dest.replace(/\/$/, "") + req.url, function(err, res, body) {
    callback(err, body)
  })
}<commit_msg>Const module imports<commit_after>const http = require('http')
const url = require('url')
const request = require('request')

const PORT = 3000

var params = process.argv
var destUrl = ''
var token = ''
var readOnly = false

// Parse CLI parameters
params.forEach(function (val, index, array) {
  switch (val) {
    // URL to proxy to
    case '--url':
      destUrl = params[index + 1]
    // Token for request
    case '--token':
      token = params[index + 1]
    // Read only flag
    case '--read-only':
      readOnly = true
  }
})

const server = http.createServer(function(req, res) {
  if (req.method == 'GET') {
    proxyRequest(destUrl, req, function(err, body) {
      if (!err) {
        res.setHeader('Content-Type', 'application/json')
        res.write(body)
        res.end()
      }
    })
  }
}).listen(PORT)

function proxyRequest(dest, req, callback) {
  return request(dest.replace(/\/$/, "") + req.url, function(err, res, body) {
    callback(err, body)
  })
}<|endoftext|>


<commit_before>import React from 'react';
import { render } from 'react-dom';
import { Router, Route, browserHistory } from 'react-router';

import './static/bootstrap/css/bootstrap.css';

import App from './Main/App';

import ErrorBoundary from './Main/ErrorBoundary';
import { unregister } from './registerServiceWorker';

function isError(x) {
  return x instanceof Error;
}
function toMessage(x) {
  return isError(x) ? x.message : x;
}
function toStack(x) {
  return isError(x) ? x.stack : undefined;
}

window.addEventListener('unhandledrejection', event => {
  const message = toMessage(event);
  console.error(`Unhandled rejection: ${message}`);
  Raven && Raven.captureException(event.reason || new Error('Unhandled promise rejection'), { // eslint-disable-line no-undef
    extra: {
      reason: message,
      originalEvent: event,
      stack: toStack(event),
    },
  });
});

render(
  <ErrorBoundary>
    <Router history={browserHistory}>
      <Route path="/" component={App}>
        <Route path="report/:reportCode" />
        <Route path="report/:reportCode/:fightId" />
        <Route path="report/:reportCode/:fightId/:playerName" />
        <Route path="report/:reportCode/:fightId/:playerName/:resultTab" />
      </Route>
    </Router>
  </ErrorBoundary>,
  document.getElementById('app-mount')
);
unregister();
<commit_msg>Undo custom `unhandledrejection` and just use Raven's suggestions (too much spam of events that provide no information to resolve them)<commit_after>import React from 'react';
import { render } from 'react-dom';
import { Router, Route, browserHistory } from 'react-router';

import './static/bootstrap/css/bootstrap.css';

import App from './Main/App';

import ErrorBoundary from './Main/ErrorBoundary';
import { unregister } from './registerServiceWorker';

// Source: https://docs.sentry.io/clients/javascript/usage/#promises
window.onunhandledrejection = function(evt) {
  Raven && Raven.captureException(evt.reason); // eslint-disable-line no-undef
};

render(
  <ErrorBoundary>
    <Router history={browserHistory}>
      <Route path="/" component={App}>
        <Route path="report/:reportCode" />
        <Route path="report/:reportCode/:fightId" />
        <Route path="report/:reportCode/:fightId/:playerName" />
        <Route path="report/:reportCode/:fightId/:playerName/:resultTab" />
      </Route>
    </Router>
  </ErrorBoundary>,
  document.getElementById('app-mount')
);
unregister();
<|endoftext|>


<commit_before>import _ from 'lodash';
import path from 'path';
import net from 'net';
import { Observable, Disposable, AsyncSubject } from 'rx';
import sfs from 'fs';

const spawnOg = require('child_process').spawn;
const isWindows = process.platform === 'win32';
const fs = require('pify')(sfs);

const d = require('debug')('surf:promise-array');

export function findActualExecutable(fullPath, args) {
  // POSIX can just execute scripts directly, no need for silly goosery
  if (process.platform !== 'win32') return { cmd: fullPath, args: args };

  // NB: When you write something like `surf-client ... -- surf-build` on Windows,
  // a shell would normally convert that to surf-build.cmd, but since it's passed
  // in as an argument, it doesn't happen
  const possibleExts = ['.exe', '.bat', '.cmd', '.ps1'];
  let extension = _.find(possibleExts, (x) => sfs.existsSync(runDownPath(`${fullPath}${x}`)));

  if (extension) {
    return findActualExecutable(`${fullPath}${extension}`, args);
  }

  if (fullPath.match(/\.ps1$/i)) {
    let cmd = path.join(process.env.SYSTEMROOT, 'System32', 'WindowsPowerShell', 'v1.0', 'PowerShell.exe');
    let psargs = ['-ExecutionPolicy', 'Unrestricted', '-NoLogo', '-NonInteractive', '-File', fullPath];

    return { cmd: cmd, args: psargs.concat(args) };
  }

  if (fullPath.match(/\.(bat|cmd)$/i)) {
    let cmd = path.join(process.env.SYSTEMROOT, 'System32', 'cmd.exe');
    let cmdArgs = ['/C', fullPath];

    return { cmd: cmd, args: cmdArgs.concat(args) };
  }

  if (fullPath.match(/\.(js)$/i)) {
    let cmd = process.execPath;
    let nodeArgs = [fullPath];

    return { cmd: cmd, args: nodeArgs.concat(args) };
  }

  // Dunno lol
  return { cmd: fullPath, args: args };
}

export function asyncMap(array, selector, maxConcurrency=4) {
  return Observable.from(array)
    .map((k) =>
      Observable.defer(() =>
        Observable.fromPromise(selector(k))
          .map((v) => ({ k, v }))))
    .merge(maxConcurrency)
    .reduce((acc, kvp) => {
      acc[kvp.k] = kvp.v;
      return acc;
    }, {})
    .toPromise();
}

export async function asyncReduce(array, selector, seed) {
  let acc = seed;
  for (let x of array) {
    acc = await selector(acc, x);
  }

  return acc;
}

export function delay(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

export function retryPromise(func) {
  return Observable.defer(() =>
      Observable.fromPromise(func()))
    .retry(3)
    .toPromise();
}

export async function statNoException(file) {
  try {
    return await fs.stat(file);
  } catch (e) {
    return null;
  }
}

export function statSyncNoException(file) {
  try {
    return sfs.statSync(file);
  } catch (e) {
    return null;
  }
}

export async function readdirRecursive(dir) {
  let acc = [];

  for (let entry of await fs.readdir(dir)) {
    let target = path.resolve(dir, entry);
    let stat = await statNoException(target);

    if (stat && stat.isDirectory()) {
      let entries = await readdirRecursive(target);
      _.each(entries, (x) => acc.push(x));
    } else {
      acc.push(target);
    }
  }

  return acc;
}

function runDownPath(exe) {
  // NB: Windows won't search PATH looking for executables in spawn like
  // Posix does

  // Files with any directory path don't get this applied
  if (exe.match(/[\\\/]/)) {
    d('Path has slash in directory, bailing');
    return exe;
  }

  let target = path.join('.', exe);
  if (statSyncNoException(target)) {
    d(`Found executable in currect directory: ${target}`);
    return target;
  }

  let haystack = process.env.PATH.split(isWindows ? ';' : ':');
  for (let p of haystack) {
    let needle = path.join(p, exe);
    if (statSyncNoException(needle)) return needle;
  }

  d('Failed to find executable anywhere in path');
  return exe;
}

export function spawnDetached(exe, params, opts=null) {
  if (!isWindows) return spawn(exe, params, _.assign({}, opts || {}, {detached: true }));
  const newParams = [exe].concat(params);

  let target = path.join(__dirname, '..', 'vendor', 'jobber', 'jobber.exe');
  let options = _.assign({}, opts || {}, { detached: true, jobber: true });

  d(`spawnDetached: ${target}, ${newParams}`);
  return spawn(target, newParams, options);
}

export function spawn(exe, params=[], opts=null) {
  let spawnObs = Observable.create((subj) => {
    let proc = null;

    let { cmd, args } = findActualExecutable(exe, params);
    if (!opts) {
      d(`spawning process: ${cmd} ${args.join()}`);
      proc = spawnOg(cmd, args);
    } else {
      d(`spawning process: ${cmd} ${args.join()}, ${JSON.stringify(opts)}`);
      proc = spawnOg(cmd, args, _.omit(opts, 'jobber'));
    }

    let bufHandler = (b) => {
      if (b.length < 1) return;
      let chunk = "<< String sent back was too long >>";
      try {
        chunk = b.toString();
      } catch (e) {
        chunk = `<< Lost chunk of process output for ${exe} - length was ${b.length}>>`;
      }

      subj.onNext(chunk);
    };

    let stderrCompleted = null;
    let stdoutCompleted = null;
    let noClose = false;

    if (proc.stdout) {
      stdoutCompleted = new AsyncSubject();
      proc.stdout.on('data', bufHandler);
      proc.stdout.on('close', () => { stdoutCompleted.onNext(true); stdoutCompleted.onCompleted(); });
    } else {
      stdoutCompleted = Observable.just(true);
    }

    if (proc.stderr) {
      stderrCompleted = new AsyncSubject();
      proc.stderr.on('data', bufHandler);
      proc.stderr.on('close', () => { stderrCompleted.onNext(true); stderrCompleted.onCompleted(); });
    } else {
      stderrCompleted = Observable.just(true);
    }

    proc.stderr.on('data', bufHandler);
    proc.on('error', (e) => {
      noClose = true;
      subj.onError(e);
    });

    proc.on('close', (code) => {
      noClose = true;
      let pipesClosed = Observable.merge(stdoutCompleted, stderrCompleted)
        .reduce((acc) => acc, true);

      if (code === 0) {
        pipesClosed.subscribe(() => subj.onCompleted());
      } else {
        pipesClosed.subscribe(() => subj.onError(new Error(`Failed with exit code: ${code}`)));
      }
    });

    return Disposable.create(() => {
      if (noClose) return;

      d(`Killing process: ${cmd} ${args.join()}`);
      if (!opts.jobber) {
        proc.kill();
        return;
      }

      // NB: Connecting to Jobber's named pipe will kill it
      net.connect(`\\\\.\\pipe\\jobber-${proc.pid}`);
      setTimeout(() => proc.kill(), 5*1000);
    });
  });

  return spawnObs.publish().refCount();
}

export function spawnPromise(exe, params, opts=null) {
  return spawn(exe, params, opts).toPromise();
}

export function spawnDetachedPromise(exe, params, opts=null) {
  return spawnDetached(exe, params, opts).toPromise();
}
<commit_msg>Fix some path rundown issues<commit_after>import _ from 'lodash';
import path from 'path';
import net from 'net';
import { Observable, Disposable, AsyncSubject } from 'rx';
import sfs from 'fs';

const spawnOg = require('child_process').spawn;
const isWindows = process.platform === 'win32';
const fs = require('pify')(sfs);

const d = require('debug')('surf:promise-array');

export function findActualExecutable(fullPath, args) {
  // POSIX can just execute scripts directly, no need for silly goosery
  if (process.platform !== 'win32') return { cmd: fullPath, args: args };

  if (!sfs.existsSync(fullPath)) {
    // NB: When you write something like `surf-client ... -- surf-build` on Windows,
    // a shell would normally convert that to surf-build.cmd, but since it's passed
    // in as an argument, it doesn't happen
    const possibleExts = ['.exe', '.bat', '.cmd', '.ps1'];
    for (let ext of possibleExts) {
      let possibleFullPath = runDownPath(`${fullPath}${ext}`);

      if (sfs.existsSync(possibleFullPath)) {
        return findActualExecutable(possibleFullPath, args);
      }
    }
  }

  if (fullPath.match(/\.ps1$/i)) {
    let cmd = path.join(process.env.SYSTEMROOT, 'System32', 'WindowsPowerShell', 'v1.0', 'PowerShell.exe');
    let psargs = ['-ExecutionPolicy', 'Unrestricted', '-NoLogo', '-NonInteractive', '-File', fullPath];

    return { cmd: cmd, args: psargs.concat(args) };
  }

  if (fullPath.match(/\.(bat|cmd)$/i)) {
    let cmd = path.join(process.env.SYSTEMROOT, 'System32', 'cmd.exe');
    let cmdArgs = ['/C', fullPath];

    return { cmd: cmd, args: cmdArgs.concat(args) };
  }

  if (fullPath.match(/\.(js)$/i)) {
    let cmd = process.execPath;
    let nodeArgs = [fullPath];

    return { cmd: cmd, args: nodeArgs.concat(args) };
  }

  // Dunno lol
  return { cmd: fullPath, args: args };
}

export function asyncMap(array, selector, maxConcurrency=4) {
  return Observable.from(array)
    .map((k) =>
      Observable.defer(() =>
        Observable.fromPromise(selector(k))
          .map((v) => ({ k, v }))))
    .merge(maxConcurrency)
    .reduce((acc, kvp) => {
      acc[kvp.k] = kvp.v;
      return acc;
    }, {})
    .toPromise();
}

export async function asyncReduce(array, selector, seed) {
  let acc = seed;
  for (let x of array) {
    acc = await selector(acc, x);
  }

  return acc;
}

export function delay(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

export function retryPromise(func) {
  return Observable.defer(() =>
      Observable.fromPromise(func()))
    .retry(3)
    .toPromise();
}

export async function statNoException(file) {
  try {
    return await fs.stat(file);
  } catch (e) {
    return null;
  }
}

export function statSyncNoException(file) {
  try {
    return sfs.statSync(file);
  } catch (e) {
    return null;
  }
}

export async function readdirRecursive(dir) {
  let acc = [];

  for (let entry of await fs.readdir(dir)) {
    let target = path.resolve(dir, entry);
    let stat = await statNoException(target);

    if (stat && stat.isDirectory()) {
      let entries = await readdirRecursive(target);
      _.each(entries, (x) => acc.push(x));
    } else {
      acc.push(target);
    }
  }

  return acc;
}

function runDownPath(exe) {
  // NB: Windows won't search PATH looking for executables in spawn like
  // Posix does

  // Files with any directory path don't get this applied
  if (exe.match(/[\\\/]/)) {
    d('Path has slash in directory, bailing');
    return exe;
  }

  let target = path.join('.', exe);
  if (statSyncNoException(target)) {
    d(`Found executable in currect directory: ${target}`);
    return target;
  }

  let haystack = process.env.PATH.split(isWindows ? ';' : ':');
  for (let p of haystack) {
    let needle = path.join(p, exe);
    if (statSyncNoException(needle)) return needle;
  }

  d('Failed to find executable anywhere in path');
  return exe;
}

export function spawnDetached(exe, params, opts=null) {
  if (!isWindows) return spawn(exe, params, _.assign({}, opts || {}, {detached: true }));
  const newParams = [exe].concat(params);

  let target = path.join(__dirname, '..', 'vendor', 'jobber', 'jobber.exe');
  let options = _.assign({}, opts || {}, { detached: true, jobber: true });

  d(`spawnDetached: ${target}, ${newParams}`);
  return spawn(target, newParams, options);
}

export function spawn(exe, params=[], opts=null) {
  let spawnObs = Observable.create((subj) => {
    let proc = null;

    let { cmd, args } = findActualExecutable(exe, params);
    if (!opts) {
      d(`spawning process: ${cmd} ${args.join()}`);
      proc = spawnOg(cmd, args);
    } else {
      d(`spawning process: ${cmd} ${args.join()}, ${JSON.stringify(opts)}`);
      proc = spawnOg(cmd, args, _.omit(opts, 'jobber'));
    }

    let bufHandler = (b) => {
      if (b.length < 1) return;
      let chunk = "<< String sent back was too long >>";
      try {
        chunk = b.toString();
      } catch (e) {
        chunk = `<< Lost chunk of process output for ${exe} - length was ${b.length}>>`;
      }

      subj.onNext(chunk);
    };

    let stderrCompleted = null;
    let stdoutCompleted = null;
    let noClose = false;

    if (proc.stdout) {
      stdoutCompleted = new AsyncSubject();
      proc.stdout.on('data', bufHandler);
      proc.stdout.on('close', () => { stdoutCompleted.onNext(true); stdoutCompleted.onCompleted(); });
    } else {
      stdoutCompleted = Observable.just(true);
    }

    if (proc.stderr) {
      stderrCompleted = new AsyncSubject();
      proc.stderr.on('data', bufHandler);
      proc.stderr.on('close', () => { stderrCompleted.onNext(true); stderrCompleted.onCompleted(); });
    } else {
      stderrCompleted = Observable.just(true);
    }

    proc.stderr.on('data', bufHandler);
    proc.on('error', (e) => {
      noClose = true;
      subj.onError(e);
    });

    proc.on('close', (code) => {
      noClose = true;
      let pipesClosed = Observable.merge(stdoutCompleted, stderrCompleted)
        .reduce((acc) => acc, true);

      if (code === 0) {
        pipesClosed.subscribe(() => subj.onCompleted());
      } else {
        pipesClosed.subscribe(() => subj.onError(new Error(`Failed with exit code: ${code}`)));
      }
    });

    return Disposable.create(() => {
      if (noClose) return;

      d(`Killing process: ${cmd} ${args.join()}`);
      if (!opts.jobber) {
        proc.kill();
        return;
      }

      // NB: Connecting to Jobber's named pipe will kill it
      net.connect(`\\\\.\\pipe\\jobber-${proc.pid}`);
      setTimeout(() => proc.kill(), 5*1000);
    });
  });

  return spawnObs.publish().refCount();
}

export function spawnPromise(exe, params, opts=null) {
  return spawn(exe, params, opts).toPromise();
}

export function spawnDetachedPromise(exe, params, opts=null) {
  return spawnDetached(exe, params, opts).toPromise();
}
<|endoftext|>


<commit_before>import { SmoothScrollbar } from './smooth_scrollbar';

import './apis/index';
import './events/index';
import './internals/index';

angular.module('SmoothScrollbar', [])
    .service('ScrollbarService', class ScrollbarService{
        static $inject = ['$q'];

        constructor($q) {
            this.scrollbarInstances = {};
            this.deferreds = {};
            this.$q = $q;
        }

        /**
         * @method
         * Get scrollbar instance
         * If instance isn't existed,
         * callback wiil be invoked after instance is created
         *
         * @param {String} name: scrollbar name
         */
        getInstance(name) {
            let { scrollbarInstances, deferreds, $q } = this;

            if (scrollbarInstances.hasOwnProperty(name)) {
                return $q.resolve(scrollbarInstances[name]);
            }

            let deferred = deferreds[name] = deferreds[name] || $q.defer();

            return deferred.promise;
        }

        /**
         * @method
         * Create scrollbar instance
         *
         * @param {String} name: scrollbar name
         * @param {Element} elem: container element
         * @param {Object} options: as is explained in scrollbar constructor
         *
         * @return {Scrollbar} scrollbar instance
         */
        createInstance(name, elem, options) {
            let { scrollbarInstances, deferreds } = this;

            if (scrollbarInstances.hasOwnProperty(name)) {
                return scrollbarInstances[name];
            }

            let instance = scrollbarInstances[name] = new SmoothScrollbar(elem, options);

            if (deferreds.hasOwnProperty(name)) {
                deferreds[name].resolve(instance);
            }

            return instance;
        }

        /**
         * @method
         * Destroy scrollbar instance
         *
         * @param {String} name: scrollbar name
         */
        destroyInstance(name) {
            let { scrollbarInstances, deferreds } = this;
            let instance = scrollbarInstances[name];

            if (instance) {
                instance.destroy();
                delete scrollbarInstances[name];
                delete deferreds[name];
            }
        }
    })
    .directive('scrollbar', ['ScrollbarService', (ScrollbarService) => {
        return {
            restrict: 'AE',
            transclude: true,
            template: `

            `,
            scope: {
                name: '@scrollbar',
                speed: '@',
                stepLength: '@',
                easingDuration: '@',
                easingCurve: '@'
            },
            link(scope, elem) {
                let { name, speed, stepLength, easingDuration, easingCurve } = scope;
                if (!name) throw new Error('scrollbar name is required');

                let scrollbar = ScrollbarService.createInstance(name, elem[0], {
                    speed,
                    stepLength,
                    easingDuration,
                    easingCurve
                });

                let original = {
                    addListener: scrollbar.addListener,
                    infiniteScroll: scrollbar.infiniteScroll
                };

                scrollbar.addListener = (cb) => {
                    if (typeof cb !== 'function') return;

                    original.addListener.call(scrollbar, (...args) => {
                        cb(...args);
                        scope.$apply();
                    });
                };

                scrollbar.infiniteScroll = (cb, threshold) => {
                    if (typeof cb !== 'function') return;

                    original.infiniteScroll.call(scrollbar, (...args) => {
                        cb(...args);
                        scope.$apply();
                    });
                };

                scope.$on('$destroy', () => {
                    ScrollbarService.destroyInstance(name);
                });
            }
        };
    }]);<commit_msg>update: patch scrollbar#update, scrollbar#scrollTo with angular scope<commit_after>import { SmoothScrollbar } from './smooth_scrollbar';

import './apis/index';
import './events/index';
import './internals/index';

angular.module('SmoothScrollbar', [])
    .service('ScrollbarService', class ScrollbarService{
        static $inject = ['$q'];

        constructor($q) {
            this.scrollbarInstances = {};
            this.deferreds = {};
            this.$q = $q;
        }

        /**
         * @method
         * Get scrollbar instance
         * If instance isn't existed,
         * callback wiil be invoked after instance is created
         *
         * @param {String} name: scrollbar name
         */
        getInstance(name) {
            let { scrollbarInstances, deferreds, $q } = this;

            if (scrollbarInstances.hasOwnProperty(name)) {
                return $q.resolve(scrollbarInstances[name]);
            }

            let deferred = deferreds[name] = deferreds[name] || $q.defer();

            return deferred.promise;
        }

        /**
         * @method
         * Create scrollbar instance
         *
         * @param {String} name: scrollbar name
         * @param {Element} elem: container element
         * @param {Object} options: as is explained in scrollbar constructor
         *
         * @return {Scrollbar} scrollbar instance
         */
        createInstance(name, elem, options) {
            let { scrollbarInstances, deferreds } = this;

            if (scrollbarInstances.hasOwnProperty(name)) {
                return scrollbarInstances[name];
            }

            let instance = scrollbarInstances[name] = new SmoothScrollbar(elem, options);

            if (deferreds.hasOwnProperty(name)) {
                deferreds[name].resolve(instance);
            }

            return instance;
        }

        /**
         * @method
         * Destroy scrollbar instance
         *
         * @param {String} name: scrollbar name
         */
        destroyInstance(name) {
            let { scrollbarInstances, deferreds } = this;
            let instance = scrollbarInstances[name];

            if (instance) {
                instance.destroy();
                delete scrollbarInstances[name];
                delete deferreds[name];
            }
        }
    })
    .directive('scrollbar', ['ScrollbarService', (ScrollbarService) => {
        return {
            restrict: 'AE',
            transclude: true,
            template: `

            `,
            scope: {
                name: '@scrollbar',
                speed: '@',
                stepLength: '@',
                easingDuration: '@',
                easingCurve: '@'
            },
            link(scope, elem) {
                let { name, speed, stepLength, easingDuration, easingCurve } = scope;
                if (!name) throw new Error('scrollbar name is required');

                let scrollbar = ScrollbarService.createInstance(name, elem[0], {
                    speed,
                    stepLength,
                    easingDuration,
                    easingCurve
                });

                let original = {
                    update: scrollbar.update,
                    scrollTo: scrollbar.scrollTo,
                    addListener: scrollbar.addListener,
                    infiniteScroll: scrollbar.infiniteScroll
                };

                let applyChange = (cb) => {
                    if (typeof cb !== 'function') return;

                    return (...args) => {
                        cb(...args);
                        scope.$apply();
                    };
                };

                scrollbar.update = (cb) => {
                    original.update.call(scrollbar, applyChange(cb));
                };

                scrollbar.scrollTo = (x, y, duration, cb) => {
                    original.scrollTo.call(scrollbar, x, y, duration, applyChange(cb));
                };

                scrollbar.addListener = (cb) => {
                    if (typeof cb !== 'function') return;

                    original.addListener.call(scrollbar, applyChange(cb));
                };

                scrollbar.infiniteScroll = (cb, threshold) => {
                    if (typeof cb !== 'function') return;

                    original.infiniteScroll.call(scrollbar, applyChange(cb));
                };

                scope.$on('$destroy', () => {
                    ScrollbarService.destroyInstance(name);
                });
            }
        };
    }]);<|endoftext|>


<commit_before>var React = require('react');
var stringify = require('json-stringify-pretty-compact');

function isDefaultProp(defaultProps, key, value) {
  if (!defaultProps) {
    return false;
  }
  return defaultProps[key] === value;
}

function jsxToString(component, options) {

  let componentData = {
    name: component.type.displayName || component.type
  };

  let opts = Object.assign({
    spacing: 0,
    keyValueOverride: {},
    ignoreProps: []
  }, options);

  if (component.props) {
    componentData.props = Object.keys(component.props).map(
      (key) => {
        if (key === 'children' ||
          isDefaultProp(component.type.defaultProps, key, component.props[key]) ||
          opts.ignoreProps.includes(key)) {
          return '';
        } else {
          let value = component.props[key];
          if (typeof value === 'string') {
            return ` ${key}="${value}"`;
          } else if (React.isValidElement(value)) {
            value = jsxToString(value, opts);
          } else if (typeof value === 'object') {
            value = stringify(value);
          } else if (typeof value === 'function') {
            value = '...';
          }
          return ` ${key}={${opts.keyValueOverride[key] || value}}`
        }
      }
    ).join('');
  }

  if (component.props.children) {
    opts.spacing += 2;
    let indentation = new Array(opts.spacing + 1).join(' ');
    if (typeof component.props.children === 'string') {
      componentData.children = component.props.children;
    } else if (typeof component.props.children === 'object' &&
      !Array.isArray(component.props.children)) {
      componentData.children = jsxToString(component.props.children, opts);
    } else {
      componentData.children = component.props.children.map((child) => {
        if (typeof child === 'string') {
          return child;
        } else {
          return jsxToString(child, opts);
        }
      }).join(`\n${indentation}`);
    }
    return `<${componentData.name}${componentData.props}>\n${indentation}${componentData.children}\n${indentation.slice(0, -2)}</${componentData.name}>`;
  } else {
    return `<${componentData.name}${componentData.props} />`;
  }
};

export default jsxToString;
<commit_msg>Check component.type.name for es6 components<commit_after>var React = require('react');
var stringify = require('json-stringify-pretty-compact');

function isDefaultProp(defaultProps, key, value) {
  if (!defaultProps) {
    return false;
  }
  return defaultProps[key] === value;
}

function jsxToString(component, options) {

  let componentData = {
    name: component.type.displayName || component.type.name || component.type
  };

  let opts = Object.assign({
    spacing: 0,
    keyValueOverride: {},
    ignoreProps: []
  }, options);

  if (component.props) {
    componentData.props = Object.keys(component.props).map(
      (key) => {
        if (key === 'children' ||
          isDefaultProp(component.type.defaultProps, key, component.props[key]) ||
          opts.ignoreProps.includes(key)) {
          return '';
        } else {
          let value = component.props[key];
          if (typeof value === 'string') {
            return ` ${key}="${value}"`;
          } else if (React.isValidElement(value)) {
            value = jsxToString(value, opts);
          } else if (typeof value === 'object') {
            value = stringify(value);
          } else if (typeof value === 'function') {
            value = '...';
          }
          return ` ${key}={${opts.keyValueOverride[key] || value}}`
        }
      }
    ).join('');
  }

  if (component.props.children) {
    opts.spacing += 2;
    let indentation = new Array(opts.spacing + 1).join(' ');
    if (typeof component.props.children === 'string') {
      componentData.children = component.props.children;
    } else if (typeof component.props.children === 'object' &&
      !Array.isArray(component.props.children)) {
      componentData.children = jsxToString(component.props.children, opts);
    } else {
      componentData.children = component.props.children.map((child) => {
        if (typeof child === 'string') {
          return child;
        } else {
          return jsxToString(child, opts);
        }
      }).join(`\n${indentation}`);
    }
    return `<${componentData.name}${componentData.props}>\n${indentation}${componentData.children}\n${indentation.slice(0, -2)}</${componentData.name}>`;
  } else {
    return `<${componentData.name}${componentData.props} />`;
  }
};

export default jsxToString;
<|endoftext|>